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?
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
};
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 :)
@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?
Most helpful comment
In this case it's actually the same as
get(key: $Keys<T>): empty-- but the difference betweenemptyandanyin 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:
This doesn't currently work due to a limitation in the way
$PropertyTypeworks (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
foofunction. 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 ofpand the type the function returns should be the same type. So if you callfoo(..)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 theTtype 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: numberproperty. The:is a way of setting a boundary on the possible types that can be used to fulfill the relationship betweenpand the return type.So here,
foo(42);is a type error (numbers don't have alengthproperty). Butfoo({length: 42, stuff: "asdf"})is ok since the object has alengthproperty andfoo({length: 42, otherStuff: "asdf")is also ok since it also has alengthproperty."
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
emptyuntil 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 staysempty."
get<KeyT: $Keys<T>>(key: KeyT): $PropertyType <T, KeyT>;"Ok now we're getting pretty advanced. Here we create a type parameter called
KeyTthat we say must be compatible with$Keys<T>(AKA: The set of keys on theTobject). SinceKeyTis a type parameter, it doesn't say exactly what the type is...only that it fits within$Keys<T>(the set of keys on theTobject).Then we relate the function parameter,
keywith the return type by saying that the type ofkeyis the same as the "property type ofT'sKeyTtype".$PropertyTypeis a built-in type that looks up the type of the property on an object type. So we pass inT(the record object) andKeyT(the type variable that represents the key the caller requested).