Flow: [question] difference between function foo<A>(): A and function foo(): any

Created on 1 Dec 2016  路  7Comments  路  Source: facebook/flow

What is the difference between the function types

function foo<A>(): A

and

function foo(): any

?

It seems to me that both have the same effects - the first one seems more "sophisticated" and I see it sometimes in some projects, but it is the same as the second one, right?

Most helpful comment

declare class Record<T: Object> { get<A>(key: $Keys<T>): A; }

In this case it's actually the same as get(key: $Keys<T>): empty -- but the difference between empty and any in this position is pretty nuanced and might even not be observable...so it may not be important here.

I suspect whoever did this just used a type param to state that the returned type isn't known because Flow doesn't really have a way to express the correct type -- but didn't want to suggest that it's any type, just some type that Flow doesn't have the facilities to express just yet.

Here's a strawman (that doesn't currently work) for how we might fill this out to be more complete:

declare class Record<T: Object> {
  get<Key: $Keys<T>>(key: Key): $PropertyType <T, Key>;
}

This doesn't currently work due to a limitation in the way $PropertyType works (and it may/may not be tractable to fix), but the idea is that we're using the type parameter to establish relationships between the types without actually saying what the specific type is.

Refresher on type parameters:

"function foo<T>(p: T): T { return p; }"

This makes a type parameter for the foo function. Type parameters are much like function parameters in that they are a variable that can be filled in later -- except they are a variable for a type rather than a variable for a value.

Here we create a type parameter, T, in order to say: Any time this function is called, the type of p and the type the function returns should be the same type. So if you call foo(..) with a string, then it should also return a string. If you call it with a number, it returns a number.

Type parameters let you establish type relationships without saying what the specific type is.

"function foo<T: {length: number}>(p: T): T { return p; }"

When we add the : {length: number} to the type parameter, we're saying that whatever the T type parameter is fulfilled with, it has to be compatible with the type on the right-hand side of the :.

In this case, we're saying that you can send any type into this function as long as it is an object that has a length: number property. The : is a way of setting a boundary on the possible types that can be used to fulfill the relationship between p and the return type.

So here, foo(42); is a type error (numbers don't have a length property). But foo({length: 42, stuff: "asdf"}) is ok since the object has a length property and foo({length: 42, otherStuff: "asdf") is also ok since it also has a length property.

"get<A>(key: $Keys<T>): A;"

Here we're creating a type parameter but we're not relating it to anything, so it's kind of useless. Type parameters basically start out as empty until they are filled in in some way. Since we're not filling it in here (and we're not relating it to anything), it just stays empty.

"get<KeyT: $Keys<T>>(key: KeyT): $PropertyType <T, KeyT>;"

Ok now we're getting pretty advanced. Here we create a type parameter called KeyT that we say must be compatible with $Keys<T> (AKA: The set of keys on the T object). Since KeyT is a type parameter, it doesn't say exactly what the type is...only that it fits within $Keys<T> (the set of keys on the T object).

Then we relate the function parameter, key with the return type by saying that the type of key is the same as the "property type of T's KeyT type". $PropertyType is a built-in type that looks up the type of the property on an object type. So we pass in T (the record object) and KeyT (the type variable that represents the key the caller requested).

All 7 comments

I see it sometimes in some projects

function foo<A>(): A is not implementable, could you point to some examples?

Hm, you are right. But I have seen it in some declaration files. For example, here, immutable.js (again :))

https://github.com/facebook/immutable-js/blob/master/type-definitions/immutable.js.flow#L626

declare class Record<T: Object> { get<A>(key: $Keys<T>): A; }

this is effectively get(key: $Keys<T>): any, right

declare class Record<T: Object> { get<A>(key: $Keys<T>): A; }

In this case it's actually the same as get(key: $Keys<T>): empty -- but the difference between empty and any in this position is pretty nuanced and might even not be observable...so it may not be important here.

I suspect whoever did this just used a type param to state that the returned type isn't known because Flow doesn't really have a way to express the correct type -- but didn't want to suggest that it's any type, just some type that Flow doesn't have the facilities to express just yet.

Here's a strawman (that doesn't currently work) for how we might fill this out to be more complete:

