Choosing Between 'any' and Specific Types in Ceylon Collections
A decision guide for choosing between Ceylon’s universal type 'any' and safer alternatives like unions, variance‑annotated generics, or traits when designing heterogeneous collections.
08 Oct 2025, 07:13 UTC

Decision: When to use the top type 'any' versus explicit type constraints in Ceylon
Ceylon’s static type system offers the universal type any as the root of the hierarchy, but relying on it can weaken compile‑time guarantees. This guide helps you decide whether to model a heterogeneous collection with any, a union type, a generic with variance, or a trait‑based approach.
Constraints and goals
- Maintain type safety without excessive casting.
- Preserve interoperability with existing Java code.
- Keep the API ergonomic for callers.
- Avoid runtime surprises when the collection is consumed.
Comparison of options
| Option | How it works | Safety guarantees | Interoperability impact | Typical use case |
|---|---|---|---|---|
List<any> |
Elements are stored as the top type; any operation requires a runtime type check or explicit narrowing. | Lowest compile‑time safety; errors surface only at runtime. | Seamless with Java Object‑based APIs. | Quick prototyping or when the element set is truly unknown. |
List<String|Integer> (union) |
Explicitly enumerates allowed concrete types. | High safety; the compiler knows the exact set. | Requires wrapping/unwrapping when passing to Java. | Fixed, small set of heterogeneous values (e.g., config entries). |
List<out Printable> (covariant generic) |
Uses a trait Printable with variance annotation out to allow sub‑type safety. |
High safety for read‑only access; writes are prohibited. | Maps cleanly to Java’s List<? extends Printable>. |
Collections of items that share a behavior but differ in implementation. |
List<Printable> (invariant generic) |
Exact type match; both reads and writes are type‑checked. | Strongest safety for mutable collections. | Needs explicit conversion when interacting with Java wildcards. | When you need to add elements of the exact trait type. |
Trade‑off summary
- Safety vs. flexibility:
anyoffers maximum flexibility but sacrifices static guarantees; unions and generics restore safety at the cost of expressing the allowed set. - Mutability: Covariant generics (
out) are ideal for read‑only scenarios; invariant generics are required when you need to insert elements. - Java interop: Mapping
anytojava.lang.Objectis trivial, while trait‑based generics translate to Java wildcards (? extendsor? super) and may need adapter methods.
Concrete implementation: a trait‑based collection
Suppose we model a set of document elements that can be rendered to a string. We define a Printable trait and two implementations: Paragraph and Image. A function accepts a read‑only list of Printable using covariance, ensuring callers can pass lists of either concrete type without losing type safety.
// Define the trait
abstract class Printable() {
shared formal String render();
}
// Concrete implementations
class Paragraph(String content) extends Printable() {
shared actual String render() => "<p>" + content + "</p>";
}
class Image(String url) extends Printable() {
shared actual String render() => "<img src='" + url + "'>";
}
// Function that works on any List of Printable (covariant)
shared void printAll(List<out Printable> items) {
for (item in items) {
print(item.render());
}
}
// Usage
shared void run() {
val paras = [Paragraph("Hello"), Paragraph("World")];
val imgs = [Image("http://example.com/a.png"), Image("http://example.com/b.png")];
printAll(paras); // OK: List<Paragraph> is subtypes of List<out Printable>
printAll(imgs); // OK: List<Image> also fits
// The following would fail to compile if we tried to add an element:
// paras.add(Image("x")); // error: add not available on covariant list
}
Verification steps
- Install the Ceylon SDK (version 1.3.3 or later, the last stable release).
- Save the code above to a file named
print.ceylon. - Compile:
ceylon compile --src=. print(run in a terminal with appropriate permissions). - Run the generated module:
ceylon run print/1.0.0. - Expected output: the rendered strings for each paragraph and image, each on its own line.
If compilation succeeds and the output matches the expected strings, the trait‑based covariant generic is working as intended.
Limitations and practical checks
- Ceylon’s tooling and community activity have diminished; ensure you have a working JDK and the Ceylon compiler before starting a project.
- When interacting with Java APIs that expect mutable lists, you may need to copy the Ceylon list into a Java
List<?>or write a thin adapter. - To confirm variance behavior, try changing the function signature to
List<Printable>(invariant) and attempt to pass aList<Paragraph>. The compiler should reject it, confirming the invariance rule.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.