2016-09-28

Every once in a while, someone publishes an article comparing support for functional programming in Scala and in other more mainstream languages like Java or C#. These articles usually make some valid points, but quite often, they miss the big picture. I decided to write down my thoughts on the subject, so in this article, we will discuss how functional programming is supported in Java 8, Kotlin and Scala in comparison to Haskell.

Java 8

In 2014, Java became the last mainstream language to get function literals. This put to rest the majority of outspoken critics but also produced several more questions about the future of programming. If Java is getting lambdas, does it mean that FP is finally becoming mainstream? Should we call Java 8 a functional language or should we consider C# an FP language as well? Do lambdas in Java 8 make Scala irrelevant? There are certainly some people who would say so, but let's think about it for a moment.

Functional programming as a thing has many definitions, but some of them are too philosophical to be practical. I personally like the definition that Runar gives in his talk about "Purely Functional I/O":

Functional programming is programming with pure functions

This definition actually makes a lot of sense, and while it's very basic, it forces language designers to introduce into their language a set of very specific supporting features. Of course, among these features, there will be functions as first-class citizens, but there will be many others. Java 8 pretty much stops at this stage, however, which renders it as an entry-level functional language.

Kotlin

Even after introducing lambdas, Java, at its core, remains an imperative language. It doesn't encourage you to write code as pure functions, because at least two things stay in the way - statements and mutability.

For example, the if statement in Java is still a statement, and it doesn't return a value:

String name = "Anonymous" if (user.isLoggedIn) { name = user.name }

Moreover, defining a value in Java as we did above creates a mutable variable by default. This is a small thing, but still, Java forces you to type more characters if you want to introduce a constant. Interestingly, C#, which has always been one step ahead of Java in terms of language features, also got it wrong when it introduced the var keyword but not val .

Both Scala and Kotlin have val s and var s and use expressions instead of statements most of the time. The previous example, therefore, could be rewritten in either language the following way:

val name = if (user.isLoggedIn) { user.name } else "Anonymous"

Both Scala and Kotlin also encourage you to use immutable data by providing case classes and data classes respectively.

In its standard library, Scala has many types with the flatMap and map methods (essentially, monads) and even includes a syntactic construct that supports combining those types. For example, there is a type called Try that can contain either an error or a successfully calculated value:

val r1T = Try { queryNextValue() } val r2T = Try { queryNextValue() } val result = for { r1 <- r1T r2 <- r2T } yield r1 + r2

In Java, monads are considered a very advanced (and, quite frankly, not very useful - see my next post) feature. In Scala, they are very common even in newbie code. If your organization is switching from, say, Ruby on Rails to Play framework, you may expect for comprehensions in your code all over the place by the end of the first week.

Kotlin comes very close here, and while there aren't monads in its standard library, there are plenty of them in third-party libraries. Examples include Kovenant and funKTionale, which add things like Promise s and Disjunction s. The above Scala example, therefore, could be rewritten in Kotlin the following way:

val r1D = disjunctionTry { queryNextValue() } val r2D = disjunctionTry { queryNextValue() } val result = r1D.flatMap { r1 -> r2D.map { r2 -> r1 + r2 } }

This is essentially a desugared version of the Scala code. Of course, it would be better if Kotlin had something similar on the language level, but it's OK already. Besides, according to Andrey Breslav, this feature could be added in the future, so I definitely consider Kotlin an improvement over Java 8.

Haskell

Even if you look very carefully at the Scala standard library, you will not find a type called Monad . Nor will you find other concepts from category theory, such as Applicative or Monoid . Moreover, the standard Future class, even though it's often used in for comprehensions, is not a very pure construct. Once a Future is created, it starts executing. Once it's executed, you can extract a result out of it but not restart the computation. Often, it's not a problem. However, when you try to take the idea of working with pure function further, this behavior becomes a serious drawback.

Let's put Scala aside for a moment and take a look at Haskell. While it is usually considered a purely functional language, Haskell programs can and usually do have side effects. In fact, without side effects we wouldn't be able to even write a "Hello World", so Haskell conveniently offers the IO type constructor:

main = putStrLn "Hello World"

If we inspect the putStrLn function in the REPL, we will see the following:

Prelude> :info putStrLn putStrLn :: String -> IO ()

This signature could be translated into Scala as String => IO[Unit] , but this isn't the point here. The point is that IO is used to connect pure programs with the real world. More interestingly, the IO is, or rather has the Monad , Functor and Applicative instances, all defined on the standard library level:

