Elm vs PureScript I

War of the Hello, Worlds

I’m building a web application with Haskell, and I’d like the front end to be functional as well. I could write it all in JavaScript, but that sounds boring. I could go the other direction and write it all in Haskell, but I can’t figure out how to build GHCjs (and have concerns about performance of compiled Haskell). I could learn ClojureScript, but that’s a big investment, and I mostly want to get something built.

That leaves Elm and PureScript. Elm is a fairly simple functional language with a focus on developing browser UIs and making it easy to learn and use. PureScript is an advanced functional language that is quite a bit more general and powerful. Where Elm prioritises easy development, PureScript prioritizes powerful language features and abstractions. PureScript’s libraries and frameworks for developing applications are a bit more immature than Elm, but that’s somewhat to be expected given the relative age of the languages.

This post seeks to evaluate both PureScript and Elm for the purpose of building a single page application from the perspective of a relative newbie.

“Hello, World”

Elm:

Elm’s CLI is rather nice. Getting started with a project is just:

$ mkdir elm-project && cd elm-project
$ elm package install

And we can get “Hello World” on the screen with a few commands:

$ cat > Main.elm
module Main where
import Html exposing (..)
main = h1 [] [text "Hello World!"]
^C
$ elm package install evancz/elm-html --yes
$ elm reactor

http://localhost:8000, click on Main.elm and you’ll see Hello World. Nice!

Getting trivial functionality in applications via signals is pretty easy, as demonstrated by the excellent Elm Architecture tutorial. Elm’s reactor server is very fast, which makes for a rather nice development cycle.

PureScript:

The pulp build tool is excellent, and we can get a project started pretty easily:

$ mkdir purs-project && cd purs-project
$ pulp init
$ cat > index.html
<head><script src="app.js"></script></head>
^C
$ pulp server

Now we can go to http://localhost:1337, open the console and see that "Hello sailor!" has been logged.

PureScript, being more general and modular by default than Elm, requires a bit more work before you can have something up on the screen. There are a few frameworks for PureScript apps:

purescript-react

is a library for low level React bindings.

purescript-halogen

is a high level framework based on virtual-dom that seems extremely advanced and powerful. Unfortunately, the power comes with complexity, and the documentation, API, and examples seem to be in a state of flux.

purescript-thermite

is a higher level framework based on purescript-react. It looks nicer to use and more abstract, but at the cost of some missing features. The examples and documentation are kept up to date and are quite readable. For this reason, I’ll go with it!

Getting Hello World on the screen…

First, we need to install our dependencies:

$ pulp dep install --save purescript-thermite

Well, there’s quite a bit of boilerplate…

module Main where

import Prelude

import Data.Maybe
import Control.Monad.Eff
import Data.Maybe.Unsafe
import Data.Nullable (toMaybe)

import qualified Thermite as T
import qualified Thermite.Action as T

import qualified React as R
import qualified React.DOM as R
import qualified React.DOM.Props as RP
import qualified DOM as DOM
import qualified DOM.HTML as DOM
import qualified DOM.HTML.Document as DOM
import qualified DOM.HTML.Types as DOM
import qualified DOM.HTML.Window as DOM

(see what I meant about the granularity and modularity?)

The actual code to get “Hello World” up isn’t so bad:

render :: T.Render _ {} _ {}
render _ _ _ _ = R.div' [ R.h1' [ R.text "Hello world!" ] ]

spec :: T.Spec _ {} _ {}
spec = T.simpleSpec {} perfAction render

perfAction :: T.PerformAction _ {} _ {}
perfAction _ _ = T.modifyState (const {})

main :: forall eff. Eff (dom :: DOM.DOM | eff) R.ReactElement
main = body >>= R.render (R.createFactory (T.createClass spec) {})
    where
        body = do
            win <- DOM.window 
            doc <- DOM.document win
            elm <- fromJust <$> toMaybe <$> DOM.body doc
            return $ DOM.htmlElementToElement elm

Thermite requires that we declare a State and action handlers. I used Unit for everything, and just rendered those divs to the screen. The index.html page needs to be modified to include a link to React in the head, and link to app.js after the body loads.

So that’s a comparison on “Hello World” for the two. Elm’s quite a bit simpler, but we’ll see how PureScript’s more powerful language plays out in the more complex examples.


