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+}.
We may construct an instance of Iterable using braces:
{String+} words = { "hello", "world" };
{String+} moreWords = { "hola", "mundo", *words };
The prefix * is called the spread operator. It "spreads" the values of a
          stream. So moreWords produces the values "hola", "mundo", "hello", "world"
          when iterated.
As we'll see later, the braces may even contain a comprehension, making them much more powerful than what you see here.
Iterable is a subtype of the interface
          Category,
          so we can use the in operator to test if a value is produced by the
          Iterable.
if (exists char = text[i],
    char in {',', '.', '!', '?', ';', ':'}) {
    //...
}
"index must be between 1 and 100"
assert (index in 1..100);
The in operator is just syntactic sugar for the method
          contains() of
          Category.
Iterating a stream
To iterate an instance of Iterable, we can use a
          for loop:
for (word in moreWords) {
    print(word);
}
(Note that in this context, the in keyword isn't the operator we just met
          above, it's just part of the syntax of the for loop.)
If, for any reason, we need an index for each element produced by a stream,
          we can use a special variation of the for loop that is designed for
          iterating streams of Entrys:
for (i -> word in moreWords.indexed) {
    print("``i``: ``word``");
}
The indexed
          attribute returns a stream of entries containing the indexed elements of
          the original stream.
(Note: the arrow -> is syntax sugar for the class Entry. So we can
          write the type of the entry stream as {<Integer->String>*}.)
It's often useful to be able to iterate two sequences at once. The
          zipEntries() function
          comes in handy here:
for (name -> place in zipEntries(names,places)) {
    print(name + " @ " + place);
}
Now there's one more very important thing to know when you start mixing streams with mutable objects, variables, or impure functions. This is a common source of error for folks new to Ceylon.
Gotcha!
Streams created using the { ... } syntax are always lazy. That is:
- their elements are not evaluated until the stream is iterated, and
- each element is reevaluated each time the stream is iterated.
Consider this code:
variable value counter = 0;
value stream = { for (i in 0:5) counter++ }; //curly braces means LAZY!
print(stream); //evaluate elements
print(stream); //reevaluate elements
The code prints:
{ 0, 1, 2, 3, 4 }
{ 5, 6, 7, 8, 9 }
If this behavior is not what you're looking for, you'll need a different sort of stream! One option is to use a sequence instead.
variable value counter = 0;
value stream = [ for (i in 0:5) counter++ ]; //square brackets means EAGER!
print(stream); //elements already evaluated
print(stream); //elements already evaluated
This code prints:
[0, 1, 2, 3, 4]
[0, 1, 2, 3, 4]
An even more confusing example arises when one attempts to form a stream by incrementally "cons"-ing elements at the head of the stream. A first, naive attempt might look like this:
variable value stream = { 0 };
stream = { 1, *stream };
stream = { 2, *stream };
This code results in an infinite stream of 2s, instead of the stream {2,1,0}.
          That's because the spread references to stream is evaluated lazily!
The recommended solution is to use the follow() method, which forces the
          references to stream to be evaluated eagerly:
variable value stream = { 0 };
stream = stream.follow(1);
stream = stream.follow(2);
Alternatively, one could use a sequence:
variable [Integer+] sequence = [0];
sequence = [1, *sequence];
sequence = [2, *sequence];
So now, naturally, its time to learn about sequences.
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*]orX[],
- the type Sequence<X>represents a nonempty sequence, and may be abbreviated[X+], and
- the type Emptyrepresents 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.
void printBounds(String[] strings) {
    if (nonempty strings) {
        //strings is of type [String+] here
        print(strings.first + ".." + strings.last);
    }
    else {
        print("Empty");
    }
}
Notice how this is just a continuation of the
          pattern established for
          null value handling. In fact, both these constructs are just syntactic
          abbreviations for type narrowing:
