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.