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

Streams, sequences, and tuples

This is the sixth leg of the Tour of Ceylon. In the
previous leg we covered anonymous classes and
member classes. Now we're going to look at streams, sequences, and
tuples. These are examples of generic container objects. Don't worry,
we'll come back to talk more about generics later.

Streams (Iterables)

An iterable object, or stream, is an object that produces a stream of
values. Streams satisfy the interface
Iterable.

Ceylon provides some syntax sugar for working with streams:

the type Iterable<X,Null> represents a stream that might not produce any
values when it is iterated, and may be abbreviated {X*}, and

the type Iterable<X,Nothing> represents a stream that always produces at
least one value when it is iterated, and is usually abbreviated {X+}.

Sequences

Some kind of array or list construct is a universal feature of all programming
languages. The Ceylon language module defines support for sequence types via
the interfaces Sequential,
Sequence,
and Empty.

Again, there is some syntax sugar associated with sequences:

the type Sequential<X> represents a sequence that may be empty, and may be
abbreviated [X*] or X[],

the type Sequence<X> represents a nonempty sequence, and may be abbreviated
[X+], and

the type Empty represents an empty sequence and is abbreviated [].

Some operations of the type Sequence aren't defined by Sequential, so you
can't call them if all you have is X[]. Therefore, we need the
if (nonempty ... ) construct to gain access to these operations.

In fact, this is just a sneak preview of the fact that almost all operators
in Ceylon are just sugar for method calls upon a type. We'll come back to this
later, when we talk about operator polymorphism.

Ceylon doesn't need C-style for loops. Instead, combine for with the
range operator:

Correspondence provides the capability to access elements of the sequence
by index, and

Iterable provides the ability to iterate the elements of the sequence.

Now open the class Range
in the IDE, to see a concrete implementation of the Sequence interface.

Empty sequences and the bottom type

Finally, check out the definition of
Empty.
Notice that Empty is declared to be a subtype of List<Nothing>. This special
type Nothing, often called the bottom type, represents:

the empty set, or equivalently

the intersection of all types.

Since the empty set is a subset of all other sets, Nothing is assignable to
all other types. Why is this useful here? Well, Correspondence<Integer,Element>
and Iterable<Element> are both covariant in the type parameter Element. So
Empty is assignable to Correspondence<Integer,T> and Iterable<T> for any
type T. That's why Empty doesn't need a type parameter.

Since there are no actual instances of Nothing, if you ever see an attribute
or method of type Nothing, you know for certain that it can't possibly ever
return a value. There is only one possible way that such an operation can
terminate: by throwing an exception.

Another cool thing to notice here is the return type of the
first and
item() operations
of Empty. You might have been expecting to see Nothing? here, since they
override supertype members of type T?. But as we saw in the
first part of the Tour, Nothing? is just an abbreviation for
Null|Nothing. And Nothing is the empty set, so the union Nothing|T of
Nothing with any other type T is just T itself.

The Ceylon compiler is able to do all this reasoning automatically. So when
it sees an Iterable<Nothing>, it knows that the operation first is of type
Null, i.e. that its value is null.

Cool, huh?

Sequence gotchas for Java developers

Superficially, a sequence type looks a lot like a Java array, but really it's
very, very different! First, of course, a sequence type Sequential<String> is
an immutable interface, it's not a mutable concrete type like an array. We
can't set the value of an element:

String[] operators = .... ;
operators[0] = "^"; //compile error

Furthermore, the index operation operators[i] returns an optional type
String?, which results in quite different code idioms. To begin with, we
don't iterate sequences by index like in C or Java. The following code does
not compile:

Tuple extends Sequence, so we can do all the usual kinds of sequency
things to a tuple, iterate it, and so on. As with sequences, we can access
a tuple element by index. But in the case of a tuple, Ceylon is able to
determine the type of the element when the index is a literal integer:

This tuple contains two Floats followed by an unknown number of Strings.

Now we can see that a sequence type like [String*] or [String+] can
be viewed as a degenerate tuple type!

Destructuring

Individually accessing the elements of a tuple by numeric index can be
a little verbose, so Ceylon supports a sophisticated sort of parallel
assignment called destructuring. We can rewrite the code above like
this:

value [x, y, label] = point;

This introduces three new values, x and y of inferred type Float,
and label of inferred type String.