Team blog

Abstracting over functions

Last week, I was finally able to write the functions compose() and curry() in Ceylon and compile them with the Ceylon compiler. These functions are general purpose higher order functions that operate on other functions at a very high level of abstraction. In most programming languages, it's very easy to define a compose() function that works for functions with just one parameter. For example, in Ceylon:

X compose<X,Y,Z>(X(Y) f, Y(Z) g)(Z z) => f(g(z));

In this function signature, f() and g() are functions with one parameter where the return type of g() is the same as the parameter type of f(). The resulting function is also a function with one parameter, and has the same return type as f() and the same parameter type as g().

String format(Float|Integer number) { .... }
value printf = compose(print,format);
printf(1.0); //prints 1.0

Now, things get trickier when g() has more than one parameter. Imagine the following definition of format():

String format(String pattern, Float|Integer number) { .... }

This function has the type String(String,Float|Integer), which can't be a Y(Z), no matter what argument types we choose or infer for Y and Z. To see this more clearly, and to see how we can come up with a more powerful definition of compose(), we're going to have to understand how Ceylon really represents function types. But first we're going to need to understand tuples.

Tuples

Ceylon recently grew tuples. I've heard all sorts of arguments for including tuples in the language, but what finally convinced me was realizing that they were a simpler way to represent function types.

So a major goal of adding tuples to the language was to add zero additional complexity to the type system. So tuples are instances of an ordinary class, Tuple, which you can see defined here.

A tuple is a linked list where each link in the list encodes the static type of the element. Without syntax sugar, we can write:

value point = Tuple(0.0, Tuple(0.0, Tuple("origin")));

I bet you're wondering what type point has. Well, if you really have to know, it's:

Tuple<Float|String,Float,Tuple<Float|String,Float,Tuple<String,String,Empty>>> 

Phew! Fortunately we never see this type, because Ceylon lets us abbreviate it to [Float,Float,String], and the IDE always shows us the abbreviated form. We can also use the square brackets to instantiate tuples, letting us write:

[Float,Float,String] point = [0.0, 0.0, "origin"];

(Yep, tuples are square in Ceylon. Don't worry, you'll quickly get used to that.)

A tuple is a sequence (Tuple is a subclass of Sequence), so the following is well-typed:

<Float|String>[] seq = point;
Null|Float|String first = seq[0];

Now, what makes a tuple special is that we can access its elements without losing their static type information. We can write:

Float x = point.first;
Float y = point.rest.first;
String label = point.rest.rest.first;

I should emphasize that we're not seeing any magic here. You could write your own MyTuple class and reproduce the exact same behavior!

I bet you're wondering what happens if I type point.rest.rest.rest.first. Well, take a closer look at the verbose type of point again. The chain of "links" is terminated by Empty, Ceylon's empty sequence type. And the type of first on Empty is Null. So we have:

Null zippo = point.rest.rest.rest.first;

That is, the expression is provably null. Provable within the type system, that is.

Of course, we don't want to make you write out suff like point.rest.rest.first whenever you need to get something out of a tuple. A tuple is a sequence, so you can access its elements using the index operator, for example:

Integer i = ... ;
Null|Float|String ith = point[i];

But, as a special convenience, when the index is a literal integer, the compiler will treat it as if it were a chain of calls to rest and first:

Float x = point[0];
Float y = point[1];
String label = point[2];
Null zippo = point[3];

A chain of Tuple instances doesn't need to be terminated by an Empty. It may, alternatively, be terminated by a nonempty sequence, any instance of Sequence. We can write the following:

String[] labels = ["origin", "center"];
[Float, Float, String*] point = [0.0, 0.0, *labels];

The type [Float, Float, String*] is an abbreviation for:

Tuple<Float|String,Float,Tuple<Float|String,Float,String[]>> 

It represents a sequence of two Floats, followed by an unknown number of Strings. So the following is well-typed:

[Float, Float, String*] point0 = [0.0, 0.0];
[Float, Float, String*] point1 = [0.0, 0.0, "origin"];
[Float, Float, String*] point2 = [0.0, 0.0, "origin", "center"];

We'll now see how all this is useful for abstracting over function parameter types.

Function types

An instance of the interface Callable is a function.

shared interface Callable<out Return, in Arguments> 
        given Arguments satisfies Anything[] {}

Going back to our function format() above, its type is:

Callable<String,[String,Float|Integer]>

You can see that we've represented the parameter types of the function using a tuple type. That is to say, Ceylon views a function as accepting a tuple of arguments, and producing a single value. Indeed, Ceylon even lets use write that explicitly:

[String,Float] args = ["%5.2f", 1.0];
print(format(*args));

