OVO Tech Blog

A Brief Tour of Haskell for Scala Programmers

Introduction

Ed Conolly

Ed Conolly

I lead the tech teams at OVO where we're building the intelligent energy company of the future. Affordable and Sustainable energy for everybody.


Scala haskell

A Brief Tour of Haskell for Scala Programmers

Posted by Ed Conolly on .
Featured

Scala haskell

A Brief Tour of Haskell for Scala Programmers

Posted by Ed Conolly on .

Haskell is a purely functional programming language, it's interesting to draw a comparison between Scala and Haskell to see how Scala's hybrid functional / OO approach alters the way it expresses problems. Here's a (very) brief overview of the language and how it compares against Scala.

Haskell and Scala have many similar features as both have clear functional concepts, however Haskell's purely functional nature gives rise to a slightly simpler syntax in certain cases.

Definitions

Haskell handles all values and data as immutable structures, this means it can lazily evaluate expressions without fear of the underlying information changing.

val, var and def don't have any direct counterparts in Haskell since all evaluation of variables and functions are performed lazyily. In particular var is the most removed from Haskell as it declares a mutable value.

Scala

val x = 3
val y = 5

def sum(a: Int, b: Int) = a + b

lazy val sentence = "the quick brown fox jumps over the lazy dog"

Haskell

x = 3
y = 5

sum :: Int -> Int -> Int
sum a b = a + b

sentence = "the quick brown fox jumps over the lazy dog"

The most notable differences are the lack of initialisation keyword and the function type signature. In Haskell the type signature is seperate from the implementation, although the compiler will complain if an implemention is missing. All functions are curried in Haskell and the style of the type signature reflects this, sum takes an Int and returns a function that takes another Int. That function will perform the addition and return the summed value.

When it comes to defining values within functions its common to use where and let in Haskell. Let's take a quick look.

doubleAndAdd :: Int -> Int -> Int
doubleAndAdd a b = doubleA + doubleB
    where doubleA = a * 2
          doubleB = b * 2

or

doubleAndAdd :: Int -> Int -> Int
doubleAndAdd a b = 
    let doubleA = a * 2
        doubleB = b * 2
    in doubleA + doubleB

Using where both doubleA and doubleB are scoped to the entire function, the let keyword by contrast is an expression in itself and scopes the definitions to everything after in.

Function application

Function application in Haskell is done simply with juxtaposition and without any parentheses, if you do see parentheses it exists purely to handle execution precedence.

Scala

double(5)

sum(5, 2)

sum((10 / 2), 2)

Haskell

double 5
sum 5 2
sum (10 / 2) 2

Anonymous functions, currying, maps and filters

Haskell's maps and filters achieve the same outcome as scala's but are standalone functions opposed to scala collection methods. As a result they read in opposite directions. We can define lambda functions in Haskell using the \ character.

Scala

val foo = List(1, 4, 6, 8, 3, 7, 3, 7, 3, 5)

foo.filter(x => x > 3).map(x => x * 2)

Haskell

foo = [1, 4, 6, 8, 3, 7, 3, 7, 3, 5]

map (\x -> x * 2) (filter (\x -> x > 3) foo)

There are more concise ways we can express the lambdas though in both Scala and Haskell

Scala

foo.filter(_ > 3).map(_ * 2)

Haskell

map (*2) $ filter (>3) foo

In Haskell operators like * and > are functions that take two arguments. Because functions are curried in Haskell calling those operators with just one argument returns a function we can use nicely in our map and filter functions. We call this partial application, it's worth remembering it because we'll use it later.

I've also used the $ operator here, that's a function application operator and importantly it's right-associative. It's the equivalent of writing something like this with parentheses.

Haskell

map (*2) (filter (>3) foo)

List comprehensions

We could also express the map and filter operations using a list comprehension.

Scala

val foo = List(1, 4, 6, 8, 3, 7, 3, 7, 3, 5)

for (x <- foo if x > 3) yield x*2

Haskell

foo = [1, 4, 6, 8, 3, 7, 3, 7, 3, 5]

[x*2 | x <- foo, x > 3]

List comprehensions in Haskell have the output (or yield) to the left of the pipe |, followed by our iterator definition. We can also add predicates into the comprehension to filter out certain values.

