Droid2Samples

This is a bunch code samples in the Droid language. It has been put in an order that may help introduce the language and it's strengths to newcomers. Here's the grammar.

If you prefer, you can take a look at the core language. That description is much more technical, but also more precise. It is mostly meant as a help for compiler and tool implementors. It will be even less informal later on.

The language presents few original concepts, but combines concepts that are currently not widely used outside academia and not widely available in mainstream languages with more widespread features. The core is somewhat minimalistic.

The package system is being designed separately, and this page contains the information. Dependency management is currently planned to be part of the package system.

Hello World!

The classic, but boring, first impression of a language.

object Hello do
    Console.writeLine("Hello, world!")
end

This isn't exactly BASIC's print "Hello, world!" , but at least there's no main method to implement. The syntax is chosen to avoid having all sorts of stuff in the global namespace. Only programs as small as Hello will suffer from it. In the following examples, we will omit the scaffolding and concentrate on the code you can put in the do ... end block. The top level constructs will be explained last.

Good Old Control Structures

I can't think of very many imperative languages that don't have all of these. However, their syntax and semantics are a tiny bit different.

if a > b do
    # what to do if a is greater than b
elseif a < b do
    # what to do if it's less
else
    # what do do if they're equal
end

You can have as many elseifs as you like (even none) and the else part is optional. However, it also returns the value it evaluates:

val x = if hot do 30 else 10 end

In the above, x is 30 if hot is true and 10 otherwise. If the else part is missing, the return type is Void .

var x = 10
while x > 0 do
    x -= 1 # subtract one from x
end

The above while loop counts repeatedly subtracts one from x until it becomes zero. Surprised? Didn't think so!

Foreach With Whipped Cream

One of the most common kinds of loops is the one that iterates over a collection and examines each element in it. Most modern imperative languages have a construct for this called "foreach". Droid takes it a bit further.

val names = ["Jordan", "Michelle", "Peter"]
for name in names do
    Console.writeLine(name)
end

This is the standard foreach loop behavior. It prints:

Jordan
Michelle
Peter

Comprehensions

There are certain uses of the foreach loop that occur very frequently in any given program. One of them is this:

val xs = Array[Int](ys.Size)
for y in ys do
    xs.push(foo(y))
end
val result = xs

Where foo can be more or less involved. This is commonly called mapping in the functional programming world. The foreach loop in Droid actually returns a list of the result of each of it's iterations. This means that this is equivalent to the above (except it returns a List , not an Array ):

val result = for y in ys do foo(y) end

Sometimes you only want some of the elements. Removing those elements that are of no interest to you is often called filtering or selecting .

val result = for y in ys where y > 100 do foo(y) end

The above selects only those elements that are greater than 100, and applies foo(y) to each of them before returning them in a list. It is similar to a list comprehension (as in Python, Haskell and math), and to the (pseudo) SQL statment SELECT foo(y) FROM ys AS y WHERE y > 100 .

Iterable Types

A foreach loop works on everything that implements the Iterable interface:

for x in 5 do 
    Console.writeLine(x) 
end

Integers are iterable. The iterator for a given integer N will iterate from 0 to N - 1 (both inclusive, and empty if N <= 0). The above will print:

0
1
2
3
4

This is especially useful if you want to iterate over array indices:

val fruits = ["bannana", "apple", "orange"].toArray()
for i in fruits.Size do
    Console.writeLine("Fruit #" + i + " is " + fruits(i))
end

Which prints:

Fruit #0 is bannana
Fruit #1 is apple
Fruit #2 is orange

When iterating over a map/dictionary of some kind, you get an object back that that has both the key and the value. You can use pattern matching to pull out the values:

for (Key: k, Value: v) in myDictionary do
    Console.writeLine("" + k + " => " + v)
end

Deterministic Resource Management

Garbage collection takes care of freeing memory and other resources that you don't need. However, sometimes you don't want to wait for the garbage collector to come around and clean up for you. When you have acquired sparse resources such as files and mutex locks, you'd better hurry up and release them as soon as you no longer need them, so that other parts of your program (or indeed, other programs) may use them.

C++ has a neat solution: RAII (Resource Acquisition Is Initialization). The weird name aside, it means that you allocate the instance that holds the resource on the stack, so that the destructor is called immediately, when you exit the function in which it was allocated. That way you only have to care about the resource management in one place (at construction time).

Scoped Resources

In Droid there is a similar construct, although it does not have anything to do with stack allocation. It is called a scoped resource :

