Working with types and named constructors in Leaf

As types are a keystone in Leaf, working with them must be fluid and simple. I recently improved this area by adding named constructors and bare type names. The results are interesting.

1
2
3
4
5
6
7
8
var s = vector()

var t = typevector
var p = t.with_ends(3,5)

var rt = vector
var x = rt.with_angle
var q = x(0.5, 3)

Named constructors

Named constructors are what set me down this path. I noticed a lot of my non-Leaf code is littered with static functions that construct an object. I wanted to get this behavior into the core syntax.

This vector class demonstrates one use for named constructors:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
class vector {
    var x : float
    var y : float

    defn with_ends = ( x : float, y : float ) -> construct {
        this.x = x
        this.y = y
    }

    defn with_angle = ( r : float, len : float ) -> construct {
        this.x = math.cos(r) * len
        this.y = math.sin(y) * len
    }

    defn default = -> construct {
    }
}

Previously only a default constructor was available. I’ve left it in here so we can still create default initialized vector types. I’ll revisit later whether there should be a special syntax to keeping or excluding the default constructor.

I needed a way to call these constructors so I introduced type:

1
2
3
var p = typevector.with_ends( 3, 8 )
var q = typevector.with_angle( 0.5, 2 )
var r = typevector」()

I wrote the syntax before the implementation, leaving the question of what type actually is. I’ll get back to the redundancy a bit later.

type「」

Defining what type is requires us to go a bit into the confusing side of compilers and type systems. At first I implemented type as a special syntax to construct a class. It worked with default constructors, but didn’t expand well to named constructors. It was also quite inflexible.

I changed type to have an actual value. This would allow something like type「vector」 to exist on its own, without having to call it. It also meant I could add a .name property to describe the type. Being a value also meant it could be assigned.

1
2
3
4
var t = typevector
var p = t.with_ends( 3, 8 )

std.print([t.name,"\n"])

This may look like dynamic type construction, but it’s not. The trick is in what the inferred type of t is. It’s where the naming gets circular, t is of type type「vector」. The type token here is distinct from the type token used in the expression: one is a type name, the other is an expression keyword. Understanding this distinction, or what is actually happening here, won’t be necessary for most users of Leaf.

Since t is of type type「vector」 it’s easy for the compiler to understand t.with_ends. It’s resolved statically; there is no runtime resolution on the value of t. Objects of this type don’t even have a real value: they are zero-length.

Constructors are still functions

Look again at the syntax for a constructor:

1
2
3
4
defn with_ends = ( x : float, y : float ) -> () construct {
    this.x = x
    this.y = y
}

Other than the construct flag these are normal functions in the class. This means the following is also possible:

1
2
3
var t = typevector
var x = t.with_angle
var q = x( 0.5, 3 )

It again works without any kind of runtime resolution. t and x are typed strongly enough that the compiler knows exactly what to do at x( 0.5, 3 ).

As there is still no runtime value involved here, dynamic construction is not possible. The type type also has no common base class, so it’s not possible to convert to a more fundamental type. It can still be used as a parametric argument though:

1
2
3
4
defn tuple_init = ( x:float, y:float, t ) -> {
    return t(x, y)
}
var n = tuple_init( 1, 2, typevector.with_ends )

It’s more of just a curiosity here, as this tuple_init function is kind of pointless. The feature can be used to provide advanced parametric functions, container class support, as well as things like object pool. This aspect is not a special feature, it just naturally arises from how I implemented the rest of the type feature.

Removing the type「」 part

I don’t think anybody will be too exited about coding type「vector」. It’s a bit bulky.

Why do I need the type part at all? It’s because vector is not a name in the expression scope. It exists solely as a type name. The type「」 syntax switches into the type namespace, allowing us to find the vector name.

It goes beyond just the name: it’s the full type syntax at this point. You could also create a shared vector with this syntax:

1
var q = typevector shared.with_ends(1,5)

Sure, that’s great, but what if I just want a vector, which is a very common case:

1
var q = vector.with_ends(1,5)

To get this working I merged the type names into the normal scoped namespace. Only the intrinsic type name is a symbol though, so vector works, but vector shared doesn’t — which also couldn’t work due to parsing issues on the type syntax.

This helped clean up a big question mark in the compiler code. The type parser previously had a special lookup to add the core types, like integer. These are now proper scope symbols.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s