Elm vs PureScript II

The Elm Architecture, In PureScript

There’s a fantastic introduction to the Elm programming style and application architecture called, as you might expect, The Elm Architecture. The tutorial begins with a rather trivial application, and demonstrates how to extend the application to be more useful via composition of components and managing signals.

In the previous post, I compared “Hello World” with PureScript and Elm. I’d like to compare some fairly trivial programs, to get an idea on what simpler programs in the two languages look and feel like. Since The Elm Architecture already does a fantastic job of doing that for Elm, I’ve decided to simply recreate it in PureScript.

As it happens, purescript-thermite is very much like React, and relies a lot on internal state. As a result, it doesn’t work quite as naturally with the Elm architecture examples, especially for using things as nested components. purescript-halogen seems to have an easier way to handle events and inputs, so I’ve decided to focus on that library instead.

The repository with the code is available here.

0. Hello World!

Since I didn’t do it in the previous post, here’s “Hello World!” in Halogen:

-- src/Example/Zero.purs
data Input a = Input a

ui :: forall g. (Functor g) => Component Unit Input g
ui = component render eval
  where
    render state =
      H.div_ [ H.h1_ [ H.text "Hello, World!" ] ]
    eval :: Eval Input Unit Input g
    eval (Input a) = pure a

-- src/Main.purs
import qualified Example.Zero as Ex0

main = runAff throwException (const (pure unit)) $ do
    node <- runUI Ex0.ui unit
    appendToBody node.node

Halogen expects the Input type to be have kind * -> *, and refers to it as a query algebra. We’ll get more into the details of that in the later examples, but we aren’t really using it at the moment. I find it helpful to think of the query algebra as a “public interface” for the component.

The ui function defines the component that we’ll be using, and has two parts: render and eval. render defines the layout in terms of the current state. eval uses the Input query algebra to determine how to modify the state.

The type signature for ui is a bit intimidating, but it’s not too bad:

This is easy enough, so let’s move on to the first interactive example: Counter!

1. Counter

First, the necessary imports:

-- src/Example/One.purs
module Example.One where

import Prelude

import Halogen
import qualified Halogen.HTML.Indexed as H
import qualified Halogen.HTML.Events.Indexed as E

Now, we’ll define the type of our state. In this case, it’s a type alias to a record with a count field. Our query algebra is just like Elm but with the extra type parameter.

type State =
  { count :: Int
  }

data Input a
  = Increment a
  | Decrement a

And now, the ui function!

ui :: forall g. (Functor g) => Component State Input g
ui = component render eval
  where
    render state =
      H.div_ 
        [ H.button [ E.onClick $ E.input_ Decrement ] 
                   [ H.text "-" ]
        , H.p_ [ H.text (show state.count)]
        , H.button [ E.onClick $ E.input_ Increment ] 
                   [ H.text "+" ]
        ]

We’re using E.input_ to send an event to the eval function. If we cared about the event itself, then we could use E.input and provide a function that would accept the event information and provide a value on the Input. We don’t, so we’ll skip that for now.

    eval :: Eval Input State Input g
    eval (Increment next) = do
      modify (\state -> state { count = state.count + 1 }) 
      pure next
    eval (Decrement next) = do
      modify (\state -> state { count = state.count - 1 })
      pure next

Halogen has get and modify functions for use in the eval functions, which let us either view the current state or modify it. Halogen uses the type variable associated with our query algebra to type the eval function. Even though we’re not using it yet, we still need for the function to evaluate to something of the same type. That’s why we pass next along.

Running the UI is essentially the same:

-- src/Main.purs
main = runAff throwException (const (pure unit)) $ do
    -- node <- runUI Ex0.ui unit
    node <- runUI Ex1.ui { count: 0 }
    appendToBody node.node

We use runUI with our ui definition and an initial state, and append that to the body. Nice!

2. Counter Pair

This is where the two examples begin to differ. In Elm, the render function has an address to send actions to, which are then evaluated later. This makes it very easy to lift a child component’s layout and rendering logic into a parent component: just provide a forwarding address and an Input constructor, and update the state. The state is all kept in the top level component, and passed down to children.

As a result, the parent component has access to all of the state of the application, and can inspect it at will. Both Thermite and Halogen instead encapsulate the state, such that parents don’t know about the internal state of their children. Halogen’s query algebra (the Input a type we’ve been using) is meant to provide an API for the components, allowing them to be interacted with.