- 
if (nonempty strings)is an abbreviation forif (is [String+] strings), just like
- 
if (exists name)is an abbreviation forif (is Object name).
Sequence syntax sugar
There's lots more syntactic sugar for sequences. We can use a bunch of familiar Java-like syntax:
String[] operators = [ "+", "-", "*", "/" ];
String? plus = operators[0];
String[] multiplicative = operators[2..3];
Oh, and the expression [] evaluates to an instance of Empty.
[] none = [];
However, unlike Java, all these syntactic constructs are pure abbreviations. The code above is exactly equivalent to the following de-sugared code:
Sequential<String> operators = ... ;
Null|String plus = operators.get(0);
Sequential<String> multiplicative = operators.span(2,3);
(We'll come back to what the list of values in brackets means in a minute!)
The Sequential interface extends
          Iterable,
          so we can iterate a Sequential using a for loop:
for (op in operators) {
    print(op);
}
Ranges
A Range
          is a kind of Sequence. The span
          function creates a Range. The following:
Character[] uppercaseLetters = 'A'..'Z';
Integer[] countDown = 10..0;
Is just sugar for:
Sequential<Character> uppercaseLetters = span('A','Z');
Sequential<Integer> countDown = span(10,0);
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:
variable Integer fac=1;
for (n in 1..100) {
    fac*=n;
    print("Factorial ``n``! = ``fac``");
}
Sequence and its supertypes
It's probably a good time to see some more advanced Ceylon code. What better place to find some than in the language module itself?
You can find the API documentation and source code of
          Sequence
          online, or you can go to Navigate > Open Ceylon Declaration... to view the
          declaration of Sequential directly inside Ceylon IDE.
The most important operations of Sequential are inherited from
          Correspondence,
          and Iterable.
- 
Correspondenceprovides the capability to access elements of the sequence by index, and
- 
Iterableprovides 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:
for (i in 0..operators.size-1) {
    String op = operators[i]; //compile error
    // ...
}
Here, operators[i] is a String?, which is not directly assignable to
          String.
Instead, if we need access to the index, we use the special form of for
          shown below.
for (i -> op in operators.indexed) {
    // ...
}
Likewise, we don't usually do an upfront check of an index against the sequence length:
if (i>operators.size-1) {
    throw IndexOutOfBoundException();
}
else {
    return operators[i]; //compile error
}
Instead, we do the check after accessing the sequence element:
if (exists op = operators[i]) {
    return op;
}
else {
    throw IndexOutOfBoundException();
}
Indeed, this is a common use for assert:
assert (exists op = operators[i]);
return op;
We especially don't ever need to write the following:
if (i>operators.size-1) {
    return "";
}
else {
    return operators[i]; //compile error
}
This is much cleaner:
return operators[i] else "";
All this may take a little getting used to. But what's nice is that all the
          exact same idioms also apply to other kinds of Correspondence, including
          Maps .
Tuples
A tuple is a linked list which captures the static type of each individual element in the list. For example:
[Float,Float,String] point = [0.0, 0.0, "origin"];
This tuple contains two Floats followed by a String. That information
          is captured in its static type, [Float,Float,String].
Each link of the list is an instance of the class
          Tuple.
          If you really must know, the code above is syntax sugar for the following:
Tuple<Float|String,Float,Tuple<Float|String,Float,Tuple<String,String>>>
        point = Tuple(0.0, Tuple(0.0, Tuple("origin", [])));
Therefore, we always use syntax sugar when working with tuples.
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:
Float x = point[0];
Float y = point[1];
String label = point[2];
Null zippo = point[3];
A unterminated tuple is a tuple where the last link in the list is
          a sequence, not an Empty. For example:
String[] labels = ... ;
[Float,Float,String*] point = [0.0, 0.0, *labels];
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.
The syntax [x, y, label] is called a tuple pattern.
We can even use destructuring in for loops:
for ([x, y, label] in points) {
    print("``label``: (``x``, ``y``)");
}
And in if:
if (nonempty [name, *rest] = process.arguments) {
    print("Hello ``name``!");
}
And in let:
print(let ([x, y] = [1.0, 2.0]) "(``x``, ``y``)");
We can also destructure Entrys. We've already seen this used in a
          for loop:
for (i -> op in operators.indexed) {
    // ...
}
The syntax i -> op is called an entry pattern.
More complex destructuring patterns may be formed by nesting tuple and entry patterns, for example:
for (i -> [en, es] in translations.indexed) {
    print("``i``: ``en`` ``es``");
}
Ceylon doesn't yet support destructuring in the cases of a switch,
          but that's something that will definitely be added to a future release
          of the language.
There's more...
If you're interested, you can find a more in-depth discussion of tuples here.
You can read more about destructuring here.
Next up we'll explore some more details of the type system, starting with type aliases and type inference.