declare class Record<T: Object> {
  get<Key: $Keys<T>>(key: Key): $PropertyType <T, Key>;
}

This doesn't currently work due to a limitation in the way $PropertyType works (and it may/may not be tractable to fix), but the idea is that we're using the type parameter to establish relationships between the types without actually saying what the specific type is.

Refresher on type parameters:

"function foo<T>(p: T): T { return p; }"

This makes a type parameter for the foo function. Type parameters are much like function parameters in that they are a variable that can be filled in later -- except they are a variable for a type rather than a variable for a value.

Here we create a type parameter, T, in order to say: Any time this function is called, the type of p and the type the function returns should be the same type. So if you call foo(..) with a string, then it should also return a string. If you call it with a number, it returns a number.

Type parameters let you establish type relationships without saying what the specific type is.

"function foo<T: {length: number}>(p: T): T { return p; }"

When we add the : {length: number} to the type parameter, we're saying that whatever the T type parameter is fulfilled with, it has to be compatible with the type on the right-hand side of the :.

In this case, we're saying that you can send any type into this function as long as it is an object that has a length: number property. The : is a way of setting a boundary on the possible types that can be used to fulfill the relationship between p and the return type.

So here, foo(42); is a type error (numbers don't have a length property). But foo({length: 42, stuff: "asdf"}) is ok since the object has a length property and foo({length: 42, otherStuff: "asdf") is also ok since it also has a length property.

"get<A>(key: $Keys<T>): A;"

Here we're creating a type parameter but we're not relating it to anything, so it's kind of useless. Type parameters basically start out as empty until they are filled in in some way. Since we're not filling it in here (and we're not relating it to anything), it just stays empty.

"get<KeyT: $Keys<T>>(key: KeyT): $PropertyType <T, KeyT>;"

Ok now we're getting pretty advanced. Here we create a type parameter called KeyT that we say must be compatible with $Keys<T> (AKA: The set of keys on the T object). Since KeyT is a type parameter, it doesn't say exactly what the type is...only that it fits within $Keys<T> (the set of keys on the T object).

Then we relate the function parameter, key with the return type by saying that the type of key is the same as the "property type of T's KeyT type". $PropertyType is a built-in type that looks up the type of the property on an object type. So we pass in T (the record object) and KeyT (the type variable that represents the key the caller requested).

Yeah, that's what I tried, but of course $PropertyType doesn't work like that, so the excercise is rather moot :))

Anyway, I found out Records are not really as useful as I thought. For example, you cannot capture state with more "options" (I think it's called "disjoined union" in docs); for example, you cannot have immutable Record with type definition like this (from docs)

type OptionalObject = { type: 'success', result: number } | { type: 'error', message: string };

  • you cannot create a Immutable Record with this type.

I recognize now that using Immutable Record for typed application state is not really an option (too many complications) and I will need to take care of application state immutability on my own.

it would be nice if at least Flow could enforce immutability somehow :)

....buuut it seems this feature request already exist, with issue number two orders of magnitude smaller than this one. OK :)

https://github.com/facebook/flow/issues/18

@runn1ng: One way you can enforce immutable "records" in Flow now is by using covariance + exact types:

https://flowtype.org/try/#0C4TwDgpgBAChBOBnA9gOygXigbwD4CgooBqAM1QEMBbCALikWHgEtUBzAGkJIBtKb6jFuy5FiAV0QJ+dBk1Zt8uAL4BufPh4RgUeONSoAjO3pwkaTDm7lqsgEQBpCvAg87oqH1v07AIQC3WgDW7tyS0t5QdnoGxmyhahoA9ElQAJLAAOSIUMhBUMDIuhAUACZQFFAAxsgAbs7MFKg6YPDIkPCg+AAUMUbsAHQ2AnLCbACU6vgpUACi8G3wAIRQAMJNmToA7izA0IUV1XUNTS1tHV0pfXFDMpZ2qO0QdqpAA

Very nice!!!

Doesn't this fix #18, at least for some cases?

Was this page helpful?
0 / 5 - 0 ratings