One feature often requsted for Scala in the last few years has been support for named and default arguments to methods and constructors. Version 2.8 adds that support, and opens up a variety of opportunities for improving APIs and making your code clearer.
I'll show you what this looks like at the call-site first, and then show you how you can write your own methods and constructors to take advantage of this new feature.
Let's say we have a method that sends an email to a somebody. Before Scala 2.8, I might call this method as follows:
sendEmail("jon.pretty@example.com", List("recipient@example.com"), "Test email", b, Nil, Nil, Nil)
This sends an email to recipient@example.com
with the subject, "Test email"
, and a body, b
(which I'm not going to detail here, but let's just assume it's some text). Probably like the majority of emails you write, the CC and BCC lists are empty, and the email has no attachments. So it's quite a short and concise method call, but it offers no help in reminding me which way round my CC, BCC and attachment lists go, either when I'm writing the code or when I'm reading it back later.
The same method call taking advantage of Scala 2.8's named and default arguments might look like this:
sendEmail(from = "jon.pretty@example.com", to = List("recipient@example.com"), subject = "Test email", body = b)
Isn't that much clearer? By naming the arguments, I can happily leave out those which have default values, shuffle their order, and spend less time worrying about the (maybe arbitrary) order in which they were defined.
sendEmail( body = b, subject = "Test email", to = List("recipient@example.com"), from = "jon.pretty@example.com", attachments = List(file1, file2) )
Named and default arguments can be used in all parameter lists, class or case class constructors included. As a bonus, all case classes now come with a copy method, allowing you to replicate your case class instance by specifying (by name, of course) those arguments which should differ in the copy, e.g.:
case class Fruit(name : String, color : String, citrus : Boolean) val apple = Fruit("Apple", "green", false) val pear = apple.copy(name = "Pear") val lime = apple.copy(name = "Lime", citrus = true)
One small point worth noting is that it's not unusual to have identifiers in the scope of your method call with the same names as the method's arguments. Rather than add indirection and have to rename your identifiers, there's no problem at all writing:
val to = ... val subject = ... val body = ... sendEmail(to = to, subject = subject, body = body)
and having it do just what you want it to!
One particularly convenient aspect of the named arguments feature is that all existing Scala method and constructor definitions can already be called in the named-argument style straight out of the box, for example:
List("Hello", "world").mkString(sep = " ", end = "!", start = "")
will give you Hello world!
.
However, there comes a trap with this convenience! Until now, the names of arguments were a somewhat arbitrary choice for library developers, and weren't considered an important part of the API. This has suddenly changed, so that a method call to mkString(sep = " ")
will fail to compile if the argument sep
were renamed to separator
in a later version.
Scala 2.9 implements a neat solution to this problem, but while we're waiting for that, be cautious about referring to arguments by name if their names may change in the future.
So, we've already learned that all Scala methods can be called using the new syntax without any change to their definitions, but you still have to supply every parameter unless defaults are provided. Luckily adding them is very intuitive. Taking our sendEmail
example, I want to specify default values for the subject, cc, bcc and attachments arguments:
def sendEmail( to : List[String], from : String, subject : String = "No subject", cc : List[String] = Nil, bcc : List[String] = Nil, attachments : List[File] = Nil ) = ...
It's really as simple as writing = default value
after each parameter you want to have a default!
The default values needn't be constant literals either; you can call methods or put blocks of code. You can even refer to antecedent arguments, provided they are in a different parameter list. Here's a more complex example with a side-effect on a default parameter. Needless to say, default parameters are evaluated lazily!
def fibonacci(a : Int = 0)(b : Int = { log("Using default!") a + 1 }) = ...
Finally, it's possible to provide default values for implicit arguments too, to be used when no implicit value of that type is within scope, thus ensuring that a call to such a method will never fail to compile due to an unresolved implicit, e.g.:
def log(msg : => String)(implicit logger : PrintStream = System.out) = logger.println(msg)
Hopefully this will give you a good overview of the possibilities Scala 2.8's named and default arguments opens up! There's more detail available in Lukas Rytz's original Named and Default Arguments SID, who very kindly helped with this article, and in Section 6.6.1 of the Scala Language Specification.
The definitive book on Scala is Programming in Scala: http://www.artima.com/shop/programming_in_scala |
The Scala programming language website is at:
http://www.scala-lang.org
The Scala 2.8 release notes are at:
http://www.scala-lang.org/node/7009
The Scaladoc collections API is at:
http://lampwww.epfl.ch/~odersky/whatsnew/collections-api/collections.html
Have an opinion? Readers have already posted 2 comments about this article. Why not add yours?
-
Artima provides consulting and training services to help you make the most of Scala, reactive
and functional programming, enterprise systems, big data, and testing.