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