TypeScript icon indicating copy to clipboard operation
TypeScript copied to clipboard

Easier destructuring with type annotations on binding patterns

Open Richiban opened this issue 6 years ago • 64 comments

Search Terms

type inference destructuring syntax conflict

Suggestion

It's currently not possible to destructure a value in Typescript and provide types for each of the values due to the syntax clash with destructuring and renaming at the same time. You can see exactly this issue in the Typescript FAQs at: https://github.com/Microsoft/TypeScript/wiki/FAQ#why-cant-i-use-x-in-the-destructuring-function-f-x-number------

This is frustrating when programming in React where it's very common to see this pattern:

const MyComponent = ({ a, b }) => {
    // ...
}

But in Typescript a and b are untyped (inferred to have type any) and type annotation must be added (either to aid in type safety or to avoid compiler errors, depending on the state of the user's strict flags). To add the correct type annotation it feels natural to write:

const MyComponent = ({ a : string, b : number }) => {
    // ...
}

but that's not what the user thinks due to the aforementioned syntax clash. The only valid syntax in Typescript is actually this:

const MyComponent = ({ a, b } : { a : string, b : number }) => {
    // ...
}

Which is very strange to write and difficult to read when the object has more than two parameters or the parameters have longer names. Also the value names have been duplicated -- once in the destructuring and once in the type annotation.

I suggest we allow some other symbol (my current thinking is a double colon) to make the syntax unambiguous in this specific scenario:

const MyComponent = ({ a :: string, b :: number }) => {
	// ...
}

Although this is really the only place it would be used, for the sake of consistency, I think is should be allowed everywhere:

const a :: string = "";
const b :: number = 1;

Use Cases

It would allow for type-safe destructuring of values where the type cannot be inferred by the compiler (such as function parameters).

Examples

A good example of the sort of React components I'm talking about (and one of the first Google results for React Functional Components) can be found at https://hackernoon.com/react-stateless-functional-components-nine-wins-you-might-have-overlooked-997b0d933dbc. We can use my proposed syntax in the functional component definition:

import React from 'react'

const HelloWorld = ({name :: string}) => {
	const sayHi = (event) => {
		alert(`Hi ${name}`)
	}

	return (
		<div>
			<a href="#"
			   onclick={sayHi}>Say Hi</a>
		</div>
	)
}

Checklist

My suggestion meets these guidelines:

  • [x] This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • [x] This wouldn't change the runtime behavior of existing JavaScript code
  • [x] This could be implemented without emitting different JS based on the types of the expressions
  • [x] This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
  • [x] This feature would agree with the rest of TypeScript's Design Goals.

Richiban avatar Jan 22 '19 16:01 Richiban

Duplicate/related to #7576, #29019

j-oliveras avatar Jan 22 '19 16:01 j-oliveras

I know expression level syntax is a no-no, but how about:

const MyComponent = ({ ... } : { a : string, b : number }) => {
    // ...
}

With the ... meaning auto-spread all the rest of the properties in the object.

You can still have a traditional binding list if you want to remap any names:

const MyComponent = ({ a:aa, ... } : { a : string, b : number }) => {

}

There is a reason this issue keeps poping up. The current solution is painful.

dragomirtitian avatar Jan 22 '19 22:01 dragomirtitian

I agree it's a duplicate but it really does suck. We should try again.

RyanCavanaugh avatar Jan 23 '19 18:01 RyanCavanaugh

With React hooks just released, class components are being... slowly deprecated in favor of functional components all the way.

This feature is now more important than ever.

The :: syntax sounds good to me.

olmobrutall avatar Feb 06 '19 23:02 olmobrutall

There are indeed multiple duplicates of this request. Heavyweights of the JS ecosystem have said they want it. Random dudes such as myself want it. Unscientific polls show that a large majority of JS developers use argument object destructuring (https://twitter.com/rauschma/status/987471929585143809). It seems to me that the time has come for some solution to be made :)

FWIW, the double colon syntax seems good to me.

scottmas avatar Apr 30 '19 16:04 scottmas

Yeah, this seems like the 3rd iteration of this issue with the previous two just being closed as being too complicated to implement but I suspect that people are going to continue to ask for it and I'll add my voice to the list. This is something that's actually making it harder for me to convince my teammates to switch to TypeScript because this type of destructuring in function calls is pretty common in our existing code.

TazmanianD avatar May 23 '19 14:05 TazmanianD

The current syntax for this scenario does look bad... I wish ES6 could choose different syntax for renaming, instead of the colon, e.g 'as'; the 'as' keyword is more meaningful for renaming purpose :)

Anyway, although the proposal of double colon syntax does not look bad, it could ergonomically cause troubles for developers to understand what does it mean since people get used to using a single colon as the type annotation. I would prefer another way to address the problem. Actually, I like dragomirtitian's proposal better.

gynet avatar Jul 31 '19 07:07 gynet

While I also think @dragomirtitian 's solution is a reasonable one, I'd like something more in keeping with Typescript's philosophy of not introducing any new syntax other than type annotations. one of the reasons for Typescript's success has been its mantra of "It's just Javascript, but with types". It's being able to look at a declaration and immediately parse out what's TS and what's JS:

//          The Javascript bit
//               --------
const myFunction({ a, b } : SomeType) { ... }
//                        ----------
//                     The Typescript bit

If TS starts introducing its own expression / destructuring syntax I fear that starts us down a slope of TS being its own language, rather than a type system for JS.

Richiban avatar Jul 31 '19 10:07 Richiban

Actually the more I think about :: the more I like it 😁, but with a slightly different meaning. Let's not consider :: as a new way to introduce a type annotation, but rather an empty rename.

// rename, no type annotations
const HelloWorld = ({name : newName }) => { 

}
// rename, with annotation 
const HelloWorld = ({name : newName : string }) => { 

}

// empty rename, with annotation 
const HelloWorld = ({name :: string }) => { 

}
// empty rename, with annotation, and default
const HelloWorld = ({name :: string = "default" }) => { 

}

I personally think it flows nicely. The first : always introduces a name binding, which can be empty, the second : always introduces a type annotation, and since a new : not valid here in ES it would not be ambiguous.

Would work decently with objects too:

type FooBar = { foo: string; bar: number }
const HelloWorld = ({ a, b:{ foo: fooLocal, bar: barLocal }: FooBar  }) => {
  
}

const HelloWorld = ({ a, b::FooBar  }) => {
  
}

const HelloWorld = ({ 
  a: number, 
  b: { 
    foo:fooLocal:string, 
    bar:barLocal:string
  }
}) => {
  
}

dragomirtitian avatar Jul 31 '19 11:07 dragomirtitian

@dragomirtitian

Let's not consider :: as a new way to introduce a type annotation, but rather an empty rename.

Works for me! 👍

Richiban avatar Jul 31 '19 13:07 Richiban

@dragomirtitian Good point!

gynet avatar Aug 02 '19 06:08 gynet

Were angle brackets considered? https://github.com/microsoft/TypeScript/issues/34748

danm avatar Oct 30 '19 19:10 danm

Has any progress been made on this? The current solution is not good since it is not DRY, e.g. ({foo, bar, baz}: {foo: string, bar: number, baz: boolean}) has a lot of duplicated information.

fr0 avatar Dec 09 '19 21:12 fr0

DRY?

On 9 Dec 2019, at 21:03, Chris Frolik [email protected] wrote:

Has any progress been made on this? The current solution is not good since it is not DRY, e.g. ({foo, bar, baz}): {foo: string, bar: number, baz: boolean}) has a lot of duplicated information.

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/microsoft/TypeScript/issues/29526?email_source=notifications&email_token=ABQZHXW5ME4SAYR634QZ7STQX2XCVA5CNFSM4GRUGZRKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEGKWCEA#issuecomment-563437840, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABQZHXWA7OFF26Q7FGJFUTLQX2XCVANCNFSM4GRUGZRA.

