Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Difference between List, Tuple, Sequence, Sequential, Iterable, Array, etc. in Ceylon

Ceylon has several different concepts for things that might all be considered some kind of array: List, Tuple, Sequence, Sequential, Iterable, Array, Collection, Category, etc. What's is different about these these types and when should I use them?

like image 905
drhagen Avatar asked Jan 30 '16 11:01

drhagen


2 Answers

The best place to start to learn about these things at a basic level is the Ceylon tour. And the place to learn about these things in depth is the module API. It can also be helpful to look at the source files for these.

Like all good modern programming languages, the first few interfaces are super abstract. They are built around one formal member and provide their functionality through a bunch of default and actual members. (In programming languages created before Java 8, you may have heard these called "traits" to distinguish them from traditional interfaces which have only formal members and no functionality.)

Category

Let's start by talking about the interface Category. It represents types of which you can ask "does this collection contain this object", but you may not necessarily be able to get any of the members out of the collection. It's formal member is:

shared formal Boolean contains(Element element)

An example might be the set of all the factors of a large number—you can efficiently test if any integer is a factor, but not efficiently get all the factors.

Iterable

A subtype of Category is the interface Iterable. It represents types from which you can get each element one at a time, but not necessarily index the elements. The elements may not have a well-defined order. The elements may not even exist yet but are generated on the fly. The collection may even be infinitely long! It's formal member is:

shared formal Iterator<Element> iterator()

An example would be a stream of characters like standard out. Another example would be a range of integers provided to a for loop, for which it is more memory efficient to generate the numbers one at a time.

This is a special type in Ceylon and can be abbreviated {Element*} or {Element+} depending on if the iterable might be empty or is definitely not empty, respectively.

Collection

One of Iterable's subtypes is the interface Collection. It has one formal member:

shared formal Collection<Element> clone()

But that doesn't really matter. The important thing that defines a Collection is this line in the documentation:

All Collections are required to support a well-defined notion of value equality, but the definition of equality depends upon the kind of collection.

Basically, a Collection is a collection who structure is well-defined enough to be equatable to each other and clonable. This requirement for a well-defined structure means that this is the last of the super abstract interfaces, and the rest are going to look like more familiar collections.

List

One of Collection's subtypes is the interface List. It represents a collection whose elements we can get by index (myList[42]). Use this type when your function requires an array to get things out of, but doesn't care if it is mutable or immutable. It has a few formal methods, but the important one comes from its other supertype Correspondence:

shared formal Item? get(Integer key)

Sequential, Sequence, Empty

The most important of List's subtypes is the interface Sequential. It represents an immutable List. Ceylon loves this type and builds a lot of syntax around it. It is known as [Element*] and Element[]. It has exactly two subtypes:

  • Empty (aka []), which represents empty collections
  • Sequence (aka [Element+]), which represents nonempty collections.

Because the collections are immutable, there are lots of things you can do with them that you can't do with mutable collections. For one, numerous operations can fail with null on empty lists, like reduce and first, but if you first test that the type is Sequence then you can guarantee these operations will always succeed because the collection can't become empty later (they're immutable after all).

Tuple

A very special subtype of Sequence is Tuple, the first true class listed here. Unlike Sequence, where all the elements are constrained to one type Element, a Tuple has a type for each element. It gets special syntax in Ceylon, where [String, Integer, String] is an immutable list of exactly three elements with exactly those types in exactly that order.

Array

Another subtype of List is Array, also a true class. This is the familiar Java array, a mutable fixed-size list of elements.

like image 134
drhagen Avatar answered Oct 05 '22 19:10

drhagen


drhagen has already answered the first part of your question very well, so I’m just going to say a bit on the second part: when do you use which type?

In general: when writing a function, make it accept the most general type that supports the operations you need. So far, so obvious.

Category is very abstract and rarely useful.

Iterable should be used if you expect some stream of elements which you’re just going to iterate over (or use stream operations like filter, map, etc.).

Another thing to consider about Iterable is that it has some extra syntax sugar in named arguments:

void printAll({Anything*} things, String prefix = "") {
    for (thing in things) {
        print(prefix + (thing?.string else "<null>"));
    }
}

printAll { "a", "b", "c" };
printAll { prefix = "X"; "a", "b", "c" };

Try online

Any parameter of type Iterable can be supplied as a list of comma-separated arguments at the end of a named argument list. That is,

printAll { "a", "b", "c" };

is equivalent to

printAll { things = { "a", "b", "c" }; };

This allows you to craft DSL-style expressions; the tour has some nice examples.

Collection is, like Correspondence, fairly abstract and in my experience rarely used directly.

List sounds like it should be a often used type, but actually I don’t recall using it a lot. I’m not sure why. I seem to skip over it and declare my parameters as either Iterable or Sequential.

Sequential and Sequence are when you want an immutable, fixed-length list. It also has some syntax sugar: variadic methods like void foo(String* bar) are a shortcut for a Sequential or Sequence parameter. Sequential also allows you to do use the nonempty operator, which often works out nicely in combination with first and rest:

String commaSeparated(String[] strings) {
    if (nonempty strings) {
        value sb = StringBuilder();
        sb.append(strings.first); // known to exist
        for (string in strings.rest) { // skip the first one
            sb.append(", ").append(string);
            // we don’t need a separate boolean to track if we need the comma or not :)
        }
        return sb.string;
    } else {
        return "";
    }
}

Try online

I usually use Sequential and Sequence when I’m going to iterate over a stream several times (which could be expensive for a generic Iterable), though List might be the better interface for that.

Tuple should never be used as Tuple (except in the rare case where you’re abstracting over them), but with the [X, Y, Z] syntax sugar it’s often useful. You can often refine a Sequential member to a Tuple in a subclass, e. g. the superclass has a <String|Integer>[] elements which in one subclass is known to be a [String, Integer] elements.

Array I’ve never used as a parameter type, only rarely as a class to instantiate and use.

like image 37
Lucas Werkmeister Avatar answered Oct 05 '22 21:10

Lucas Werkmeister