Here we "spread" the elements of the tuple args across the parameters of the function format(), just like you can do in some dynamically typed languages!

Now consider a function with a variadic argument:

 String format(String pattern, Float|Integer* numbers) { .... }

This function accepts a String, followed by any number of Floats and Integers. We can represent its type as follows:

 Callable<String,[String,Float|Integer*]>

We usually abbreviate function types, writing String(String,Float|Integer) or String(String,Float|Integer*) for the function types above.

Ceylon also has defaulted parameters. The function type String(String,String=) means Callable<String,[String]|[String,String]> and represents a function whose second parameter has a default value.

Notice how, given the definitions we've just seen, assignability between function types works out correctly:

  • a String(Object) is an instance of Object(String),
  • a String(String=) is an instance of String() and of String(String), and
  • a String(String*) is an instance of String(), of String(String), and of String(String,String).

Function composition and currying

Finally we have the machinery we need to define compose() and curry().

The signature of compose() is:

shared Callable<X,Args> compose<X,Y,Args>(X(Y) x, Callable<Y,Args> y) 
        given Args satisfies Anything[]

Notice how the type parameter Args abstracts over all possible parameter lists, just as it does in the definition of Callable. To actually implement compose(), I'm going to need to resort to two native functions that I can't yet implement within the language. I can write down their signatures, however, so this isn't a limitation of the type system itself:

shared native Callable<Return,Args> flatten<Return,Args>
            (Return tupleFunction(Args tuple))
        given Args satisfies Anything[];

shared native Return unflatten<Return,Args>
            (Callable<Return,Args> flatFunction)(Args args)
        given Args satisfies Anything[];

If you're not extremely interested, you can skip over these declarations, and just look at an example of what they do:

String([String,Float|Integer]) unflatFormat = unflatten(format);
String(String,Float|Integer) flatFormat = flatten(unflatFormat);

That is, unflatten() takes any function with any parameter list, and returns a function that accepts a single tuple of the same length as the original parameter list. On the other hand, flatten() does the opposite. It takes a function that accepts a single tuple, and returns a function with a parameter list of the same length as the tuple.

OK, now we can implement compose():

shared Callable<X,Args> compose<X,Y,Args>(X(Y) f, Callable<Y,Args> g) 
        given Args satisfies Anything[]
               => flatten((Args args) => f(unflatten(g)(args)));

Perhaps I should unpack this slightly for readability:

shared Callable<X,Args> compose<X,Y,Args>(X(Y) f, Callable<Y,Args> g) 
        given Args satisfies Anything[] {
    X composed(Args args) {
        Y y = unflatten(g)(args);
        return f(y);
    }
    return flatten(composed);
}

The definition of curry() is similarly dense:

shared Callable<Return,Rest> curry<Return,Argument,First,Rest>
            (Callable<Return,Tuple<Argument,First,Rest>> f)
            (First first)
        given First satisfies Argument 
        given Rest satisfies Argument[] 
                => flatten((Rest args) => unflatten(f)(Tuple(first, args)));

This function accepts a function with at least one parameter and returns a function with two parameter lists, the first parameter list has the first parameter of the original function, and the second parameter list has the rest of the parameters.

Great! Now we can write:

value printf = compose(print,format);
printf("%5.2f", 1.0); //prints 1.00
value printFloat = curry(printf)("%5.2f");
printFloat(2.5); //prints 2.50

A final word

This post has been long and quite involved. I hope some of you made it this far. This isn't typical Ceylon code we're looking at here. We're starting to move into the field of metaprogramming, which is just becoming possible in Ceylon. What I'm really trying to demonstrate here is how Ceylon layers sophisticated constructs as pure syntax sugar over a relatively simple type system. I'm trying to show that you can have things like tuple and function types without defining them primitively as part of the type system, or resorting to inelegant hacks like F1, F2, F3, Tuple1, Tuple2, Tuple3, etc. And indeed it works out better this way, I believe.

Ceylon Test Eclipse Plugin

Today we would like to introduce a new eclipse plugin that allows you to run ceylon unit tests, and easily monitor their execution and their output.

It offers similar comfort and integration with IDE as JUnit and even more. Finally, it is part of the Ceylon IDE, so no additional installation is needed.

Getting started with your first ceylon unit test

The new test framework is in the Ceylon SDK module ceylon.test, though this is still an early version and contains only basic functionality, because we need annotations and meta-model support to make it really flamboyant.

So let’s start by importing the ceylon.test module in our module descriptor and writing our first test.

module com.acme.foo '1.0.0' {
    import ceylon.test '0.4';
}
import ceylon.test { ... }