So, let’s get started!

-- src/Example/Two.purs
-- .. imports ..

newtype CounterSlot = CounterSlot Int
-- ord/eq instances for CounterSlot

type StateP =
  { topCounter :: CounterSlot
  , bottomCounter :: CounterSlot
  }

init :: StateP
init = { topCounter: CounterSlot 0, bottomCounter: CounterSlot 1 }

Since we’re now talking about a component that contains other components, we want some way to talk about how it contains them. Halogen carries around a lot more information in the type system about what’s going on with each components state, effects, etc. So now, the state of our CounterPair is just going to be a pair of slots for counters.

The slot is used to give an identifier to the element that the component contains.

The query algebra is much simpler:

data Input a = Reset a

Since the counters are keeping track of their own internal state, all we need to do is know when to reset them.

Before we write the parent component, we’ll want to define some type synonyms to make it easier to refer to the component.

type State g =
  InstalledState StateP Ex1.State Input Ex1.Input g CounterSlot

So, the real state of our component is going to be an InstalledState of our newly defined StateP type on top of the Ex1.State type. We’ll also have the Input over the Ex1.Input query types. Finally, we’ll mention the g functor, and then refer to the CounterSlot as the type of slot that the counters will go in. Now, to recap:

type State g
  = InstalledState -- we're installing a state into another state,
    StateP         -- our parent state
    Ex1.State      -- child state
    Input          -- parent query
    Ex1.Input      -- child query
    g              -- functor variable
    CounterSlot    -- slot for child components

Alright! Next up, we’ve got our query type synonym. It’s using some fanciness in the form of coproduct, but it’s not too complex.

type Query =
  Coproduct Input (ChildF CounterSlot Ex1.Input)

Actually, this one is a lot simpler! A Coproduct is newtype Coproduct f g a = Coproduct (Either (f a) (g a)). In actual English, a coproduct is a way of saying “I have a value of type a, and it’s either in a functor f or a functor g.” So, our type synonym is saying something like “The query type is either some value inside the Input functor, or a value inside the slot-indexed child input functor.”

It’s pretty complex, but the safety and composability make it worth it. I promise!

Now, let’s write the component function! We’ll use the type synonyms to simplify the component type:

ui :: forall g. (Plus g) 
   => Component (State g) Query g

Not bad! Note that we’re using Plus g instead of just Functor because we’re doing a parent component now.

On to the function body!

ui = parentComponent render eval
  where
    render state =
      H.div_
        [ H.slot state.topCounter mkCounter
        , H.slot state.bottomCounter mkCounter
        , H.button [ E.onClick $ E.input_ Reset ]
                   [ H.text "Reset!" ]
        ]
 
    mkCounter :: Unit -> { component :: Component Ex1.State Ex1.Input g, initialState :: Ex1.State }
    mkCounter _ = { component: Ex1.ui, initialState: Ex1.init 0 }

We use parentComponent now

The H.slot function accepts two arguments:

  1. Some value of our CounterSlot type that we’re using to identify child components,
  2. A function from Unit to a record containing the component and initial state.

Halogen uses the function to lazily render the page.

The button sends the Reset message, which will get handled by our eval function. Finally, the fun stuff!

    eval (Reset next) = 
      -- ... wait, how do we change the child state?

Ah! We can’t! Not quite yet, anyway. The parent component has no way to directly set the state of the child component. Our counter in Example.One only supports the following actions: Increment and Decrement.

If we want to reset the counter, we’ll have to add that to the list of actions our Counter supports.

-- src/Example/Counter.purs
-- mostly unchanged, but the query algebra:
data Input a
  = Increment a
  | Decrement a
  | Reset a

-- ... and the eval function:
    eval :: Eval Input State Input g
    eval (Increment next) = ...
    eval (Decrement next) = ...
    eval (Reset next) = do
      modify (const (init 0))
      pure next

modify (const (init 0)) is equivalent to modify \state -> init 0, so we’re – as expected – resetting the counter state to 0.

Now, the counters themselves don’t have a control for Reseting themselves. Fortunately, we can easily send actions to child components from the parent component. Let’s get back to that eval function from the counter pair:

