The Life of a Programmer

Optional Types and Leaf: Removing automatic dereference

Optional values are one of the fundamental extrinsic types in Leaf, as are shared values. Originally optional values were used just like regular values, dereferenced automatically. I was always uncertain of that. It made the code simple but it didn’t hold up to a promise of safe code. It was essentially a null pointer error in disguise. I’ve since removed the automatic dereferencing.

Quick syntax introduction

Optional values can be created using the ? operator. Unlike the traditional cond ? true_val : false_val ternary operator, Leaf splits this up into two fundamental operators.

1
2
var a : integer = 5
var b = cond() ? a

The above creates an optional integer value, either it is 5 or unset.

1
var c = b | 10

The | is a default operator. The above expression will be the value of b if set, or 10 if not set. Together these look like the traditional ternary operator:

1
var d = cond() ? 5 | 10

If cond() is true then d == 5 otherwise d == 10. By splitting up the syntax we get the more generic ? and | operators. These help integrate optional types fully into the language.

Type declaration

The syntax for declaring an optional in Leaf is simply add optional to the type:

1
2
var a : integer optional
var b : pine optional

It can also be added to an inferred type, for example in the case of a parametric function that can take any optional argument:

1
defn leaf = ( a : optional ) -> { ... }

Dereferencing

Originally I had the language automatically derefernce optionals. In the below function the optional arguments could be used directly.

1
2
3
defn leaf = ( a : integer, b : optional integer ) -> {
    return a + b
}

The same could be done for fields in a class or tuple.

1
2
3
4
5
6
7
typedef point : [
    x : integer
    y : integer
]

var p : optional point
std.print(p.x)

The above would raise an error since p has no value (the default of any optional type is to not have a value).

As convenient as this was, it started to feel like it was just hiding errors. I decided this automatic dereferencing should be removed.

Using the | operator

The | can be used to avoid any explicit dereferncing.

1
2
3
defn leaf = ( a : integer, b : optional integer ) -> {
    return a + (b | 5)
}

The expression b | 5 will use the value of b if available, or 5 if it is not set. This is the desired way to work with optionals. The compiler doesn’t let you ignore the fact that b may not be defined, thus no error is now possible.

The future ?. operator

It’s not quite complete when dealing with fields however:

1
2
var p : optional point
str.print( p.x | 5 )

This will still raise an error as p.x cannot be resolved. Without a terse way to deal with this we’d end up with syntax bloat on optionals. I am going to add an optional operator ?. to deal with this:

1
std.print( p?.x | 5 )

p?.x evaluates to the value of x if p has a value, otherwise it evalutes to an optional unset value. This will work in a chain as well:

1
std.print( a?.b?.c?.d | 5 )

Explicit dereference

There are some situations where an explicit dereferencing of the optional are required. For that I added the unopt operator.

1
has(p) then std.print( unopt(p).x )

unopt(p) raises an error if p is not set, that’s why I check if with has(p) first. I’m not entirely certain of the name unopt yet, and it might still be changed. I just wanted to start with an unambiguous name.

Please join me on Discord to discuss, or ping me on Mastadon.

Optional Types and Leaf: Removing automatic dereference

A Harmony of People. Code That Runs the World. And the Individual Behind the Keyboard.

Mailing List

Signup to my mailing list to get notified of each article I publish.

Recent Posts