Evlan
Type Definitions
Last updated at 2005/10/16 16:17:48 PDT by Temporal

Functions

Declaring the type of a function is sort of like defining a function which returns a type, but using "->" rather than "=>". For example:

add: (a: Scalar, b: Scalar) -> Scalar

This declares "add" as a function taking two scalars as parameters and returning a scalar.

If the function has only one parameter, you don't have to name it:

squareRoot: Scalar -> Scalar

You can actually use the input parameters to compute the result type:

random: (max: Integer, seed: Integer) -> Integer.InRange(0, max)
pickElement: (items: Array) -> items.Element

A function type A is a subclass of another function type B if and only if all of the following are true:

  • A and B have the same number of inputs.

  • The type of each input to B is a subclass of the type of the corresponding input to A.

  • The result type of A is a subclass of the result type of B.

Classes

Class definitions define types with a certain set of members. They look like object definitions, except that only the type of each member is given (not the value).

Vector3 = class of
   x: Scalar
   y: Scalar
   z: Scalar

As you would expect, for an object to satisfy this class, it must contain members "x", "y", and "z", each of which is a scalar.

Class A is a subclass of class B if and only if, for each member of B, A contains a member by the same name with a type that is a subclass of the type of the corresponding member in B.

At the time of this writing, I am still considering a number of questions related to class declarations:

  • Should objects have to explicitly declare that they satisfy a particular class, or is it enough to simply have all the right members of the right types? The former seems safer in theory (since it would not allow an object to "accidentally" implement a class), but in practice it could be very hard to determine if any two expressions evaluate to the same logical class.

  • Should classes have "static" fields? I believe they should, but I'm not sure what the syntax should be.

  • Should it be possible to define "default" values for members so that objects can optionally omit them? I am leaning against this, since it would probably lead to the exact kinds of poor design that are common in inheritance-based object models.

Intersections

Two types can be intersected to form a new type. A value which satisfies both of the intersected types satisfies the resulting intersection. This can be used to simulate concepts like multiple inheritance, but more often it is used to apply refinements to a type. For example, to declare an array of integers, you could intersect the type "Array" with the type "{Element <: Integer}". (In practice, there is a convenient function you can call to get a specific Array type, but that function is implemented using the intersection operator.)

At this time, the exact syntax for intersections has not been decided, but it will probably be a simple binary operator.

Unions

You can also declare types which are the union of two types. Any value which satisfies either of the two input types satisfies the union.

Unions can be tricky because you usually need some way to figure out which of the possible types a union variable contains before you can access it. One way to get around this is to only create unions of types where all the input types share some particular member, but each one defines a different range of values for that member. For instance, if all the input types had a member field called "typeId" and each input type declared that input type as having some specific, unique numeric value, then you could distinguish the types by checking their typeId. The compiler is smart enough to type-check this technique. (In practice, you would probably use atoms as your type ID, not numbers.)

At this time, the exact syntax for unions has not been decided, but it will probably be a simple binary operator.

evlan.org © Copyright 2003-2005 Kenton Varda
Powered by Io Community Manager, Evlan, and FreeBSD