Blog of Gavin King

Planning the future of Ceylon 1.x

With the release of Ceylon 1.1, we've reached a point where we need to do some serious thinking about what are our priorities for the development of Ceylon 1.1.5, 1.2, and beyond. I definitely don't yet have a crystal clear vision of what is going to be in 1.2, so we're also looking for community feedback on this.

I do know of one item which is the top priority right now, and will be the main feature of Ceylon 1.1.5:

This was a feature that slipped from Ceylon 1.0, and which again narrowly missed out on inclusion in Ceylon 1.1. The concept behind serialization in Ceylon is to have an API responsible for assembling and disassembling objects that is agnostic as to the actual format of the serialized stream. Of course, this API also has to be platform neutral, in order to allow serialization between programs running on the JVM and programs running on a JavaScript VM. Tom Bentley already has a working prototype implementation. Once this feature is done, we can start working on serialization libraries supporting JSON and whatever else.

I also count the following as a high priority areas of work:

  • Java EE integration, and support for technologies like JPA and CDI.
  • Adding properties to the language, that is, a new syntax for attribute references, allowing easy MVC UI bindings.
  • Improving the Cayla web framework, and ceylon.html.

Beyond that, we're not sure where else we should concentrate development effort. Here are some things that stick out to me:

  • Addition of named constructors, allowing multiple ways to instantiate and initialize a class.
  • AST transformers—a system of compiler plugins, based around ceylon.ast, enabling advanced compile-time metaprogramming, which would form the foundation for LINQ-style queries, interceptors and proxies, and autogeneration of equals(), hash, and string, and more.
  • Addition of a syntax for expressing patterns in BNF.
  • The Ceylon plugin for IntelliJ IDEA.
  • Android support.
  • Assemblies—a facility for packaging multiple modules into a deployable "application".
  • New platform modules defining dynamic interfaces for typesafe interaction with JavaScript APIs such as the DOM, jQuery, etc.
  • Interoperation with dynamic languages on the JVM, via Ceylon's dynamic blocks and dynamic interfaces.
  • Enabling the use of Ceylon for scripting.

We can't do all of this in Ceylon 1.2. Therefore, we're looking for feedback from the community. Let us know, here in comments, or on the mailing list, what you feel is missing from Ceylon, either from the above list, or whatever else you think is important.

Typesafe APIs for the browser

A new feature in Ceylon 1.1, that I've not blogged about before, is dynamic interfaces. This was something that Enrique and I worked on together with Corbin Uselton, one of our GSoC students.

Ordinarily, when we interact with JavaScript objects, we do it from within a dynamic block, where Ceylon's usual scrupulous typechecking is suppressed. The problem with this approach is that if it's an API I use regularly, my IDE can't help me get remember the names and signatures of all the operations of the API.

Dynamic interfaces make it possible to ascribe static types to an untyped JavaScript API. For example, we could write a dynamic interface for the HTML 5 CanvasRenderingContext2D like this:

dynamic CanvasRenderingContext2D {
    shared formal variable String|CanvasGradient|CanvasPattern fillStyle;
    shared formal variable String font;

    shared formal void beginPath();
    shared formal void closePath();

    shared formal void moveTo(Integer x, Integer y);
    shared formal void lineTo(Integer x, Integer y);

    shared formal void fill();
    shared formal void stroke();

    shared formal void fillText(String text, Integer x, Integer y, Integer maxWidth=-1);

    shared formal void arc(Integer x, Integer y, Integer radius, Float startAngle, Float endAngle, Boolean anticlockwise);
    shared formal void arcTo(Integer x1, Integer y1, Integer x2, Float y2, Integer radius);

    shared formal void bezierCurveTo(Integer cp1x, Integer cp1y, Integer cp2x, Float cp2y, Integer x, Integer y);

    shared formal void strokeRect(Integer x, Integer y, Integer width, Integer height);
    shared formal void fillRect(Integer x, Integer y, Integer width, Integer height);
    shared formal void clearRect(Integer x, Integer y, Integer width, Integer height);

    shared formal CanvasGradient createLinearGradient(Integer x0, Integer y0, Integer x1, Integer y1);
    shared formal CanvasGradient createRadialGradient(Integer x0, Integer y0, Integer r0, Integer x1, Integer y1, Integer r1);
    shared formal CanvasPattern createPattern(dynamic image, String repetition);

    //TODO: more operations!!
}

dynamic CanvasGradient {
    shared formal void addColorStop(Integer offset, String color);
}