danm avatar Dec 09 '19 21:12 danm

DRY?

https://en.wikipedia.org/wiki/Don%27t_repeat_yourself

fr0 avatar Dec 09 '19 21:12 fr0

Not really related, but it'd be much more easier if object destructuring alias uses as instead of :, just like how import does.

tienpv222 avatar Mar 08 '20 07:03 tienpv222

@pynnl that ship sailed a long time ago unfortunately.

dragomirtitian avatar Mar 08 '20 10:03 dragomirtitian

TypeScript really needs this! I can't think of a more annoying thing than this. Any syntax is okay, as long as it gets implemented. Please. This has been discussed for such a long time. There are a lot of options that don't collide with the ES spec.

VanCoding avatar Mar 25 '20 01:03 VanCoding

I'd like to propose a variant of the multi-colon syntax:

{ x:: number }
{ x:: number = 123 }
{ xin: xout:: number }
{ xin: xout:: number = 123 }

The idea is to keep the double colon, even when remapping the name, so that the type is always identified by the colon-block (or colon-square) operator ::. The point of this subtlety is to make it easy for the reader to visually parse where the name ends and where the type begins. This is achieved through the use of a different operators rather than being purely syntax-based.

mathieucaroff avatar Jul 03 '20 23:07 mathieucaroff

