Paul Snively home

If It Quacks Like a Function...

Scala sometimes comes in for criticism along a couple of recurring dimensions. One is that it is "complex," as measured by some infrequently-articulated yardstick. Another is that it differs from other, more popular programming languages in arbitrary ways. One of those ways is that some collection types, such as arrays and maps, take their index or key in parenthesis as opposed to the overwhelmingly more popular square brackets from other languages. In other words, accessing elements in these collections looks like a function call. As with most things in Scala, there is a method to Martin and friends' madness. Consider the following code:

If you run this code, either from a .scala file or by copying and pasting it into a running scala REPL and then typing "MapDemo.main(Array[String]())", you'll see "List(Mustard, Onions, Ketchup)". If you've studied functional programming in Scala, you've probably learned that the map() method on Iterable takes a function of one argument and returns another Iterable representing the results of applying the function to each element of the Iterable being mapped over. Thanks to the magic of Scala X-Ray, you can see this if you roll over the "map" in the code above, which has the type "((java.lang.String) => java.lang.String)List[java.lang.String]". But if you roll over "condiments," you'll see that its type is "scala.collection.immutable.Map[java.lang.String,java.lang.String]", just as it's declared to be. In other words, the "condiments" Map not only looks like a function call when a key is looked up, but seems to be acting like a function taking the key to its value when used as an argument to map()! What's going on here?

It's a shame that Scala X-Ray doesn't generate links to the Scala API documentation for standard library types. Here's the documentation for scala.collection.immutable.Map. Notice that it inherits the apply() method from the Map trait and is defined with the PartialFunction trait.

A Map accesses its elements using syntax that looks like a function call and can be passed to anything that takes a function of one argument because it is a function of one argument. Specifically, it's a PartialFunction of one argument. In formal mathematics, a "partial function" is a function that is not defined for all values in its domain. To perhaps help clarify, consider the venerable factorial function. A good factorial implementation has to check to see if its argument is less than zero, because factorial makes no sense (is "undefined") for negative numbers. So if we take the domain of factorial to be the integers, we say that factorial is a partial function in the integers. However, if we take the domain of factorial to be the natural numbers, then we say that factorial is "total" in the natural numbers.

In programming, the "domain" of a function is the type of its argument. We say "the" argument because, formally, all functions of N arguments can be recast as N-1 higher-order functions of one argument, and you see this in Scala when you "partially apply" a function to fewer arguments than it takes, the value of which is a function that takes the rest of the arguments. Note that partial application and partial functions have nothing to do with each other, a point that Dean Wampler and Alex Payne's otherwise outstanding Programming Scala is somewhat confused about. When a partial function is applied to a value in its domain for which it is undefined, we say formally that the function "diverges". In programming, "divergence" generally shows up in one of three ways: an infinite loop, a hard crash, or throwing an exception. Maps in Scala show their partial-functionness by throwing an exception when you try to look up a key that they don't contain.

As you may suspect by now, arrays in Scala exhibit exactly the same property: they are PartialFunctions from their indices to their values, and so can also be used anywhere a one-function argument that can throw an exception can.

This brings me to the charges that Scala is a complex language with arbitrary differences from other popular languages. I want to make the argument that these charges are false. Scala is a very well-thought-out, highly orthogonal language. Much of what I've learned about Scala I've learned by attempting to generalize from concrete examples, and overwhelmingly those generalizations are successful. As I've shown in this post, the "arbitrary" move from the square-bracket syntax for collection element access to the parenthesis is anything but. Instead, it reflects a deep understanding on the part of Martin Odersky and his team of the algebraic structure of what they're building, and affords the Scala programmer great expressive power that is reusable in a much broader range of contexts than is the case in most other languages.

Comments

blog comments powered by Disqus
© 2003-2009 Paul Snively
Fork me on GitHub