void shouldAlwaysSuccess() {
    assertEquals(1, 1);
}

void shouldAlwaysFail() {
    fail("crash !!!");
}

These tests can be run like any ordinary ceylon application with the following code:

void run() {
    TestRunner testRunner = TestRunner();
    testRunner.addTestListener(PrintingTestListener());
    testRunner.addTest("com.acme.foo::shouldAlwaysSuccess", shouldAlwaysSuccess);
    testRunner.addTest("com.acme.foo::shouldAlwaysFail", shouldAlwaysFail);
    testRunner.run();
}

Which outputs:

======================== TESTS STARTED =======================
com.acme.foo::shouldAlwaysSuccess
com.acme.foo::shouldAlwaysFail
======================== TESTS RESULT ========================
run:     2
success: 1
failure: 1
error:   0
======================== TESTS FAILED ========================

Now let’s see the IDE integrations!

Launch configuration

Once you have created your tests, you can create a Ceylon Test Launch Configuration. In this dialog you can specify the tests to run, their arguments, run-time environment, etc…

launch-config

In the launch configuration you can directly add test methods or whole containers, like classes, packages, modules, or entire projects.

select-test

As you would expect, a quick way to run ceylon tests is to right click in the Ceylon Explorer or in open editor and select Run-As → Ceylon Test.

Viewing test results

The Ceylon Test View shows you the tests run progress and their status. The toolbar has familiar functions:

  • show next/previous failure,
  • collapse/expand all nodes,
  • filter to only show failing tests,
  • scroll lock,
  • buttons for relaunching or terminating,
  • test runs history, and
  • menu for view customisation (show elapsed time or show tests grouped by packages).

test-result-view

Comparing test results

In Test Runs History you can review test runs history and switch between runs.

But the real killer feature here happens if you open Compare Test Runs dialog, where you can see which tests have regressed, which tests have been fixed and which tests have been added or removed between two test runs, making it extra easy to get an idea of what your recent work did to the test suite.

test-runs-history

compare-test-runs

Try it now

You can try it right away by downloading one of our Ceylon IDE development build.

Tuples and function types

Tuples

Ceylon is getting tuples in the next version. For those who don´t know what a tuple is, it´s a bit like a struct in C: a finite sequence of heterogeneous typed elements values, with no methods (unlike a class) and no names (unlike C structures). But let´s start with an example.

In Ceylon, [1, 2.0, "Hello"] is a tuple value of size 3 with type [Integer, Float, String]. Now let´s see a more useful example:

// convert a sequence of strings into a sequence of tuples with size, string
// and number of uppercase letters
Iterable<[Integer,String,Integer]> results = 
    {"Merry Christmas", "Happy Cheesy Holidays"}
        .map((String s) [s.size, s, s.count((Character c) c.uppercase)]);
for([Integer,String,Integer] result in results){
    Integer size = result[0];
    String s = result[1];
    Integer uppercaseLetters = result[2];
    print("Size: " size ", for: '" s "', uppercase letters: " uppercaseLetters "");
}

Which outputs:

Size: 15, for: 'Merry Christmas', uppercase letters: 2
Size: 21, for: 'Happy Cheesy Holidays', uppercase letters: 3

As you can see, you can access each tuple element using the Correspondence.item syntax sugar result[i], and that's because our Tuple type satisfies Sequence, underneath.

You may ask, but then what is the sequence type of a [Integer, String, Integer] tuple, then? Easy: it´s Sequencial<Integer|String>.

Then how do we possibly know that result[2] is an Integer and not an Integer|String? Well, that´s again syntactic sugar.

You see, our Tuple type is defined like this:

shared class Tuple<out Element, out First, out Rest>(first, rest)
        extends Object()
        satisfies Sequence<Element>
        given First satisfies Element
        given Rest satisfies Element[] {

    shared actual First first;
    shared actual Rest rest;

    // ...
}

So a tuple is just a sort of elaborate cons-list and result[2] is syntactic sugar for result.rest.rest.first, which has type Integer.

Similarly, the [Integer, String, Integer] type literal is syntactic sugar for:

Tuple<Integer|String, Integer, Tuple<Integer|String, String, Tuple<Integer, Integer, Empty>>>

And last, the [0, "foo", 2] value literal is syntactic sugar for:

Tuple(0, Tuple("foo", Tuple(2, empty)))

As you can see, our type inference rules are smart enough to infer the Integer|String sequential types by itself.

Function types

So tuples are nice, for example to return multiple values from a method, but that´s not all you can do with them.

Here´s how our Callable type (the type of Ceylon functions and methods) is defined:

shared interface Callable<out Return, in Arguments> 
    given Arguments satisfies Void[] {}