fun printLines(fileName: String) do
    val file = scope File.readText(fileName)
    for line in file.Lines do
        Console.writeLine(line)
    end
end

The above example defines a function printLines that prints all the lines of a text file. Experienced coders will notice that it doesn't explicitly close the file. Don't be alarmed - the scope keyword where the file is opened ensures that the file is closed immediately when the function returns. If an exception is thrown that isn't catched inside the function, it will also be closed immediately.

In other words, when you allocate a sparse resource, you can just scope it and forget about further management of it. Just make sure the scope that you're currently in corresponds roughly to it's optimal lifetime. Sometimes that lifetime is smaller than the full extend of the enclosing scope. You can define a smaller scope with the scope do ... end construct:

scope do
    scope myMutex.Lock
    # modify or read data protected by the mutex
end

In fact, all block constructs have their own scope. That includes if statements, while loops and so on. As a rule of thump, every time you indent you start a scope, and every time you unindent you end a scope.

Custom Disposables

All objects that can act as scoped resources implement the Disposable interface. It has one method dispose that should dispose of the sparse resource. Calling that method after disposing it should have no effect (it will sometimes be called twice by the runtime system).

If you don't scope the resource, it will still be automatically disposed when the object is garbage collected. And in between, you can simply call dispose yourself.

Exceptions

Exceptions are used to signal that an unusual thing has happened, such as if you try to open a file that does not exist. In such cases we throw an exception, which unwinds all calls you have made until it finds a catch block that can handle it. If no catch blocks are found, the entire call stack is unwinded and when we reach the top level, the program (or thread, or process) stops and the exception is shown to the user.

fun foo() do
    val r = file.readText("some-file-that-doesn't-exist.txt")
catch
    FileNotFoundException do
        Console.writeLine("Whoops, it looks like the file does not exist!")
    end
end

The above function prints the "Whoops, ..." message when called. What happens is that the call to readText calls some other function to open the file, which throws a FileNotFoundException since the file does not exist. It then propagates upwards until it lands in the foo function, where there is a catch block that matches this specific exception. The handler for this exception is then invoked, which outputs the "Whoops, ..." message.

Any block that can contain code can have a catch block after it, like above. Compared with the usual try...catch you find in most languages, it saves you from an extra indentation around the main code of the block.

Pattern Matching

The catch block above contains a pattern and an action for each of the exceptions it can handle. In that case, it can only handle one kind of exception, but it could just as well have had other exception patterns below it, like one for FileAccessDeniedException . These are very simple patterns that only mention a class name. But patterns are useful for much more than simple type detection. Consider the switch statement:

switch myNumber do
    0 do "Zero" end
    1 do "One" end
    2 do "Two" end
end

This code converts a number x (between 0 and 2, inclusive) to it's textual name. That is, if x is 0, it returns "Zero", if x is 1, it returns "One", and if x is 2, it returns "Two". 0, 1, and 2 are very simple patterns, each matching the obvious corresponding number. But patterns can be more complex:

switch myList do
    [] do "This is an empty list." end
    [e] do "This is a list with a single element: " ~ e ~ "." end
    [a, b] @ r do 
        "This is a list with multiple elements, " ~
        "the first two of which is " ~ a ~ " and " ~ b ~ 
        ", and after it comes " ~ r.Size ~ " elements." 
    end
end

The first pattern matches any empty list. The second pattern matches a list with exactly one element, and binds e to the element's value, so that it can be used inside the action. The third pattern matches any list with at least two elements, and binds the two first elements to a and b , and the rest of the list to r .

There are different kinds of patterns, including ones that match classes, lists, strings, integers and wildcards (e, a, and b above). Patterns can be nested, as shown above where wildcard patterns are nested inside list patterns.

Regular Expressions

Regular expressions are another kind of pattern matching which is more specific. Namely, they are designed to match sequences inside strings. Here's a good reference. The syntax for them is tilde, slash, regular expression, slash, options, plus various escape sequences, including \/ which is a literal slash.

val re = ~/([0-9]+)/
switch re.match("I've got 75 cents.") do
    Some(m) do
        Console.writeLine("The string contained the integer: " ~ m(1))
    end
    None do
        Console.writeLine("The string contained no integers.")
    end
end

This regular expression matches integers in a string. Or more precisely, it matches one or more characters in the character range from "0" to "9" inclusive. The parenthesis mark that whatever this matches is going to be stored into group 1. The output of this snippet is "The string contained the integer: 75".

