Jekyll2017-11-17T18:21:37-05:00/Kevin Clancy’s BlogA blog about programming languages and computer science.Monotonicity Through Types2017-11-09T14:05:41-05:002017-11-09T14:05:41-05:00/2017/11/09/monotonicity-through-types<p>A partially ordered set is a set <script type="math/tex">P</script> endowed with a binary relation <script type="math/tex">\leq</script> on <script type="math/tex">P</script> such that for all <script type="math/tex">p, q, r \in P</script> we have:</p>
<p>1.) <script type="math/tex">p \leq p</script> (reflexivity)</p>
<p>2.) <script type="math/tex">p \leq q</script> and <script type="math/tex">q \leq r</script> implies <script type="math/tex">p \leq r</script> (transitivity)</p>
<p>3.) <script type="math/tex">p \leq q</script> and <script type="math/tex">q \leq p</script> implies <script type="math/tex">p = q</script> (anti-symmetry)</p>
<p>If <script type="math/tex">P</script> and <script type="math/tex">Q</script> are partially ordered sets, we say that a function <script type="math/tex">f : P \to Q</script> between them is monotone if for all <script type="math/tex">p_1, p_2 \in P</script> with <script type="math/tex">p_1 \leq p_2</script>, we have <script type="math/tex">f(p_1) \leq f(p_2)</script>.</p>
<p>Several recent research papers (for example <a href="http://christophermeiklejohn.com/publications/ppdp-2015-preprint.pdf">Lasp</a> and <a href="http://www.neilconway.org/docs/socc2012_bloom_lattices.pdf">BloomL</a>) propose programming frameworks which utilize monotone functions as primitives of program composition, but they provide the user with a fixed set of monotone functions to work with. A type system capable of proving program functions monotone may enable the development of extensible versions of such frameworks.</p>
<p>Lately, I’ve been designing such a type system, for an extension of the simply typed lambda calculus. Since the programmer only cares about the monotonicity of a select group of functions, a special syntax construct, the <em>sfun abstraction</em>, serves as a signal to the type checker: unlike the simply typed world outside of the sfun abstraction, the body of the sfun abstraction is type checked using a special type system, which I call the lifted type system, in which monotonicity is tracked.</p>
<p>Reasoning about pointwise orderings on function spaces seems a bit heavy-weight and hasn’t been necessary for any of my use cases. An sfun is therefore first order; that is, both its return type and all of its argument types must be data types rather than function types. We would like to be able to prove that a multi-argument function is monotone <em>separately</em> in each of its
arguments; that is, for <script type="math/tex">i \in 1..n</script>, if <script type="math/tex">p_i \leq p_i'</script> then <script type="math/tex">f(p_1, \ldots, p_i, \ldots, p_n) \leq f(p_1, \ldots p_i', \ldots p_n)</script>, etc.</p>
<p>The monotonicity of an sfun is typically derived from the monotonicity of the primitives used to implement it, which are also sfuns. Here are some example sfun primitives, addition and subtraction on integers:</p>
<p>1.) plus : <script type="math/tex">(x : Int, y : Int) \Rightarrow Int[\uparrow x, \uparrow y]</script></p>
<p>2.) minus : <script type="math/tex">(x : Int, y : Int) \Rightarrow Int[\uparrow x, \downarrow y]</script></p>
<p>An <em>sfun type</em>, written with <script type="math/tex">\Rightarrow</script> rather than <script type="math/tex">\rightarrow</script>, names its formal arguments and also <em>qualifies</em> each one. A qualifier is an argument-specific constraint on the behavior of the function. In the above types, the qualifier <script type="math/tex">\uparrow</script> is associated with arguments that are separately monotone and <script type="math/tex">\downarrow</script> is associated with arguments that are separately antitone. The second argument of a binary function <script type="math/tex">f</script> is separately antitone if <script type="math/tex">p_2 \leq p_2'</script> implies <script type="math/tex">f(p_1, p_2) \geq f(p_1, p_2')</script>.</p>
<p>Terms outside of sfun abstractions are typed using a <em>global</em> typing relation,
which, aside from an sfun abstraction typing rule, is not different from the
typing relations we are familiar with. A global typing judgment has the following form.</p>
<script type="math/tex; mode=display">\Gamma \vdash t : T</script>
<p>A typing judgment of the lifted type system, used to type check the body of an sfun, has the following form:</p>
<script type="math/tex; mode=display">\Gamma;\Omega;\Phi \vdash t : T</script>
<p>Here the <em>global type environment</em> <script type="math/tex">\Gamma</script> contains all of the variables bound outside of the sfun, the <em>ambient type environment</em> <script type="math/tex">\Omega</script> contains the list of the sfun’s formal arguments, and the
<em>lifted type environment</em> <script type="math/tex">\Phi</script> contains those variables in <script type="math/tex">t</script>’s context which are bound inside the sfun. Before getting into the significance of lifted typing judgments, let’s look
at a specific instance of the global typing rule for sfun abstractions, which uses a single lifted premise.</p>
<script type="math/tex; mode=display">\frac{\Gamma;x:Int;x:Int[=~x] \vdash plus(x,x) : Int[\uparrow~x]}
{\Gamma \vdash \tilde{\lambda} x : Int. plus(x,x) : ( x : Int ) \Rightarrow Int[\uparrow~x]}</script>
<p>Here we type a single-argument sfun abstraction <script type="math/tex">\tilde{\lambda} x:Int. plus(x,x)</script>. As you might
have guessed, <script type="math/tex">\tilde{\lambda}</script> is used rather that <script type="math/tex">\lambda</script> to distinguish this as an
sfun abstraction rather than a standard one. Examine the ambient and lifted type environments
used in the premise. Perhaps surprisingly, the abstraction’s bound variable <script type="math/tex">x</script> is entered into both environments. When variables occur in types, they are considered references to formal arguments
rather than actual arguments; that is, an occurrence of <script type="math/tex">x</script> in a type (for example <script type="math/tex">Int[\uparrow x]</script>) does not refer to some integer, but instead a “slot” named <script type="math/tex">x</script> which expects to receive some integer from an external source.
Inside the scope of the sfun abstraction, we would like the ability to refer to the abstraction’s formal argument <script type="math/tex">x</script>, and therefore we add <script type="math/tex">x : Int</script> to the ambient environment.
We would also like to include occurrences of <script type="math/tex">x</script> as terms in the body of the abstraction; for these, we add the entry <script type="math/tex">x : Int[=~x]</script> into the lifted type environment, to be used as a
placeholder for the actual argument supplied to the formal argument <script type="math/tex">x</script>. Because references to formal arguments occur only in types, and references to actual arguments occur only in terms,
we can add entries with the same name to both the ambient and lifted environments without creating any ambiguity.</p>
<p>The premise of the above rule application includes the strange looking types <script type="math/tex">Int[=~x]</script> and <script type="math/tex">Int[\uparrow~x]</script>.
Normally, we would expect occurrences of x, which serve as placeholders for the actual argument
of the the function, to have type <script type="math/tex">Int</script>, and we would expect our abstraction’s body <script type="math/tex">plus(x,x)</script> to
have type <script type="math/tex">Int</script> as well. This traditional approach to typing a function abstraction
characterizes the operational behavior of a single function <em>after</em> it has been applied.
Unfortunately, this isn’t adequate for reasoning about properties such as monotonicity,
which involve multiple calls to the same function. My approach instead takes the
perspective of inside of a function, <em>before</em> it has been applied. Lifted typing then
characterizes the structure of a function as the composition of its constituent parts.
In the above example, an occurrence of the variable <script type="math/tex">x</script> in the term <script type="math/tex">plus(x,x)</script>
has type <script type="math/tex">Int[=~x]</script>, meaning that it is a function which takes the value provided to <script type="math/tex">x</script>
(the enclosing sfun’s formal argument) as an input, and produces that value unchanged
as a result. We ultimately care about the input/output relation of this function,
and so the concrete values which inhabit this type are set-of-pairs function representations.
The type <script type="math/tex">Int[=~x]</script> happens to be a singleton type, containing the set of pairs
<script type="math/tex">\{ (0,0), (1,1), (-1,-1), (2,2), (-2-2), \ldots \}</script>.</p>
<p>The sfun application <script type="math/tex">plus(x,x)</script> is viewed as a function composition,
where the outputs of the functions represented by the two occurrences of <script type="math/tex">x</script>
are forwarded into the left and right arguments of the sfun <script type="math/tex">plus</script>. The domain
of this composite function matches the domain <script type="math/tex">x:Int</script> of the enclosing sfun, which it inherits from
the two occurrences of <script type="math/tex">x</script>. Since <script type="math/tex">plus</script> returns an <script type="math/tex">Int</script>, so does the
composite function. The premise of the above typing rule application tells
us that <script type="math/tex">plus(x,x)</script> has type <script type="math/tex">Int[\uparrow~x]</script>, but this premise must
be derived. How do we go about proving that the composite function
<script type="math/tex">plus(x,x)</script> is monotone?</p>
<p>First, pretend that the two occurrences of <script type="math/tex">x</script> reference different formal arguments
<script type="math/tex">x_1</script> and <script type="math/tex">x_2</script>. Holding the left formal argument fixed gives a single-argument
function <script type="math/tex">plus(-,x_2)</script>, which the type signature of <script type="math/tex">plus</script> tells us must
be monotone.
<script type="math/tex">x_1</script>, representing the identity function on integers, is clearly monotone,
since for all integers <script type="math/tex">z_1, z_2</script> with <script type="math/tex">z_1 \leq z_2</script>, we have
<script type="math/tex">id(z_1) = z_1 \leq z_2 = id(z_2)</script>. <script type="math/tex">plus(x_1, x_2)</script> is then the composition
of two monotone functions, which itself must be monotone. The same reasoning tells
us that <script type="math/tex">plus(x_1,x_2)</script> is monotone as a function of <script type="math/tex">x_2</script> when <script type="math/tex">x_1</script>
is held fixed. <script type="math/tex">plus(x_1, x_2)</script> is therefore monotone separately
in both <script type="math/tex">x_1</script> and <script type="math/tex">x_2</script>. However, we are interested in <script type="math/tex">plus(x,x)</script>,
which is the function we get when we contract <script type="math/tex">x_1</script> and <script type="math/tex">x_2</script> into a single
argument, supplying both of <script type="math/tex">plus</script>’s “slots” with the same value.
Contracting two arguments of a function which are both separately monotone
results in a new argument which is also separately monotone, and so we
can conclude that <script type="math/tex">plus(x,x)</script> has type <script type="math/tex">Int[\uparrow~x]</script>.</p>
<p>The lifted sfun application typing rule utilizes two binary operators
<script type="math/tex">\circ</script> and <script type="math/tex">+</script> on qualifiers, which describe how monotonicity
is propagated across function composition and argument contraction.
The above example utilized the facts that <script type="math/tex">= \circ \uparrow</script> is equal to
<script type="math/tex">\uparrow</script> and <script type="math/tex">\uparrow + \uparrow</script> is equal to <script type="math/tex">\uparrow</script>.
These operators are defined as lookup tables, recording a set of predefined facts
about the propagation of monotonicity.</p>
<p>I’ll wrap things up by leaving you
with one of the central features of my calculus.
Namely, that the “global world” outside of an sfun abstraction is viewed
as a degenerate subset of the “lifted world” inside the sfun abstraction.
A globally well-typed sfun application is viewed as a projection onto this
degenerate subset. Inside the sfun abstraction, we track the way in which each term depends
on the sfun’s arguments, but terms originating outside of the sfun
(both literal constants and occurrences of variables from the global type environment <script type="math/tex">\Gamma</script>)
depend on the sfun’s arguments in a specific way: they are not affected by them
at all. So, for any sfun with ambient environment <script type="math/tex">\Omega</script>,
we can view the literal integer <script type="math/tex">1</script> as a constant-valued
function which, given any valuation of <script type="math/tex">\Omega</script>, produces the value one
as a result. Of course, constant functions are monotone, and so a lifted subtyping relation
allows 1 to occur in any context where Integer-valued functions with monotone
dependence on the ambient environment <script type="math/tex">\Omega</script> are expected.
I view this as a weird refinement type system. Instead of starting
with a simply typed system and decomposing its base types into preorders to induce a
subtyping relation, I started with a simply typed system and positioned its base
types as refinements of base types in a system with larger types (larger in that
they denote larger sets of values).
Consider the ambient environment <script type="math/tex">\Omega = x:Int</script>. Letting
<script type="math/tex">Int[?~x]</script> be the type of Int-valued functions which share the enclosing sfun’s formal parameter <script type="math/tex">x</script>, the following diagram decomposes the type <script type="math/tex">Int[?~x]</script> into refinements.</p>
<p><img src="/assets/DiagramArrows.png" alt="Refinement diagram" /></p>
<p>The red arrows indicate that projection from <script type="math/tex">Int[?~x]</script> into the refinement
<script type="math/tex">Int</script> plays a special role. Still curios? See <a href="https://infoscience.epfl.ch/record/231867/files/monotonicity-types.pdf">this paper</a> for motivating examples, a full formalization, and a soundness proof.</p>A partially ordered set is a set endowed with a binary relation on such that for all we have:A Class System for Typed Lua2016-08-18T13:04:58-04:002016-08-18T13:04:58-04:00/2016/08/18/a-class-system-for-typed-lua<p>I spent this summer implementing a class system for Typed Lua, as part of the Google Summer of Code 2016. To try out the new class sytem, clone my fork of the TypedLua repository, cd to the root directory of the cloned repository, and run the my modified version of the Typed Lua compiler via “tlc <em>source_filename</em>”.</p>
<p>I haven’t merged any of my changes yet, and still plan to clean up some of the code within the next few weeks. Below is a description of the features that I have added.</p>
<h1 id="the-basics">The Basics</h1>
<p>The following syntax is used to declare a class</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>class Vector2
x : number
y : number
constructor new(x : number, y : number)
self.x = x
self.y = y
end
method DotProduct(other : Vector2) : number
return self.x*other.x + self.y*other.y
end
method Offset(offset : Vector2)
self.x = self.x + offset.x
self.y = self.y + offset.y
end
end
</code></pre></div></div>
<p>The Vector2 class first declares two data members x and y, both having Lua’s 64-bit floating-point type number.</p>
<p>Next, it declares a constructor named new. The body of the constructor must assign values to all data members that the class defines. Unlike languages such as Java, where all variables can be assigned to a nil value, variables in Typed Lua are generally non-nil, and so leaving data members with a the default value nil would not be safe.</p>
<p>Finally, two methods called DotProduct and Offset are defined. Notice that the x and y fields of the invoked object are accessed via the <em>self</em> variable. This class system does not yet have encapsulation, so we can access the x and y fields of <em>other</em> externally. Omitting a return type annotation on a method declaration sets the expected return type to an infinite tuple of nils, which is appropriate for methods which, like Offset, do not return values.</p>
<p>Now let’s try instantiating the class and calling some of its methods.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>local v = Vector2.new(1,0)
local u = Vector2.new(0,1)
print(u:DotProduct(v))
</code></pre></div></div>
<p>A class declaration adds a <em>class value</em> of the same name into scope. A class value is a table which contains all of the class’s constructors. In this case, our class table is <em>Vector2</em>, which contains one constructor <em>new</em>. Running tlc on the concatenation of the above code blocks should produce a lua file which prints 0 when it is run.</p>
<h1 id="inheritance">Inheritance</h1>
<p>We can inherit using the <em>extends</em> keyword. Inheritance creates a child class which reuses code from the parent class and has an instance type that is a subtype of the parent class’s instance type.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>local debugVectorId = 0
class DebugVector2 extends Vector2
const id : string
constructor new(x : number, y : number)
self.x = x
self.y = y
self.id = tostring(debugVectorId)
debugVectorId = debugVectorId + 1
print("DebugVector #" .. self.id .. " created with x = " .. tostring(x) .. " and y = " .. tostring(y))
end
method Offset(offset : Vector2)
local strInitial = "(" .. tostring(self.x) .. ", " .. tostring(self.y) .. ")"
self.x = self.x + offset.x
self.y = self.y + offset.y
local strFinal = "(" .. tostring(self.x) .. ", " .. tostring(self.y) .. ")"
print("DebugVector #" .. self.id .. " offset from " .. strInitial .. " to " .. strFinal)
end
end
v = DebugVector2.new(1,0)
u = DebugVector2.new(0,1)
print(v:DotProduct(u))
u:Offset(v)
print(v:DotProduct(u))
</code></pre></div></div>
<p>Vector2’s fields x and y, as well as the methods Offset and DotProduct, are automatically included into the DebugVector2 class. However, DebugVector2 overrides the Offset with its own implementation.</p>
<p>Concatenating the above code block and then running the result should give the following output.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>DebugVector #0 created with x = 1 and y = 0
DebugVector #1 created with x = 0 and y = 1
0
DebugVector #1 offset from (0, 1) to (1, 1)
1
</code></pre></div></div>
<h1 id="the-super-keyword">The super keyword</h1>
<p>We can improve the above code by reusing the functionality of the parent class via the <em>super</em> reference.</p>
<p>A constructor may call exactly one superclass constructor on its first line using the syntax “super.constructorname(arguments)”. If this happens, then the child class constructor does not need to initialize inherited members.</p>
<p>A superclass method may be called from anywhere inside a child class method, using the syntax “super:<em>methodname(arguments)</em>”.</p>
<h1 id="interfaces">Interfaces</h1>
<p>We may want to declare a type, without any associated implementation, that describes a set of operations which several classes share in common. Such a type is called an <em>interface</em>, and is declared using the following syntax.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>interface Showable
method ToString() : () => (string)
end
</code></pre></div></div>
<p>We can associate a class to an interface by using an <em>implements clause</em>:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>class Vector2 implements Showable
...
method ToString() : string
return "(" .. self.x .. ", " .. self.y .. ")"
end
...
end
</code></pre></div></div>
<p>Instances of class Vector2 can then be used in contexts where instances of the Showable interface are expected.</p>
<h1 id="typedefs">Typedefs</h1>
<p>Typedefs are a lightweight alternative mechanism for defining types. Consider the following type, which represents a linked list of numbers.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>typedef NumList = { "val" : number, "next" : NumList }?
</code></pre></div></div>
<p>Here we have defined a typedef called NumList, which expands to { val : number, next : NumList }?. Like classes and interfaces, typedefs may contain recursive references to themselves. This can be useful for defining data-description schemas and data structures of unbounded size. NumList, for example, describes finite number lists of any size; nil, { val = 1, next = nil }, and { val = 1, next = { val = 1, next = nil } } are each NumLists. They represent the empty list, [1], and [1,1] respectively.</p>
<p>There’s an important difference between classes and interfaces on the one hand, and typedefs on the other. Classes and interfaces are <em>nominal</em> types, whereas typedefs are <em>structural</em> ones. Subtyping relations between nominal types are declared explicitly by the programmer, whereas subtyping relations between structural types are deduced by the subtyping algorithm. For example, this implies that instances of the following two classes are not interchangeable:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>class Foo1
x : boolean
constructor new(x : boolean)
self.x = x
end
end
class Foo2
x : boolean
constructor new(x : boolean)
self.x = x
end
end
</code></pre></div></div>
<p>In fact, passing an instance of Foo1 into a function that expects an instance of Foo2 will generate a type error:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>local fooFunction(foo : Foo2) : boolean
return foo.x
end
local f1 = Foo1.new(true)
fooFunction(f1)
</code></pre></div></div>
<p>The following code block, however, is considered well-typed by the Typed Lua compiler.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>typedef NumList1 = { "val" : number, "next" : NumList1 }?
typedef NumList2 = { "val" : number, "next" : NumList2 }?
local function listFunction(l1 : NumList1)
local l2 : NumList2 = l1
if l2 then
print(tostring(l2.val) .. "\n")
listFunction(l2.next)
else
print("done.")
end
end
</code></pre></div></div>
<h1 id="mutually-recursive-types">Mutually recursive types</h1>
<p>By default a type definition can only refer to previously defined types. But what if we want to have two types A and B such that A is defined in terms of B and B is defined in terms of A? One of these types must be defined first; how can it reference the other one?</p>
<p>To handle this, several mutually recursive type definitions can be chained together with the <em>and</em> keyword, as in the following code block.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>typedef WeirdList1 = { "val" : boolean, "next" : WeirdList2 }?
and typedef WeirdList2 = { "val" : number, "next" : WeirdList1 }?
</code></pre></div></div>
<p><em>and</em> can be used to join an arbitrary collection of typedefs, classes, and interfaces into a mutually recursive bundle.</p>
<h1 id="global-typenames-and-aliases">Global typenames and aliases</h1>
<p>When a type is declared, it is given both a <em>global name</em> and an <em>alias</em>. The global name is the name specified by the programmer, with the name of the module containing the definition prepended to it. The global name of a class Foo defined in a module Bar is Bar.Foo. Once a global name is generated, it lasts throughout the duration of the type-checking process. Referring to all types by their global names would be tedious, and so an alias mechanism is also provided. An alias is a short name that is translated into a global name. All type definitions generate an alias, associating the name that the programmer typed with the generated global name. Our Foo class would generate an alias Foo that maps to the global name Bar.Foo.</p>
<p>Unlike global names, aliases are locally scoped. In particular, they are not preserved across multiple files. Types defined in external files must therefore be referred to by their global names.</p>
<h1 id="class-value-lookup">Class value lookup</h1>
<p>The expression class(<em>typename</em>) will evaluate to the class value of the class named <em>typename</em>. This is useful for instantiating classes defined in external modules.</p>
<p>A module Foo could define a class Test:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>class Test
constructor new()
end
end
</code></pre></div></div>
<p>and then Test could be instantiated in a module Bar as follows:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>require("Foo")
local t = class(Foo.Test).new()
</code></pre></div></div>
<h1 id="generic-classes">Generic classes</h1>
<p>Classes, interfaces, functions, and methods can all be parameterized by types, a feature often called <em>generics</em>. Among other things, this allows us to create collections that are parameterized by their element types.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>class Stack<ElementType>
contents : {ElementType}
constructor new()
self.contents = {}
end
method push(x : ElementType)
self.contents[#self.contents + 1] = x
end
method pop() : ElementType
local ret = self.contents[#self.contents]
self.contents[#self.contents] = nil
assert(ret)
return ret
end
end
</code></pre></div></div>
<p>NOTE: The above code snippet will not compile in my fork, because assertions have not been integrated with occurrence typing. This functionality has been implemented in a separate fork.</p>
<p>A class’s type parameters appear in a comma-separated list between angle brackets, after the class name. The above Stack class contains a single type parameter called ElementType. We can instantiate the above class as follows:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>local stack = Stack.new<number>()
stack.push(3)
print(stack.pop())
</code></pre></div></div>
<p>The type parameters of a class definition are wrapped around each of its constructors. We can instantiate these parameters by providing type arguments between angle brackets before the constructor’s normal arguments. In the above code, we instantiate ElementType with number. This produces an instance whose type matches the instance type described by Stack, but with each occurrence of ElementType replaced with number.</p>
<h1 id="generic-functions-and-methods">Generic functions and methods</h1>
<p>Programmers may define their own type-parameterized functions. This can be useful for standard library functions.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>local function init_array<ElementType>(x : ElementType, size : number) : {ElementType}
local ret : {ElementType} = {}
for i = 1, size do
ret[i] = x
end
return ret
end
local r = init_array<string>("thumbs up", 2)
for i=1,#r do
local s = r[i]
if s then
print(s)
end
end
</code></pre></div></div>
<p>We can also provide type parameters for methods.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>class Foo
method id<T>(x : T) : T
return x
end
end
interface Bar
method id<T> : (T) => (T)
end
</code></pre></div></div>
<h1 id="type-parameter-bounds">Type parameter bounds</h1>
<p>We can restrict type parameters so that they can only be instantiated with types which are a subtype of a specified type. This specified type is referred to as the bound of the type parameter that it is attached to. Suppose that we have the following interface:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>interface Showable
method Show : () => (string)
end
</code></pre></div></div>
<p>We can use type parameter bounds to implement a function which prints out a string representation of all elements of an array, assuming that the element type of the array implements the Showable interface.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>local function show_array<ElementType <: Showable>(x : {ElementType})
for i=1,#x do
local e = x[i]
if e then
print(e:Show())
end
end
end
</code></pre></div></div>
<p>As the above example demonstrates, we attach a bound to a type parameter by writing “<: BoundType” after the type parameter’s occurrence in the parameter list.</p>
<h1 id="generic-subtyping">Generic subtyping</h1>
<p>We can inherit from and implement generic classes.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>interface Container<ElementType>
method GetElements : () => ({ElementType})
end
class Array<ElementType> implements Container<ElementType>
contents : {ElementType}
constructor new(contents : {ElementType})
self.contents = contents
end
method GetElements() : {ElementType}
return self.contents
end
end
</code></pre></div></div>
<p>An implements clause generally consists of a nominal type applied to arbitrary type arguments which may include occurrences of the parameters of the class being defined. When class parameters occur in the implements clause, a family of subtyping relations is generated; the above implements clause implies that for <em>all</em> types ElementType, Array<ElementType> is a subtype of Container<ElementType>.</p>
<p>As the next example demonstrates, the parameters of a class do not have to correspond to the parameters of the nominal types that it implements. The below class definition implies that for all pairs of types KeyType and ValueType, Map<KeyType, ValueType> is a subtype of Container<ValueType>.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>class Map<KeyType, ValueType> implements Container<ValueType>
contents : { any : ValueType }
constructor new()
self.contents = {}
end
method set(key : KeyType, val : ValueType)
self.contents[key] = val
end
method get(key : KeyType) : ValueType?
return self.contents[key]
end
method GetElements() : {ValueType}
local ret : {ValueType} = {}
for k,v in pairs(self.contents) do
ret[#ret + 1] = v
end
return ret
end
end
</code></pre></div></div>
<p>The arguments to a nominal type constructor occurring in an implements clause can be <em>arbitrary</em> types, both structural and nominal. The next example demonstrates the use of a structural type, namely Point2, as an argument in an implements clause.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>typedef Point2 = { x : number, y : number }
class Polygon implements Container<Point2>
points : {Point2}
constructor new(points : {Point2})
self.points = points
end
method GetElements() : {Point2}
return self.points
end
end
</code></pre></div></div>
<h1 id="recursive-inheritance-and-shapes">Recursive inheritance and shapes</h1>
<p>Suppose we want to bound a type parameter in a way that requires values of its argument to be comparable to each other. We might use the following interface, which describes types that are comparable to some type T.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>interface Ordered<T>
method lessThanEq : (T) => (boolean)
end
</code></pre></div></div>
<p>Then we would implement this interface with the following class.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>class NumSet implements Ordered<NumSet>
contents : { number : boolean? }
method lessThanEq(other : NumSet) : boolean
for k,v in pairs(self.contents) do
if not other.contents[k] then
return false
end
end
return true
end
constructor new(contents : { number : boolean? })
self.contents = contents
end
end
</code></pre></div></div>
<p>However, the compiler generates an error from the above implements clause. The reason for this is that it creates a cycle in the <em>type graph</em>. The type graph is a graph in which nominal typenames are nodes. Every implements and extends clause adds an edge from the class being defined (NumSet in this case), to every nominal typename occurring in the implements or extends clause (here this includes both Ordered and NumSet) labeled with the name of the outer nominal type of the implements or extends clause (Ordered in this case). To ensure that our subtyping algorithm terminates, we forbid any implements or extends clause which introduces cycles into this graph.</p>
<p>Still, there’s a need for recursive inheritance as described above. To deal with this, we include another kind of nominal type definition in addition to classes and interfaces: shapes. A shape definition is exactly like an interface definition, but the word interface is replaced with shape:</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>shape Ordered<T>
method lessThanEq : (T) => (boolean)
end
</code></pre></div></div>
<p>When searching for cycles in our type graph, we ignore those edges that are labeled with shapes. After changing Ordered to a shape, the NumSet class should compile without producing any errors. Occurrences of shape types are restricted to the outer level of type bounds and implements clauses; for a somewhat technical reason, this ensures that our subtyping algorithm terminates even when our type graph has cycles which include shape edges.</p>
<p>To utilize recursively inheriting types, we use type bounds which refer to the variable being bounded.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>local comparable<T <: Ordered<T>>(x : T, y : T) : boolean
return (x:lessThanEq(y) or y:lessThanEq(x))
end
</code></pre></div></div>
<h1 id="variance-annotations">Variance annotations</h1>
<p>To give nominal types greater subtyping flexibility, we allow the user to provide variance annotations for the type parameters of classes, interfaces, and shapes. Prefixing a type parameter with + designates that the parameter is covariant, whereas prefixing a parameter with - designates that it is contravariant.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>interface Describable<+DescriptionType>
method Describe : () => (DescriptionType)
end
</code></pre></div></div>
<p>The above interface indicates that its type parameter DescriptionType is covariant by prefixing it with a +. What this implies for subtyping is that if A and B are types and B is a subtype of A, the Ord<B> is a subtype of Ord<A>. Covariant means <em>with change</em>. A covariant parameter is one whose subtyping precision changes in the same direction as the type that contains it; Ord<B> is more precise than Ord<A> only when B is more precise than A.</p>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>interface Accumulator<-ValueType>
method Accumulate : (ValueType) => ()
end
</code></pre></div></div>
<p>Here is an interface for classes which describe objects which can “absorb” values of some type ValueType. ValueType has been marked as contravariant by prefixing it with a -. Contravariance means <em>against change</em>. A contravariant type parameter is one whose subtyping precision changes in the opposite direction of the subtyping precision of the type that contains it; if B is a subtype of A then Accumulator<A> is a subtype of Accumulator<B>.</p>
<p>The positions of the occurrences of a type parameter in a type definition dictate which variance annotations it can have. Roughly, types variables occurring only in input positions are allowed to be contravariant, whereas type variables occurring only in output positions are allowed to be covariant.</p>
<p>DescriptionType is allowed to be covariant because it occurs as a method return type, and classifies values that instances of the class are outputting into an external context. Suppose DetailedDescription is a subtype of Description; then retrieving a DetailedDescription from the Describe method of Describable<Description> is perfectly acceptable, because we were expecting a Description, and values of type DetailedDescription can be used in any context where values of type Description are expected.</Description></p>
<p>On the other hand, ValueType is allowed to be contravariant because it occurs as a method input type. Suppose OddInteger is a subtype of Integer. Then passing an OddInteger into the Accumulate method of an object of type Accumulator<Integer> is perfectly acceptable, and so we can use an object of type Accumulator<Integer> where an object of type Accumulator<OddInteger> is expected.</p>
<h1 id="additional-features">Additional features</h1>
<ul>
<li>A class can implement multiple interfaces by using a comma-separated list for an implements clause.</li>
</ul>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>class Foo extends Interface1, Interface2
</code></pre></div></div>
<ul>
<li>A nominal subtyping edge may be added without introducing any new nominal type definitions using an implements statement.</li>
</ul>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>...
a = 5
Foo implements Bar
a = a + 1
</code></pre></div></div>
<ul>
<li>A class may be used as an interface</li>
</ul>
<div class="highlighter-rouge"><div class="highlight"><pre class="highlight"><code>class Foo ... end
class Bar implements Foo ... end
</code></pre></div></div>
<ul>
<li>Failed subtyping queries now generate explanations of why the specified subtyping judgment does not hold. These can currently be a bit messy, which is something I hope to improve soon.</li>
</ul>I spent this summer implementing a class system for Typed Lua, as part of the Google Summer of Code 2016. To try out the new class sytem, clone my fork of the TypedLua repository, cd to the root directory of the cloned repository, and run the my modified version of the Typed Lua compiler via “tlc source_filename”.