dynamic CanvasPattern {
    //todo
}

Now, if we assign an instance of JavaScript's CanvasRenderingContext2D to this interface type, we won't need to be inside a dynamic block when we call its methods. You can try it out in your own browser by clicking the "TRY ONLINE" button!

CanvasRenderingContext2D ctx;
dynamic {
    //get the CanvasRenderingContext2D from the 
    //canvas element using dynamically typed code
    ctx = ... ;
}

//typesafe code, checked at compile time 
ctx.fillStyle = "navy";
ctx.fillRect(50, 50, 235, 60);
ctx.beginPath();
ctx.moveTo(100,50);
ctx.lineTo(60,5);
ctx.lineTo(75,75);
ctx.fill();
ctx.fillStyle = "orange";
ctx.font = "40px PT Sans";
ctx.fillText("Hello world!", 60, 95);

Notice that we don't need to ascribe an explicit type to every operation of the interface. We can leave some methods, or even just some parameters of a method untyped, by declaring them dynamic. Such operations may only be called from within a dynamic block, however.

A word of caution: dynamic interfaces are a convenient fiction. They can help make it easier to work with an API in your IDE, but at runtime there is nothing Ceylon can do to ensure that the object you assign to the dynamic interface type actually implements the operations you've ascribed to it.

Ceylon 1.1.0 is now available

Ten whole months in the making, this is the biggest release of Ceylon so far! Ceylon 1.1.0 incorporates oodles of enhancements and bugfixes, with well over 1400 issues closed.

Ceylon is a modern, modular, statically typed programming language for the Java and JavaScript virtual machines. The language features a flexible and very readable syntax, a unique and uncommonly elegant static type system, a powerful module architecture, and excellent tooling, including an awesome Eclipse-based IDE.

Ceylon enables the development of cross-platform modules that execute portably in both virtual machine environments. Alternatively, a Ceylon module may target one or the other platform, in which case it may interoperate with native code written for that platform.

For the end user, the most significant improvements in Ceylon 1.1 are:

  • performance enhancements, especially to compilation times in the IDE,
  • even smoother interoperation with Java overloading and Java generics,
  • out of the box support for deployment of Ceylon modules on OSGi containers,
  • enhancements to the Ceylon SDK, including the new platform modules ceylon.promise, ceylon.locale, and ceylon.logging, along with many improvements to ceylon.language, ceylon.collection, and ceylon.test,
  • many new features and improvements in Ceylon IDE, including
  • ceylon.formatter, a high-quality code formatter written in Ceylon,
  • support for command line tool plugins, including the new ceylon format and ceylon build plugins, and
  • integration with vert.x.

A longer list of changes may be found here.

In the box

This release includes:

  • a complete language specification that defines the syntax and semantics of Ceylon in language accessible to the professional developer,
  • a command line toolset including compilers for Java and JavaScript, a documentation compiler, and support for executing modular programs on the JVM and Node.js,
  • a powerful module architecture for code organization, dependency management, and module isolation at runtime,
  • the language module, our minimal, cross-platform foundation of the Ceylon SDK, and
  • a full-featured Eclipse-based integrated development environment.

Language

Ceylon is a highly understandable object-oriented language with static typing. The language features:

  • an emphasis upon readability and a strong bias toward omission or elimination of potentially-harmful or potentially-ambiguous constructs and toward highly disciplined use of static types,
  • an extremely powerful and uncommonly elegant type system combining subtype and parametric polymorphism with:
    • first-class union and intersection types,
    • both declaration-site and use-site variance, and
    • the use of principal types for local type inference and flow-sensitive typing,
  • a unique treatment of function and tuple types, enabling powerful abstractions, along with the most elegant approach to null of any modern language,
  • first-class constructs for defining modules and dependencies between modules,
  • a very flexible syntax including comprehensions and support for expressing tree-like structures, and
  • fully-reified generic types, on both the JVM and JavaScript virtual machines, and a unique typesafe metamodel.

More information about these language features may be found in the feature list and quick introduction.

This release introduces the following new language features:

  • support for use-site variance, enabling complete interop with Java generics,
  • dynamic interfaces, providing a typesafe way to interoperate with dynamically typed native JavaScript code,
  • type inference for parameters of anonymous functions that occur in an argument list, and
  • a Byte class that is optimized by the compiler.

Language module

The language module was a major focus of attention in this release, with substantial performance improvements, API optimizations, and new features, including the addition of a raft of powerful operations for working with streams.

