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

Made with Slides.com