$30 off During Our Annual Pro Sale. View Details »

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 Haskell can learn from Scala
    Lars Hupel Miles Sabin
    October 9th, 2015

    View Slide

  2. View Slide

  3. View Slide

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

    View Slide

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

    View Slide

  6. 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

    View Slide

  7. 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

    View Slide

  8. First Class Citizens
    “ Inprogramminglanguagedesign,afirst-classcitizen[...] isanentity
    whichsupportsalltheoperationsgenerallyavailabletootherentities.
    Theseoperationstypicallyincludebeingpassedasanargument,returned
    fromafunction,andassignedtoavariable.
    Wikipedia

    5

    View Slide

  9. Encoding of Type Classes
    class Ord a where
    (<=) :: a -> a -> Bool
    trait Ord[A] {
    def lte(a1: A, a2: A): Boolean
    }
    6

    View Slide

  10. 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

    View Slide

  11. 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

    View Slide

  12. 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

    View Slide

  13. 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

    View Slide

  14. 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

    View Slide

  15. 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?

    View Slide

  16. 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

    View Slide

  17. 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

    View Slide

  18. 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

    View Slide

  19. 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

    View Slide

  20. 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

    View Slide

  21. 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

    View Slide

  22. 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

    View Slide

  23. 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

    View Slide

  24. 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

    View Slide

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

    View Slide

  26. 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

    View Slide

  27. Compiler support
    19

    View Slide

  28. ... 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

    View Slide

  29. 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

    View Slide

  30. 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

    View Slide

  31. “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

    View Slide

  32. Ecosystem
    cabal vs. SBT
    24

    View Slide

  33. Ecosystem
    ≈ 684k
    ≈ 430k
    24

    View Slide

  34. 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

    View Slide

  35. View Slide

  36. View Slide

  37. 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

    View Slide

  38. View Slide

  39. Q & A
     larsr h  larsrh

    View Slide