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?
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.)
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.
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.
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
Collection
s 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.
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)
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 collectionsSequence
(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).
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.
Another subtype of List
is Array
, also a true class. This is the familiar Java array, a mutable fixed-size list of elements.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With