Let's look at a slightly more indepth example. What about finding the length of the sides of a right angle triangle with total perimeter of 24?

Scala

for {
    x <- 1 to 10
    y <- 1 to x
    z <- 1 to y
    if Math.pow(z, 2) + Math.pow(y, 2) == Math.pow(x, 2)
    if x + y + z == 24
} yield (x, y, z)

Haskell

[(x, y, z) | x <- [1..10], y <- [1..x], z <- [1..y], z^2 + y^2 == x^2, x + y + z == 24]

Pattern matching

Haskell offers pattern matching quite similar to Scala's, but offers some nice syntatic sugar for pattern matching on function arguments. Let's start by looking at a normal pattern match

Scala

foo match {
    case Nil => "Empty List"
    case xs => "The list is " + xs.length + " long"
}

Haskell

case foo of [] -> "Empty list"
            xs -> "The list is " ++ show (length xs) ++ " long"

Its worth noting that strings are concatonated just like lists in Haskell using the ++ operator. We also have to explicitly convert length xs to a string using the show function.

The above examples seem pretty similar, but when we start looking at pattern matching on function arguments Haskell starts doing things a little differently.

Scala

def sumList(a: List[Int]): Int = a match {
    case Nil => 0
    case (x::xs) => x + sumList(xs)
}

//or with an anonymous function

val sumList2: (List[Int]) => Int = { 
    case Nil => 0
    case (x::xs) => x + sumList2(xs)
}

Haskell

sumList :: [Int] -> Int
sumList [] = 0
sumList (x:xs) = x + sumList xs

Haskell's implementation is very clear and concise IMO. It's also possible to add guards to the pattern match using the | operator in haskell.

Scala

def sumLargeOnly(a: List[Int]): Int = a match {
    case Nil => 0
    case (x::xs) if x > 10 => x + sumList(xs)
    case (x::xs) => sumList(xs)
}

Haskell

sumLargeOnly :: [Int] -> Int
sumLargeOnly [] = 0
sumLargeOnly (x:xs) 
    | x > 10 = x + sumList xs
    | otherwise = sumList xs

Custom data types

Since Haskell is purely functional data structures are just representations of data, and do not contain methods like Scala's classes, case classes and objects. It has however become quite common for scala developers to use case class as a way to represent data structures with no methods, and since case classes are available for pattern matching they're probably the best comparison to Haskell's data.

Scala

case class Car(make: String, model: String, year: Int)

lazy val myCar = Car("Citroen", "C4", 2006)

// or with arguments specified

lazy val yourCar = Car(model="Mustang", make="Ford", year=1969)

Haskell

data Car = Car { make :: String
               , model :: String
               , year :: Int 
               }

myCar = Car "Citroen" "C4" 2006

// or with arguments specified

yourCar = Car {model="Mustang", make="Ford", year=1969}

It's also possible to data types without naming the arguments, kind of like an inbetween of a tuple and a case class.

Haskell

data Point = Point Float Float

center = Point 0 0

Here we're defining the data structure Point as something which is created from 2 Float arguments.

Pattern matching with these data structures is probably as you'd expect

Scala

myCar match {
    case Car("Citroen", _, _) => "Your car is a Citroen"
    case _ => "Support the French, buy a Citroen!"
}

Haskell

case myCar of (Car "Citroen" _ _) -> "Your Car is a Citroen"
              _ -> "Support the French, buy a Citroen!"

These data structures in Haskell are really just functions, so we get all of the benefits of functions in Haskell. Here we use partial application to produce a list of Citroen's from 2000 - 2007.

Haskell

map (Car "Citroen" "C4") [2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007]

Conclusion

We've only covered a small part of both of these languages but it shows some of the basic syntactic differences between Haskell and Scala. The languages diverge further as we progress the conversation around data types and the type system, Haskell's type system in particular is incredibly rich and provides excellent compiler feedback. Scala's real world application is very compelling when compared to Haskell, access to Java libraries and industry tested frameworks and tools like akka and Apache Spark still make it a favourite for organisations like OVO looking for cutting edge technology with practical applications.

Ed Conolly

Ed Conolly

I lead the tech teams at OVO where we're building the intelligent energy company of the future. Affordable and Sustainable energy for everybody.

View Comments...