Tuesday, March 19, 2013

Scala as a Functional Language

I've been using Scala for about three months now, and during that time have not delved that deeply into the arcana of the language. I do however feel I have a sufficient grasp to comment on the style and coherence of the language.

Scala tries to be many things. It is object oriented, with a more sophisticated inheritance mechanism than Java, that allows for a form of multiple inheritance. It is functional, in that it is relatively easy to access and manipulate function objects. It is strongly typed with type inferencing. This set of features makes it quite powerful, but at the same time lands it in some corners where the expression of the intent of the language becomes quite complex. Witness the awkward full signature of the map method on List:

def map[B, That](f: A => B)(implicit bf: CanBuildFrom[Repr, B, That]): That =

There is an implicit type converter in the mix that ensures the result of the mapping function can be converted into the desired member type of the resultant list. Nice, but it takes a relatively sophisticated understanding of the language to properly realize the underlying behavior. On the other hand, given the right set of programmers, one can easily see how the language's power can be leveraged in quickly assembling and iterating over a program design. I'd call this an acceptable tradeoff.

There are however parts of the language that I find much more annoying.

(A Lack Of) Function Application in Scala

Tuples are great. Until you try to apply them to a function. I'll compare with a language whose functional capabilities are supposedly less sophisticated: Common Lisp. CL has two modes of function invocation:

(funcall #'max 1 2 3)
==> 3
(apply #'max (list 1 2 3))
==> 3

#' is syntactic sugar that translates the name to the corresponding function. There isn't a straightforward way to express this type of function invocation in Scala. The closest I've been able to get is something like this:

(List(1, 2, 3).foldLeft(Int.MinValue)) { _.max(_) }
==> 3

In all fairness, the scala version uses a reduction and does not rely on an n-ary max function. The reduction in CL would be:

(reduce #'max (list 1 2 3))
==> 3

In Scala you must create an anonymous function for the reduction. Because functions aren't really stand-alone entities, they are tied to objects or classes. Just as in Java. If max were defined on an object, however, we would be able to access it with the same succinctness as the CL version.

Now let's introduce another twist and see how far we can push Scala. In CL:

(reduce (lambda (n1 n2) (+ (* n1 n2))) (list 1 2 3) (list 4 5 6))
==> 32

(mapcar #'list (list 1 2 3) (list 4 5 6))
==> ((1 4) (2 5) (3 6))

The first one computes the "sum of products". Reduction happens over two lists simultaneously, and we're able to apply functions to pairs of arguments. This in Scala requires something like this:

(List(1, 2, 3).zip(List(4, 5, 6)).foldLeft(0)) {
    case (sum, (n1, n2)) => sum + (n1 * n2) }
==> 32

Not too bad. Note however that you have to introduce zipping and pattern matching into the mix of concepts required for this computation. The CL version has a much smaller cognitive footprint. Now let's consider the second one. It constructs a list of pairs, something like the zipping operation that scala natively provides. You can't really write zip as a mapping operation, as you can't map over more than one list. But zip gives you a list of tuples. If you wanted a list of lists, you'd do something like this:

scala> List(1, 2, 3).zip(List(4, 5, 6)).map { case (n1, n2) => List(n1, n2) }
res0: List[List[Int]] = List(List(1, 4), List(2, 5), List(3, 6))

Note that we can't directly use List.apply. There appears to be a deficiency in Scala's ability to apply a tuple of arguments to a function. You can't directly do that. Let's take a little detour to see what you can actually do with Scala. Let's define a simple function and try a few ways to use it:

scala> val f = { (_: Int) + (_: Int) }
f: (Int, Int) => Int = <function2> 

scala> f(1, 2)
res7: Int = 3 

scala> List(1, 2, 3).zip(List(4, 5, 6)).map(f)
<console>:9: error: type mismatch;
 found   : (Int, Int) => Int
 required: ((Int, Int)) => ?
              List(1, 2, 3).zip(List(4, 5, 6)).map(f)

scala> List(1, 2, 3).zip(List(4, 5, 6)).map(f.tupled)
res12: List[Int] = List(5, 7, 9)

So, you can't directly apply a tuple to a function. You need to get a tupled version of the function. Now, let's see if we can use this in constructing our list of lists:

scala> List(1, 2, 3).zip(List(4, 5, 6)).map(List.apply.tupled)
<console>:8: error: missing arguments for method apply in object List;
follow this method with `_' if you want to treat it as a partially applied function
              List(1, 2, 3).zip(List(4, 5, 6)).map(List.apply.tupled)

scala> List(1, 2, 3).zip(List(4, 5, 6)).map(List.apply _)
<console>:8: error: polymorphic expression cannot be instantiated to expected type;
 found   : [A]Seq[A] => List[A]
 required: ((Int, Int)) => ?
              List(1, 2, 3).zip(List(4, 5, 6)).map(List.apply _)

Nope, can't do that. It appears n-ary functions in Scala can't be tupled. So you have to resort to a much more pattern matching based expression to get the same outcome. One can imagine the language being extended with tuples that support n-ary types. That would perhaps simplify some of the expressions above. But I can't imagine that would be straightforward.

My expectations of what a functional language should be able to do are perhaps a bit high. But I feel there are serious deficiencies in Scala's use as a true functional language. Objects are still the primary mode of organizing code in Scala. So Scala is at best object oriented with easier access and application of functions. This is still a huge step up from Java. And I plan on enjoying the language for what it offers.

Being in the JVM isn't a sufficient reason for Scala to suffer these deficiencies. I know of at least two languages that are able to be more effectively functional within the JVM: Clojure and ABCL, which is an implementation of Common Lisp. Maybe future versions will find more effective ways of bridging this gap. Or perhaps not, the language can be sufficiently effective even with the behavior it currently possesses.

Edit (3/20/2013): Poking around the web I found this post. It captures some of my immediate reactions, except one: in Scala, some functions are different from others. n-ary functions behave differently, for example. There is a lot you can do with functions in Scala, but the language on the whole feels more OO than FP.