Calculator

Here we will build a simple calculator, following pioneers of the space Luite Stegemann, Ryan Trinkle, and Ivan Perez.

Hello World

Let’s start with a "Hello World" app.

main :: IO ()
main = runJSorWarp 8080 $ (1)
  simple (2)
    runParDiff (3)
    () (4)
    (const "hello world") (5)
    getBody (6)
1 This function either runs a server or generates a JavaScript-enabled web page. If built with GHC, it will run a server on port 8080. If built with GHCjs, it will just start the application as a normal JavaScript file.
2 This is a wrapper around the shpadoinkle function.
3 No matter how simple the app, you must still choose a backend explicitly. This function chooses the ParDiff backend.
4 The initial state. Because this is a "hello world" example, it’s just ().
5 This is the view. Because we don’t care about the state (()) we’re using const here. "hello world" is making use of the OverloadedStrings pragma.
6 This the DOM node on the page that will hold our application.

Addition

Now that we have our hello world, let’s add some interactivity and perform addition on the part of the user:

To start, we will need a simple single number input:

num :: Int -> Html m Int
num x = input'
 [ value . pack $ show x (1)
, onInput (const . fromMaybe 0 . readMay . unpack) (2)
 ]
1 Set the value of the input to the current state of the application.
2 When an "input" event occurs, update the state with the provided function.

Now we can use our single number input in our view to add two numbers.

view :: (Int, Int) -> Html m (Int, Int)
view (l,r) = (1)
  div_
    [ liftC (,r) fst $ num l (2)
    , " + "
    , liftC (l,) snd $ num r (3)
    , text $ " = " <> pack (show $ l + r) (4)
    ]
1 For now we can just use a tuple to house our two numbers.
2 We use the num component, rendering it with l.
3 We use the num component, rendering it with r.
4 We display the result of l + r to the user.
Heterogeneous Composition

We are using liftC and a TupleSection here to compose our num component. This is because Html Int and Html (Int, Int) obviously do not unify. liftC comes from the Continuous type class, which underpins event handling in Shpadoinkle.

liftC :: (Functor m, Continuous f) => (a -> b -> b) -> (b -> a) -> f m a -> f m b

To lift Html Int to Html (Int,Int) we need to provide liftC with both a getter function and a setter function.

Selectable Operations

Now that we have some inputs that can perform addition, let the user select common operations. We start by making a new ADT:

data Operation
  = Addition
  | Subtraction
  | Multiplication
  | Division
  deriving (Eq, Show, Read, Enum, Bounded)

We will need some functions to get human readable display, as well as mapping to functions:

opFunction :: Operation -> (Int -> Int -> Int)
opText :: Operation -> Text

We will use a traditional Html