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

An Overview of the PureScript Type System

An Overview of the PureScript Type System

Phil Freeman

June 28, 2017
Tweet

More Decks by Phil Freeman

Other Decks in Programming

Transcript

  1. AN OVERVIEW OF THE
    TYPE SYSTEM

    View Slide

  2. Intro
    “PureScript is a small strongly-typed programming language that
    compiles to JavaScript”
    ● Learn more at purescript.org or on #purescript
    ● Goals:
    ○ Build the language I want to use every day
    ○ Explore how types can improve web programming
    ○ Try out some new type system ideas
    ○ Provide a fun project for Haskell OSS newcomers

    View Slide

  3. Agenda
    ● Recap some Haskell extensions
    ● Talk about the implementation
    ● Demo some novel features
    ● Future work

    View Slide

  4. Some Haskell Extensions

    View Slide

  5. Type Classes
    class Eq a where
    eq :: a -> a -> Boolean
    instance eqBoolean :: Eq Boolean where
    eq True True = True
    eq False False = True
    eq _ _ = False
    instance eqList :: Eq a => Eq (List a) where
    ...

    View Slide

  6. Higher-kinded types
    data Free f a
    = Pure a
    | Impure (f (Free f a))
    class Functor f where
    map :: (a -> b) -> f a -> f b

    View Slide

  7. Functional Dependencies
    class MonadState s m | m -> s where
    state :: (s -> Tuple s a) -> m a
    data Z
    data S n -- Peano naturals
    class Add n m r | n m -> r
    instance addZ :: Add Z n n
    instance addS :: Add n m r => Add (S n) m (S r)

    View Slide

  8. Functional Dependencies
    Functional dependencies give us “type-level Prolog”
    Examples:
    ● purescript-generics-rep
    ● purescript-type-map by @LiamGoodacre
    ● purescript-type-lang by @LiamGoodacre

    View Slide

  9. Higher-rank polymorphism
    runST :: forall a. (forall h. ST h a) -> a
    hiding
    :: forall result
    . (forall impl. Interface impl => Proxy impl -> result)
    -> result

    View Slide

  10. Implementation Notes

    View Slide

  11. Phases of the type checker
    1. Check/infer types and gather constraints
    2. Solve constraints
    3. goto 2 until no more constraints can be solved
    4. Final cleanup pass and generalization

    View Slide

  12. Unification
    Types can be partially solved during type checking
    Unification happens when we learn that two (partially known) types must be
    equal
    (Example)

    View Slide

  13. Bidirectional type checking
    Separate the type checker into two modes: inference and checking
    In practice, there are several judgments:
    ● Inference
    ● Checking
    ● Function application
    ● Subsumption

    View Slide

  14. Skolemization
    When checking something has a polymorphic type, we need to generate a
    fresh type variable which does not unify with other types.
    These are called skolem constants.
    At the end of type checking, we need to check that no skolem constants
    escaped their scope.
    (Example)

    View Slide

  15. Type Classes
    When we apply a function which has type class constraints, we insert a
    placeholder for a dictionary into the expression.
    This is called elaboration or dictionary-passing.
    These placeholders will get replaced during the solving phase.
    (Example)

    View Slide

  16. Functional Dependencies
    Functional dependencies can cause new unifications to happen during solving.
    These unifications can cause more instances to become applicable, solving
    new constraints.
    So the solver loops until no new type information can be learned.
    (Example)

    View Slide

  17. Cleanup
    ● Check no skolems escaped
    ● Generalize any unsolved type variables
    ● Generalize over any unsolved constraints

    View Slide

  18. Novel Features

    View Slide

  19. Row Polymorphism: Extensible Records
    getter :: forall a r
    . { foo :: a | r }
    -> a
    getter rec = rec.foo
    setter :: forall a r
    . a
    -> { foo :: a | r }
    -> { foo :: a | r }
    setter a rec = rec { foo = a }

    View Slide

  20. Row Polymorphism: Extensible Effects
    getFromNetwork
    :: forall eff
    . Eff (network :: NETWORK | eff) X
    main :: Eff (console :: CONSOLE, network :: NETWORK) Unit
    main = do
    x <- getNetworkResource
    logShow x

    View Slide

  21. Row Polymorphism: Implementation
    ● Rows are maps from labels to types
    (actually, association lists)
    ● Rows can have duplicate labels
    ● Unification of rows must ignore order of different labels but preserve order
    of duplicates
    ● “row of k”, spelled # k is a new kind for each kind k
    (Example)

    View Slide

  22. -- a magic type class
    class Union r1 r2 r3 | r1 r2 -> r3, r1 r3 -> r2
    merge :: forall r1 r2 r3.
    . Union r1 r2 r3
    => Record r1
    -> Record r2
    -> Record r3
    See purescript-records by @doolse, also Ermine
    Row Constraints: Unions

    View Slide

  23. Polymorphic Labels
    -- another magic type class
    class RowCons l a r1 r2 | l a r1 -> r2, l r1 r2 -> a
    prop :: forall l a r1 r2.
    . RowCons l a r1 r2
    => SProxy l
    -> Lens’ (Record r2) a
    See purescript-profunctor-lenses

    View Slide

  24. Polymorphic Labels: Variants
    ctor :: forall l a r1 r2.
    . RowCons l a r1 r2
    => SProxy l
    -> Prism’ (Variant r2) a
    See purescript-variants by @natefaubion

    View Slide

  25. Type-Directed Search
    flatten :: List (Maybe Int) -> Maybe (List Int)
    flatten = ?IForgot
    Hole 'IForgot' has the inferred type
    List (Maybe Int) -> Maybe (List Int)
    You could substitute the hole with one of these values:
    Data.Monoid.mempty :: forall m. Monoid m => m
    Data.Traversable.sequence :: forall a m t. Traversable t => Applicative m => t (m a) -> m (t a)

    View Slide

  26. Type-Directed Search: Implementation
    ● Brute-force search through the context
    ● Uses subsumption to test for compatibility
    See @kritzcreek’s thesis :)

    View Slide

  27. Future Work

    View Slide

  28. Instance Chains
    class TypeEq a b r | a b -> r
    instance typeEq :: TypeEq a a True
    instance typeNotEq :: TypeEq a b False -- ← overlapping!
    instances
    instance typeEq :: TypeEq a a True
    instance typeNotEq :: TypeEq a b False

    View Slide

  29. Constraint Kinds
    class MapRow (c :: Symbol -> Type -> Type -> Constraint)
    (r1 :: # Type)
    (r2 :: # Type)
    -- auto-generated instances
    instance mapEmpty :: MapRow c () ()
    instance mapNonEmpty
    :: (c l a b, MapRow xs ys) =>
    MapRow (l :: a | xs) (l :: a | ys)

    View Slide

  30. References

    View Slide

  31. Haskell 98
    ● Typing Haskell in Haskell
    Mark P. Jones

    View Slide

  32. Higher-rank Polymorphism
    ● Complete and Easy Bidirectional Typechecking for Higher-Rank Polymorphism
    Joshua Dunfield and Neelakantan R. Krishnaswami
    ● HMF: Simple Type Inference for First-Class Polymorphism
    Daan Leijen

    View Slide

  33. Type Classes and Functional Dependencies
    ● How to make ad-hoc polymorphism less ad hoc
    Philip Wadler
    ● Type Classes with Functional Dependencies
    Mark P. Jones
    ● Instance Chains: Type Class Programming Without Overlapping Instances
    J. Garrett Morris and Mark P. Jones

    View Slide

  34. Extensible Records
    ● Row Polymorphism Isn’t Subtyping
    Brian McKenna
    ● Extensible records with scoped labels
    Daan Leijen
    ● First-class labels for extensible rows
    Daan Leijen
    ● Koka: Programming with Row Polymorphic Effect Types
    Daan Leijen
    ● Ermine compiler

    View Slide

  35. Questions?

    View Slide