Note
Some of the information here is outdated. Follow the installation instructions in the README. Using a virtual machine is no longer recommended and the ghcjs-build repository is no longer maintained.
Updated examples can be found in the ghcjs-examples repository, or click the source link. The safety specifications in the JavaScript foreign function interface have been changed slightly, see GHCJS.Foreign for more information. In particular, you need "interruptible" instead of "safe" for asynchronous imports.
Introduction
Since the last post we have made a number of changes to GHCJS. Dan Frumin, who is using GHCJS for his Google Summer of Code project, has contributed an improved build script. Hamish Mackenzie has ported an initial version of ghcjs-dom to our new code generator. Ghcjs-dom contains generated bindings for the DOM API that allow you to write applications that run in the browser with GHCJS, but also as native code, with a webkit frame.
I have added travis support to the repository, so our test suite will be run for every commit and pull request to the GHCJS and shims repositories. I have also updated the prebuilt vagrant virtual machine image to incorporate the latest bugfixes and updates to ByteArray#
and pointers.
This time we are going to be looking at functional reactive programming for the web. Since GHCJS has a full-featured Haskell runtime, with support for threading, async exceptions and STM, any existing FRP library should work. We will be using sodium for the examples. If you have trouble getting your favourite FRP library to work with GHCJS, you can contact us on IRC (#ghcjs
on freenode), the mailing list, or file a bug report.
Quick start
Since GHCJS depends on GHC HEAD and Cabal, with a few patches that have not yet been merged, the easiest way to get started is with a vagrant virtual machine:
$ git clone https://github.com/ghcjs/ghcjs-build.git
$ cd ghcjs-build
$ git checkout prebuilt
$ vagrant up
Log into the virtual machine with:
$ vagrant ssh
You can find the examples in /home/vagrant/ghcjs-examples/weblog
. To view the result in the browser, start the preinstalled warp web server on the virtual machine with
$ vagrant ssh -c warp
and go to http://localhost:3030/ghcjs-examples/weblog/ (Warp listens on port 3000, Vagrant is configured to forward port 3030 of the host machine to that).
For more information and other installation options, see the GHCJS introduction post on this weblog.
Events and behaviours
Functional reactive programming is based on two composable abstractions: behaviours (time-varying values) and events (happening at discrete points in time). Sodium is a simple push-based library that implements these abstractions: Everything is driven by something pushing a new value to a behaviour or firing a new event.
Note that sodium depends on weak references to clean up unused events. If you want to customize GHCJS’ memory management, do not disable the heap scanner completely if you often create new behaviours and events.
Let’s get started with a simple example. First we create a button that fires an event every time it’s clicked. We count the number of events using sodium’s built-in count
behaviour. Finally, we listen to the values in this behaviour and update the text in counterDiv
after a change:
-- run code: http://hdiff.luite.com/ghcjs/examples/counter/
-- full source: https://github.com/ghcjs/ghcjs-examples/blob/master/weblog/counter/counter.hs
{-# LANGUAGE OverloadedStrings #-}
module Main where
import Control.Monad
import Control.Monad.IO.Class
import Data.Default
import Data.Text (Text)
import qualified Data.Text as T
import JavaScript.JQuery hiding (Event)
import FRP.Sodium
main :: IO ()
main = do
body <- select "body"
buttonEvent <- reactiveButton "Click Me!" body
counterDiv <- select "<div />"
appendJQuery counterDiv body
sync $ do
counter <- count buttonEvent
listen (values counter) (\n -> void $
setText (T.pack . show $ n) counterDiv)
return ()
reactiveButton :: Text -> JQuery -> IO (Event ())
reactiveButton label parent = do
(evt, a) <- sync newEvent
button <- select "<button />"
setText label button
appendJQuery button parent
let handler _ = sync (a ())
on handler "click" def button
return evt
Since we use ghcjs-jquery to add the user interface elements, we need to load the jQuery library in our HTML file, also we need some CSS for the examples:
<!DOCTYPE html>
<html>
<head>
<script language="javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js"></script>
<script language="javascript" src="lib.js"></script>
<script language="javascript" src="rts.js"></script>
<script language="javascript" src="lib1.js"></script>
<script language="javascript" src="out.js"></script>
<style type="text/css"> html, body { width: 100%; height: 100%; margin: 0; padding: 0; }</style>
</head>
<body>
</body>
<script language="javascript">
h$main(h$mainZCMainzimain);
</script>
</html>
We will use the same HTML throughout this weblog post.
Button clicks are a typical Event
, since they happen at discrete points in time, but many user interface elements have values that change over time. These are better modeled with a Behaviour
. The next example uses text input fields and a select menu as reactive elements:
-- run code: http://hdiff.luite.com/ghcjs/examples/calculator/
-- full source: https://github.com/ghcjs/ghcjs-examples/blob/master/weblog/calculator/calculator.hs
-- begin collapse
{-# LANGUAGE OverloadedStrings, ScopedTypeVariables #-}
module Main where
import Control.Applicative
import Control.Monad
import Data.Default
import Data.Text (Text)
import qualified Data.Text as T
import Text.Read
import JavaScript.JQuery
import FRP.Sodium
-- end collapse
main :: IO ()
main = do
body <- select "body"
[op1, op2] <- replicateM 2 $
fmap (fmap (readMaybe . T.unpack)) (reactiveTextInput "0" body)
let items = [ ("add" , arithBehaviour op1 op2 (+))
, ("multiply", arithBehaviour op1 op2 (*))
]
sel <- reactiveSelect items body
output <- select "<div />"
appendJQuery output body
sync $ do
result <- switch sel
listen (values result) $ \v -> void $
setText (maybe "invalid input" (T.pack.show) v) output
return ()
arithBehaviour :: Behaviour (Maybe Integer)
-> Behaviour (Maybe Integer)
-> (Integer -> Integer -> Integer)
-> Behaviour (Maybe Integer)
arithBehaviour op1 op2 f = liftA2 f <$> op1 <*> op2
reactiveTextInput :: Text -> JQuery -> IO (Behaviour Text)
reactiveTextInput value parent = do
-- begin collapse
(b, a) <- sync (newBehaviour value)
input <- select "<input type='text' />"
setVal value input
appendJQuery input parent
let handler _ = sync . a =<< getVal input
on handler "keyup change" def input
return b
-- end collapse
reactiveSelect :: [(Text,a)] -> JQuery -> IO (Behaviour a)
reactiveSelect items parent = do
-- begin collapse
(b, a) <- sync (newBehaviour . snd . head $ items)
sel <- select "<select />"
forM_ (zip [(0::Int)..] items) $ \(n,(name,_)) -> do
opt <- select "<option />"
setAttr "value" (T.pack . show $ n) opt
when (n == 0) $ void (setAttr "selected" "true" opt)
setText name opt
appendJQuery opt sel
appendJQuery sel parent
let handler _ = sync . a =<< snd.(items !!).read.T.unpack <$> getVal sel
on handler "change" def sel
return b
-- end collapse
The text input field is of type Behaviour Text
, it has a time-dependent text value, which gets updated by handling the keyup
and change
JavaScript events on the HTML input element with jQuery.
The select menu is polymorphic: Every menu item has a label an a value of type a
. The values can themselves be Behaviours: In the example, every value is a Behaviour (Maybe Integer)
. We use sodium’s switch
function to create a new Behaviour that dynamically switches between multiplying and adding the numbers.
Beside sodium’s built-in primitives, the most important way to work with Behaviour
s is their Applicative
instance. We use this in the example to combine the values of the two operands into a new Behaviour
(Actually we use Applicative
twice! One more time to lift the operator from Integer -> Integer -> Integer
to Maybe Integer -> Maybe Integer -> Maybe Integer
).
Handling mouse input
Not only form elements can be a behaviour. In the next example, we have the current mouse pointer position as a Behaviour (Double, Double)
, indicating the distance in pixels from the top left corner of the document.
Open the example and move your mouse pointer over the page. The changes of the mouse Behaviour automatically trigger updates of the position of the objects. Again we use the Applicative instance for Behaviour to combine two inputs: The time (derived from the mouse position) and the position of the parent object.
-- run code: http://hdiff.luite.com/ghcjs/examples/mouse/
-- full source: https://github.com/ghcjs/ghcjs-examples/blob/master/weblog/mouse/mouse.hs
-- begin collapse
{-# LANGUAGE CPP, JavaScriptFFI, ForeignFunctionInterface,
EmptyDataDecls, OverloadedStrings,
ScopedTypeVariables
#-}
module Main where
import Control.Applicative
import Data.Default
import Data.Text (Text)
import qualified Data.Text as T
import GHCJS.Types
import GHCJS.Foreign
import GHCJS.Marshal
import JavaScript.JQuery
import FRP.Sodium
#ifdef __GHCJS__
-- create an element in the SVG namespace
foreign import javascript unsafe "document.createElementNS('http://www.w3.org/2000/svg',$1)"
createSvg :: JSString -> IO Element
foreign import javascript unsafe "document.getElementsByTagName($1)"
getElementsByTagName :: JSString -> IO (JSArray a)
foreign import javascript unsafe "$3.setAttribute($1,$2)"
setAttribute :: JSString -> JSRef a -> JSRef b -> IO ()
foreign import javascript unsafe "$2.appendChild($1)"
appendChild :: Element -> Element -> IO ()
#else
createSvg = undefined
appendChild = undefined
getElementsByTagName = undefined
setAttribute = undefined
#endif
setAttribute' :: ToJSRef a => JSString -> a -> JSRef b -> IO ()
setAttribute' a v o = toJSRef v >>= \v' -> setAttribute a v' o
-- end collapse
main = do
body <- indexArray 0 =<< getElementsByTagName "body"
svg <- createSvg "svg"
appendChild svg body
setAttribute' "width" (400::Int) svg >> setAttribute' "height" (400::Int) svg
t <- fmap ((*5) . fst) <$> mousePosition body
let sun = pure (200, 200)
earth = object (1/365) 150 sun t
moon = object (1/30) 25 earth t
drawObject svg "yellow" 20 sun
drawObject svg "blue" 8 earth
drawObject svg "grey" 3 moon
object :: Double
-> Double
-> Behaviour (Double, Double)
-> Behaviour Double
-> (Behaviour (Double, Double))
object speed r center time =
(,) <$> liftA2 xpos center time <*> liftA2 ypos center time
where
xpos (x,_) t = x + r * cos (speed * t)
ypos (_,y) t = y + r * sin (speed * t)
drawObject :: Element -> Text -> Double -> Behaviour (Double, Double) -> IO ()
drawObject parent color r x = do
putStrLn (T.unpack color)
circle <- createSvg "circle"
let p .= v = setAttribute' p v circle
"fill" .= color >> "r" .= r
appendChild circle parent
sync $ listen (values x) $
\(x,y) -> "cx" .= x >> "cy" .= y
return ()
mousePosition :: Element -> IO (Behaviour (Double, Double))
mousePosition elem = do
(b, push) <- sync $ newBehaviour (0,0)
let handler ev = do
x <- pageX ev
y <- pageY ev
sync $ push (x,y)
on handler "mousemove" def =<< selectElement elem
return b
Timers and animation
So far, all events we have seen were caused directly by user input from the keyboard or mouse. In the next example we will see an external (timer) event source and behaviours with an internal state to do a simple (mostly incorrect) physics simulation of balls being attracted to your mouse pointer.
If you compile the example yourself, you need to have ball.png in your executable directory, in addition to the index.html
listed above.
Main creates 10 balls, displayed as absolutely positioned image elements. After that, it enters a main loop, which repeatedly fires a stepper
event that contains the time elapsed since the last event. The event is combined with the current mouse position and browser window size, using the snapshotWith
primitive, which samples the current value of a behaviour at the time an event fires.
After each stepper
update, the loop calls threadDelay 1
, which lets the Haskell scheduler yield briefly (since we don’t have any other runnable Haskell threads), to let the browser redraw the window and process new mouse events.
The balls themselves use sodium’s collectE
primitive to listen for timestep events, updating the postion and velocity of the ball’s internal state by doing a simple numerical integration step. Externally, only the position is visible.
The number of steps per second depends on the browser and the machine it runs on, therefore the update function uses the time value in the event, the number of milliseconds since the last event, to determine the step size, making the acceleration and velocity of the balls mostly independent of the machine and browser (faster updates, and thus smaller steps, still result in a slightly more accurate simulation).
-- run code: http://hdiff.luite.com/ghcjs/examples/balls1/
-- full source: https://github.com/ghcjs/ghcjs-examples/blob/master/weblog/balls1/balls1.hs
-- begin collapse
{-# LANGUAGE CPP, OverloadedStrings, TypeFamilies, JavaScriptFFI #-}
module Main where
import Control.Applicative
import Control.Concurrent
import Control.Monad
import Data.Default
import Data.VectorSpace
import System.Random
import FRP.Sodium
import JavaScript.JQuery hiding (Event)
import GHCJS.Types
import GHCJS.Foreign
#ifdef __GHCJS__
foreign import javascript unsafe "Date.now()"
now :: IO Double
foreign import javascript unsafe "$3.css($1,$2+'px')"
setCssPx :: JSString -> Double -> JQuery -> IO ()
#else
now = return 0
setCssPx _ _ _ = undefined
#endif
data R2 = R2 { _x :: Double, _y :: Double }
deriving (Show, Eq, Ord)
instance AdditiveGroup R2 where
zeroV = R2 0 0
R2 x1 y1 ^+^ R2 x2 y2 = R2 (x1+x2) (y1+y2)
negateV (R2 x y) = R2 (negate x) (negate y)
instance VectorSpace R2 where
type Scalar R2 = Double
s *^ (R2 x y) = R2 (s*x) (s*y)
instance InnerSpace R2 where
(R2 x1 y1) <.> (R2 x2 y2) = (x1*x2)+(y1*y2)
-- end collapse
main :: IO ()
main = do
body <- select "body"
bodySize <- size body
mouse <- mousePosition body
startSize <- sync (sample bodySize)
(stepper, pushStepper) <- sync newEvent
let stepper' = snapshotWith (,) stepper ((,) <$> mouse <*> bodySize)
replicateM_ 10 (startPos bodySize >>= \start -> ball body start stepper')
let step t0 = do
t1 <- now
sync (pushStepper $ t1-t0)
threadDelay 1
step t1
step =<< now
startPos :: Behaviour R2 -> IO R2
startPos size = do
R2 mx my <- sync (sample size)
R2 <$> randomRIO (0,mx) <*> randomRIO (0,my)
ball :: JQuery
-> R2
-> Event (Double, (R2, R2))
-> IO (Behaviour R2)
ball parent startPos step = do
b <- select "<img src='ball.png' width='25' height='25' />"
setCss "position" "absolute" b
appendJQuery b parent
let updCss prop f x = void (setCssPx prop (f x) b)
pos <- sync (hold startPos =<< collectE upd initial step)
sync (listen (values pos) $ \x -> updCss "left" _x x >> updCss "top" _y x)
return pos
where
initial = (startPos, R2 0 0)
upd (dt,(m,s)) (x,v) =
let r = m ^-^ x
a = (5 * recip (300 + magnitudeSq r)) *^ normalized r
t@(x', v') = clamp s 25 (x ^+^ (dt *^ v)) ((0.9995 ** dt) *^ (v ^+^ (dt *^ a)))
in (x',t)
clamp :: R2 -> Double -> R2 -> R2 -> (R2, R2)
clamp size objSize x v = (R2 xx xy, R2 vx vy)
where
(xx, vx) = clamp' _x
(xy, vy) = clamp' _y
clamp' f
| x' < 0 = (-x', abs v')
| x' > m = (2 * m - x', negate (abs v'))
| otherwise = (x', v')
where
x' = f x
v' = f v
m = f size - objSize
-- size of the element, in pixels
size :: JQuery -> IO (Behaviour R2)
size elem = do
-- begin collapse
(b, push) <- sync . newBehaviour =<< dims
let handler _ = sync . push =<< dims
on handler "resize" def elem
return b
where
dims = R2 <$> getWidth elem
<*> getHeight elem
-- end collapse
-- the mouse position, in pixels from the top-left corner
mousePosition :: JQuery -> IO (Behaviour R2)
mousePosition elem = do
-- begin collapse
(b, push) <- sync $ newBehaviour (R2 0 0)
let handler ev = do
x <- pageX ev
y <- pageY ev
sync $ push (R2 x y)
on handler "mousemove" def elem
return b
-- end collapse
Note that we use some foreign imports, getting the current time as a Double
, and updating the CSS position of an element through jQuery. In both cases, we could have used existing functions like Data.Time.Clock.getCurrentTime
and JavaScript.JQuery.setCss
with the Show
instance for Double
to get the same result.
These functions are both supported but go through a lot of code: getCurrentTime
uses emulation for the POSIX gettimeofday
call, instance Show Double
decodes the floating point to a significand and exponent, using Integer under the hood.
Since we run this from the animation loop, this affects the performance of our program. Fortunately, importing the lighter weight JavaScript alternatives can be done in just a few lines of code.
Smoother animation with requestAnimationFrame
The above example uses a loop in Haskell to trigger each animation step, calling threadDelay
every iteration. While this works, it does not always result in a smooth animation: The updates are not synchronized with browser redraws, and the GHCJS scheduler uses JavaScript’s setTimeout
function to reschedule itself, which can be a bit unpredictable in the time it takes for this.
Modern browsers support a better alternative: The window.requestAnimationFrame
method (unfortunately the name is not standardized across browsers, so we have to test for a few different names) lets the browser run a JavaScript function just before the window is repainted. We can use this function to call back into Haskell and let sodium run one animation step.
GHCJS has two different ways to let JavaScript call back into Haskell code. Asynchronous callbacks (run directly with h$run
or create from Haskell with GHCJS.Foreign.asyncCallback
) start a regular Haskell thread in the background. The callback itself returns immediately.
For the animation here, we don’t want an asynchronous callback: The function used with requestAnimationFrame
should perform an animation step immediately, updating all objects before returning. The other option, a synchronous callback, does exactly that. Synchronous code is more limited than asynchronous code: The callback will return immediately when the Haskell code tries to do an operation that would block (for example taking an empty MVar
, or doing an asynchronous blocking FFI operation).
In the example below, it is possible that the thread blocks when trying run sync
, since sodium uses an MVar
lock internally. We can choose what happens when a synchronous thread blocks: Either the thread is aborted immediately, or it continues running asynchronously. Here we choose to abort the thread: The animation frame is simply dropped when something is holding the FRP lock.
-- run code: http://hdiff.luite.com/ghcjs/examples/balls2/
-- full source: https://github.com/ghcjs/ghcjs-examples/blob/master/weblog/balls2/balls2.hs
-- begin collapse
{-# LANGUAGE CPP, OverloadedStrings, TypeFamilies, JavaScriptFFI #-}
module Main where
import Control.Applicative
import Control.Monad
import Data.Default
import Data.IORef
import Data.VectorSpace
import System.Random
import FRP.Sodium
import JavaScript.JQuery hiding (Event)
import GHCJS.Types
import GHCJS.Foreign
#ifdef __GHCJS__
foreign import javascript unsafe "Date.now()" now :: IO Double
foreign import javascript unsafe "$3.css($1,$2+'px')"
setCssPx :: JSString -> Double -> JQuery -> IO ()
-- end collapse
foreign import javascript unsafe
"var req = window.requestAnimationFrame ||\
window.mozRequestAnimationFrame ||\
window.webkitRequestAnimationFrame ||\
window.msRequestAnimationFrame;\
var f = function() { $1(); req(f); };\
req(f);" -- fixstr "
animate :: JSFun (IO ()) -> IO ()
-- begin collapse
#else
now = return 0
setCssPx _ _ _ = undefined
animate _ = undefined
#endif
data R2 = R2 { _x :: Double, _y :: Double }
deriving (Show, Eq, Ord)
instance AdditiveGroup R2 where
zeroV = R2 0 0
R2 x1 y1 ^+^ R2 x2 y2 = R2 (x1+x2) (y1+y2)
negateV (R2 x y) = R2 (negate x) (negate y)
instance VectorSpace R2 where
type Scalar R2 = Double
s *^ (R2 x y) = R2 (s*x) (s*y)
instance InnerSpace R2 where
(R2 x1 y1) <.> (R2 x2 y2) = (x1*x2)+(y1*y2)
-- end collapse
main :: IO ()
main = do
body <- select "body"
bodySize <- size body
mouse <- mousePosition body
startSize <- sync (sample bodySize)
(stepper, pushStepper) <- sync newEvent
let stepper' = snapshotWith (,) stepper ((,) <$> mouse <*> bodySize)
replicateM_ 10 (startPos bodySize >>= \start -> ball body start stepper')
t <- newIORef =<< now
let step = do
t0 <- readIORef t
t1 <- now
sync (pushStepper $ t1-t0)
writeIORef t t1
animate =<< syncCallback False step
-- begin collapse
startPos :: Behaviour R2 -> IO R2
startPos size = do
R2 mx my <- sync (sample size)
R2 <$> randomRIO (0,mx) <*> randomRIO (0,my)
ball :: JQuery
-> R2
-> Event (Double, (R2, R2))
-> IO (Behaviour R2)
ball parent startPos step = do
b <- select "<img src='ball.png' width='25' height='25' />"
setCss "position" "absolute" b
appendJQuery b parent
let updCss prop f x = void (setCssPx prop (f x) b)
pos <- sync (hold startPos =<< collectE upd initial step)
sync (listen (values pos) $ \x -> updCss "left" _x x >> updCss "top" _y x)
return pos
where
initial = (startPos, R2 0 0)
upd (dt,(m,s)) (x,v) =
let r = m ^-^ x
a = (5 * recip (300 + magnitudeSq r)) *^ normalized r
t@(x', v') = clamp s 25 (x ^+^ (dt *^ v)) ((0.9995 ** dt) *^ (v ^+^ (dt *^ a)))
in (x',t)
clamp :: R2 -> Double -> R2 -> R2 -> (R2, R2)
clamp size objSize x v = (R2 xx xy, R2 vx vy)
where
(xx, vx) = clamp' _x
(xy, vy) = clamp' _y
clamp' f
| x' < 0 = (-x', abs v')
| x' > m = (2 * m - x', negate (abs v'))
| otherwise = (x', v')
where
x' = f x
v' = f v
m = f size - objSize
-- size of the element, in pixels
size :: JQuery -> IO (Behaviour R2)
size elem = do
(b, push) <- sync . newBehaviour =<< dims
let handler _ = sync . push =<< dims
on handler "resize" def elem
return b
where
dims = R2 <$> getWidth elem
<*> getHeight elem
-- the mouse position, in pixels from the top-left corner
mousePosition :: JQuery -> IO (Behaviour R2)
mousePosition elem = do
(b, push) <- sync $ newBehaviour (R2 0 0)
let handler ev = do
x <- pageX ev
y <- pageY ev
sync $ push (R2 x y)
on handler "mousemove" def elem
return b
-- end collapse
Conclusion
We have seen several examples of functional reactive programming with GHCJS, handling user input and doing animations. Unfortunately for all of the examples we had to do some low-level work ourselves: Setting up mouse event handlers, adding HTML elements to the document. Even though Haskell and GHCJS make these steps relatively easy, we would really like to build a functional reactive user interface library that does this for us, with a collection of ready-made reactive widgets and a declarative way to set up the user interface.
If you have ideas on how to structure such a library, or are interested in helping, please comment, post to the mailing list, or join us on IRC in #ghcjs
on freenode.
(I’m the author of Sodium.) That is very cool! I ran all the examples and enjoyed every minute of it.
I think that UI widgets based on a reactive system would be a great thing. I haven’t looked at it at all, but I know that Heinrich Apfelmus has done a lot of UI work with reactive-banana.
The way I would structure a UI library would be (abstractly) to model widgets as
Behaviour Value -> Event InputEvent -> Reactive (Event Change, Behaviour WhatToDraw)
where “InputEvent” refers to mouse and keyboard events. Assuming a system like OpenGL rather than a browser, placing a widget at a particular position is a matter of transforming the input event and the output draw commands (where the positioning is calculated from more reactive logic). Composition would be done with layout combinators that work like Layout classes in an OOP implementation, such as ‘gridLayout’, ‘flowLayout’, etc.
Here’s how you would feed the changes back into the widget to view:
data TextModification = DeleteCharacterRange Int | InsertCharacter Int Char | …
modifyText :: TextModification -> Text -> Text
textWidget :: Behaviour Text -> Event InputEvent -> Reactive (Event TextModification, Behaviour WhatToDraw)
rec
name <- hold "" $ snapshotWith modifyText eMod name
(eMod, drawName) <- textWidget name eInput
By adding more code, you can do things like only allowing numbers to be typed, etc. To do this properly you'd need to add more complex state, e.g. Behaviour (Text, SelectedRange). All of the widget's state must go into the behaviour.
Obviously I'm only scratching the surface. Wrapping this function in a newtype like 'Widget a' would be the way to go.
I have often thought that OOP is a terrible way to implement a widget library, and we functional programmers should show everyone how to do it properly. Reactive programming is definitely the right thing to base a widget library on.
In the examples, how is listen different to callback based programming done in js? It seems to defeat the whole purpose of frp:
listen (values counter) (\n -> void $ setText (T.pack . show $ n) counterDiv)
I would rather expect something like createCounterDiv n where n is some Behavior Int or such.
The demo VM’s Vagrantfile uses host port 3031, not 3030.
Congratulations and a google of kudos. This works!
Here is something people should know so they don’t get frustrated and give up. “git clone https://github.com/ghcjs/ghcjs-build.git” produces a Vagrantfile with the line:
“config.vm.network :forwarded_port, guest: 3000, host: 3031″
Your blog says: “and go to http://localhost:3030/ghcjs-examples/weblog/ (Warp listens on port 3000, Vagrant is configured to forward port 3030 of the host machine to that).” It would be so sad if someone went to port 3030, saw the 404 “Not Found” message, and mistakenly concluded that your code is half-baked crap They just need to go to port 3031 (or edit the Vagrantfile) to see how amazingly wonderful your generous gift to us fellow website developers really is.
On another topic: I am working in Ubuntu 14.04. This is my first experience with Vagrant. I haven’t been able to use a text editor in the virtual site. When I try to add a repository, I get a failure message saying the requested url can’t be resolved. I would love to have emacs haskell-mode inside of Vagrant, rather than have to edit files in my base environment where ghc 7.8.2, cabal >= 1.18, and I have created the worst dependency Hell I have ever seen. My Haskell code compiles only in the Vagrant ghcjs virtual environment, but I can edit it only in a shared subdirectory in my base environment. — David