Here is another idea of syntax, which resembles the "Auto spread" proposition of @dragomirtitian: {...}: { x: number }. It actually extends the syntax of "Interface spread to variable" proposed by @antanas-arvasevicius in #14856, adding support for renaming variables:

let ...{ x: number } = v // x is now available
let ...{ x: number = 123 } = v
let ...{ xin xout: number } = v // xout is now available
let ...{ xin xout: number = 123 } = v
let ...{ left { data dataLeft: string }, right { data dataRight: string } } = v
// dataLeft and dataRight are now avaliable

Example with a function, a let together with the rest parameter:

const formatSidedData = (...{ left { data a: string }, right { data b: string } }) => `${a}<->${b}`

let ...{ person { name: string, age: number }, ...metadata: Record<string, string> } = getInfo()

Note: the ellipsis ... "type annotated destructuring-declaration" can only be used at the top level, either in a const, let or var list of variable declarations, or among the arguments of a function. I suggest a standard destructuring-declaration should not be allowed to use ... inside it to create a zone of "type annotated destructuring-declaration".

One of the unique nice properties only found in @dragomirtitian proposal, that of @antanas-arvasevicius and this one is that in the simple case --without renaming--, the syntax used is just the usual syntax for interfaces. This covers the use case of easily transforming a type annotated destructuring into an interface (by unedited copy-paste). This also applies to copy-pasting an interface to a "type annotated destructuring".

Below is an example to clarify.

Let there be a function magnitude, computing the length of a vector. Using my "Extended interface spread to variable" syntax, we can write this:

function magnitude(...{ x: number, y: number }) {
  return (x ** 2 + y ** 2) ** 0.5;
}

We then realize we want to name the interface. It is easy to just copy-paste it to a named type:

interface Point { x: number, y: number }

function magnitude({ x, y }: Point) {
  return (x ** 2 + y ** 2) ** 0.5;
}

If you use Prettier, it will automatically reformat the interface to this:

interface Point {
  x: number;
  y: number;
}

Doing the opposite change (going back) is also easy, though you may need to remove some semicolons and add commas:

function magnitude(...{ x: number, y: number }) {
  return (x ** 2 + y ** 2) ** 0.5;
}

This syntax can be used without any issue with the traditional ellipses for varargs functions:

function join(...{ begin: string, sep: string, end: string }, ...parts: string[]) {
    return begin + parts.join(sep) + end;
}

mathieucaroff avatar Jul 04 '20 01:07 mathieucaroff

Content

  • Content
  • Vocabulary
  • All the issues I could find
  • Reason(s) to decline the feature request (back in 2016)
  • Proposition(s) with unusable syntax
  • Formatting variants among valid syntaxes
  • Typescript 3.9.2 (current situation)
  • All valid syntax propositions I could find
  • Pros and cons of each syntax
    • as_type
    • angle_bracket_before
    • angle_bracket_after_remap
    • auto_spread
    • spread_with_remap
    • double_colon
    • colon_square
    • concatenation_after_remap
    • concatenation_before_colon
    • parentheses