Prelude> :info IO -- irrelevant output omitted instance Monad IO -- Defined in ‘GHC.Base’ instance Functor IO -- Defined in ‘GHC.Base’ instance Applicative IO -- Defined in ‘GHC.Base’

The word "instance" here refers to the fact that for doing ad-hoc polymorphism, Haskell uses type classes, and they are literally everywhere. For instance, take a look at how the power operator (function) is defined:

Prelude> :info (^) (^) :: (Num a, Integral b) => a -> b -> a

Here, both Num and Integral are type classes, and their instances are passed into this function implicitly. Yes, that's right: Haskell doesn't have an implicit keyword, but implicits are there, and this is a good thing.

To summarize, Haskell encourages you to write pure code but allows side effects via IO , has category theory abstractions defined on the library level and uses type classes with implicit resolution.

Scala

In Scala, the type class pattern is possible thanks to the magic of implicits. This pattern is actively used by third-party libraries, but it is present in the standard library as well. For example, take a look at the following signature:

def sorted[B >: A](implicit ord: math.Ordering[B]): List[A]

This is one of the methods defined on the List class. The most important thing here is that the function expects an implicit parameter that has a type of Ordering . The Ordering itself is a type class, and when the sorted function is called, the resolution happens automatically just like in Haskell:

val list = List(4, 3, 2, 1) println(list.sorted) // prints List(1, 2, 3, 4)

The above code works without any involvement on our part because the Scala standard library defines an instance of Ordering for the Int type. Likewise, if you want to sort a list of custom objects, simply provide a corresponding Ordering instance:

case class Person(name: String) implicit val personOrd = new math.Ordering[Person] { def compare(a: Person, b: Person) = a.name.compare(b.name) }

It's usually more convenient to put the type class instance inside the companion object, but this isn't the point here. The point is that once you have this instance defined, everything just works:

val list = List(Person("Lisa"), Person("Joe")) println(list.sorted) // prints List(Person(Joe), Person(Lisa))

Note that we didn't have to make the Person class extend or subclass anything - ad-hoc polymorphism in Scala is just as powerful as it is in Haskell, and it works the same way. If you want to see a more involved demonstration of these techniques, take a look at this presentation by Creighton Kirkendall, in which he solves a classical expression problem using Clojure, OCaml, Haskell and Scala.

As I noticed already, the standard Future class is not a particularly good fit for pure functional programming. However, there are better alternatives, namely the Monix Task and ScalaZ Task. Both are lazy by default, and both are perfectly capable of capturing side effects inside a container. For example:

import monix.eval.Task val task1 = Task { println(42); 42 } val task2 = Task { println(21); 21 }

Since the Task class has map and flatMap methods, we can combine two tasks using a for comprehension:

val resultT = for { t1 <- task1 t2 <- task2 } yield t1 + t2

It may appear that this program prints two numbers, but it doesn't. At this point, the resultT contains everything needed to calculate the result, but nothing has been calculated so far. And this is good because we can consider this part of the program pure and do whatever we want without worrying about side effects. Triggering the execution, therefore, can be postponed for as long as it's needed. Also, calculated values are not memoized by default, so we can restart the execution as many times as we please and experience the same side effects.

Talking of category theory, its abstractions are defined in several Scala libraries such as Cats and ScalaZ. These libraries are usually quite fundamental, and other projects are either built on top of them or provide integration modules. The integration aspect is particularly useful and could be easily illustrated. Say, a library X is built on top of Cats, and it defines a method with the following signature:

def combineI[M[_]](m1: M[Int], m2: M[Int]) (implicit monad: cats.Monad[M]): M[Int]

We could easily make this method even more generic by extracting Int as a type parameter, but it wasn't our goal here. More importantly, the implicit evidence enables the library authors to use monadic methods (or for comprehensions) on M values even though they have no knowledge of what M actually will be.

Now, suppose that we use Monix for doing asynchronous work, and we want to pass two Monix tasks to this method. The monix.eval.Task doesn't relate to cats.Monad in any way, but there is an integration library that provides a corresponding type class instance:

import monix.cats._ val resultT = combineI(task1, task2)

There are many more ways to make use of libraries like Cats, ScalaZ, Monix (and this is the main focus of my book), but here, we're using category theory concepts for integration, and type classes play a key role.

Conclusion

It should be quite obvious by now that in Scala you can do pretty much everything you can do in Haskell. Moreover, most of the time, it is even possible to leverage the same ideas and techniques. This hopefully shows that feature wise, Scala is one of the most advanced languages that exist today, and there is no reason not to take advantage of this fact. I also think that it's too early to compare Java with Scala because in terms of functional programming support, Java still has a long way to go.