Whenever people ask me whether Hungarian is difficult to learn, I half-jokingly say that it can’t be too hard given that I had learned it by the time I turned three. Having said that, I must admit that learning a new language as a grown-up is a whole new ball game. Our struggle for efficiency is reflected in the way we learn languages: we focus on the most common patterns, and reuse what we know as often as possible.
Programming languages are no different. When I started at Sumo Logic just two months ago, I wanted to become fluent in Scala as quickly as possible. Having a soft spot for functional languages such as Haskell, a main factor in deciding to do an internship here was that we use Scala. I soon realized that a large subset of Haskell can easily be translated into Scala, which made the learning process a lot smoother so far.
You’ve probably guessed by now that this post is going to be a Scala phrasebook for Haskellers. I’m also hoping that it will give new insights to seasoned Scalaists, and spark the interest of programmers who are new to the functional paradigm. Here we go.
Basics
module Hello where main :: IO () main = do putStrLn "Hello, World!" |
object Hello { def main(args: Array[String]): Unit = println("Hello, World!") } |
While I believe that HelloWorld examples aren’t really useful, there are a few key points to make here.
The object keyword creates a singleton object with the given name and properties. Pretty much everything in Scala is an object, and has its place in the elaborate type hierarchy stemming from the root-type called Any. In other words, a set of types always has a common ancestor, which isn’t the case in Haskell. One consequence of this is that Scala’s ways of emulating heterogeneous collections are more coherent. For example, Haskell needs fairly involved machinery such as existential types to describe a list-type that can simultaneously hold elements of all types, which is simply Scala’s List[Any].
In Scala, every function (and value) needs an enclosing object or class. (In other words, every function is a method of some object.) Since object-orientation concepts don’t have direct analogues in Haskell, further examples will implicitly assume an enclosing object on the Scala side.
Haskell’s () type is Scala’s Unit, and its only value is called () just like in Haskell. Scala has no notion of purity, so functions might have side-effects without any warning signs. One particular case is easy to spot though: the sole purpose of a function with return type Unit is to exert side effects.
Values
answer :: Int answer = 42 |
lazy val answer: Int = 42 |
Evaluation in Haskell is non-strict by default, whereas Scala is strict. To get the equivalent of Haskell’s behavior in Scala, we need to use lazy values (see also lazy collections). In most cases however, this makes no difference. From now on, the lazy keyword will be dropped for clarity. Besides val, Scala also has var which is mutable, akin to IORef and STRef in Haskell.
Okay, let’s see values of some other types.
question :: [Char] question = "What's six by nine?" |
val question: String = "What's six by nine?" |
Can you guess what the type of the following value is?
judgement = (6*9 /= 42) |
val judgement = (6*9 != 42) |
Well, so can Haskell and Scala. Type inference makes it possible to omit type annotations. There are a few corner cases that get this mechanism confused, but a few well-placed type annotations will usually sort those out.
Data Structures
Lists and tuples are arguably the most ubiquitous data structures in Haskell.
In contrast with Haskell’s syntactic sugar for list literals, Scala’s notation seems fairly trivial, but in fact involves quite a bit of magic under the hood.
list :: [Int] list = [3, 5, 7] |
val list: List[Int] = List(3, 5, 7) |
Lists can also be constructed from a head-element and a tail-list.
smallPrimes = 2 : list |
val smallPrimes = 2 :: list |
As you can see, : and :: basically switched roles in the two languages. This list-builder operator, usually called cons, will come in handy when we want to pattern match on lists (see Control Structures and Scoping below for pattern matching).
Common accessors and operations have the same name, but they are methods of the List class in Scala.
head list |
list.head |
tail list |
list.tail |
map func list |
list.map(func) |
zip list_1 list_2 |
list_1.zip(list_2) |
If you need to rely on the non-strict evaluation semantics of Haskell lists, use Stream in Scala.
Tuples are virtually identical in the two languages.
tuple :: ([Char], Int) tuple = (question, answer) |
val tuple: (String, Int) = (question, answer) |
Again, there are minor differences in Scala’s accessor syntax due to object-orientation.
fst tuple |
tuple._1 |
snd tuple |
tuple._2 |
Another widely-used parametric data type is Maybe, which can represent values that might be absent. Its equivalent is Option in Scala.
singer :: Maybe [Char] singer = Just "Carly Rae Jepsen" |
val singer: Option[String] = Some("Carly Rae Jepsen") |
song :: Maybe [Char] song = Nothing |
val song: Option[String] = None |
Algebraic data types translate to case classes.
data Tree = Leaf | Branch [Tree] deriving (Eq, Show) |
sealed abstract class Tree case class Leaf extends Tree case class Branch(kids: List[Tree]) extends Tree |
Just like their counterparts, case classes can be used in pattern matching (see Control Structures and Scoping below), and there’s no need for the new keyword at instantiation. We also get structural equality check and conversion to string for free, in the form of the equals and toString methods, respectively.
The sealed keyword prevents anything outside this source file from subclassing Tree, just to make sure exhaustive pattern lists don’t become undone.
See also extractor objects for a generalization of case classes.
Functions
increment :: Int -> Int increment x = x + 1 |
def increment(x: Int): Int = x + 1 |
If you’re coming from a Haskell background, you’re probably not surprised that the function body is a single expression. For a way to create more complex functions, see let-expressions in Control Structures and Scoping below.
three = increment 2 |
val three = increment(2) |
Most of the expressive power of functional languages stems from the fact that functions are values themselves, which leads to increased flexibility in reusing algorithms.
Composition is probably the simplest form of combining functions.
incrementTwice = increment . increment |
val incrementTwice = (increment: Int => Int).compose(increment) |
Currying, Partial Application, and Function Literals
Leveraging the idea that functions are values, Haskell chooses to have only unary functions and emulate higher arities by returning functions, in a technique called currying. If you think that isn’t a serious name, you’re welcome to call it schönfinkeling instead.
Here’s how to write curried functions.
addCurry :: Int -> Int -> Int addCurry x y = x + y |
def addCurry(x: Int)(y: Int): Int = x + y |
five = addCurry 2 3 |
val five = addCurry(2)(3) |
The rationale behind currying is that it makes certain cases of partial application very succinct.
addSix :: Int -> Int addSix = addCurry 6 |
val addSix: Int => Int = addCurry(6) |
val addSix = addCurry(6) : (Int => Int) |
|
val addSix = addCurry(6)(_) |
The type annotation is needed to let Scala know that you didn’t forget an argument but really meant partial application. If you want to drop the type annotation, use the underscore placeholder syntax.
To contrast with curried ones, functions that take many arguments at once are said to be uncurried. Scalaists seem to prefer their functions less spicy by default, most likely to save parentheses.
addUncurry :: (Int, Int) -> Int addUncurry (x, y) = x + y |
def addUncurry(x: Int, y: Int): Int = x + y |
seven = addUncurry (2, 5) |
val seven = addUncurry(2, 5) |
Uncurried functions can still be partially applied with ease in Scala, thanks to underscore placeholder notation.
addALot :: Int -> Int addALot = x -> addUncurry (x, 42) |
val addALot: Int => Int = addUncurry(_, 42) |
val addALot = addUncurry(_: Int, 42) |
When functions are values, it makes sense to have function literals, a.k.a. anonymous functions.
(brackets :: Int -> [Char]) = x -> "<" ++ show x ++ ">" |
val brackets: Int => String = x => "<%s>".format(x) |
brackets = (x :: Int) -> "<" ++ show x ++ ">" |
val brackets = (x: Int) => "<%s>".format(x) |
Infix Notation
In Haskell, any function whose name contains only certain operator characters will take its first argument from the left side when applied, which is infix notation if it has two arguments. Alphanumeric function names surrounded by backticks also behave that way. In Scala, any single-argument function can be used as an infix operator by omitting the dot and parentheses from the function call syntax.
data C = C [Char] bowtie (C s) t = s ++ " " ++ t (|><|) = bowtie |
case class C(s: String) { def bowtie(t: String): String = s + " " + t val |><| = bowtie(_) } |
(C "James") |><| "Bond" |
C("James") |><| "Bond" |
(C "James") `bowtie` "Bond" |
C("James") bowtie "Bond" |
Haskell’s sections provide a way to create function literals from partially applied infix operators. They can then be translated to Scala using placeholder notation.
tenTimes = (10*) |
val tenTimes = 10 * (_: Int) |
Again, the type annotation is necessary so that Scala knows you meant what you wrote.
Higher-order Functions and Comprehensions
Higher order functions are functions that have arguments which are functions themselves. Along with function literals, they can be used to express complex ideas in a very compact manner. One example is operations on lists (and other collections in Scala).
map (3*) (filter (<5) list) |
list.filter(_ < 5).map(3 * _) |
That particular combination of map and filter can also be written as a list comprehension.
[3 * x | x <- list, x < 5] |
for(x <- list if x < 5) yield (3 * x) |
Control Structures and Scoping
Pattern matching is a form of control transfer in functional languages.
countNodes :: Tree -> Int countNodes t = case t of Leaf -> 1 (Branch kids) -> 1 + sum (map countNodes kids) |
def countNodes(t: Tree): Int = t match { case Leaf() => 1 case Branch(kids) => 1 + kids.map(countNodes).sum } |
For a definition of Tree, see the Data Structures section above.
Even though they could be written as pattern matching, if-expressions are also supported for increased readability.
if condition then expr_0 else expr_1 |
if (condition) expr_0 else expr_1 |
Let expressions are indispensable in organizing complex expressions.
result = let v_0 = bind_0 v_1 = bind_1 -- ... v_n = bind_n in expr |
val result = { val v_0 = bind_0 val v_1 = bind_1 // ... val v_n = bind_n expr } |
A code block evaluates to its final expression if the control flow reaches that point. Curly brackets are mandatory; Scala isn’t indentation-sensitive.
Parametric Polymorphism
I’ve been using parametric types all over the place, so it’s time I said a few words about them. It’s safe to think of them as type-level functions that take types as arguments and return types. They are evaluated at compile time.
[a] |
List[A] |
(a, b) |
(A, B) // desugars to Tuple2[A, B] |
Maybe a |
Option[A] |
a -> b |
A => B // desugars to Function1[A, B] |
a -> b -> c |
A => B => C // desugars to Function2[A, B, C] |
Type variables in Haskell are required to be lowercase, whereas they’re usually uppercase in Scala, but this is only a convention.
In this context, Haskell’s type classes loosely correspond to Scala’s traits, but that’s a topic for another time. Stay tuned.
Comments
-- single-line comment |
// single-line comment |
{- Feel free to suggest additions and corrections to the phrasebook in the comments section below. :] -} |
/* Feel free to suggest additions and corrections to the phrasebook in the comments section below. :] */ |
Here Be Dragons
Please keep in mind that this phrasebook is no substitute for the real thing; you will be able to write Scala code, but you won’t be able to read everything. Relying on it too much will inevitably yield some unexpected results. Don’t be afraid of being wrong and standing corrected, though. As far as we know, the only path to a truly deep understanding is the way children learn: by poking around, breaking things, and having fun.