Vocabulary

  • Type annotation: telling Typescript what value the assignment should validate
  • Type assertion: telling Typescript to ignore the assigned value and use ours instead
  • Remapping (renaming): Operation of giving a local name different than the one in the map while destructuring the map.
  • Remapping (sub-destructuring): Operation of destructuring the nested value inside an object, making them available as local variables
  • Default value: Operation of providing a value to use in replacement of a missing or undefined property in the destructured object.
  • "Type annotated destructuring": Operation of destructuring and provide type annotations for the resulting local variables, without repeating the object property name, nor the local variable name
  • "Complete type annotated destructuring": Type annotated destructuring which supports remapping (both for renaming and sub-destructuring) and providing default values while destructuring.

All the issues I could find

  • #240 "Destructuring (ES6)" (2014-07)
  • #1912 "Optional object literal typing" (2015-02)
  • #5034 "Destructuring variable declarations with types" (2015-09)
  • #7576 "Destructuring with type annotations" (2016-03)
  • #13471 "Destructuring Type Syntax Sugar" (2017-01)
  • #14856 "Simplify object destructuring in argument" (2017-03)
  • #18229 ~~"Suggestion: Support type assertion on destructuring syntax"~~ (2017-09)
  • #29526 "Easier destructuring with type annotations on binding patterns" (2019-01) (@HERE)
  • #30574 "Omitting duplicate definition of properties on object deconstruction" (2019-03)
  • #31049 "[Feature request] allow set type when use let { a } = argv" (2019-04)
  • #34748 "Destructure parameters and type at the same time." (2019-08)

Note:

  • The issue which produced the most work on the subject before June 2018 was #7576 "Destructuring with type annotations".
  • Corresponding issue in Flowtype: "Type annotations inside destructuring" #235

Reason(s) to decline the feature request (back in 2016)

@mhegazy

  • ultimately, every new syntax added is added complexity, for the user to learn and recognize, and for the compiler code base to maintain, so usually we try to limit complexity unless the value warrants it. and for destructuring, the syntax is already complex to start with, and we did not want to add yet another layer on top.

-- seconded by @DanielRosenwasser

Proposition(s) with unusable syntax

Here are a few propositions I found whose syntax cannot be used.

  • as_remap_and_colon_type #7576 @odiak
{ x: number }
{ xin: number = 123 }
{ xin as xout: number }
{ xin as xout: number = 123 }

The first two lines are valid JS and TS. This syntax is incompatible.

Formatting variants among valid syntaxes

(This is about formatting recommendations and auto-formatting)

  • double_colon.sparse1
{ x:: number }
{ xin:: number = 123 }
{ xin: xout: number }
{ xin: xout: number = 123 }
  • double_colon.sparse2
{ x :: number }
{ xin :: number = 123 }
{ xin: xout: number }
{ xin: xout: number = 123 }
  • double_colon.compact2 (formatting variant)
{ x::number }
{ xin::number = 123 }
{ xin: xout: number }
{ xin: xout: number = 123 }
  • double_colon.compact4 (formatting variant)
{ x::number }
{ xin::number = 123 }
{ xin:xout:number }
{ xin:xout:number = 123 }

Typescript 3.9.2 (current situation)

  • current_grouped
{ x }: { x: number }
{ xin = 123 }: { xin: number }
{ xin: xout }: { xin: number }
{ xin: xout = 123 }: { xin: number }
  • current_split_and_named
interface Prop { x: number }
{ x }: Prop

interface Prop { x: number }
{ xin = 123 }: Prop

interface Prop { xin: number }
{ xin: xout }: Prop

interface Prop { xin: number }
{ xin: xout = 123 }: Prop

All valid syntax propositions I could find

All these are proposals for "Complete type annotated destructuring".

