Features

Leaf is a rich language with a lot of familiar features, as well as a lot of new features. Here we present a variety of the interesting, and already functioning, Leaf features. For a more complete list of the essential, but perhaps not-so-exciting features, take a look at the status page.

Inferred and partial typing

The type of a variable can generally be inferred by the value it is assigned.

1
2
3
4
5
//a is an integer
var a = 5

//b is a string
var b = "Test"

At times it necessary to say something about the type, but without knowing everything. Leaf allows partial type specifications that change, or limit, what type is inferred.

1
2
3
4
5
//'a' is an optional integer
var a : optional = 5 

//'b' is an optional boolean
var b : optional = True 

Tuples

A tuple is an essential data type in Leaf. These are simple structures which contain an ordered list of other types. The fields can be anonymous, or named.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
//tuple of 3 numbers
var a = [ 1, 2, 3 ] 
trace( a#1 )

//nested tuples
var b = [ a, [ 8 ] ]
//prints '3' (the first subscript returns the value of 'a', and the second one the part of 'a')
trace( b#0#2 )

//named fields
var c = [ 
    one = 1,
    two = 2,
] 
trace( c.one )

The simple syntax of tuples is made possible by type inferencing and conversion. All of the values above have concrete static types, but you don't have to write them. Consider how troublesome it'd be to declare the static type of b!

Safe implicit type conversion

Leaf allows implicit type conversion when no data would be lost in the conversion.

1
2
3
4
5
var a : integer 32bit
var b : integer 64bit

b = a //okay
a = b //compile-error

The safe type conversion also allows a clean handling of constants. Constants stay as rational numbers until their type is actually needed. Combine this with tuples and it becomes very easy to initialize complex structures without worrying about the type.

1
2
3
4
5
type point [ x : integer, y : integer ]
type complex [ i : float, r: float ]

a : point = [ 4, 5 ]
a : complex = [ 8, 2.5 ]

And the constants truly are rational numbers. Integers and floating point can both be assigned a rational, without accidental truncation.

1
2
3
4
5
6
7
8
// a = 2
var a : integer = 10/5
// b = 3 (result is integral, so a safe assignment)
var b : integer = 7.5/2.5
// c = 0.5 (no accidental integer operations)
var c : float = 1/2
// d = 1000 (easy large numbers)
var d = 1e3

(The constant folding is not yet complete, though the rational aspect is.)

Out-of-order declarations

Root level declarations in Leaf have no order. This allows forward referencing of types, and variables, without any extra forward-declarations.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
func pine = -> {
    //function defined later in the source file
    cone() 
}

//Note here we put the full function signature, the `pine` definition is using short-hand
func cone = ()->() {
}

//type defined later
var sample : branch 

type branch = [
    n : twig,
    q : integer
]

type twig = [
    x : integer
]

This rule applies across all of the source files of a module. How you split the files, and the order of the declarations does not change the result.

(Later this allows another great feature: parallel builds. As the order of the declarations is irrelevant, every processor can be used to process them in parallel!)

Optionals

Types can be decorated with the optional modifier indicating the variable may or may not be set. The core syntax has been designed around optionals to provide a simple and consistent use.

Refer to (understanding conditionals)[/features/conditionals.html] for a thorough explanation.

1
2
3
4
5
6
7
var a : optional integer = maybe_get_number()

//copies a's value, raises an error if a has not been set
var b : integer = a 

//copies a's value, or uses 8 if as has no value
var c = a | 8 

The conditional operator creates optionals, allowing assignment or construction of a traditional ternary operator.

1
2
3
4
5
//a is an 'optional integer' either set to 5, or not set
var a = x < y ? 5 

//b is set to either 8 or 9
var b = y < z ? 8 | 9 

To keep with the goals of Leaf, this syntax is also used to do conditional code execution. The do statement is understood as taking an optional code block, and executing it if set, or doing nothing if not set.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
//if statement
do x < y ? {
    //code
}

//if-else statement
do x < y ? {
    //then
} | {
    //else
}

Type unification and select

Type unification allows types to be inferred when the assigned data may not be of a single type. This allows a natural use of a variety of compatible data types.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
var a : integer
var b : integer 16bit
var c : optional integer
var d : shared integer 20bit

// e is integer 20bit
var e = b + d

// f is float, since b safely converts
var f = cond ? b | 7.5

//types unified to the common `integer` since all can be safely converted
var result = select(
    cond1 ? a,
    cond2 ? b,
    cond3 ? c,
    cond4 ? d,
    15 //else
)

Closures

All Leaf functions are first-class entities and closures by default. This can be seen by looking closely at the syntax of declaring functions.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
//an anonymous closure is created and assigned to variable a
var a = -> { 
    code 
}

//so this works
var b = a

//as well as this
fun( a )

Part of the fun of closures is their ability to refer to variables from their enclosing scope.

1
2
3
4
5
6
7
8
9
var create_adder = (x : integer)-> {
    return (y :integer)-> {
        return x + y
    }
}

var add5 = create_adder(5)
//prints 7
trace( add5(2) )

When the function omits a return type it will be inferred. Here the type is (y : integer)->(:integer).

Multiple execution models

Modern development has a variety of needs. Leaf tries to serve many of them by offering multiple ways to get your code running. From the traditional compile and link for distribution, to just-in-time compilation for quick testing.

Refer to execution model for more details.