-- src/Example/Two.purs
-- first, change the import to the new counter:
import qualified Example.Counter as Ex1

-- ... the rest of the file...

    eval (Reset next) = do
      query (CounterSlot 0) (action Ex1.Reset)
      query (CounterSlot 1) (action Ex1.Reset)
      pure next

query allows us to use the query algebra (or, public interface) that our components define. We provide an identifier for the query, so we know where to look, and an action. The action in this case is simply Reset, and we don’t care about the return value. Halogen also defines request, which we can use to get some information out of the component.

Finally, running the counter works pretty smooth:

-- src/Main.purs
runEx2 = runUI Ex2.ui (installedState Ex2.init)

main = -- boilerplate elided...
  app <- runEx2
  appendToBody app.node

We have to use installedState since we’re dealing with parent/children components.

Alright, that’s the first two examples from Elm Architecture in PureScript Halogen! I’ll be covering the rest in a future installment.


Elm Architecture in PureScript III

Dynamic Lists of Counters

On the last post, we implemented a pair of counters. Now, we’ll generalize that out to a dynamic list of counters, and later, give them all remove buttons. In the process, we’ll learn how to combine components, stack them, peek on them, and otherwise deal with them appropriately.

The code for this is available in this repository.

Let’s get started! We want a list of counters, a button to add a counter, and a button to remove a counter. Let’s define our state and inputs:

type StateP =
  { counterArray :: Array Int
  , nextID :: Int
  }

initialState :: StateP
initialState =
  { counterArray: []
  , nextID: 0
  }

data Input a
  = AddCounter a
  | RemoveCounter a

Another quick detour to define our parent-level state and query types:

type State g =
  InstalledState StateP Counter.State Input Counter.Input g CounterSlot

type Query =
  Coproduct Input (ChildF CounterSlot Counter.Input)

And, our UI function:

ui :: forall g. (Plus g)
   => Component (State g) Query g
ui = parentComponent render eval
  where
    render state = 
      H.div_ 
        [ H.h1_ [ H.text "Counters" ]
        , H.ul_ $ map (\i -> mslot (CounterSlot i) Counter.ui (Counter.init 0)) state.counterArray
        , H.button [ E.onClick $ E.input_ AddCounter ]
                   [ H.text "Add Counter" ]
        , H.button [ E.onClick $ E.input_ RemoveCounter ]
                   [ H.text "Remove Counter" ]
        ]

    eval :: EvalParent Input StateP Counter.State Input Counter.Input g CounterSlot
    eval (AddCounter next) = do
      modify addCounter
      pure next
    eval (RemoveCounter next) = do
      modify removeCounter
      pure next

mslot :: forall s f g p i. p -> Component s f g -> s -> HTML (SlotConstructor s f g p) i
mslot slot comp state = H.slot slot \_ -> { component: comp, initialState: state }

Basically the same thing we’ve been working with already! Instead of keeping a CounterSlot 0 and CounterSlot 1 around, we’ve got an array of integers. When we want to render them, we map over them with the slot type constructor and the H.slot to give them a place to go. Halogen figures out all of the event routing for us.

Removing a Counter

Alright, it’s time to give counters their own remove button. Rather than touch the counter at all, we’re simply going to wrap the existing counter component in a new component. The sole responsibility of this component will be handling the removal of counters.

There’s a bit of boiler plate around the State and Query, but after that, the result is pretty tiny!

-- src/Example/CounterRem.purs
data Input a = Remove a

type State g =
  InstalledState Unit Counter.State Input Counter.Input g CounterSlot
type Query =
  Coproduct Input (ChildF CounterSlot Counter.Input)

ui :: forall g. (Plus g)
   => Component (State g) Query g
ui = parentComponent render eval
  where
    render _ =
        H.div_ 
          [ mslot (CounterSlot 0) Counter.ui (Counter.init 0)
          , H.button [ E.onClick $ E.input_ Remove ]
                     [ H.text "Remove" ]
          ]
    eval :: EvalParent Input Unit Counter.State Input Counter.Input g CounterSlot
    eval (Remove a) = pure a

Since we’re not maintaining any state, we’ll just use the Unit type to signify that. Our eval function is going to punt the behavior to the parent component.

Now… Halogen does some impressive type trickery. Coproducts, free monads, query algebrae… it can be pretty intimidating. There’s a decent amount of associated boilerplate as well. We’re about to get into some of that.

