This is the ninth installment in a series of articles introducing the Ceylon language. Note that some features of the language may change before the final release.
Named arguments
Consider the following method:
void printf(OutputStream to, String format, Object... values) { ... }
(Remember, the last parameter is a sequenced parameter which accepts multiple arguments, just like a Java varargs
parameter.)
We've seen lots of examples of invoking a method or instantiating a class using a familiar C-style syntax where arguments are delimited by in parentheses and separated by commas. Arguments are matched to parameters by their position in the list. Let's see just one more example, just in case:
printf(process, "Thanks, %s. You have been charged %.2f. Your confirmation number is %d.",
user.name, order.total, order.confimationNumber);
This works fine, I suppose. However, Ceylon provides an alternative method invocation protocol that is usually easier to read when there are more than one or two arguments:
printf {
to = process;
format = "Thanks, %s. You have been charged %.2f. Your confirmation number is %d.";
user.name, order.total, order.confimationNumber
};
This invocation protocol is called a named argument list. We can recognize a named argument list by the use of braces as delimiters instead of parentheses. Notice that arguments are separated by semicolons, except for arguments to the sequenced parameter, which are separated by commas. We explicitly specify the name of each parameter, except for the sequenced parameter, whose arguments always appear at the end of the named parameter list. Note that it's also acceptable to call this method like this, passing a sequence to the named value parameter:
printf {
to = process;
format = "Thanks, %s. You have been charged %.2f. Your confirmation number is %d.";
values = { user.name, order.total, order.confimationNumber };
};
We usually format named argument invocations across multiple lines.
Declarative object instantiation syntax
Named arguments are very commonly used for building graphs of objects. Therefore, Ceylon provides a special abbreviated syntax that simplifies the declaration of an attribute getter, named parameter, or method that builds an object by specifying named arguments to the class initializer.
We're allowed to abbreviate an attribute definition of the following form:
Payment payment = Payment {
method = user.paymentMethod;
currency = order.currency;
amount = order.total;
};
or a named argument specification of this form:
payment = Payment {
method = user.paymentMethod;
currency = order.currency;
amount = order.total;
};
to the following more declarative (and less redundant) style:
Payment payment {
method = user.paymentMethod;
currency = order.currency;
amount = order.total;
}
We're even allowed to write a method of the following form:
Payment createPayment(Order order) {
return Payment {
method = user.paymentMethod;
currency = order.currency;
amount = order.total;
};
}
using the following abbreviated syntax:
Payment createPayment(Order order) {
method = user.paymentMethod;
currency = order.currency;
amount = order.total;
}
Perhaps you're worried that this looks like a method that assigns the values of three attributes of the declaring class, rather than a shortcut syntax for a named argument instantiation of the Payment class. And that's a very fair point. To a Java developer, that is what it looks like. There's two things that should alert you to what's really going on. The above method:
- has no return statement, but it's not declared void, and
- contains a list of = specification statements instead of := assignment expressions.
Once you're used to Ceylon's more flexible syntax, these differences will usually stand out immediately.
More about named arguments
The following classes define a data structure for building tables:
class Table(String title, Natural rows, Border border, Column... columns) { ... }
class Column(String heading, Natural width, String content(Natural row)) { ... }
class Border(Natural padding, Natural weight) { ... }
Of course, we could built a Table using positional argument lists:
String x(Natural row) { return row.string; }
String xSquared(Natural row) { return (row**2).string; }
Table table = Table("Squares", 5, Border(2,1), Column("x",10, x), Column("x**2",12, xSquared));
However, it's far more common to use named arguments to build a complex graph of objects. In this section we're going to meet some new features of named argument lists, that make it especially convenient to build object graphs.
First, note that the syntax we've already seen for specifying a named argument value looks exactly like the syntax for refining a formal attribute. If you think about it, taking into account that a method parameter may accept references to other methods, the whole problem of specifying values for named parameters starts to look a lot like the problem of refining abstract members. Therefore, Ceylon will let us reuse much of the member declaration syntax inside a named argument list. (But note that this has not yet been implemented in the compiler.)
It's legal to include the following constructs in a named argument list:
- method declarations — specify the
value
of a parameter that accepts a function, - object (anonymous class) declarations — are most useful for specifying the value of a parameter whose type is an interface or abstract class, and
- getter declarations — lets us compute the value of an argument inline.
This helps explain why named argument lists are delimited by braces: the fully general syntax for a named argument list is very, very close to the syntax for a class, method, or attribute body. Notice, again, how flexibility derives from language regularity.
So we could rewrite the code that builds a Table as follows:
Table table = Table {
title="Squares";
rows=5;
border = Border {
padding=2;
weight=1;
};
Column {
heading="x";
width=10;
String content(Natural row) {
return row.string;
}
},
Column {
heading="x**2";
width=12;
String content(Natural row) {
return (row**2).string;
}
}
};
Notice that we've specified the value of the parameter named content using the usual syntax for declaring a method.
Even better, our example can be abbreviated like this:
Table table {
title="Squares";
rows=5;
Border border {
padding=2;
weight=1;
}
Column {
heading="x";
width=10;
String content(Natural row) {
return row.string;
}
},
Column {
heading="x**2";
width=10;
String content(Natural row) {
return (row**2).string;
}
}
}
Notice how we've transformed our code from a form which emphasized invocation into a form that emphasizes declaration of a hierarchical structure. Semantically, the two forms are equivalent. But in terms of readability, they are very different.
We could put the above totally declarative code in a file by itself and it would look like some kind of mini-language
for defining tables. In fact, it's executable Ceylon code that may be validated for syntactic correctness by the Ceylon compiler and then compiled to Java bytecode. Even better, the Ceylon IDE (when it exists) will provide authoring support for our mini-language. In complete contrast to the DSL support in some dynamic languages, any Ceylon DSL is completely typesafe! You can think of the definition of the Table, Column and Border classes as defining the schema
or grammar
of the mini-language. (In fact, they are really defining the syntax tree for the mini-language.)
Now let's see an example of a named argument list with an inline getter declaration:
shared class Payment(PaymentMethod method, Currency currency, Float amount) { ... }
Payment payment {
method = user.paymentMethod;
currency = order.currency;
Float amount {
variable Float total := 0.0;
for (Item item in order.items) {
total += item.quantity * item.product.unitPrice;
}
return total;
}
}
Finally, here's an example of a named argument list with an inline object declaration:
shared interface Observable {
shared void addObserver(Observer<Bottom> observer) { ... }
}
shared interface Observer<in Event> {
shared formal on(Event event);
}
observable.addObserver {
object observer satisfies Observer<UpdateEvent> {
shared actual void on(UpdateEvent e) {
writeLine("Update:" + e.string);
}
}
};
Note that Observer<T> is assignable to Observer<Bottom> for any type T, since Observer<T> is contravariant in its type parameter T. If this doesn't make sense, please read these two sections again. ;-)
Of course, as we saw in Part 8, a better way to solve this problem might be to eliminate the Observer interface and pass a method directly:
shared interface Observable {
shared void addObserver<Event>(void on(Event event)) { ... }
}
observable.addObserver {
void on(UpdateEvent e) {
writeLine("Update:" + e.string);
}
};
A quick tangent here: note that we need a type parameter T of the method addObserver() here only because Ceylon inherits Java's limitation that function types are nonvariant in their parameter types. This is actually pretty unnatural. We should probably eventually come up with a workaround to make function types contravariant in their parameter types, allowing us to write:
shared interface Observable {
shared void addObserver(void on(Bottom event)) { ... }
}
Defining user interfaces
One of the first modules we're going to write for Ceylon will be a library for writing HTML templates in Ceylon. A fragment of static HTML would look something like this:
Html {
Head head {
title = "Hello World";
cssStyleSheet = 'hello.css';
}
Body body {
Div {
cssClass = "greeting";
"Hello World"
},
Div {
cssClass = "footer";
"Powered by Ceylon"
}
}
}
A complete HTML template might look like this:
import ceylon.html { ... }
doc "A web page that displays a greeting"
page '/hello.html'
Html hello(Request request) {
Head head {
title = "Hello World";
cssStyleSheet = 'hello.css';
}
Body body {
Div {
cssClass = "greeting";
Hello( request.parameters["name"] ).greeting
},
Div {
cssClass = "footer";
"Powered by Ceylon"
}
}
};
There's more...
There's plenty of potential applications of this syntax aside from user interface definition. For example, Ceylon lets us use a named argument list to the specify arguments of a program element annotation. But we'll have to come back to the subject of annotations in a future installment. In Part 10 we're going to discuss some of the basic types from the language module, in particular numeric types, and introduce the idea of operator polymorphism.
Hmmm... I guess I am starting to like this language...
When and why is the
$syntax needed to refer to a parameter?From the usage it looks like a shortcut for .toString()
It's the operator. Sorry, I have not discussed it yet, so I should not have used it here. I'll discuss it in a future installment, but all it does is transform an object to a String via a pluggable mechanism. I'll change the code example to just use Object.string instead.
Actually, $ is one of the things I'm looking for a bit of feedback on. It doesn't yet look totally natural to my eyes.
Can the same syntax be used to initialize publicly accessible attributes of a class like C Sharp (http://msdn.microsoft.com/en-us/library/bb384062.aspx) or is it restricted to the named parameters of the constructor?
Given that there is no constructor overloading, that type of object initialization would give a lot of flexibility...
P.S. Whats up with session timeouts? If I start typing a comment, then leave it up for a few minutes I get kicked to the home page and I lose what I typed.
No, at one stage I was considering that idea (long before we discovered that C# would have this feature, actually) and decided against it because we're really trying to encourage a lot more use of immutability. And with Ceylon's very strict handling of null, you're really forced to do instantiation and initialization in one step anyway. You wouldn't want to declare all your attributes both optional and variable.
return $row**2; did throw me off as well, perhaps because of its use in Perl and PHP. return #row**2; would probably look a little better to me, but perhaps some kind of brace syntax would make it even clearer. e.g.
return ${row**2}; or return $(row**2); or return #{row**2}; or return #(row**2);
So, how would it look like when a format is given?
So to give a bit more information. The $ operator, and the string template syntax "foo" expr "bar", are defined to be the result of calling the attribute fomatted of Format.
shared interface Format { shared formal String formatted; }The only type which directly implements Format is String. All other types need to be decorated in order to be Formatable:
interface DefaultNumberFormat satisfies Number & Format { shared actual String formatted { return string; } }Then, in some file where we need to be able to format Numbers:
Then $num means num.string and "foo" num "bar" means "foo" + num.string + "bar" for any Number, within the lexical scope of the decorate statement, i.e. within the file.
The idea is that this means you can have different ways of formatting types for different uses, e.g. one way in user interfaces, another way in debug messages.
Of course this whole idea is something I'm looking for feedback on.
Hum. For some reason I also find ${row**2} easier on the eyes. Can't really think of any rational reason why... Well, could it be because the precedence of $ is not something our brains are trained to understand?
The only major problem with that syntax would be that now ${...} would mean the exact opposite of what it means in languages like Groovy or JSP which use that syntax for string interpolation.
Actually it's not really the opposite meaning (it still means ). It's just the opposite context (outside of a string literal instead of inside).
Another objection could be that ${row**2} is not really any less verbose that ""row**2"", which currently works, since it's a valid string template. Perhaps that's the answer: we don't need $ at all, because this works:
Column { heading="x**2"; width=10; String content(Natural row) { return ""row**2""; } }observable.addObserver { void on(UpdateEvent e) { writeLine("Update:"e""); } };And, of course, you can always just call formatted directly like (row**2).formatted, if you wanted to be really explicit...
WDYT?
By the way, since we're having this discussion here, it's probably worth mentioning the reason for the slightly nontraditional string interpolation syntax.
Originally, I wanted to use "Hello, ${name}!", like many other languages. But we've found this impossible to lex in Ant. We checked how Groovy does it, and apparently they resort to a complicated hand-written lexer. Anyway, I think "Hello, " name "!" is a little cleaner, even if perhaps a little bit harder to parse visually. And I think with syntax highlighting the visual-parsing issues go away.
I dunno. I was already on the fence about "Hello, " name "!". I think ""row**2"" is pushing it in terms of being able to visually parse code. Syntax highlighting is nice, but I should still be able read code from a basic text editor or you know, a blog post that doesn't use syntax highlighting ;-)
Quick side question. When you use the multi-line format to for strings, what happens to to the spaces at the beginning. For example, does this string
doc "The root type, supertype of both Object (definite values) and Nothing (the null value)."equate to
or
The first, but String has a normalize() method to merge whitespace characters. At least, that's what seems to make sense to me...
Yea, I guess there is no getting round it. normalize() would help, but then you still end up with an extra space at the beginning of every line except the first right? What is the output expected to look like coming out of your doc generator?
I'm including \n as a whitespace character. (Actually normalize() accepts an optional list of whitespace characters.) If you're producing HTML or docbook output, you don't need to preserve newlines.
Well, actually in that case you don't even need to normalize whitespace at all...
How does it decide which whitespace character to keep? does it just keep the first one it encounters in a group? In that case the output would vary depending on whether or not there is a space char before the newline char.
Can Ceylon's anonymous class be used for creating adhoc objects that don't implement and interface? These types of objects are used alot in C# for Linq, but they also have other convenient uses like creating object graphs to be converted to JSON without defining a type beforehand. Link
Yes, they actually have a type, which is an ordinary class, it's just that the class doesn't have a well-defined name. They're not crippled in any other way.
Heh, had not thought of that. Hey, it's not like I've actually implemented the normalize() method yet!
I suppose it puts a single space in place of any string of whitespace characters....
Well, I think ""row**2"" is quite ugly... Someone on Lambda the Ultimate did note that the Fortress people did back off from using simple concatenation because strings like "Hi, " name ", the " job ", how are you?" are difficult to read. I think the problem is that opening quote and closing quote cannot be distinguished. Therefore the eye should be helped by some operator + or ++ or whatever to clearly separate the parts inside and outside the string.
I think row.formatted is fine, especially with the general tendency of Ceylon to use meaningful words instead of symbolic operators.
This would result in "Hi, " + name.formatted + ", the " + job.formatted + ", how are you?" which is more readable in my opinion.
Actually for localized messages something more elaborate is needed for formatting like Smalltalk's StringParameterSubstitution or Java's MessageFormat with indexed parameters, like "Hi, <1s>, the <2f>, how are you?".formattedWith(name, job) where s means a string is given, f would mean to call formatted on the parameter.
This cannot be made typesafe when reading localized strings at runtime, though -- except if only string parameters would be allowed (modulo parameter count) which would preclude useful and needed features like modifying parts of the message to use singular or plural forms based on a number, e.g. "Really delete <1f> <1#file:files>?".
Haskell solves this with a backslash at the end and beginning of each line:
doc "The root type, supertype of \ \both Object (definite values) \ \and Nothing (the null value)."That's less pretty but non-ambiguous.
Well, I think that's true if you're assuming that lots of people are reading/writing code w/o syntax highlighting. But really, the whole point of a language like this is to enable sophisticated tools. Yeah, I understand the argument about blogs w/o syntax highlighting, but, well, it's not really that technically hard to solve that problem.
Yeah, I'm starting to lean to the view that it's better than the potentially cryptic $ operator, esp. in regular procedural code.
But for user interfaces defined using the object builder syntax, we definitely still need a clean string interpolation syntax.
I'm really not keen on that. Takes away much of the convenience and readability of the feature.
I think auto-semicolon insertion (which is typically just something that happens in the lexer, so it's pretty independent of the grammar of the rest of the language) works out pretty nicely in languages where you don't expect expressions to span multiple lines very often. But that's not really the expectation in Ceylon. Especially not if we allow control-structure-y expression like:
repeat (3) perform { writeLine("Hello"); };assert ("must be positive") that (x>10);Which is something we have not covered yet. (And something that we're unsure about and looking for feedback on.)
Honestly I have never found semicolons a significant barrier to readability.
I'm not saying I don't love Python's clean look (I do, very much). But Python's syntax was designed from the ground for significant whitespace, and it works especially well there. I'm not sure that hacking semicolon insertion onto a C-like syntax works out quite as nicely as what Python has.
Agreed. Maybe as an option when you want to be explicit with a sensible default (replace all whitespace between lines with a single space) when not using it?
What would that look like? For e.g in C# I might write
return Json(new { success = false, msg = "somebody set up us the bomb", errors = errorList });Sorry Gavin, that looks too much like ECS. Bad idea. http://jakarta.apache.org/ecs/
Well, I suppose:
object result { shared Boolean success = false; shared String msg = "somebody set up us the bomb"; shared Error[] errors = errorList; } return Json(result);I don't think it's really meant for the usecase you're interested in.
Yes, I suppose it's a bit like that. Really what we're getting at is something that could form the view layer for something like Wicket or JSF or Tapestry, without defining the whole web framework request processing lifecycle.
I think that there's real problems making something like ECS usable in Java, simply because Java doesn't have the language constructs to support it, stuff like the declarative object instantiation syntax, and higher-order function support, giving you the ability to define callbacks inline. (Look at some Wicket code, and then reimagine what Wicket would look like in Ceylon instead of Java.)
I agree with other sentiments that ""row**2"" is somewhat hard to grok. But then, I have a concern with the java-esque overload of + with this "" + row**2 + "", especially since + was supposed to mean Numeric.plus. It raises questions like
On the other hand--due to the x.formatted mixin-ability--a string format syntax would be really simple:
format("Hello, %1", name); //java-esque "Hello, %1' % (name); //python-esque "Hello, %1" with (name); //seems similar to other Ceylon examplesOf course, these do have to deal with missing parameters that the current syntax avoids:
But then you have to deal with these kinds of things when looking at i18n and differing word order. You're more likely defining the format in a resource file somewhere and plugging in the holes in the program.
Perhaps, string interoperability is not a problem for the compiler and deserves a run-time exception.
Just remembered that you introduced printf in installment 8.
But my last observation still stands: string interoperability need not be a compiler problem--at least in the name of consistency, if not also in the name of simplicity.
Well, there's something I haven't mentioned in these articles, because I'm not totally happy with it. But you've caught me out :-)
There's an interface called Summable, which declares a method called plus(). The + operator operates against Summable things. So the answers are:
I'm not totally in love with the use of + for something that's clearly not numeric addition, but it's just so conventional, and something that all Java developers are so accustomed to. And it's still not like operator overloading, which would let you make + for Set<String> accept a String and return a Boolean. No, in this case, you can at least be sure that when you see +, then it's a binary operator that accepts two values of the same type and returns a value of the same type.
That was just an example. The Ceylon libraries almost certainly won't have printf because it's not very typesafe.
Why String interpolation is not supported? E.g. like Ruby?
I rather like the Monoid in Haskell which has mappend() with the ++ operator and mempty for the zero element. Will there be something like that in Ceylon?
It is. Check Part 1.
It's a very strong possibility that this functionality will be eventually redefined in terms of type classes. The language spec currently has a proposal for how type classes () could work. But that won't be in early versions of the language, and it doesn't really solve the problem of what symbol to use of the join operator.
Looking forward to that!
Well, using ++ for Monoid's append and + for Numeric's plus() would result in separate operators for these operations (probably no need for Summable anymore as that's just one the Monoid instances for Numbers). And making String a Monoid would mean that ++ should be used for appending strings instead of juxtaposition.
How would you approach this usecase in Ceylon? Is there a literal syntax for a mapping/dictionary type (arbitrary name/value pairs)?
I think supporting ${} would kill two birds with one stone. It would provide syntactic sugar for .formatted() and it could help with the readability of the string interpolation syntax e.g.
"Hello, " ${name} ", this is Ceylon " ${process.languageVersion} " running on Java " ${process.javaVersion} " !"Coming back to the inline getter declaration Payment.amount, I suppose the code will be evauluated each time? Will there be some support for lazy-initialization, so that it would be easy to define attributes calculated from other (immutable) attributes, but only once?
Adam
I believe that something like Fantom's once annotation can be pretty easily implemented using an interceptor.
Right, nice :)
Adam
Div {
cssClass = "greeting";
"Hello World"
}
you could write
Div(cssClass = "greeting") {
"Hello World"
}
and there could be a visible distinction between (using XML-speak) "attributes" (inside "(...)") and "content" (inside "{...}")?