The language module now includes an API for deploying Ceylon modules programmatically from Java.

The language module is now considered stable, and no further breaking changes to its API are contemplated.

Command line tools

The ceylon command now supports a plugin architecture. For example, type:

ceylon plugin install ceylon.formatter/1.1.0

To install the ceylon format subcommand.

IDE

This release of the IDE features dramatic improvements to build performance, and introduces many new features, including:

  • a code formatter,
  • seven new refactorings and many improvements to existing refactorings,
  • many new quick fixes/assists,
  • IntelliJ-style "chain completion" and completion of toplevel functions applying to a value,
  • a rewritten Explorer view, with better presentation of modules and modular dependencies,
  • synchronization of all keyboard accelerators with JDT equivalents,
  • Quick Find References, Recently Edited Files, Format Block, Visualize Modular Dependencies, Open in Type Hierarchy View, Go to Refined Declaration, and much more.

SDK

The platform modules, recompiled for 1.1.0, are available in the shared community repository, Ceylon Herd.

This release introduces the following new platform modules:

  • ceylon.promise, cross-platform support for promises,
  • ceylon.locale, a cross-platform library for internationalization, and
  • ceylon.logging, a simple logging API.

In addition, there were many improvements to ceylon.collection, which is now considered stable, and to ceylon.test.

The Ceylon SDK is available from Ceylon Herd, the community module repository.

Vert.x integration

mod-lang-ceylon implements Ceylon 1.1 support for Vert.x 2.1.x, and may be downloaded here.

Community

The Ceylon community site, http://ceylon-lang.org, includes documentation, and information about getting involved.

Source code

The source code for Ceylon, its specification, and its website is freely available from GitHub.

Issues

Bugs and suggestions may be reported in GitHub's issue tracker.

Acknowledgement

We're deeply indebted to the community volunteers who contributed a substantial part of the current Ceylon codebase, working in their own spare time. The following people have contributed to this release:

Gavin King, Stéphane Épardaud, Tako Schotanus, Emmanuel Bernard, Tom Bentley, Aleš Justin, David Festal, Max Rydahl Andersen, Mladen Turk, James Cobb, Tomáš Hradec, Ross Tate, Ivo Kasiuk, Enrique Zamudio, Roland Tepp, Diego Coronel, Daniel Rochetti, Loic Rouchon, Matej Lazar, Lucas Werkmeister, Akber Choudhry, Corbin Uselton, Julien Viet, Stephane Gallès, Paco Soberón, Renato Athaydes, Michael Musgrove, Flavio Oliveri, Michael Brackx, Brent Douglas, Lukas Eder, Markus Rydh, Julien Ponge, Pete Muir, Henning Burdack, Nicolas Leroux, Brett Cannon, Geoffrey De Smet, Guillaume Lours, Gunnar Morling, Jeff Parsons, Jesse Sightler, Oleg Kulikov, Raimund Klein, Sergej Koščejev, Chris Marshall, Simon Thum, Maia Kozheva, Shelby, Aslak Knutsen, Fabien Meurisse, Sjur Bakka, Xavier Coulon, Ari Kast, Dan Allen, Deniz Türkoglu, F. Meurisse, Jean-Charles Roger, Johannes Lehmann, Alexander Altman, allentc, Nikolay Tsankov, Chris Horne, gabriel-mirea, Georg Ragaller, Griffin DeJohn, Harald Wellmann, klinger, Luke, Oliver Gondža, Stephen Crawley.

Ceylon 1.1 progress report

Ceylon 1.1 has been in development for 6 months already, so it's way past time for a progress report! Since the release is nearly ready, this is going to have to take the form of a summary of what we've been working on. Well, that's a daunting task, since we've already closed more than 650 issues in the compiler and language module, and 300 in the IDE. Phew!

The top priorities for Ceylon 1.1 were:

  • Finalize and freeze the language module APIs.
  • Clean up and minimize the use of Java and JavaScript native code in the language module.
  • Mop up remaining issues affecting Java interop.
  • Performance.
  • IDE build performance.
  • Finish ceylon.collection and freeze its public API.

Naturally, we've also fixed hundreds of bugs unrelated to those priorities.

Language changes

There have been very few changes to the language, which has been considered stable since last year's 1.0 release. The big new features in 1.1 are:

  • Support for use-site variance.
  • Introduction of a Byte class that may be optimized by the compiler to byte on the JVM.
  • Type inference for parameters of anonymous functions that occur as arguments in positional argument lists.

Other notable changes are:

  • Powerful disjointness analysis for sequence and tuple types.
  • Allow comprehensions to begin with an if clause.
  • New sealed annotation to prevent extension or instantiation of a type outside the module in which it is defined.
  • Introduction of dynamic interfaces, designed for wrapping native JavaScript APIs.
  • Redefined - operator to work for any Invertible.
  • Allow metamodel references to objects and members of objects.
  • try (x) was changed to distinguish between the lifecycles of Obtainable and Destroyable resources.
  • The type of the expression {} is now {Nothing*} instead of [].
  • Allow refinement of multiple overloaded versions of a Java supertype method.
  • Added ability to catch instances of Throwable.
  • Minor adjustment to type argument inference algorithm for covariant and contravariant type parameters.
  • Change to the syntax for dynamic enumeration expressions in native JavaScript interop.
  • case (foo, bar) is now written case (foo|bar).
  • Removal of operator-style invocation expressions.

The last four changes are breaking changes but should not impact very many programs.

Finally, note that:

  • Runnable functions must now be shared, eliminating an inconsistency between Ceylon on JVM and on JS.

Language module changes

For the 1.1 release, we've invested a lot of thought and development effort in the language module, carefully reviewing its design and scope, reducing the use of native code to an absolute minimum, optimizing performance, and picking on anything that looked like a mistake.

Therefore, this release makes several breaking changes, which will impact existing programs. As of Ceylon 1.1, the language module is considered stable, and we won't make further breaking changes.

  • Addition of a raft of new methods and functions for working with streams.
  • Optimization of the performance of Array, along with some minor improvements to interop with Java native arrays.
  • Removal of the Cloneable interface, and addition of a clone() method to Collection.
  • Addition of Throwable.
  • Replacement of Closeable with Obtainable and Destroyable.
  • Correspondence.items() changed to getAll().
  • Maps and Entrys may now have null items.
  • Various minor changes to the operations of Iterable, List, and Map, including breaking changes to the signatures of Iterable.sequence() and Iterable.fold().
  • ArraySequence is now sealed and may be instantiated via the sequence() function.
  • Substantial redesign of Enumerable and Range.
  • Several changes to the type hierarchy for numeric types.
  • Improvements to StringBuilder.
  • Removal of SequenceBuilder, of redundant functions entries() and coalesce(), and of LazyList, LazySet, and LazyMap.
  • Addition of Array.sortInPlace().

Modularity

We're currently investing effort in trying to make it easier to use Ceylon modules outside of the Ceylon module runtime.

  • Ceylon .car archives now include automatically generated OSGi and Maven metadata, and can execute in an OSGi container.
  • New API for cross-platform resource loading.
  • Support for deploying Ceylon modules to Vert.x.

SDK

Notable changes to the SDK include:

  • Introduction of ceylon.locale, ceylon.logging, and ceylon.promise.
  • Many enhancements to ceylon.collection, including addition of ArrayList, TreeSet, TreeMap, and PriorityQueue classes, along with Stack and Queue interfaces.
  • Various improvements to ceylon.dbc.

The collections module is now considered stable, and its API is frozen.

Additionally, the ceylon.test module has been significantly enhanced, including the following improvements:

  • New testSuit, testListeners, and testExecutor annotations.
  • Redesigned events model.
  • HTML report generation in ceylon.test.
  • Support for TAP v13 (Test Anything Protocol).
  • Many improvements to ceylon.test.
  • Addition of ceylon test-js command.

IDE

Development of the IDE has been extremely active, with many new features and major performance enhancements.

  • Complete rework of build process, for much improved performance.
  • New refactorings: Move Out, Make Receiver, Move to Unit, Extract Parameter, Collect Parameters, Invert Boolean, Safe Delete.
  • Major enhancements to the Change Parameters refactoring.
  • Inline refactoring now works for shared class/interface members.
  • Much better handling of anonymous functions and function references in Extract and Inline refactorings.
  • Brand new high quality code formatter.
  • Rewritten Ceylon Explorer with much better presentation of modules and modular dependencies.
  • New navigation actions: Open in Type Hierarchy View, Go to Refined Declaration.
  • Popup Quick Find References and Recently Edited Files.
  • Graphical Visualize Modular Dependencies.
  • Further integration of "linked mode" with refactorings and quick assists.
  • Useful Format Block source action.
  • Auto-escape special characters when pasting into string literals.
  • Synchronization of all keyboard accelerators with JDT equivalents (by popular request).
  • Save actions in Ceylon Editor preferences.
  • IntelliJ-style "chain completion" (hit ctrl-space twice).
  • Propose toplevel functions applying to a type alongside members of the type.
  • Several new options for customizing autocompletion and appearance in Ceylon Editor preferences.
  • New quick fixes/assists: convert between string interpolation and concatenation, convert to/from verbatim string, add satisfied interfaces, add type parameter, change named argument list to positional, fill in argument names, export module, convert to verbose form refinement, print expression, fix refining method signature, change to if (exists), change module version, assign to for/try/if (exists)/if (nonempty)/if (is).
  • Run As Ceylon Test on node.js.
  • Support for running all tests in a project or source folder.
  • New default color scheme for syntax highlighting and many other aesthetic improvements.

Why I distrust wildcards and why we need them anyway

In any programming language that combines subtype polymorphism (object orientation) with parametric polymorphism (generics), the question of variance arises. Suppose I have a list of strings, type List<String>. Can I pass that to a function which accepts List<Object>? Let's start with this definition:

interface List<T> {
    void add(T element);
    Iterator<T> iterator();
    ...
}

Broken covariance

Intuitively, we might at first think that this should be allowed. This looks OK:

void iterate(List<Object> list) {
    Iterator<Object> it = list.iterator();
    ...
}
iterate(ArrayList<String>());

Indeed, certain languages, including Eiffel and Dart do accept this code. Sadly, it's unsound, as can be seen in the following example:

//Eiffel/Dart-like language with 
//broken covariance:
void put(List<Object> list) {
    list.add(10);
}
put(ArrayList<String>());

Here we pass a List<String> to a function accepting List<Object>, which attempts to add an Integer to the list.

Java makes this same mistake with arrays. The following code compiles:

//Java:
void put(Object[] list) {
    list[0]=10;
}
put(new String[1]);

It fails at runtime with an ArrayStoreException.

Use-site variance

Java takes a different approach, however, for generic class and interface types. By default, a class or interface type is invariant, which is to say, that:

  • L<U> is assignable to L<V> if and only if U is exactly the same type as V.

Since this is extremely inconvenient much of the time, Java supports something called use-site variance, where:

  • L<U> is assignable to L<? extends V> if U is a subtype of V, and
  • L<U> is assignable to L<? super V> if U is a supertype of V.

The ugly syntax ? extends V or ? super V is called a wildcard. We also say that:

  • L<? extends V> is covariant in V, and that
  • L<? super V> is contravariant in V.

Since Java's wildcard notation is so ugly, we're not going to use it anymore in this discussion. Instead, we'll write wildcards using the keywords in and out for contravariance and covariance respectively. Thus:

  • L<out V> is covariant in V, and
  • L<in V> is contravariant in V.

A given V is called the bound of the wildcard:

  • out V is an upper-bounded wildcard, and V is its upper bound, and
  • in V is a lower-bounded wildcard, and V is its lower bound.

In theory, we could have a wildcard with both an upper and lower bound, for example, L<out X in Y>.

We can express multiple upper bounds or multiple lower bounds using an intersection type, for example, L<out U&V> or L<in U&V>.

Note that the type expressions L<out Anything> and L<in Nothing> refer to exactly the same type, and this type is a supertype of all instantiations of L.

You'll often see people refer to wildcarded types as existential types. What they mean by this is that if I know that list is of type List<out Object>:

List<out Object> list;

Then I know that there exists an unknown type T, a subtype of Object, such that list is of type List<T>.

Alternatively, we can take a more Ceylonic point of view, and say that List<out Object> is the union of all types List<T> where T is a subtype of Object.

In a system with use-site variance, the following code does not compile:

void iterate(List<Object> list) {
    Iterator<Object> it = list.iterator();
    ...
}
iterate(ArrayList<String>()); //error: List<String> not a List<Object>

But this code does:

void iterate(List<out Object> list) {
    Iterator<out Object> it = list.iterator();
    ...
}
iterate(ArrayList<String>());

Correctly, this code does not compile:

void put(List<out Object> list) {
    list.add(10); //error: Integer is not a Nothing
}
put(ArrayList<String>());

Now we're at the entrance to the rabbit hole. In order to integrate wildcarded types into the type system, while rejecting unsound code like the above example, we need a much more complicated algorithm for type argument substitution.

Member typing in use-site variance

That is, when we have a generic type like List<T>, with a method void add(T element), instead of just straightforwardly substituting Object for T, like we do with ordinary invariant types, we need to consider the variance of the location in which the type parameter occurs. In this case, T occurs in a contravariant location of the type List, namely, as the type of a method parameter. The complicated algorithm, which I won't write down here, tells us that we should substitute Nothing, the bottom type, in this location.

Now imagine that our List interface has a partition() method with this signature:

interface List<T> {
    List<List<T>> partition(Integer length);
    ...
}

What is the return type of partition() for a List<out Y>? Well, without losing precision, it is:

 List<in List<in Y out Nothing> out List<in Nothing out Y>>

Ouch.

Since nobody in their right mind wants to have to think about types like this, a sensible language would throw away some of those bounds, leaving something like this:

List<out List<out Y>>

Which is vaguely acceptable. Sadly, even in this very simple case, we're already well beyond the point where the programmer can easily follow along with what the typechecker is doing.

So here's the essence of why I distrust use-site variance:

  • A strong principle in the design of Ceylon is that the programmer should always be able to reproduce the reasoning of the compiler. It is very difficult to reason about some of the complex types that arise with use-site variance.
  • It has a viral effect: once those wildcard types get a foothold in the code, they start to propagate, and it's quite hard to get back to my ordinary invariant types.

Declaration-site variance

A much saner alternative to use-site variance is declaration-site variance, where we specify the variance of a generic type when we declare it. This is the system we use in Ceylon. Under this system, we need to split List into three interfaces:

interface List<out T> {
     Iterator<T> iterator();
     List<List<T>> partition(Integer length);
     ...
}

interface ListMutator<in T> {
    void add(T element);
}

interface MutableList<T>
    satisfies List<T>&ListMutator<T> {}

List is declared to be a covariant type, ListMutator a contravariant type, and MutableList an invariant subtype of both.

It might seem that the requirement for multiple interfaces is a big disadvantage of declaration-site variance, but it often turns out to be useful to separate mutation from read operations, and:

  • mutating operations are very often invariant, whereas
  • read operations are very often covariant.

Now we can write our functions like this:

void iterate(List<Object> list) {
    Iterator<Object> it = list.iterator();
    ...
}
iterate(ArrayList<String>());

void put(ListMutator<Integer> list) {
    list.add(10);
}
put(ArrayList<String>()); //error: List<String> is not a ListMutator<Integer>

You can read more about declaration-site variance here.

Why we need use-site variance in Ceylon

Sadly, Java doesn't have declaration-site variance, and clean interoperation with Java is something that is very important to us. I don't like adding a major feature to the typesystem of our language purely for the purposes of interoperation with Java, and so I've resisted adding wildcards to Ceylon for years. In the end, reality and practicality won, and my stubborness lost. So Ceylon 1.1 now features use-site variance with single-bounded wildcards.

I've tried to keep this feature as tightly constrained as possible, with just the minimum required for decent Java interop. That means that, like in Java:

  • there are no double-bounded wildcards, of form List<in X out Y>, and
  • a wildcarded type can not occur in the extends or satisfies clause of a class or interface definition.

Furthermore, unlike Java:

  • there are no implicitly-bounded wildcards, upper bounds must always be written in explicitly, and
  • there is no support for wildcard capture.

Wildcard capture is a very clever feature of Java, which makes use of the "existential" interpretation of a wildcard type. Given a generic function like this one:

List<T> unmodifiableList<T>(List<T> list) => ... :

Java would let me call unmodifiableList(), passing a wildcarded type like List<out Object>, returning another wildcarded List<out Object>, reasoning that there is some unknown X, a subtype of Object for which the invocation would be well-typed. That is, this code is considered well-typed, even though the type List<out Object> is not assignable to List<T> for any T:

List<out Object> objects = .... ;
List<out Object> unmodifiable = unmodifiableList(objects);

In Java, typing errors involving wildcard capture are almost impossible to understand, since they involve the unknown, and undenoteable, type. I have no plans to add support for wildcard capture to Ceylon.

Try it out

Use-site variance is already implemented and already works in Ceylon 1.1, which you can get from GitHub, if you're super-motivated.

Even though the main motivation for this feature was great Java interop, there will be other, hopefully rare, occasions where wildcards will be useful. That doesn't, however, indicate any significant shift in our approach. We will continue using declaration-site variance in the Ceylon SDK except in extreme cases.

UPDATE:

I just realized I forgot to say thanks to Ross Tate for helping me with the finer points of the member typing algorithm for use site variance. Very tricky stuff that Ross knows off the top of his head!