|
This is actually row polymorphism rather than structural subtyping. The difference is rather small, but important: Under structural subtyping (or any kind of subtyping, for that matter), the subtyping relation admits a rule called "subsumption", which can lead to information loss. Consider this behavior, under structural subtyping: fun f {x : real, y : real} = {x = 2 * x, y = 2 * y};
(* f : {x : real, y : real} -> {x : real, y : real} *)
f {x = 2.0, y = 3.0, z = 4.0};
(* it = {x = 4.0, y = 6.0}; *)
Above, when `f` is applied to a subrecord with an additional `z : real` field, the result is missing the extra field! This is because the subrecord was subsumed by the (super-) record type taken by `f`, leading to information loss. This is the reason why you often see people using up-casts in languages like Java (especially before generics were introduced) and Go (which is lacking generics for some befuddling reason): up-casts allow you to circumvent the type system and force a particular value to take a particular type, thus recovering the lost information, but can be unsafe.Under row polymorphism, however, you can achieve basically the same effect, but you get to save any unused/irrelevant type information inside of a row variable (the `..` in your example) and include it in the result. Then, no information is lost, and no safety is compromised: fun f {x : real, y : real, R} = {x = 2 * x, y = 2 * y, R};
(* f : {x : real, y : real, _ : ..a} -> {x : real, y : real, _ : ..a} *)
f {x = 2.0, y = 3.0, z = 4.0};
(* it = {x = 4.0, y = 6.0, z = 4.0}; *)
Users of languages like Lua or Python might be reminded of "duck typing", and not without reason: row polymorphism enables many of the same programming patterns/techniques/abstractions in typed languages that are enabled by duck "typing" in untyped ones. It's a shame that more typed languages don't incorporate the idea, because it's a really wonderful one, and it can lighten the relative "burden" of writing typed code in some cases :)The folks at INRIA has come up with some really neat stuff, and, if you haven't already, it's definitely worth it to check out their publications, too! |
And, indeed, "static duck typing" is exactly what I'm thinking about when I'm dealing with OCaml objects.
FWIW, while it was relatively obscure, TypeScript is currently popularizing this concept (albeit with a lot less type inference). Although they also refer to it as "structural subtyping":
https://www.typescriptlang.org/docs/handbook/type-compatibil...
But if I understand correctly, they're basically doing the same thing that's going on here - the only difference is that the ".." is implicit in any TS interface type. Or am I wrong?