Structural subtyping in flow may lead to information loss:
type O = {x: number, y: number};
type P = {x: number, y: number, z: number}
function f(o: O) {
return o.x * 2, o.y * 2, o;
}
const p: P = {x: 2, y: 3, z: 100};
const r = f(p);
r.z // type error (property not found)
(This code is terrible, because it performs visible mutations. It is for illustrative purposes only.)
I've read that row polymorphism is a concept to avoid this information loss without jeopardizing type safety.
Is there a way to achieve the same with subtype polymorphism?
[EDIT]
To address a greater audience I provide a brief explanation of the somewhat scary terminology:
- Polymorpishm is just a fancy word for determining if two types are equivalent, i.e. it makes a rigid type system more flexible
- Parametric polymorphism (generics in flow) states that two types are always equivalent, because types doesn't matter at all
- Subtype polymorphism (subtyping in flow) states that two types are equivalent if you can derive a hierarchy from them, i.e. subsume the subtype under its supertype
- Row polymorphism is similar to subtyping but solves the information loss issue (technically, however, there is no subtype relationship anymore, so it isn't a form of subtyping)
- Bounded polymorphism states that two types are equivalent for a specific purpose only, e.g. equality, order, mapping-over etc.
For what I have read about Flowtype I am pretty sure that your function is the problem.
If you do this instead:
This works because of bounded polymorphism. Now the body type checks under the assumption that T is a subtype of O. Furthermore, no information is lost across call sites. Read more about it here.
Also, I hadn't heard of row polymorphism before, so I went and looked it up. While looking it up I have read several things that seem to indicate that row polymorphism isn't subtyping. 1, 2, 3.
To expand on this answer and to clarify why the OPs function doesn't work but the one that I proposed will work correctly. This is a nice reference as well but is specific to Java.
By having the function as:
The function specifies that it is explicitly looking for an object of type O, rather than an object of O or a subtype of O is allowed. In the OPs function we are downcasting the parameter o to type O, rather than using a generic (this is bad). The proper way to handle this is utilizing generics to specify that it can be type O or subtype of O, which can be done as follows:
Check out the docs on how flow handles generics and how it is related to function parameters, object, etc..
The relevant part is: