This document compares the functional programming languages Haskell and Scala. It outlines their similarities such as being high-level, statically typed, and supporting functions as first-class values. It describes some of their conceptual differences like Haskell emphasizing purity and lazy evaluation while Scala allows side effects. It then provides examples to illustrate extra features of each language, such as Haskell's pointfree style and compiler extensions, and Scala's support for object-oriented and imperative programming. Finally, it discusses some practical considerations for using each language.
5. Overview
●
High-level functional language
●
Product of programming language research
●
Haskell: Purely functional with strong side-effect handling
●
Scala: Functional and object-oriented
●
Automatic memory management
●
Static type checking
●
Type inference
• Haskell: global, variant of Hindley-Milner
• Scala: local, arguments require type annotations
6. Expressions
●
Everything is an expression producing a value
●
No operators, only infix functions with symbolic names
●
Haskell
let list = [1 + 2, 3]
in list !! 0
(#%) :: String -> String
(#%) x = x ++ "!"
●
Scala
val list = List(1 + 2, 3)
list(0)
def #%(x: String): String = x + "!"
7. Higher order functions
Functions as arguments and values
●
Haskell
applyAndInc f x = f x + 1
double x = x * 2
applyAndInc double 3
●
Scala
def applyAndInc(f: Int => Int, x: Int) = f(x) + 1
def double(x: Int): Int = x * 2
applyAndInc(double, 3)
8. Anonymous functions
Lambda expressions
●
Haskell
map (x -> x + 1) [1, 2, 3]
map (+ 1) [1, 2, 3]
●
Scala
List(1, 2, 3) map(x => x + 1)
List(1, 2, 3) map(_ + 1)List(1, 2, 3) map(x => x + 1)
List(1, 2, 3) map(_ + 1)
// partial function
List(1, 2, "3") collect { case x: Int => x + 1 }
9. Currying
Partial function application
●
Haskell
curried x y = x + y + 1
let partial = curried 1
in partial 2
uncurried = uncurry curried
curriedAgain = curry uncurried
●
Scala
def curried(x: Int)(y: Int): Int = x + y + 1
val partial = curried(1) _
partial(2)
val uncurried = Function.uncurried(curried _)
val curriedAgain = uncurried.curried
10. Encapsulation
Modules and access modifiers
●
Haskell
module Module (foo) where
foo x = bar x + 1
bar x = x * 2
●
Scala
object Encapsulation {
def foo(x: Int) = bar(x) + 1
private def bar(x: Int) = x * 2
}
11. Pattern matching with guards
●
Haskell
factorial 0 = 1
factorial n = n * factorial (n - 1)
pattern :: [Int] -> Int
pattern l | [x] <- l, x /= 0 = 1
| length l > 1 = 2
| otherwise = 0
●
Scala
def factorial(n: Int): Int = n match {
case 0 => 1
case n => n * factorial(n - 1)
}
def pattern(l: List[Int]): Int = l match {
case List(x) if x != 0 => 1
case l if l.size > 1 => 1
case _ => 0
}
12. List comprehensions
●
Haskell
[ (x, y) | x <- [1, 2, 3], y <- [4, 5, 6], x * y > 7 ]
[0 | _ <- [1..10] ]
●
Scala
for {
x <- List(1, 2, 3)
y <- List(4, 5, 6) if x * y > 7
} yield (x, y)
for (_ <- List(1 to 10)) yield 0
16. Algebraic data types
●
Haskell
data Tree a = Empty | Node a (Tree a) (Tree a)
●
Scala
sealed trait Tree[A]
case class Empty[A]() extends Tree[A]
case class Node[A](value: A, left: Tree[A], right: Tree[A]) extends
Tree[A]
17. Typeclasess
●
Haskell
class Equals a where
eq :: a -> a -> Bool
instance Equals Integer where
eq x y = x == y
tcEquals :: (Equals a) => a -> a -> Bool
tcEquals a b = eq a b
●
Scala
trait Equals[T] {
def eq(x: T, y: T): Boolean
}
implicit object EqualsInt extends Equals[Int] {
override def eq(x: Int, y: Int): Boolean = x == y
}
def tcEquals[T: Equals](x: T, y: T): Boolean =
implicitly[Equals[T]].eq(x, y)
24. Pointfree style
●
Omitting arguments in function definition
-- Point-wise
incp x = x + 1
expp x = 1 + 2 * x
-- Pointfree
incf = (+ 1)
expf = (1 +) . (2 *)
25. Polymorphic string literals
●
String syntax for various string-like types
string1 :: String
string1 = "string"
string2 :: Text
string2 = "text"
"test" :: IsString a => a
class IsString a where
fromString :: String -> a
26. Extended list comprehensions
●
Parallel (zip)
[ (x, y) | x <- [1, 2, 3] | y <- [4, 5, 6]]
●
Transform
cities = [ ("Berlin", 4.1), ("Milan", 5.2), ("Zurich", 1.3) ]
[ name ++ "!" | (name, size) <- cities,
then sortWith by size,
then drop 1 ]
●
Monad
[ x + y | x <- Just 1, y <- Just 2 ]
28. Higher rank polymorphism
●
Polymorphic function argument specified by the callee
id :: a -> a
id x = x
rank1 :: forall a. a -> a
rank1 x = x
rank2 :: (forall a. Num a => a -> a) -> (Int, Float)
rank2 f = (f 1, f 1.0)
rank2 (* 2)
29. Compiler extensions
●
View patterns & pattern synonyms
●
Type families
●
Data type generic programming
●
Kind polymorphism
●
And many more …
31. Imperative programming
●
Mutable state
●
Code blocks
●
While loops
var mutable = 1
mutable = 2
while (mutable < 3) {
List(1, 2, 3).foreach(_ => println("Missile fired"))
mutable += 1
}
throw new IllegalStateException("Abandon ship")
32. Object-oriented programming
●
Subtyping
●
Type variance, type bounds
●
Class-based inheritance system
●
Classes, traits, objects, abstract type members
●
Mixin class composition
●
Multiple inheritance with traits, self-typed references
33. Named and default arguments
●
Just like in Python
def very(standard: String, default: String = "value"): Unit = ()
very("text")
def handy(default: String = "value", standard: String): Unit = ()
handy(standard = "text")
34. String interpolation
●
Programmable via custom interpolators
val neat = 7
// standard library
val substitution = s"Is $neat"
val printf = f"Number is $neat%02d"
val noEscaping = raw"somenthing"
// external library
val document = json"""{ "hello": "world" }"""
35. Flexible scoping
●
Fields in traits
●
Abstract members
●
Nested function definitions
●
Multiple classes & objects in one source file
trait FlexibleScoping {
def abstractMethod(text: String): String
val abstractField: Int
def get: String = {
def compute: String = "result"
abstractMethod(compute)
}
}
36. Implicit conversion
●
Automated conversion of method arguments
●
Searches in current and various outer scopes
●
Method argument is converted using the implicit method
def more(value: Int): Int = 2 * value
implicit def convert(value: Vector[_]): Int = value.size
more(Vector(1, 2, 3))
// equivalent call with explicit conversion
more(convert(Vector(1, 2, 3)))
37. Structural typing
●
Type checking based on type contents not its name
●
Resolved at compile-time as opposed to duck-typing
def oneMore(countable: { def size: Int }): Unit = {
countable.size + 1
}
oneMore(List(1, 2, 3))
38. Dynamic typing
●
Programmable late binding at run-time
import scala.language.dynamics
object Accepts extends Dynamic {
def selectDynamic(name: String): Int = name.size
def applyDynamic(name: String)(args: Any*): String = name
}
Accepts.something
Accepts.hello("World")
39. Type system features
●
Type lambdas
●
F-bounded types
●
Self-recursive types
●
Path-dependent types
●
Instance bound types
●
Value classes
●
Type specialization
41. Practicality - Haskell
●
Great productivity
●
High runtime performance
●
Clever community
●
Solid library and tooling ecosystem
●
Reasoning about can be tricky
●
Steep learning curve (real or imagined)
42. Practicality - Scala
●
Great productivity
●
High runtime performance
●
Clever community
●
Largest library and tooling ecosystem
●
Easy to transition from Java, C++, C#
●
Java interoperability somewhat pollutes the language