Let’s look at InstalledState in the Halogen documentation:

type InstalledState s s' f f' g p = 
  { parent   :: s
  , children :: Map p (Tuple (Component s' f' g) s')
  , memo     :: Map p (HTML Void (Coproduct f (ChildF p f') Unit)) 
  }

It’s a record with a parent state, a map from child slots to child states, and a map from child slots to memoized HTML.

But what is all of this coproduct stuff again? A Coproduct is defined like this:

newtype Coproduct f g a = Coproduct (Either (f a) (g a))

It’s a way of saying “I have a value of type a inside of a functor. That functor is either f or g.” We know we can specialize f in the InstalledComponent to our Input query algebra. And ChildF p f' is a given child’s identifier and the child’s query algebra. Halogen is using the coproduct structure to keep track of the children’s query algebra inputs.

Revisiting our type synonyms again, we have:

type State g =
  InstalledState Unit Counter.State Input Counter.Input g CounterSlot

The true state of this component isn’t just Unit – it’s the result of installing the Counter.State into this component. We’re giving that a name we can reference, and allowing the caller to provide the functor.

type Query =
  Coproduct Input (ChildF CounterSlot Counter.Input)

Finally, our QueryMiddle just fills in the types for the combined query algebra.

Alright! Awesome! We’ve augmented a component with a Remove button. Let’s embed that into a list. We’ll actually get to reuse almost everything from example three!

-- src/Example/Four.purs
data Input a = AddCounter a

type State g =
  InstalledState StateP (Counter.State g) Input Counter.Query g CounterSlot

type Query =
  Coproduct Input (ChildF CounterSlot Counter.Query)

ui :: forall g. (Plus g)
   => Component (State g) Query g
ui = parentComponent' render eval peek
  where

Ah! We’re peeking! I can tell because of the peek function. And also the ' on the end of parentComponent'. The ' indicates peeking.

Peeking is the way to inspect child components in purescript-halogen. So when a child component of a peeking parent is done with an action, then the parent gets a chance to see the action and act accordingly.

    render state =
      H.div_ 
        [ H.h1_ [ H.text "Counters" ]
        , H.ul_ (map (mapSlot CounterSlot Counter.ui (installedState unit)) state.counterArray)
        , H.button [ E.onClick $ E.input_ AddCounter ]
                   [ H.text "Add Counter" ]
        ]

    eval :: EvalParent _ _ _ _ _ g CounterSlot
    eval (AddCounter next) = do
      modify addCounter
      pure next

mapSlot slot comp state index = mslot (slot index) comp state

Rendering and evalling work exactly as you’d expect. Let’s look at peeking!

    peek :: Peek (ChildF CounterSlot Counter.Query) StateP (Counter.State g) Input Counter.Query g CounterSlot
    peek (ChildF counterSlot (Coproduct queryAction)) =
      case queryAction of
        Left (Counter.Remove _) ->
          modify (removeCounter counterSlot)
        _ ->
          pure unit

So this is kind of a more complex peek than you’d normally start with. My bad. Generally, the peek function has a definition that’d look like:

peek (ChildF childSlot action) =
  case action of
       DoThing next -> -- ...

But we’re working with the installed/child components who manage their state using the coproduct machinery, and as of now, we have to manually unwrap the coproduct and pattern match on the Either value inside. When we match on the Left value, we get to see the immediate child’s actions. If we were to match on the Right value, then we’d get to inspect children’s of children’s actions.

In any case, we peek on the child component, and if it just did a Remove action, then we modify our own state. Otherwise, we ignore it.

-- src/Main.purs
main = ... do
  app <- runEx4
  appendToBody app.node

runEx4 = runUI Ex4.ui (installedState (Ex3.initialState))

And now we’ve got our dynamic list of removable embedded counters going.

Next up, we’ll be looking at AJAX, effects, and other fun stuff.

UPDATE: Modularize me, cap’n!

Ok, so I wasn’t happy with how unmodular the above example was. We had to redefine a whole component just to add a remove button. If I wanted another component that had a remove button, I’d have to redo all that work! No thanks. Instead, I made a higher order component out of it.

There’s no meaning for distinguishing between children, because it only has one. There’s no state involved either, so we’ll use Unit for both of them. The only query is Remove. So let’s put that all together!

-- src/Example/RemGeneric.purs
data QueryP a = Remove a

type State s f g =
  InstalledState Unit s QueryP f g Unit

type Query f =
  Coproduct QueryP (ChildF Unit f)

addRemove :: forall g s f. (Plus g)
          => Component s f g
          -> s
          -> Component (State s f g) (Query f) g
addRemove comp state = parentComponent render eval
  where
    render _ =
        H.div_ 
          [ H.slot unit \_ -> { component: comp, initialState: state } 
          , H.button [ E.onClick $ E.input_ Remove ]
                     [ H.text "Remove" ]
          ]
    eval :: EvalParent QueryP Unit s QueryP f g Unit
    eval (Remove a) = pure a

Easy! We’ve got a few extra type variables to represent where the child state and query will go. Fairly standard type synonym definitions for use in client components. The only kinda tricky part is rendering: we accept a component and initial state as parameters.

Cool! Let’s see what the definition for the counter looks like with the remove button added:

-- src/Example/CounterRemPrime.purs
type State g = Rem.State Counter.State Counter.Input g
type Query = Rem.Query Counter.Input

ui :: forall g. (Plus g)
   => Component (State g) Query g
ui = Rem.addRemove Counter.ui (Counter.init 0)

More type synonyms! And a fairly nice one liner function to wrap the counter.

The code for the list itself is essentially unchanged. We do have to import the RemGeneric as well as the CounterRemPrime module to be able to use the RemGeneric.Input type, but the type declarations hardly change at all.

All in all, this level of componentiziation is fairly easy! Defining the type synonyms is a bit of a pain, but you’ll likely be writing a lot fewer of them when you have more involved components.


Elm Architecture in PureScript IV: Effects

The Final Chapter-ing

In the last post, I covered higher order components and making dynamic lists of components. We’re going to get into effects and AJAXing with this. It’s almost entirely like you might expect, given the previous posts, but we’ll finally start to specialize that g functor!

As always, the code is available in this repository

Gif loader!

I’m pretty excited for this. Counters are boring and now I can get cat gifs delivered? This is precisely what I want from all web UIs, really.

So, we’re going to have a topic and a URL for the current gif. The only input we’ll need to handle is requesting a new gif. We’ll be interacting with the giphy public API to do this.

Let’s define our state and inputs:

-- src/Example/Five.purs

type State =
  { topic :: String
  , gifUrl :: String
  }

initialState :: State
initialState = { topic: "cats", gifUrl: "" }

data Input a
  = RequestMore a

STANDARD SUPER BORING, you knew it was coming. Since we know we’ll be using effects, we’ll also define a type for the effects our component will be using:

type GifEffects eff = HalogenEffects (ajax :: AJAX | eff)

Halogen defines a type of effects that it normally uses, and we’re just adding the AJAX effect on top of that.

The ui component is pretty standard. We’ve replaced the g functor with Aff (GifEffects ()) to indicate that we’ll be using the asynchronous effects monad. The render function is boring, so we’ll get right to the eval function.

ui :: Component State Input (Aff (GifEffects ()))
ui = component render eval
  where
    render :: -- *yawn* let's skip to eval

    eval :: Eval Input State Input (Aff (GifEffects ()))
    eval (RequestMore a) = do
      state <- get
      newGifUrlFn <- liftFI (fetchGif state.topic)
      modify \s -> s { gifUrl = newGifUrlFn s.gifUrl }
      pure a

liftFI is a function that lifts effects from the free monad. So we can phone home, launch missiles, write to the console, or do AJAX all from the liftFI function. Well, to be precise, we can only do those things if they’re included in the Aff (GifEffects ()) effects type! (I haven’t checked HalogenEffects…)

fetchGif uses the Affjax library to make the request, read the JSON, and return either a function to transform the current URL to the new one, or a function that doesn’t change it at all.

fetchGif :: forall eff. String -> Aff (ajax :: AJAX | eff) (String -> String)
fetchGif topic = do
    result <- AJ.get (giphyRequestUrl topic)
    let url = readProp "data" result.response >>= readProp "image_url"
    pure (either (flip const) const url)

So if we get a Left value out of the URL, then we do flip const on the Left value, and then finally on the URL in the state. If the request succeeds, then we do const result over the old URL, which sets it to be equal to the result.

readProp tries to read the JSON property of the object passed, and either returns the result or a Left error type if it wasn’t successful. That can be a quick way of dealing with data if you don’t want to write a full JSON parser.

And that’s it! We’ve got effects. NBD. Running the code in main looks the same as we’d expect:

runEx5 = runUI Ex5.ui Ex5.initialState

Multi Gif Loaders?!

Alright, how about a pair of gif loaders? This is very similar to the pair of counters we had in two, but we don’t need to worry about resetting them.

In fact, the entire bit of code (imports and all!) is 28 lines!

module Example.Six where

import Prelude
import Control.Plus (Plus)
import Data.Functor.Coproduct (Coproduct(..))
import Control.Monad.Aff (Aff())

import Halogen
import qualified Halogen.HTML.Indexed as H

import qualified Example.Five as Gif

data Input a = NoOp a

type State =
  InstalledState Unit Gif.State Input Gif.Input (Aff (Gif.GifEffects ())) Boolean

type Query =
  Coproduct Input (ChildF Boolean Gif.Input)

ui :: Component State Query (Aff (Gif.GifEffects ()))
ui = parentComponent render eval
  where
    render _ =
      H.div_
        [ H.slot true \_ -> { component: Gif.ui, initialState: Gif.initialState }
        , H.slot false \_ -> { component: Gif.ui, initialState: Gif.initialState }
        ]
    eval :: EvalParent Input Unit Gif.State Input Gif.Input (Aff (Gif.GifEffects ())) Boolean
    eval (NoOp a) = pure a

I’m using Boolean as the slot type because it naturally only has two elements, and any type that just has two elements is equivalent to boolean, and this way I don’t have to make ord/eq instances…

List of Gifs

Next up is a list of gif downloaders. But wait. Instead of making a list of gif downloaders, let’s just make another higher order component that contains a list of other components.

We’ll model it off of Example.Three, so much of the code should look pretty familiar. First we’ll need to define state, query, child slots, etc…

type StateP =
  { itemArray :: Array Int
  , nextID :: Int
  }

initialStateP :: StateP
initialStateP =
  { itemArray: []
  , nextID: 0
  }

data QueryP a
  = AddItem a
  | RemItem a

newtype Slot = Slot Int

We use the P suffix because we’ll want to create type synonyms for the installed state and child query stuff.

The Slot type needs an instance of the Eq and Ord type classes. Fortunately, the newer versions of PureScript include a mechanism for generically deriving these. We have to import Data.Generic, and then we get to do:

derive instance genericSlot :: Generic Slot

instance ordSlot :: Ord Slot where
  compare = gCompare

instance eqSlot :: Eq Slot where
  eq = gEq

Nice! Much less tedious than writing the instances out manually. (Here’s hoping that deriving (Eq, Ord) makes it into the language soon…)

Now we’ll define the listUI. Like we did with the higher-order “add a remove button” component, we’ll use two type variables for the child state and child query.

makeList :: forall g p s f. (Plus g)
         => Component s f g
         -> s
         -> Component (State s f g) (Query f) g
makeList comp initState = parentComponent render eval
  where
    render state =
      H.div_
        [ H.button [ E.onClick $ E.input_ AddItem ]
                   [ H.text "+" ]
        , H.button [ E.onClick $ E.input_ RemItem ]
                   [ H.text "-" ]
        , H.ul_ (map (\i -> H.slot (Slot i) (initComp comp initState)) state.itemArray)
        ]

    initComp :: Component s f g -> s -> Unit -> { component :: _, initialState :: _ }
    initComp c s _ = {component: c, initialState: s}

    eval :: EvalParent QueryP StateP s QueryP f g Slot
    eval (AddItem next) = modify addItem $> next
    eval (RemItem next) = modify remItem $> next

The only new thing about this is the $> operator, but it does what you’d expect given it’s place in the function.

And we’re done with the component definition! Let’s run it and see where we go:

-- src/Main.purs
runEx7 = runUI (Ex7.makeList Ex5.ui Ex5.initialState) Ex7.initialState

We don’t even need a type signature. Nice!

And thus concludes my tutorial series on the Elm Architecture in PureScript. I’m not going to cover animation because I don’t know how it works, and that’s beyond the scope of the Halogen framework.