Subtyipng
This document is bound to change once we have a proper writeup of row polymorphism
Subtyping
An argument about the extent of subtyping
Subtyping is an important part of any module or object system. The
rules these systems usually care about revolve around the subtype of
Records
and Records
in relation to functions.
About Subtyping Rules on Records
An example of a record subtyping is as follows, we will use a :> b
to
mean that a
is a subtype of b
∀x, a … v : Type. {x : Type, a : Type … v : Type} :> {a … v : Type}
∀x, a … v : Type. ∀ f : {a : Type … v : Type} → Type
f :> f : {x : Type, a : Type … v : Type} → Type
What 1 tells us is that we can forget a field, and thus a record with more fields is a sub type of the same record with less fields
What 2 tells us is that a function which accepts a record with fields
a
through v
can also accept a record with those fields and extra
fields. The intuition is that in functions the sub typing relation is
reversed, but we still need to forget information to get our
relationship.
Implications of Rules and Arguments about Behavior
These implications can be quite worrying, consider the longer-form example below. We will introduce all the forms first then make an argument about what various object systems have done and what we are proposing to do.
f0 : λX:>{x : int} -> x1 -> x2 -> x1
f0 rec1 rec2 = rec1 { x = rec1.x + rec2.x}
f1 : λX:>{x : int} -> X -> X -> X
f1 rec1 rec2 = rec1 {x = rec1.x + rec2.x}
f2 : λX:>{x : int} -> X -> X -> X
f2 rec1 rec2 = {x = rec1.x + rec2.x}
-- note that the t is opaque
sig set : int -> {x : t, type t, add : t -> t -> t}
mod set x =
type t = int
add = (+)
x
end
-- We include the set
sig bag : int -> {x : t, type t, add : t -> t -> t}
mod bag x = include (set x)
foo a b c =
let ab = a in
...
foo1 a b c =
let ab = new bag in
...
f3 : λX:>{x : int} -> X -> -> X
f3 rec1 = rec1 {x = rec1.x + rec1.x}
- NOTE
f0 uses a short hand notation. x1 and x2 are sub types of {x : int}.
The main functions we wish to discuss are f0, f1, and f2. In an object oriented system all three of these are permissible. This is due to in a sub typing relation these are all fine, we just end up with some subtype of {x : int}.
However this is a major issue, see Subtyping, Subclassing, and Trouble with OOP, written by Oleg for how insidious this can make errors.
Thus on {x : int}
, but in fact our return
type is {x : int}
. Therefore, we don't have the issue of the function
foo
in the article above, which we will now quickly detail.
In our code above, foo
and foo1
both return a derivative of the
ab
value. In an object oriented language the following type would be
applicable to both
λX:>bag X -> X -> X
The issue is that if we send in a set instead, then foo
gives us a
set, while foo1
would give us a bag. Thus in our type system
foo1
's signature would be
λX:>bag X -> X -> bag
However what was more contentious was what to do with f1 vs f0. Should we allow
f1 {x = 5; y = 3} {x = 1}
if f1 were f0 then obviously yes, as we know we get two seperate subtypes and return the first.
An argument for allowing this stems on the answer of This is how subtyping relations work. While a fair argument, we have concluded that either way is a valid way of working with sub typing, as we can merely check the two arguments are the same, then begin doing subtyping.
Another argument is around the flexibility of functors if we disallow the above example. However this argument was also flawed in that we still have a subtyping relation so f3 in our code block can be called like
f3 {x = 3; y = 4}
Thus the argument regarding functors with a single argument can be
disregarded. However, for multiple arguments the legitimacy of the rest
of the claim can be discredited by having a mechanism which allows the
user to specify that the two arguments are really subtypes of each
other. This explicit system still allows the mixup of bag
and set
in Oleg's article, however it must be done so explicitly and full
intention, with the most serious error still being impossible!
Finally the last claim for f1's legitimacy rests on flexibility of a
library user and the incorrect choices of the author. f1 should really
allow different subtypes, it just needs to specify it so! For this we
can gain back our expressiveness by adding an coerce
function which
forgets fields to make the two records the same, and thus legitimate
in f1
f1 {x = 5; y = 3} (coerce {x = 1})
-- or
f1 (coerce {x = 5; y = 3}) {x = 1}
Further Arguments/Unresolved Issues
As of
we still don't have a full answer for the following questionsHow do we handle usages
We have already have some arguments about ω and 0 usage items and how subtyping works there, however we still need to flesh out the mechanism for other usages and how subtyping will work in relation to usages inside records.
How do we handle refinements
What do we do with this information, forget it for the application and allow implicit subtyping of refinements?
Currently we plan on translating refinements to Σ's and thus this will not be the default behavior.