Lock in $30 Savings on PRO—Offer Ends Soon! ⏳

What Haskell can learn from Scala

Lars Hupel
October 09, 2015

What Haskell can learn from Scala

The opinions about Scala in the Haskell community differ widely. Most will immediately point out reasons why they think Scala is complex and that Haskell is so much better. But is it really? In this talk, we will take a radically different approach: We will outline what Haskell can learn from Scala and depart from the usual narrative that it is just a “lesser Haskell for the JVM”. Topics include dependent types, composability and modularity, and compile-time reflection and generic programming. We will also compare and contrast the appearance of several popular “design patterns” in Haskell and Scala.

Lars Hupel

October 09, 2015
Tweet

More Decks by Lars Hupel

Other Decks in Programming

Transcript

  1. What is Scala? To many Haskellers, Scala seems odd. Scala

    is not: ▶ a better Java ▶ a worse Haskell 4
  2. What is Scala? To many Haskellers, Scala seems odd. Scala

    is not: ▶ a better Java ▶ a worse Haskell ... it has unique strengths and weaknesses 4
  3. What is Scala? To many Haskellers, Scala seems odd. Scala

    is not: ▶ a better Java ▶ a worse Haskell ... it has unique strengths and weaknesses 4
  4. Encoding of Type Classes class Ord a where (<=) ::

    a -> a -> Bool trait Ord[A] { def lte(a1: A, a2: A): Boolean } 6
  5. Encoding of instances instance Ord a => Ord (List a)

    where [] <= [] = -- ... object Ord { implicit def list[A](implicit A: Ord[A]) = new Ord[List[A]] { // ... } } 7
  6. Classes & Instances are First Class We have the full

    power of the language to manipulate them. trait Ord[A] { def lte(a1: A, a2: A): Boolean def flip: Ord[A] = new Ord[A] { // ... } } 8
  7. Coherence? Confluence? Uniqueness? Contrary to popular belief: ▶ the implicitscope

    in Scala is well-defined ▶ many libraries restrict implicits to companionobjects ▶ ... which is equivalent to Haskell’s no-orphan policy ▶ “duplicate” instances are usually non-implicit 9
  8. Observations 1. Use companions as much as possible. 2. Orphans

    are fine, as long as they are hidden behind an import. 3. Avoid newtypes.* 10
  9. Language extensions for free Because typeclasses are classes, we can

    use OOP features! overriding default super classes mixins minimal complete definitions factories default signatures 11
  10. Language extensions for free Because typeclasses are classes, we can

    use OOP features! overriding default super classes mixins minimal complete definitions factories default signatures 11 It looks like you’re using design patterns. Need the Gang of Four book?
  11. Standard ML ▶ developed in the 1970s/80s ▶ main application

    domain: theorem proving ▶ type inference, garbage collection, algebraic data types, references ▶ module: named collection of values and types, possibly abstract ▶ functor: module → module 12
  12. Functors signature KEY = sig type key val ord: key

    * key -> order end signature TABLE = sig type key type ’a table val empty: ’a table val insert: key * ’a -> ’a table -> ’a table end 13
  13. Functors signature KEY = sig type key val ord: key

    * key -> order end functor Table(Key: KEY): TABLE = struct type key = Key.key datatype ’a table = (* some tree *) val empty = [] fun insert (k, v) table = (* ... *) Key.ord (* ... *) end 13
  14. Functors signature KEY = sig type key val ord: key

    * key -> order end structure Int_Key: KEY = struct type key = int val ord = int_ord end structure Inttab = Table(Int_Key) (* usage *) Inttab.empty 13
  15. Applicative vs. Generative Pop Quiz: Are these two types constructors

    compatible? structure Inttab1 = Table(Int_Key) structure Inttab2 = Table(Int_Key) Inttab1.table (* vs. *) Inttab2.table 14
  16. Functors in Scala trait Key[K] { def ord(k1: K, k2:

    K): Order } trait Tables[K] { type Table[V] def empty[V]: Table[V] def union[V](t1: Table[V], t2: Table[V]): Table[V] } 15
  17. Enforcing barriers val IntTables = Tables.fromImplicit[Int] val IntTablesRev = Tables(Key[Int].flip)

    /* only compiles if t1 and t2 belong to IntTables */ IntTables.union(t1, t2) 16
  18. Enforcing barriers val IntTables = Tables.fromImplicit[Int] val IntTablesRev = Tables(Key[Int].flip)

    /* only compiles if t1 and t2 belong to IntTables */ IntTables.union(t1, t2) Uniqueness not required! 16
  19. Generic Programming ▶ polymorphism: abstracting over types ... easy! ▶

    generic programming: abstracting over data ▶ many use cases: ▶ Scrap Your Boilerplate ▶ automatic lenses ▶ parsing & pretty printing ▶ instance derivation 17
  20. Recursive types data List a = Nil | Cons a

    (List a) How does deriving Eq proceed? 18
  21. Recursive types data List a = Nil | Cons a

    (List a) How does deriving Eq proceed? Nil == Nil = True Cons x xs == Cons y ys = x == y && _ 18
  22. ... or library support Shapeless to the rescue! implicit def

    eqCons[A](implicit L: Lazy[Eq[List[A]]]) : Eq[Const[A]] = ??? implicit def eqList[A](implicit C: Lazy[Eq[Cons[A]]]) : Eq[List[A]] = ??? 20
  23. Metaprogramming Many ecosystems have it, even Haskell ... Examples ▶

    lens derive lenses for fields of a data type ▶ yesod templating, routing ▶ invertible-syntax constructing partial isomorphisms for constructors 21
  24. Scala Macros There could be multiple talks comparing Template Haskell

    and Scala Macros ... Bottom line: ▶ macros look like regular Scala functions ▶ full power: may access network, file system, ... ▶ transparent: calls look like regular function calls, evaluated by compiler 22
  25. “Transparent macros” if (a === b) 999 else 0 ↓

    if (Eq.EqOps(a)(Eq.intEq).===(b)) 999 else 0 ↓ if (Eq.intEq.eqv(a, b)) 999 else 0 23
  26. Law Checking ▶ type classes are used pervasively in both

    Haskell & Scala ▶ most of them come equipped with laws ▶ ... some even with rather complicated ones 25
  27. Law Checking ▶ type classes are used pervasively in both

    Haskell & Scala ▶ most of them come equipped with laws ▶ ... some even with rather complicated ones ▶ managing laws becomes important 28