As you can see, its Arguments type parameter accepts a sequence of anything (Void is the top of the object hierarchy in Ceylon, above Object and the type of null), which means we can use tuple types to describe the parameter lists:

void threeParameters(Integer a, String b, Float c){
}

Callable<Void, [Integer, String, Float]> threeParametersReference = threeParameters;

So, Callable<Void, [Integer, String, Float]> describes the type of a function which returns Void and takes three parameters of types Integer, String and Float.

But you may ask what the type of a function with defaulted parameters is? Defaulted parameters are optional parameters, that get a default value if you don´t specify them:

void oneOrTwo(Integer a, Integer b = 2){
    print("a = " a ", b = " b ".");
}
oneOrTwo(1);
oneOrTwo(1, 1);

That would print:

a = 1, b = 2.
a = 1, b = 1.

So let´s see what the type of oneOrTwo is:

Callable<Void, [Integer, Integer=]> oneOrTwoReference = oneOrTwo;
// magic: we can still invoke it with one or two arguments!
oneOrTwoReference(1);
oneOrTwoReference(1, 1);

So its type is Callable<Void, [Integer, Integer=]>, which means that it takes one or two parameters. We´re not sure yet about the = sign in Integer= to denote that it is optional, so that may change. This is syntactic sugar for Callable<Void, [Integer] | [Integer, Integer]>, meaning: a function that takes one or two parameters.

Similarly, variadic functions also have a denotable type:

void zeroOrPlenty(Integer... args){
    for(i in args){
        print(i);
    }
}
Callable<Void, [Integer...]> zeroOrPlentyReference = zeroOrPlenty;
// magic: we can still invoke it with zero or more arguments!
zeroOrPlentyReference();
zeroOrPlentyReference(1);
zeroOrPlentyReference(5, 6);

Here, [Integer...] means that it accepts a sequence of zero or more Integer elements.

Now if you´re not impressed yet, here´s the thing that´s really cool: thanks to our subtyping rules (nothing special here, just our normal subtyping rules related to union types), we´re able to figure out that a function that takes one or two parameters is a supertype of both functions that take one parameter and functions that take two parameters:

Callable<Void, [Integer, Integer=]> oneOrTwoReference = oneOrTwo;

// we can all it with one parameter
Callable<Void, [Integer]> one = oneOrTwoReference;
// or two
Callable<Void, [Integer, Integer]> two = oneOrTwoReference;

And similarly for variadic functions, they are supertypes of functions that take any number of parameters:

Callable<Void, [Integer...]> zeroOrPlentyReference = zeroOrPlenty;

// we can call it with no parameters
Callable<Void, []> zero = zeroOrPlentyReference;
// or one
Callable<Void, [Integer]> oneAgain = zeroOrPlentyReference;
// or two
Callable<Void, [Integer, Integer]> twoAgain = zeroOrPlentyReference;
// and even one OR two parameters!
Callable<Void, [Integer, Integer=]> oneOrTwoAgain = zeroOrPlentyReference;

Although this is something that dynamic languages have been able to do for decades, this is pretty impressive in a statically typed language, and allows flexibility in how you use, declare and pass functions around that follows the rules of logic (and checked!) and don´t make you fight the type system.

By the way

I´ve shown here that we support method references in Ceylon, but the same is true with constructors, because after all a constructor is nothing more than a function that takes parameters and returns a new instance:

class Foo(Integer x){
    shared actual String string = "Foo " x "";
}
print(Foo(2));

Callable<Foo, [Integer]> makeFoo = Foo;
print(makeFoo(3));

Will print:

Foo 2
Foo 3

Ceylon webinar Monday 5th of November

Emmanuel and I will be presenting An introduction to Ceylon on Monday, 5th of November online as a webinar. That's this coming Monday, at 18:00 CET or 12:00 noon in NY. Come and join us!

Find out all the info online and don't forget to register.

Some new screenshots

If you're interested, here's some screenshots of some of the newer stuff in Ceylon IDE.

Here's what the brand new Repository Explorer view looks like:

repo-explorer

The repository explorer helps us find the module we're looking for, in the repositories configured for a project, all the way to Ceylon Herd. But you don't even usually need to use the Repository Explorer, because the IDE will propose module named and versions when editing the module descriptor.

module-completion

The actual repositories available to you can be configured in the New Ceylon Project wizard, or in the project properties:

module-repos

From the project properties page, you can also enable compilation to JavaScript:

compiler-settings.png

We've finally resolved the performance issues we were having in the IDE when writing code that depends upon the Java SDK. Of course, doc hover, autocompletion, and hyperlink navigation works even for Java declarations:

java-interop