None, Not Null, Not Nil

Most languages have some convenient way to represent that there is no interesting result, or a value is "missing". It's been called null, nil, none in different languages. However, they are also the source of one of the most common runtime errors, the NullPointerException. Since we're no fan of runtime errors, we've removed it from the language and told the compiler to guarantee that it cannot occur instead. Instead of pretending that every reference can be null, you are required to mark a reference if it can refer to "nothing".

val x: Int? = 23

The above says that x refers either to Some[Int] or to None. When assigned directly to an integer like above, it is autoboxed so as to be equivalent to Some(23). This kind of value is called an Option, because it may or may not refer to an interesting value. The most secure way to handle both cases is with a switch, as seen in the example for regular expressions, where there is a case for Some and one for None. If you know that the value cannot be None, you can get the value directly with the following syntax:

val y: Int = x()   # Returns the integer contained in x, 
                   # or throws OptionNoneException if x is None.

Of course if you use the above example instead of a switch, you're not better off than with a null pointer, so we recommend using the switch approach by default. Remember, it's going to be rare since "nullable" references are not needed that often. If you forget to check for None, the compiler will tell you. For example, y = x would generate an error stating that you cannot assign values of type "Option[Int]" to "Int".

References that can also refer to "nothing" are much rarer than references that always refer to a valid object. Although the secure approach is slightly more verbose in the rare case, you avoid performing implicit, dynamic casts to a not-nullable reference every time you call a method on a reference (which is what happens in Java and C#... in C++ with pointers it just blows up).

Functions

You have already seen how to declare a local function (the fun name(args) do ... end syntax). You can also create anonymous functions:

button.onClick(fun(event) do
    Console.writeLine("The button was clicked!")
end)

Here we create an anonymous function that writes "This button was clicked!" when called with an event. We pass it to a method that accepts such a function, here button's onClick method, which would call the function each time the button was clicked. Functions remember the variables that was in scope where they were created, which can be used as below:

var timesClicked = 0;
button.onClick(fun(event) do
    timesClicked += 1
    Console.writeLine("Click #" ~ timesClicked)
end)

If you then click the button three times, the output is

Click #1
Click #2
Click #3

That is, the timesClicked variable is kept alive as long as the function which captures it is alive, even if timesClicked goes out of it's original scope. Multiple functions can capture a variable and share state that way.

You may notice that we didn't provide types for the event argument or function return type. That's because onClick only takes functions that take ClickEvent and returns Void, so it's type is already obvious to the compiler and other programmers.

In summary you can pass functions as arguments to other functions, store them in variables and return them, just like any other value in the language. They remember their scope and their type can often be inferred from the expected type.

Operators

The arithmetic and comparison operators can be defined for user defined types. These operators are actually just syntactic sugar for method calls. If you implement the methods used in the expansion of an operator, you can use the operator on your types.

If the numbers to the left seem cryptic, you can chose ignore them. They just explain how grouping works, and it's pretty much like in any other ordinary language (and math). You might want to pay attention to the last four lines of this table, since they contain some unusual syntactic sugar.

P.A

Syntax

Expansion

10.L

a == b

a.equal(b)

10.L

a != b

not a.equal(b)

10.L

a < b

a.less(b)

10.L

a > b

not a.less(b) and not a.equal(b)

10.L

a <= b

not b.less(a)

10.L

a >= b

b.less(a) or a.equal(b)

11.N

a in b

b.contains(a)

12.L

a @ b

a.concatenate(b)

13.L

a + b

a.plus(b)

13.L

a - b

a.minus(b)

14.L

a * b

a.times(b)

14.L

a / b

a.over(b)

15.N

-a

a.negate()

16.N

a ^ b

a.power(b)

30.L

a(x)

a.get(x)

(where x is any number of parameters, and a is not on the form o.m )

1.N

a(x) = b

a.set(x, b)

(where x is any number of parameters, and a is not on the form o.m )

31.L

a.P

a.getP()

(the name P must begin with an uppercase letter)

1.N

a.P = b

a.setP(b)

(the name P must begin with an uppercase letter)

If you have an expression like a + b * c then it's equivalent to a + (b * c) because * has a higher precedence (P) than +. If you have an expression like a + b + c then it's equivalent to (a + b) + c because + is left-associative. All operators are either left-associative (L) or non-associative (N).

Please note that the comparison operators work like in math when associativity is invoked, ie. a < b < c == d is almost equivalent to a < b and b < c and c == d. The difference is that each operand will only be evaluated once.

Special Operators

These are (some of the) operators you can't redefine.

P.A

Syntax

Explanation

1.N

a = b

Assigns b to a. Returns Void.

1.N

a ¤= b

Where ¤ is one of + - * / ^ @ ~ and a is a variable, property access or index access expands to a = a ¤ b.

1.N

a .= f(x)

Expands to a = a.f(x) (where x can be any number of arguments).

2.L

a ~ b

Expands to Any.toString(a).concatenate(Any.toString(b)), ie. concatenates the string representations of it's operands.

3.L

a or b

Evaluates a; if it's true, b is ignored and true is the result; otherwise the result is whatever b evaluates to.

4.L

a and b

Evaluates a; if it's false, b is ignored and false is the result; otherwise the result is whatever b evaluates to.

5.N

not a

Evaluates a; if it's true, the result is false; otherwise, the result is true.

20.N

scope a

Makes sure that a will be deleted before exiting the current innermost lexical scope. Returns whatever a evaluates to.

30.L

a.m(x)

Calls the m method with the x parameters on the value of evaluating the expression a.

31.L

a.m

Gets the functional value of the method m. (methods must begin with a lowercase letter)

Any other operators in the language have lower precedence, and have no associativity (since their syntax isn't infix).

Enum Is a Disguise

The following constructs must appear at the top level of a file, that is, outside any do ... end pair.

Enumarations are used to implement some of the core features of the language. For example, booleans are implemented as:

enum Bool do
    True
    False
end

This says that any value of type Bool is either True or False. But enumerations can also have parameters. This kind of construct is often called a variant:

enum Option do
    Some(value: Int)
    None
end

This means that any value of type Option is either None or Some(value), where value is any value of type Int. We saw earlier that you can use switch to extract value. The Option we have defined so far only support Int values. If we needed support for String and Bool we could provide separate option types for these, but that's too much work. Instead, we can make Option generic:

enum Option[T] do
    Some(value: T)
    None
end

This says that for any type T, there exists an Option whose Some constructor takes a value of type T. In other words, if I write Option[String], I get an Option where the T type is replaced with String.

Earlier we saw that we could "promise" the compiler that a value wasn't None by using the syntax x(). Enums can have methods:

enum Option[T] do
    Some(value: T)
    None
methods
    fun get() do
        switch this do
            Some(v) do v end
            None do throw OptionNoneException() end
        end
    end
end

That is, when x.get() is called, we switch on x to extract the value, or throw an exception if it's None. The syntax x() expands to x.get(), which means we have obtained the syntax we saw earlier.

The constructors are accessed like Option_Some and Option_None. If the expected type is already Option, you can omit the prefix and write Some or None. For the built in types, you never need a prefix though, since they're aliased to true, false and none, and T is autoboxed to Some[T] when the expected type is Option[T].

Pure Functional Programming

Functions, classes, enums and interfaces can be pure. For functions, this is enforced by using the keyword pure instead of fun, and for the rest, pure must be put in front of the normal keyword. Examples:

pure class Foo(x: Int) do
    var mutableX = x;
methods
    pure getX(): Int do x end
    # fun setX(value: Int): Void do mutableX = value end
end

The commented code violates multiple rules. Code inside pure functions or pure constructors cannot:

And pure classes, enums and interfaces can only contain pure functions. However, pure code can:

This allows a subset of Droid programs to be purely functional. Such functions are easier to reason about because side effects are removed from the equation, so all inputs and outputs can be seen just by looking at the function in isolation.

Every type has a pure version, which only contains it's pure methods (of which there may be none). The pure version behaves as a subtype of the impure version (if the original version is pure, the types are equal).

TODOs

Enums, classes, interfaces, generics, packages and importing, and operators. I have it all in my head, and most of it can be copied from older documents. Generics are the hardest part, since they are the most advanced typing scheme in the language (with constraints and variance, using a MSR proposal for C#).

Multitasking won't get but a brief mention. A shared-all model (often called 'threads') really messes with the semantics of the language, and is hard to use. We use a lightweight shared-nothing model (often called 'processes') with message passing (possibly with built-in support for distribution). When creating a new process from a function, all state it captures is copied (and unserializable captured state will cause an exception). The same happens when sending a function. The language will support serialization and matching. The rest will be in a library.

Note: when there is no overloading, implementing the same interface twice but with different generic parameters will cause an error. Is this acceptable?