Note: information on this page refers to Ceylon 1.0, not to the
current release.

Functions

This is the eleventh part of the Tour of Ceylon. In the previous leg
we looked at packages and modules. This leg covers first class and higher-order
functions.

First class and higher order functions

Ceylon isn't a functional language: it has variable attributes and so methods
can have side effects. But one thing Ceylon has in common with functional
programming languages is that it lets you treat functions as values, which in
some people's eyes makes the language a kind of hybrid. In truth, there's
nothing remotely new about having functions-as-values in an object oriented
language—for example, Smalltalk, one of the first and still one of the
cleanest object oriented languages, was built around this idea. Anyway, Ceylon,
like Smalltalk and a number of other object oriented languages, lets you treat
a function as an object and pass it around the system.

In this installment, we're going to discuss Ceylon's support for first class
and higher order functions. A little bit of PL jargon:

First class function support means the ability to treat a function as a
value, assigning it to variables, and passing it as an argument.

A higher order function is a function that accepts other functions as
arguments, or returns another function.

It's clear that these two ideas go hand-in-hand, so we'll just use the term
"higher order functions" from now on.

Representing the type of a function

Ceylon is a (very) statically typed language. So if we're going to treat a
function as a value, the very first question that arises is: What is the
type of the function? We need a way to encode the return type and parameter
types of a function into the type system. Remember that Ceylon doesn't have
"primitive" types. A strong design principle is that every type should be
representable within the type system as a class or interface declaration.

In Ceylon, a single type
Callable
abstracts all functions. Its declaration is the following:

The type parameter Return represents the return type of the function. The
sequenced type parameter Arguments, which must be a sequence type,
represents the parameter types of the function. We can encode any parameter
list as a tuple type. For example, the parameter list (String s, Float x)
is encoded as the tuple type [String,Float].

So, take the following function:

function sum(Integer x, Integer y) => x+y;

The type of sum() is:

Callable<Integer,[Integer,Integer]>

What about void functions? Well, the return type of a void function is
considered to be Anything. So the type of a function like print() is:

Callable<Anything,[Anything]>

Folks who have a background in languages like ML might have expected that
void would be identified with some "unit" type, for example, Null, or
perhaps []. But this approach would mean that a non-void method would
not be able to refine a void method, and that a non-void function would
not be able to be assigned to a void functional parameter. Therefore,
perfectly reasonable code would be rejected by the compiler.

Note that a void function with a concrete implementation implicitly returns
the value null. This is different to a function declared to return the type
Anything, which may return any value at all, but must do it explicitly, via
a return statement. The following functions have the same type, Anything(),
but don't do exactly the same thing:

There's one problem with this. In Ceylon, as we'll see later, we often call
functions using named arguments, but the Callable type does not encode the
names of the function parameters. So Ceylon has an alternative, more elegant,
syntax for declaring a parameter of type Callable:

This version is also slightly more readable, so it's the preferred syntax.

Function references

When a name of a function appears without any arguments, like printNum does
above, it's called a function reference. A function reference is the thing
that really has the type Callable. In this case, printNum has the type
Callable<Anything,[Integer]>, or simply Anything(Integer).

Now, remember how we said that Anything is both the return type of a void
function, and also the logical root of the type hierarchy? Well that's useful
here, since it means that we can assign a function with any return type to any
parameter which expects a void function, as long as the parameter lists
match:

Static attribute references work especially well with the
map()
method of Iterable:

{String*} names = people.map(Person.name);

Curried functions

A method or function may be declared in curried form, allowing the method
or function to be partially applied to its arguments. A curried function has
multiple lists of parameters:

Float adder(Integer n)(Float x) => x+n;

The adder() function has type Float(Float)(Integer). We can invoke it
with a single integer argument to get a reference to a function of type
Float(Float), and store this reference as a function, like this:

Float addOne(Float x);
addOne = adder(1);

Or as a value, like this:

Float(Float) addOne = adder(1);

(There only real difference between these two approaches is that in the
first case we get to assign a name to the parameter of addOne().)

When we subsequently invoke addOne(), the actual body of adder() is
finally executed, producing a Float:

Float three = addOne(2.0);

Gotcha!

Did you notice that order of parameter lists is reversed in a type
expression compared to a function declaration? Look again:

The order of parameter lists in the function declaration reflects the order in
which we supply arguments when we invoke the function. But in a function type
expression, the return type comes always comes before the parameter types, so
therefore the parameters which must be supplied first come at the last in
the function type.

Anonymous functions

The most famous higher-order functions are a trio of functions for tranforming,
filtering, and summarizing sequences of values. In Ceylon, these three functions,
map(), filter(), and fold() are methods of the interface
Iterable.
(They even have a fourth, slightly less glamorous friend called find(), also a
method of Iterable.)

As you've probably noticed, all the functions we've defined so far have been
declared with a name, using a traditional C-like syntax. There's nothing wrong
with passing a named function to map() or filter(), and indeed that is often
useful:

Float max = measurements.fold(0.0, largest<Float>);

However, quite commonly, it's inconvenient to have to declare a whole named
function just to pass it to map(), filter(), fold() or find(). Instead,
we can declare an anonymous function inline, as part of the argument list:

But now all event observers have to implement the interface Observer, which
has just one method. Why don't we cut out the interface, and let event
observers just register a function object as their event listener? In the
following code, we define the addObserver() method to accept a function as
a parameter.

When the name of a method appears in an expression without a list of arguments
after it, it is a reference to the method, not an invocation of the method.
Here, the expression onEvent is an expression of type Anything(Event) that
refers to the method onEvent().

If onEvent() were shared, we could even wire together the Component and
Listener from some other code, to eliminate the dependency of Listener
on Component:

Here, the syntax listener.onEvent is a kind of partial application of the
method onEvent(). It doesn't cause the onEvent() method to be
executed (because we haven't supplied all the parameters yet). Rather, it
results in a function that packages together the method reference onEvent
and the method receiver listener.

It's also possible to declare a method that returns a function. Let's
consider adding the ability to remove observers from a Component. We could
use a Subscription interface:

Here, we define an anonymous function inside the body of the addObserver()
method, and return a reference to this function from the outer method. The
reference to the anonymous function returned by addObserver() can be called
by any code that obtains the reference.

In case you're wondering, the type of the function addObserver() is
Anything()(Anything(Event)).

Notice that the anonymous function is able to use the parameter observe of
addObserver(). We say that the inner method receives a closure of the
non-variable locals and parameters of the outer method—just like a method
of a class receives a closure of the class initialization parameters and locals
of the class initializer. In general, any inner class, method, or attribute
declaration always receives the closure of the members of the class, method, or
attribute declaration in which it is enclosed. This is an example of how regular
the language is.

We could invoke our method like this:

addObserver(onEvent)();

But if we were planning to use the method in this way, there would be no good
reason for giving it two parameter lists. It's much more likely that we're
planning to store or pass the reference to the inner method somewhere before
invoking it.

Anything() cancel = addObserver(onEvent);
// ...
cancel();

The first line demonstrates how a function reference can be stored. The second
line of code simply invokes the returned reference to cancel().

Composition and curry

The function compose() performs function composition. For example, given
the functions print() and plus() in ceylon.language, with the following
signatures:

Since the tuple type [String,Integer,String,Integer] is a subtype of
[String,Integer,Integer|String,Integer], the invocation is well-typed.
This demonstrates the relationship between tuples and function argument!

There's more...

You'll find a more detailed discussion of how Ceylon represents function
types using tupes here,
including an in-depth discussion of compose() and curry().

Now we're going to talk about Ceylon's syntax for named argument
lists and for defining user interfaces and
structured data.