UI Programming with

PureScript and Halogen

Moritz Drexl

Hamburg Haskell Meetup

Jan 27, 2016

PureScript

Differences from Haskell

https://github.com/purescript/purescript/wiki/Differences-from-Haskell

PureScript is a small strongly typed programming language that compiles to JavaScript.

  • Strict evaluation
  • No type families and functional dependencies
  • Not all Language Extensions, but included
    • RankNTypes
    • ScopedTypeVariables
    • FlexibleContexts, FlexibleInstances
    • ...
  • Better, more granular typeclass hierarchy
    (no (>>), mapM, ...)

PureScript

Differences from Haskell

https://github.com/purescript/purescript/wiki/Differences-from-Haskell

-- Explicit forall; Number & Int; no syntactic sugar for Lists
length :: forall a. Array a -> Number

-- No syntactic sugar like (), (a, b)
foo :: Tuple Int String -> State Unit

-- Different composition operator
count = sum <<< map length

-- Named instances
instance arbitraryUnit :: Arbitrary Unit where

-- Superclass semantic arrows
class (Eq a) <= Ord a where
    ...

PureScript is a small strongly typed programming language that compiles to JavaScript.

PureScript

Differences from Haskell

https://github.com/purescript/purescript/wiki/Differences-from-Haskell

-- Javascript-Style Objects
type Point = { x :: Number, y :: Number }

origin :: Point
origin = { x: 0, y: 0 }

getX :: Point -> Number
getX p = p.x

-- Extensible records
fullName :: forall r. { firstName :: String, lastName :: String | r } -> String
fullName person = person.firstName ++ " " ++ person.lastName

PureScript is a small strongly typed programming language that compiles to JavaScript.

PureScript

Differences from Haskell

https://github.com/purescript/purescript/wiki/Differences-from-Haskell

-- Extensible effects instead of IO
main :: Eff (console :: CONSOLE, dom :: DOM) Unit
main = do
    logSomething
    attachSpanTo "#myElement"

logSomething :: forall e. Eff (console :: CONSOLE | e) Unit

attachSpanTo :: forall e. String -> Eff (dom :: DOM | e) Unit

PureScript is a small strongly typed programming language that compiles to JavaScript.

PureScript

Foreign Function Interface

module Test where

add :: Int -> Int -> Int
add a b = a + b
var Test = require('Test');

Test.add(15)(20);
module Test where

foreign import add :: Number -> Number -> Number

main :: Eff (console :: CONSOLE) Unit
main = log $ add 4 5
"use strict";

// module Test

exports.add = function(a) {
  return function(b) {
    return a + b;
  };
};

Call PureScript from JS:

Call JS from PureScript:

PureScript

Resources

PureScript is a small strongly typed programming language that compiles to JavaScript.

Documentation: pursuit.purescript.org

Homepage: purescript.org

Halogen

App Structure

  • A Halogen app is a tree of components/widgets
  • Each component defines
    • A component state
    • A render function :: State -> HTML
    • A set of queries to the component
    • An eval function that interprets the inputs
      (~ Query -> State + Effects Free Monad)
  • Components can
    • communicate with children
    • subscribe to events (3rd party widgets)
  • Components are composed in a type-safe manner

Halogen

Example

type State = { on :: Boolean }

data Query a
  = ToggleState a
  | GetState (Boolean -> a)

myComponent :: forall g. (Functor g) => Component State Query g
myComponent = component render eval
  where

  render :: State -> ComponentHTML Query
  render state =
    H.div_
      [ H.h1_
          [ H.text "Toggle Button" ]
      , H.button
          [ E.onClick (E.input_ ToggleState) ]
          [ H.text (if state.on then "On" else "Off") ]
      ]

  eval :: Eval Query State Query g
  eval (ToggleState next) = do
    modify (\state -> { on: not state.on })
    pure next
  eval (GetState continue) = do
    value <- gets _.on
    pure (continue value)

Halogen

Parent Child Relationship

data TickSlot = LeftSlot | RightSlot

instance eqTickSlot :: Eq TickSlot where eq = ...
instance ordTickSlot :: Ord TickSlot where compare = ...


render :: State -> ParentHTML TickState Query TickQuery g TickSlot
render st = H.div_
  [ H.slot LeftSlot  \_ -> { component: ticker, initialState: TickState 100 }
  , H.slot RightSlot \_ -> { component: ticker, initialState: TickState 0 }
  ]


eval :: EvalParent Query State TickState Query TickQuery g TickSlot
eval (ReadTicks next) = do
  a <- query LeftSlot (request GetTick)
  ...
  pure next

Children are inserted into and referenced by "Slots"

Halogen

Parent Child Communication

peek :: Peek State TickState Query TickQuery g TickSlot p
peek (ChildF p q) = case q of
  Tick _ -> do
    a <- query p (request GetTick)
    when (a > 100) $ modify alertOn
  _ -> pure unit

Initiated by Parent: Request and Action

eval :: EvalParent Query State TickState Query TickQuery g TickSlot
eval (ReadTicks next) = do
  a <- query LeftSlot (request GetTick)
  query RightSlot (action $ SetTick a)
  pure next

Initiated by Child: Peek

(Can also directly peek at grand-children)

(Children can pass actions along to grand-children)

Peek

Request/

Action

Halogen

Metrix Frontend Components

App

Spinner

ErrorBox

Body

FileSelector

FileViewer

File

ModuleBrowser

Handsontable Wrapper

Validation

FileMenu

Halogen

Metrix Frontend Components

Live Code

End

PureScript and Halogen

By Moritz Drexl

PureScript and Halogen

  • 719