(order attempts to group propositions sharing similar traits)

  • as_type [#7576 @osi-oswald @rauschma, #30574 @AlseinX, #31049 @bluelovers]
{ x as number }
{ xin: xout as number = 123 }
  • angle_bracket_before ([#1912 @NN---] but ambiguous)
{ <number>x }
{ <number>xin: xout = 123 }
  • angle_bracket_after_remap [#34748 @danm]
{ x<number> }
{ xin: xout<number> = 123 }
  • auto_spread [#29526 @dragomirtitian]
{...}: { x: number }
{ xin = 123 }: { xin: number? }
{ xin: xout }: { xin: number? }
{ xin: xout = 123 }: { xin: number? }
  • sprea_with_remap [#29526 @mathieucaroff], ([#14856 @antanas-arvasevicius] but incomplete)
...{ x: number }
...{ xin xout: number = 123 }
  • double_colon [#240 @RyanCavanaugh @fdecampredon @mpawelski], [#29526 @dragomirtitian] ([#5034 @schungx], [#29526 @Richiban] but ambiguous)
{ x:: number}
{ xin: xout: number = 123 }
  • colon_square [#29526 @mathieucaroff]
{ x:: number }
{ x:: number = 123 }
{ xin: xout:: number }
{ xin: xout:: number = 123 }
  • concatenation_after_remap [#13471 @mightyiam]
{ x number }
{ xin: xout number = 123 }
  • concatenation_before_colon [#7576 @MichaelBuen]
{ x number }
{ xin number: xout = 123 }
  • parentheses [#7576 @rauschma]
{ x: (number) }
{ xin: (xout: number) = 123 }

Pros and cons of each syntax

as_type

{ xin: xout as number = 123 }

Cons

  • as T is currently used for type assertion rather than type annotation; this will cause confusion

Pros

  • as T already means "type"; users will feel more familiar with this new syntax

angle_bracket_before

{ <number>xin: xout = 123 }

Cons

  • <T>x is currently used for type assertion rather than type annotation; this will cause confusion

Pros

  • <T>x already means "type"; users will feel more familiar with this new syntax

angle_bracket_after_remap

Cons

  • x<T> looks like A<T>, the syntax for type parameter; this overload can feel strange

Pros

  • x<T> feels like Typescript since it's similar to an existing syntax

auto_spread

{ xin: xout = 123 }: { xin: number? }, but {...}: { x: number } in the simple case

Cons

  • only the simple case receives the improvement
  • let {...}: { x: number } = a might let the user believe that let {...} = a exists and is a meaningful unpacking strategy, but it is not since the { ... } syntax only exists with type annotation

Pros

  • this syntax is very similar to existing syntaxes

spread_with_remap

Cons

  • let ...{ x: number } = a might let the user believe that let ...T exists and is a meaningful unpacking strategy, but it is not
  • it overloads type destructuring somewhat
  • it gives a new syntax to remapping -- differing from ES6's syntax

Pros

  • this syntax is very similar to existing syntaxes

double_colon

{ xin: xout: number = 123 }

Cons

  • in the (rare) case of variable remapping, the same separator is used between the remap and the type, which can make things harder to parse visually

Pros

  • the colon is already the current operator for type annotation; adding a second colon to disambiguate from variable remapping feels like an obvious thing to do

@mhegazy

  • there is value in consistency that types always come after a :. adding another operator would increase complexity.
  • we have the : domain almost officially reserved for types.

colon_square

{ xin: xout:: number = 123 }

(disallowing spaces between the two colon)

Cons

  • in effect, the "colon square" is a new operator for type annotation, since it is kept in the variable remapping syntax

Pros

  • it uses colons, which are the usual operator for type annotation

concatenation_after_remap

{ xin: xout number = 123 }

Cons

  • concatenation without a visual separator can be visually hard to parse
  • concatenation has never been used for type annotation in TypeScript

Pros

  • concatenation is simple

concatenation_before_colon

{ xin number: xout = 123 }

Cons

  • in the case of a remap: { xin number: xout }, the type number appears before the variable xout; which is never done in TypeScript

Pros

  • concatenation is simple

parentheses

{ xin: (xout: number) = 123 }

Cons

  • having parentheses around the type for disambiguation feels quite new: { xin: (number) }

Pros

  • the type always appears right of a colon
  • parenthesis are already used with types when grouping is needed

mathieucaroff avatar Jul 04 '20 08:07 mathieucaroff

Here is my order of preference on these 10 alternatives, from most wanted to least wanted:

  • spread_with_remap
  • auto_spread
  • parentheses
  • colon_square
  • double_colon
  • --approval threshold--
  • concatenation_after_remap
  • angle_bracket_after_remap
  • as_type
  • concatenation_before_colon
  • angle_bracket_before

My "approval threshold" separates the options I would vote for from those I would not in the event of a vote using approval voting.

I'm interested in your own preferences, especially if you believe debating can lead to a consensus. In case debating fails to lead to a consensus, or debating is not attempted, I believe a vote (using approval voting) would still be a satisfying outcome.

mathieucaroff avatar Jul 04 '20 23:07 mathieucaroff

Would love to be able to type named parameters without having to duplicate the name of the parameter! In React code bases basically every component has a unique interface so you end up doing a lot of extra typing. I like the double colon, anybody who works in the TypeScript codebase have a feeling for how hard this would be to implement?

fleck avatar Jul 27 '20 19:07 fleck

I think as_type is the best proposal here, as the "as" keyword is not used in structuring/destructuring, and doesn't produce ambiguous syntax. Not to mention, it produces a lot more readable code.

eezstreet avatar Dec 21 '20 18:12 eezstreet

Search Terms

type inference destructuring syntax conflict

Suggestion

It's currently not possible to destructure a value in Typescript and provide types for each of the values due to the syntax clash with destructuring and renaming at the same time. You can see exactly this issue in the Typescript FAQs at: https://github.com/Microsoft/TypeScript/wiki/FAQ#why-cant-i-use-x-in-the-destructuring-function-f-x-number------

This is frustrating when programming in React where it's very common to see this pattern:

const MyComponent = ({ a, b }) => {
    // ...
}

But in Typescript a and b are untyped (inferred to have type any) and type annotation must be added (either to aid in type safety or to avoid compiler errors, depending on the state of the user's strict flags). To add the correct type annotation it feels natural to write:

const MyComponent = ({ a : string, b : number }) => {
    // ...
}

but that's not what the user thinks due to the aforementioned syntax clash. The only valid syntax in Typescript is actually this:

const MyComponent = ({ a, b } : { a : string, b : number }) => {
    // ...
}

Which is very strange to write and difficult to read when the object has more than two parameters or the parameters have longer names. Also the value names have been duplicated -- once in the destructuring and once in the type annotation.

I suggest we allow some other symbol (my current thinking is a double colon) to make the syntax unambiguous in this specific scenario:

const MyComponent = ({ a :: string, b :: number }) => {
	// ...
}

Although this is really the only place it would be used, for the sake of consistency, I think is should be allowed everywhere:

const a :: string = "";
const b :: number = 1;

Use Cases

It would allow for type-safe destructuring of values where the type cannot be inferred by the compiler (such as function parameters).

Examples

A good example of the sort of React components I'm talking about (and one of the first Google results for React Functional Components) can be found at https://hackernoon.com/react-stateless-functional-components-nine-wins-you-might-have-overlooked-997b0d933dbc. We can use my proposed syntax in the functional component definition:

import React from 'react'

const HelloWorld = ({name :: string}) => {
	const sayHi = (event) => {
		alert(`Hi ${name}`)
	}

	return (
		<div>
			<a href="#"
			   onclick={sayHi}>Say Hi</a>
		</div>
	)
}

Checklist

My suggestion meets these guidelines:

  • [x] This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • [x] This wouldn't change the runtime behavior of existing JavaScript code
  • [x] This could be implemented without emitting different JS based on the types of the expressions
  • [x] This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
  • [x] This feature would agree with the rest of TypeScript's Design Goals.

I vote for @Richiban's double colon syntax

imhaeussler avatar Jan 10 '21 14:01 imhaeussler

Would auto_spread work with interface types e.g. {...}: InterfaceType? Seems a bit much to have a function's local variables be defined arbitrarily far away.

randfur avatar Mar 18 '21 10:03 randfur

As I imagine it, {...}: InterfaceType would be an invalid syntax. auto_spread would be a rigid syntax with four braces in it: {...}: {}.

mathieucaroff avatar Mar 18 '21 17:03 mathieucaroff

angle_bracket_after_remap

{ x<number> }
{ xin: xout<number> = 123 }

mathieucaroff avatar Mar 18 '21 20:03 mathieucaroff

angle_bracket_before

{ <number>x }
{ <number>xin: xout = 123 }

mathieucaroff avatar Mar 18 '21 20:03 mathieucaroff

as_type

{ x as number }
{ xin: xout as number = 123 }

mathieucaroff avatar Mar 18 '21 20:03 mathieucaroff