day 3 purescript-easy-ffi and purescript-oo-ffi
day 4 purescript-canvas and purescript-free-canvas
Inspired by the excellent 24 Days of Hackage by Oliver Charles, I have decided to write a series of blog posts to highlight some of the wonderful work being done in the PureScript community.
Each day, I will choose one of the PureScript libraries written by the community, give a brief introduction, and (hopefully) provide practical code examples.
PureScript is still a young language, having recently celebrated the one year anniversary of its initial GitHub commit, but the community has grown very quickly. Hopefully, this series of blog posts will inspire you to try to write a library of your own - and we have no shortage of ideas for possible projects.
If you are interested in learning PureScript, please follow along with the posts, which will be added here each day, and check out the book and the new website.
Also, guest posts are more than welcome, so please get in touch in the comments!
Thanks!
-Phil Freeman.
To kick things off, I'm going to discuss the purescript-foreign
library, one of the earliest PureScript community-driven libraries, written by Gary Burgess and other contributors.
purescript-foreign
provides the means to interoperate with untrusted JavaScript code, which includes both parsing JSON data, and validating the types of untrusted data at runtime.
In what situations is this useful? Well, suppose we want to load application data from a web service which returns JSON data. Assuming the data is well-formed relative to some schema, we can simply give the action the appropriate (effectful) type, and use the data as it is returned, being sure to take things like exceptions and the HTTP status of the response into account. However, in reality, the returned data might be incorrectly formed according to our schema, or just plain invalid JSON. In this case, we have to resort to checking the data at runtime. purescript-foreign
provides a way to perform that task in a structured manner.
A good example of this approach is the Pursuit application, which is tool for looking up functions by name in the PureScript standard library. Pursuit loads its data from a JSON file stored on the server, and uses purescript-foreign
to turn that untrusted data into its internal, typed representation.
Pursuit's application data consists of an array of JSON objects, each with three String
fields, name
, module
and detail
:
[
{
"name": "flip",
"module": "Prelude",
"detail": "flip :: forall a b c. (a -> b -> c) -> b -> a -> c"
},
...
]
The application defines a type Entry
which respresents these objects:
data Entry = Entry String String String
To read these values from a JSON document using purescript-foreign
, the application provides an instance of the IsForeign
type class:
instance isForeignEntry :: IsForeign Entry where
read entry = Entry <$> readProp "module" entry
<*> readProp "name" entry
<*> readProp "detail" entry
Here, we use the Applicative
combinators to build larger parsers from smaller parsers, just as we would do in Haskell libraries like Parsec or Aeson.
The IsForeign
class provides a very simple interface:
type F = Either ForeignError
class IsForeign a where
read :: Foreign -> F a
The polymorphic read
function can be used to turn an untrusted value (represented by the Foreign
type) into either a checked value with some known type, or an error.
With this instance, the application can load its data from its JSON configuration file:
readData :: String -> T.Trie Entry
readData json = case readJSON json of
Left err -> error $ show err
Right arr -> ...
The Foreign
data type also solves another related problem: how do we give appropriate types to foreign functions which might return untrusted or unstructured data? The answer is that we can always use the Foreign
type in a foreign function type in any place where we are uncertain of the structure of the data. This effectively forces the user to use the purescript-foreign
API to turn the untrusted data into usable, checked data.
A simple example is when we want to interact with a JavaScript function which might return null
or undefined
. The Data.Foreign.NullOrUndefined
module provides a newtype whose IsForeign
instance handles these cases using the Maybe
type constructor:
newtype NullOrUndefined a = NullOrUndefined (Maybe a)
runNullOrUndefined :: forall a. NullOrUndefined a -> Maybe a
Now, we can define our FFI in two steps. At the lower level, we can use Foreign
to represent the untrusted data:
foreign import jsFunc :: String -> Foreign
In the module exports, however, we present a sanitised version of the function, which uses NullOrUndefined
to represent the missing data:
jsFunc' :: String -> F (Maybe String)
jsFunc' s = runNullOrUndefined <$> read (jsFunc s)
For more information on the purescript-foreign
library, check out the FFI chapter in the book, which uses the library to read application data from the browser's local storage.
Today, I'm going to look at two more libraries which help with the tricky problem of integrating with the untyped world of JavaScript. The first is purescript-easy-ffi
, by @pelotom, and the second is purescript-oo-ffi
, by @fresheyeball.
PureScript's FFI is quite easy to explain, but not quite to easy to use in many cases. For example, suppose you want to wrap a JavaScript function of several arguments. Let's take the extended JSON.stringify
function as a simple example. You have two options:
-
Write a curried version of the function:
foreign import stringify "function stringify(n) {\ \ return function (x) {\ \ return JSON.stringify(x, null, n);\ \ };\ \}" :: forall a. Number -> a -> String
-
Use
Data.Function
to write a function of multiple arguments, and then wrap that function to provide a curried alternative:import Data.Function foreign import stringify "function stringify(n, x) {\ \ return JSON.stringify(x, null, n);\ \}" :: forall a. Fn2 Number a String stringify' :: forall a. Number -> a -> String stringify' n x = runFn2 stringify n x
Both approaches involve quite a lot of boilerplate, either in JavaScript or in PureScript code. In many cases, especially for prototyping purposes, it would be preferable to define something quickly with minimal fuss. This is the problem which is solved by the purescript-easy-ffi
library.
By importing the EasyFFI
module, we can simply specify an array of function argument names, and the function body:
stringify :: forall a. Number -> a -> String
stringify = unsafeForeignFunction ["n", "x"] "JSON.stringify(x, null, n)"
Much better! The advantages quickly become clear when writing bindings to large JavaScript libraries. For applications where performance is important, one might like to revert to the Data.Function
approach for production code, but for getting an FFI project off the ground, this approach often gives an excellent time-to-first-release.
purescript-easy-ffi
also provides a way to write methods in the Eff
monad.
Suppose we wanted to modify the original example to properly take account of exceptions. In vanilla PureScript, this would involve two extra lines:
foreign import stringify
"function stringify(n) {\
\ return function (x) {\
\ return function () {\
\ return JSON.stringify(x, null, n);\
\ };\
\ };\
\}" :: forall a eff. Number -> a -> Eff (err :: Exception | eff) String
With purescript-easy-ffi
, only three extra characters are required:
stringify :: forall a eff. Number -> a -> Eff (err :: Exception | eff) String
stringify = unsafeForeignFunction ["n", "x", ""] "JSON.stringify(x, null, n)"
The inner function is represented by the empty string in the argument list.
The purescript-oo-ffi
library solves a similar problem which you might encounter when writing bindings to object-oriented JavaScript libraries. It provides FFI helpers which can be used to construct the building blocks of an object-oriented FFI binding: object instantiations, method calls, and property getters and setters.
As an example, consider the following JavaScript "class" definition:
function Greeting() {
var self = this;
self.holiday = "Christmas";
self.beMerry = true;
self.greet = function() {
var prefix = self.beMerry ? "Merry " : "Happy ";
console.log(prefix + self.holiday + "!");
};
}
We might call use this class as follows:
new Greeting().greet();
Or if we are feeling less merry:
var greeting = new Greeting();
greeting.beMerry = false;
greeting.greet();
Our class is also holiday-seasons-polymorphic!
var greeting = new Greeting();
greeting.beMerry = false;
greeting.holiday = "Easter";
greeting.greet();
Now suppose we wanted to write a binding to this class in PureScript. This task would usually involve a lot of boilerplate involving the Eff
monad. However, the purescript-oo-ffi
library makes our job much simpler.
We can start by defining our own effect type, and a foreign type for the class itself:
foreign import data Greet :: !
foreign import data Greeting :: *
To wrap the constructor, we can use the instantiate0
function, specifying the class name:
newGreeting :: forall e. Eff (greet :: Greet | e) Greeting
newGreeting = instantiate0 "Greeting"
To wrap the greet
method, we can use the method0Eff
function:
greet :: forall e. Greeting -> Eff (greet :: Greet | e) Unit
greet = method0Eff "greet"
This is enough to recreate our first example:
example1 = do
g <- newGreeting
greet g
We can also provide wrappers for our two object properties, using the getter
and setter
functions:
getMerry :: Greeting -> Eff (greet :: Greet | e) Boolean
getMerry = getter "beMerry"
setMerry :: Greeting -> Boolean -> Eff (greet :: Greet | e) Unit
setMerry = setter "beMerry"
getHoliday :: Greeting -> Eff (greet :: Greet | e) String
getHoliday = getter "holiday"
setHoliday :: Greeting -> String -> Eff (greet :: Greet | e) Unit
setHoliday = setter "holiday"
And now we can recreate the other two examples:
example2 = do
g <- newGreeting
setMerry g false
greet g
example3 = do
g <- newGreeting
setMerry g false
setHoliday g "Easter"
greet g
Hopefully, this post has shown that writing PureScript FFI bindings does not have to be a chore. These two packages make it possible to quickly write bindings to almost any existing Javascript library.
Try it out - the PureScript community has no end of suggestions for existing JavaScript libraries which could be wrapped and possibly extended in PureScript code. Join the conversation on the #purescript IRC, and we can provide plenty of guidance to get started.
Today, I'm going to show how to create an online Christmas card using the HTML5 Canvas API from PureScript. You can see the finished result here.
I started working on the purescript-canvas
library not long after I started work on the compiler itself, and it has since seen contributions from a number of community members. The purescript-free-canvas
library provides a free monad interface to the Canvas API, which hides the 2D graphics context object from the user.
At this point, both libraries provide quite a complete representation of the HTML5 API, but there are still some features missing. If you would like to help to flesh out the remaining calls, then check out the issues board.
To follow along, start a new project using grunt-init
, and install the purescript-math
and purescript-free-canvas
libraries using Bower.
We can start by getting a reference to the canvas object and its graphics context:
module Main where
import Control.Monad.Eff
import Graphics.Canvas (getCanvasElementById, getContext2D)
import Graphics.Canvas.Free
main = do
canvas <- getCanvasElementById "canvas"
context <- getContext2D canvas
runGraphics context $ do
-- Canvas API calls will go here
The first step will be to fill the canvas background with a solid color:
setFillStyle "#00FFFF"
rect { x: 0, y: 0, w: 400, h: 600 }
fill
Inside the call to runGraphics
, our code runs in the Graphics
(free) monad, so we don't need to worry about passing around the context object. We only do that once in the call to runGraphics
itself.
Next, let's draw the tree. Start by setting the fill color to green, and adding a shadow:
setFillStyle "#008000"
setShadowColor "#00FF00"
setShadowBlur 5
Note how similar this code looks to regular Canvas code written in JavaScript. However, we get all of the benefits of writing code in PureScript, including type checking!
To draw the tree, we will make use of a combinator called at
, which translates a set of drawing commands across some vector:
at x y gfx = do
save
translate x y
gfx
restore
Note how at
wraps our action in calls to save
and restore
, which means that the state of the graphics context is preserved after the call to at
. This is a good example of the benefits of higher-order functions.
Here is the code for the body of the tree:
at 200 175 do
beginPath
triangle
at 0 50 triangle
at 0 100 triangle
closePath
rect { x: (-40), y: 200, w: 80, h: 50 }
fill
Three tree consists of a path made up of three triangles and a rectangle. The triangle
action is defined as follows at the top-level:
triangle = do
moveTo 0 (-100)
lineTo 75 100
lineTo (-75) 100
Next, let's add some baubles to the tree:
setFillStyle "#FFFF00"
at (-10) (-10) $ bauble 10
at (-20) 50 $ bauble 10
at 0 100 $ bauble 10
at (-20) 140 $ bauble 10
at 20 190 $ bauble 7
at 30 50 $ bauble 7
at (-50) 75 $ bauble 7
at (-40) 180 $ bauble 7
at 50 125 $ bauble 7
at 40 175 $ bauble 7
Again, the bauble
function is defined as a reusable helper:
bauble size = do
beginPath
arc { x: 0, y: 0, r: size, start: 0, end: Math.pi * 2 }
fill
You can try rewriting this to specify the bauble positions in an array, using traverse
to loop over the array.
Finally, to add the text at the top and bottom, we can use the fillText
action, as follows:
setFillStyle "#FF0000"
setFont "48px Sans-Serif"
fillText "Merry Christmas" 25 50
fillText "From PureScript!" 20 480
And that's it! A PureScript Christmas card in under 70 lines of code. Try making your own, and tell your relatives that you made your Christmas cards with free monads!
Today, I'm going to look at the purescript-rx
library, by @anttih, which provides a PureScript binding to the RxJS library.
For me, this library is a wonderful example of the self-documenting nature of a well-designed functional library. While I have used Reactive Extensions quite a lot in C# in the past, I have never used RxJS itself. Nor have I used the purescript-rx
library before this morning, but I was able to get up and running in under 20 minutes by applying my knowledge of well-defined abstractions like Monad
and Applicative
.
Using bower install [email protected]:anttih/purescript-rx.git
will conveniently install the rxjs
, jquery
and rxjs-jquery
libraries under bower_components
directory, which we can then include in our page as follows:
<script type="text/javascript" src="../bower_components/jquery/dist/jquery.js"></script>
<script type="text/javascript" src="../bower_components/rxjs/dist/rx.all.js"></script>
<script type="text/javascript" src="../bower_components/rxjs-jquery/rx.jquery.js"></script>
My example will consist of two colored squares, and I will attempt to detect various gestures by using RxJS to combine event streams. Here is the HTML:
<div style="width: 100px; height: 100px; background-color: green; float: left" id="green"></div>
<div style="width: 100px; height: 100px; background-color: red; float: left" id="red"></div>
<pre style="clear: left;" id="output"></pre>
The imports list is straightforward:
module Main where
import Control.Monad.Eff
import Rx.JQuery
import Rx.Observable
import Control.Monad.JQuery
The first task is to get a reference to the three DOM elements, using the purescript-jquery
library:
main = do
red <- select "#red"
green <- select "#green"
output <- select "#output"
Next, let's use the Rx.JQuery
module to turn the mouseover
, mousemove
and mousedown
event streams into Observable
streams:
redOver <- "mouseover" `onAsObservable` red
redMove <- "mousemove" `onAsObservable` red
redOut <- "mouseout" `onAsObservable` red
greenOver <- "mouseover" `onAsObservable` green
greenMove <- "mousemove" `onAsObservable` green
greenOut <- "mouseout" `onAsObservable` green
We can subscribe to these event streams directly, by using the subscribe
action:
redOver `subscribe` \_ -> void $ "red mouseover" `setText` output
redMove `subscribe` \_ -> void $ "red mousemove" `setText` output
redOut `subscribe` \_ -> void $ "red mouseout" `setText` output
greenOver `subscribe` \_ -> void $ "green mouseover" `setText` output
greenMove `subscribe` \_ -> void $ "green mousemove" `setText` output
greenOut `subscribe` \_ -> void $ "green mouseout" `setText` output
However, these examples are not particularly interesting.
The documentation for purescript-rx
helpfully notes that Observable
is an instance of several common type classes: Semigroup
, Applicative
, Monad
and their respective superclasses. We can use these instances to build more interesting gesture recognizers.
Here's a simple example. Suppose we wanted to detect when the user moved the mouse from the left to the right, from the red square and onto the green square. Or the other way, from the green square onto the red square.
Ordinarily, this would involve a mess of callback handlers, but with RxJS, we can use the flatMap
function to combine dependent event streams in this way. In purescript-rx
, this function defines the >>=
function of the Observable
type constructor's Monad
instance, so we can just define our gestures using regular do
notation!
let gestureLeft = do redOver
redOut
greenOver
greenOut
return "Swipe left"
let gestureRight = do greenOver
greenOut
redOver
redOut
return "Swipe right"
Here, we could even use <-
to extract data from the various events, but let's keep things simple for now.
We can subscribe to these combined events, and display the recognized gesture on the screen:
(gestureLeft `merge` gestureRight) `subscribe` \msg ->
void $ msg `setText` output
The finished demo can be seen here.
This is just one example of the ways in which purescript-rx
and RxJS can be used to combine event streams. I haven't covered the Applicative
or Semigroup
instances here, which are interesting in their own right. Fortunately, the library is very simple to install, so fork the demo repository and give it a try for yourself.
Later in the month, I will show another approach to combining event streams in PureScript, so stay tuned!
Today I will give a very basic introduction to the purescript-lens
library, written by @joneshf.
purescript-lens
is a partial port of Edward Kmett's lens
library from Haskell to PureScript.
lens
is an impressive library which solves a number of problems in vastly more generality than I am about to present, but hopefully the following should whet your appetite.
Let's demonstrate the most simple functionality of the purescript-lens
library - using lenses to update a part of a nested data structure. I should add that I have not used lens
or purescript-lens
before today, and my experience so far is that the types are somewhat complicated, but that following the examples is a good way to get started.
Suppose we have the following data structures, representing an address book (I think these Christmas analogies are getting more convoluted ...):
data Person = Person { name :: String
, addr :: Address
, type :: PersonType
}
data Address = Address { street :: String
, city :: String
}
data PersonType = Naughty | Nice
Here, a Person
contains a name, an Address
and a PersonType
. We can create an example Person
:
examplePerson :: Person
examplePerson =
Person { name: "John Smith"
, addr: Address { street: "123 Fake St."
, city: "Los Angeles"
}
, type: Naughty
}
Now suppose we want to update the city
property of the addr
property. This requires nested record updates, and the problem gets worse if our data structures use more levels of nesting.
purescript-lens
provides one solution. For each of our properties, we can define a Lens
, using the lens
function to specify getters and setters:
name :: LensP Person String
name = lens (\(Person p) -> p.name) (\(Person p) name -> Person (p { name = name }))
address :: LensP Person Address
address = lens (\(Person p) -> p.addr) (\(Person p) addr -> Person (p { addr = addr }))
_type :: LensP Person PersonType
_type = lens (\(Person p) -> p.type) (\(Person p) ty -> Person (p { type = ty }))
street :: LensP Address String
street = lens (\(Address a) -> a.street) (\(Address a) street -> Address (a { street = street }))
city :: LensP Address String
city = lens (\(Address a) -> a.city) (\(Address a) city -> Address (a { city = city }))
In psci
, we can update various parts of the structure using the .~
operator:
> :i Optic.Core
> examplePerson
(Person "John Smith" (Address "123 Fake St." "Los Angeles") Naughty)
> examplePerson # _type.~ Nice
(Person "John Smith" (Address "123 Fake St." "Los Angeles") Nice)
> examplePerson # address..city.~ "San Diego"
(Person "John Smith" (Address "123 Fake St." "San Diego") Naughty)
Note that purescript-lens
provides the ..
operator as a synonym for function composition (<<<
), which makes the code easier to read.
We can also use the ^.
operator to read nested properties from our structure:
> examplePerson ^. _type
Naughty
> examplePerson ^. address..street
"123 Fake St."
The purescript-lens
library (and the associated refractor
and optic
libraries) provide a great deal more functionality than I have presented here, but I spent most of my time today getting up to speed with the basics. I, for one, will certainly be spending more time getting familiar with this excellent library, and I encorage you to try it for yourself.
Today I will show how to use Bodil Stokke's pulp
tool to get up and running quickly with PureScript.
pulp
is a command line tool which automates a number of common PureScript tasks, such as creating a new project, pulling Bower dependencies, and generating Javascript and documentation. It assumes a number of simple conventions, which enable a very simple command line interface.
Get started by initializing a new pulp
project on the command line:
$ pulp init
* Generating project skeleton in purescript/pulp-test
At this point, the current directory will contain a src
directory, for source files, a test
directory for tests, and a bower.json
file, where you can specify your PureScript library dependencies.
To see the list of commands available, type pulp
with no command at the shell:
$ pulp
No command specified.
Available commands:
init - Generate an example PureScript project
install - Download and install project dependencies
build - Build the project
test - Run project tests
browserify - Produce a deployable bundle using Browserify
run - Compile and run the project
docgen - Generate project documentation
psci - Launch a PureScript REPL configured for the project
Try building the project in its default state:
$ pulp build
* Building project in purescript/pulp-test
* Build successful.
The source modules will be compiled and placed in the output
directory. We also have the option of running the tool in watch mode, by using pulp build -w
. This will cause the project to be rebuilt when the sources change. In fact, all of the pulp
commands can be run in watch mode by using the -w
flag.
Now try running the compiled sources, by using pulp run
:
$ pulp run
* Building project in purescript/pulp-test
* Build successful.
Hello sailor!
We can also compile and run the tests using pulp test
:
$ pulp test
* Building project in purescript/pulp-test
* Build successful. Running tests...
You should add some tests.
* Tests OK.
Other options include the browserify
command, which will generate JavaScript for the browser, and the pulp
command which will load your sources into psci
.
Let's add some code to src/Main.purs
, and use pulp psci
to test our library:
module Main where
import Debug.Trace
greet :: Boolean -> String -> String
greet beMerry holiday
| beMerry = "Merry " <> holiday <> "!"
| otherwise = "Happy " <> holiday <> "."
main = do
trace $ greet true "Christmas"
Using pulp run
should generate a season-appropriate greeting on the command line, or we can use pulp psci
to test our function in isolation in psci
:
$ pulp psci
____ ____ _ _
| _ \ _ _ _ __ ___/ ___| ___ _ __(_)_ __ | |_
| |_) | | | | '__/ _ \___ \ / __| '__| | '_ \| __|
| __/| |_| | | | __/___) | (__| | | | |_) | |_
|_| \__,_|_| \___|____/ \___|_| |_| .__/ \__|
|_|
:? shows help
Expressions are terminated using Ctrl+D
> Main.greet false "Easter"
"Happy Easter."
Hopefully, this has shown that pulp
is a great tool which can help you to get up and running with PureScript very quickly. In a future post, I will show we might get started with pulp test
, by using one of PureScript's testing libraries to modify test/Main.purs
.
Today, I'm going to look at the purescript-machines
library by @jdegoes, which can be used to create finite state machines in PureScript.
The purecript-machines
library provides an implementation of Mealy machines, which we can use to separate our code into components which are responsible for either producing or consuming data (or both). Our machines can act over any Monad
, which as we will see, is very helpful when we need to deal with asynchronous data sources in a non-blocking way.
Here is a very simple example to get started:
module Main where
import Data.Machine.Mealy
import Control.Monad.Eff
import Debug.Trace
main = runMealy machine
where
machine = take 100 (loop (return "Merry Christmas!")) >>> sink trace
Here, we can factor our code into a source and a sink:
main = runMealy machine
where
machine = greetings >>> printer
greetings = take 100 (loop (return "Merry Christmas!"))
printer = sink trace
Note that the usual function composition operator >>>
is being used here in its more polymorphic form, taken from the Category
type class. The purescript-machines
library provides a rich array of standard type class implementations for its MealyT
type constructor, which can be used to build complex state machines.
Now suppose that we want to ask for the user's name before offering them a more personalized greeting. We can write a function which uses the continuation monad to represent a call to the readline
module, to read a string from standard input. I won't reproduce the code here, but the function will have the following type signature:
type AppC = ContT Unit App
questionC :: String -> AppC String
With that, we can modify our greetings
and printer
machines accordingly:
greetings :: Source AppC String
greetings = take 5 $ loop $ source $ questionC "What is your name? "
printer :: Sink AppC String
printer = do
name <- id
wrapEffect $ lift $ trace $ "Merry Christmas, " <> name
Note that the printer
machine uses a trick here to get hold of the upstream value: the polymorphic type of id
gets instantiated to the type MealyT f a a
, which means that the result type of our computation is actually the type of the source. We can then use wrapEffect
to wrap the effectful code which prints the greeting to the screen.
In our main
function, we simply need to call runContT
in order to run the final asynchronous computation:
main = flip runContT return $
runMealy $
greetings >>> printer
We can run the compiled code using node
, and see how the two machines interact:
What is your name? Phil
Merry Christmas, Phil
What is your name? John
Merry Christmas, John
What is your name? Mary
Merry Christmas, Mary
What is your name? ...
This has really only covered the basics of the purescript-machines
library. The Data.Machine.Mealy
module provides a lot more functionality, which is well worth exploring.
Two days ago, I promised to show how we might utilize the tests
folder in Bodil Stokke's pulp
tool. There are actually multiple options, and I hope to cover more of them in future posts, but today I will take a look at another library from @bodil called purescript-test-unit
.
purescript-test-unit
aims to bring conventional unit testing to PureScript, with support for asynchronous tests.
Let's create a new project using pulp
, add some sample code, and write some tests.
$ pulp init
Santa's software development division have created a web service to respond to inventory requests for Christmas gifts, and helpfully provided a mock service which simulates server latency. Here is their code, which we can place into src/ServiceMock.purs
:
module ServiceMock where
import Control.Monad.Eff
foreign import data HTTP :: !
foreign import checkInventory
"function checkInventory(item) {\
\ return function(k) {\
\ return function() {\
\ setTimeout(function() {\
\ switch (item) {\
\ case 'Nutcracker':\
\ case 'Spinning Top':\
\ k(true)();\
\ break;\
\ default:\
\ k(false)();\
\ }\
\ }, 500);\
\ };\
\ }\
\}" :: forall eff. String ->
(Boolean -> Eff (http :: HTTP | eff) Unit) ->
Eff (http :: HTTP | eff) Unit
Now, we can test this mock service implementation (and any code which depends on it), by placing our tests in test/Main.purs
:
module Test.Main where
import Test.Unit
import ServiceMock
main = runTest do
test "Service Tests" do
assertFn "Nutcracker is missing from inventory" $
checkInventory "Nutcracker"
assertFn "Spinning Top is missing from inventory" $
checkInventory "Spinning Top"
Here, runTest
runs a test suite, printing a test report onto the console. test
introduces a named test group, and assertFn
can be used to run an asynchronous test, by taking a callback as an argument.
We can run our test suite on the command line:
$ pulp test
* Building project in purescript/pulp-test
* Build successful. Running tests...
→ Running: Service Tests
✓ Passed: Service Tests
* Tests OK.
If we modify our tests to make a false assertion, we will see an error in the test report:
$ pulp test
* Building project in purescript/pulp-test
* Build successful. Running tests...
→ Running: Service Tests
☠ Failed: Service Tests because Bicycle is in inventory
* ERROR: Subcommand terminated with error code 1
purescript-test-unit
supports other test functions, such as assert
and assertFalse
, which can be used to build pure tests, and timeout
which can be used to ensure that an asynchronous function returns in a fixed amount of time.
Later in the series, I'll take a look at some complementary testing libraries, which can be used alongside purescript-test-unit
to provide different types of tests.
Until next time...
Today, we're going to create purely functional Christmas Carols, using @waterson's excellent purescript-webaudio
library.
If you're interested in seeing this library in action, you should check out the Asteroids demo, by the same author.
To begin, pull down a copy of the library into a new project using Bower
:
$ bower install https://github.com/waterson/purescript-webaudio
I worked from the examples projects in the purescript-webaudio
repo, and used the following imports:
module Main where
import Control.Bind
import Control.Monad.Eff
import Data.DOM.Simple.Types
import Data.DOM.Simple.Window
import Audio.WebAudio.Types
import Audio.WebAudio.AudioContext
import Audio.WebAudio.AudioParam
import Audio.WebAudio.OscillatorNode
import Audio.WebAudio.DestinationNode
The web audio API defines different types of nodes which can be connected to form networks. We will only be interested in the oscillator node in this post. The network can then be connected to a destination node on an audio context, which results in sound being generated.
We can start by creating a new audio context:
main :: forall eff. (Eff (wau :: WebAudio, dom :: DOM | eff) Unit)
main = do
ctx <- makeAudioContext
Next, we can create an oscillator node which will produce a sine wave:
osc <- createOscillator ctx
setOscillatorType Sine osc
startOscillator 0.0 osc
Now, we connect the oscillator node to the destination node on the audio context:
connect osc =<< destination ctx
And finally, we delegate to a helper function, play
, which is responsible for playing our song, passing the context and oscillator nodes as arguments.
play ctx osc
The play
function is very simple. It sets up a timer on the window object to update the oscillator every 10 milliseconds:
play :: forall eff. AudioContext -> OscillatorNode -> Eff (wau :: WebAudio, dom :: DOM | eff) Unit
play ctx osc = void $ setInterval globalWindow 10 update
The update
function uses the setValue
action from purescript-webaudio
to change the frequency of the oscillator, based on the time:
where
update = do
t <- currentTime ctx
frequency osc >>= setValue (freqAt t)
return unit
The particular melody which is played depends on the freqAt
function. Try some different functions - you might find a table of notes and frequencies to be helpful.
Here is an example, which oscillates between two octaves, producing an alarm-like sound:
freqAt t | t < 1 = 440
| t < 2 = 880
| otherwise = freqAt $ t - 2
However, in the spirit of the season, let's write a function which will render a popular Christmas carol. Here are the first four bars of Jingle Bells, in the form of a pure function:
freqAt :: Number -> Number
freqAt t | t < 4.50 = 164.81 * 2.0
| t < 5.00 = 196.00 * 2.0
| t < 5.75 = 130.81 * 2.0
| t < 6.00 = 146.83 * 2.0
| t < 8.00 = 164.81 * 2.0
| t < 10.50 = 174.61 * 2.0
| t < 12.00 = 164.81 * 2.0
| t < 13.00 = 196.00 * 2.0
| t < 13.50 = 174.61 * 2.0
| t < 14.00 = 146.83 * 2.0
| t < 16.00 = 130.81 * 2.0
| otherwise = freqAt $ t - 16
Try it out, and see what other melodies you can produce by varying the freqAt
function. You may also be interested to try out some of the different node types provided by the purescript-webaudio
library.
I'm particularly interested to see what might be done with this library in conjunction with an FRP library, like purescript-signal
or purescript-behaviors
, using functions of multiple arguments to represent various node parameters.
Today, I tried out the purescript-express
library for the first time. This library, by @dancingrobot84, provides a wrapper for the Express web framework. If you are interested in seeing this library in use in a real project, you should check out Beta Reduction Online, by the same author.
Let me start by saying that I was amazed by how simple it was to get started with this library. I was able to get up and running in a matter of a few minutes. Here is a console transcript and a few lines of code which should suffice to get started:
$ pulp init
$ npm install express
$ bower install purescript-express
$ ... modify src/Main.purs ...
$ pulp run
Here is the code which I wrote for my first test, based on the example from the project repository:
module Main where
import Node.Express.Types
import Node.Express.App
import Node.Express.Handler
handler :: Handler
handler = sendJson { greeting: "Merry Christmas!" }
app :: App
app = get "/" handler
main = listen app 8080 \_ -> return unit
This application will listen on port 8080, and you can open your browser to http://localhost:8080
to see the result.
Let's modify our routes to match a parameter. Start by changing the route pattern in app
, as follows:
app :: App
app = get "/greet/:name" handler
Next, add an import for Data.Maybe
, and change the handler to read the value of the parameter:
handler :: Handler
handler = do
Just name <- getParam "name"
sendJson { greeting: "Merry Christmas, " <> name <> "!" }
This time, when you visit, for example, http://localhost:8080/greet/Charlie%20Brown
, you should see a more personalized greeting.
purescript-express
provides much more functionality. As one final example, let's implement a (very unsafe) file server.
handler :: Handler
handler = do
m <- getQueryParam "file"
case m of
Just file -> download file
Nothing -> do
setStatus 400
sendJson { error: "Please specify the file parameter" }
app :: App
app = get "/file" handler
This example demonstrates a few other functions:
setStatus
can be used to send an error (or success) code in the HTTP response.download
can be used to download a file given its filename.getQueryParam
can be used to read a value from the query string.
Now, we can download files by navigating to, for example, http://localhost:8080/file?file=/etc/hosts
. Obviously, this is very unsafe, and such a service should not be made publically (or probably even locally) available, but it demonstrates how quickly it is possible to write useful services with this great library.
Today, I'm going to take a look at one of the earliest, most developed, and most discussed PureScript contributor libraries - purescript-react
, originally created by @andreypopp, which provides an interface to the React UI library.
On the face of it, React is a wonderful match for PureScript, emphasizing isolation of mutable state, and separation of concerns in the UI. However, while the philosophy of React seems like a great match, the actual implementation is another matter. It is somewhat challenging to provide an API for React's idiomatic JavaScript, in PureScript (or for that matter, in any pure functional language).
For that reason, I see purescript-react
as a triumph in its use of PureScript's flexible FFI. Also, I think it provides a great example of the trade-offs one can choose to make when writing FFI bindings, between type safety, and usability.
Other libraries such as virtual-dom
seem to take more cues from pure functional programming, and I look forward to seeing what will be done using those libraries from PureScript.
Let's get started. I created a new project, with a HTML file, and installed both react
and purescript-react
using Bower.
I created the following Main.purs
file, based on the example in the purescript-react
repository:
module Main where
import Control.Monad.Eff
import React
import React.DOM
ui = mkUI spec { getInitialState = pure initialState } $
view <$> readState
where
initialState = { name: "World" }
view st =
p [] [ text "Merry Christmas, "
, text name
, text "!"
]
main =
let
component = div [] [ ui {} ]
in renderToBody component
This is enough to get started - compile the code and open index.html
, and you will see the greeting "Merry Christmas, World!".
The code requires a little explanation.
The mkUI
function can be used to build a UI component. We pass the initial state object, and a function which can render the view from the current state. The React.DOM
library provides a collection of smart constructors which can be used to build views simply.
Finally, in main
, the renderToBody
function is used to render the UI component to the DOM body.
Now let's extend our application by allowing the user to modify the state object. We will add a text box to let the user enter their name.
First, modify the view
function to add the text box:
view st =
div [] [ p [] [ text "Your name: "
, input [ onChange updateName ] []
]
, case st.name of
"" -> p [] [ text "Please enter your name." ]
name -> p [] [ text "Merry Christmas, "
, text name
, text "!"
]
]
Here, the onChange
function adds an event handler to the UI component. We can provide the updateName
handler, which sets the state to the entered text:
updateName :: Event -> EventHandlerContext _ _ _ _ _
updateName e = writeState { name: getValue e }
Here, I need to specify a type signature to avoid a type error due to purescript-react
's use of rank-N types. However, notice that the new type wildcards feature allows us to only specify what is necessary.
The getValue
function needs to be provided as an FFI import:
foreign import getValue
"function getValue (e) {\
\ return e.target.value;\
\}" :: Event -> String
If you would like to see the finished application, it is available online here.
Today, I'm going to take a look at purescript-smolder
, which is the third (but not final) PureScript library to be covered in this series, which is written by @bodil.
purescript-smolder
is a domain-specific language for the problem of creating and applying DOM templates, inspired by Haskell's blaze-html
library.
To celebrate having just completed twelve days of this mini-blog series, I'm going to use the library to create a page which lists the lyrics to the song "the twelve days of Christmas" in a structured form.
For anyone who just wants to see the finished result, you can do so here.
My demo uses the following imports from the purescript-smolder
library:
import Text.Smolder.HTML (h1, p, ul, li)
import Text.Smolder.HTML.Attributes (name, content)
import Text.Smolder.Markup (Markup(), text)
import Text.Smolder.Renderer.String (render)
The Text.Smolder.HTML
and Text.Smolder.Attributes
modules contain typed representations of HTML elements and attributes respectively.
The Text.Smolder.Markup
defines the text
function which, as we will see, is used to embed plain text into a document.
Finally, the Text.Smolder.Renderer.String
module defines one of two renderers provided by the library, which renders the HTML to a string.
To start, let's define a list of gifts which my true love gave to me:
gifts :: [String]
gifts = [ "partridge in a pear tree"
, "turtle doves"
, "french hens"
, "calling birds"
, "golden rings"
, "geese a-laying"
, "swans a-swimming"
, "maids a-milking"
, "ladies dancing"
, "lords a-leaping"
, "pipers piping"
, "drummers drumming"
]
We're also going to need the following helper function:
th :: Number -> String
th 1 = "st"
th 2 = "nd"
th 3 = "rd"
th _ = "th"
Now, let's define a model for a single verse of the song:
type Model = [Tuple Number String]
day :: Number -> Model
day n = reverse (take n (zip (1..12) gifts))
To determine the model for a given day, we simply take that number of days from the list and reverse it.
Now, we can use purescript-smolder
to render our view:
view :: Markup
view = do
h1 $ text "The twelve days of Christmas"
for_ (1..12) $ \n -> do
p $ do
text "On the "
text $ show n
text $ th n
text " day of Christmas, my true love gave to me:"
ul $ do
for_ (day n) $ \(Tuple count gift) -> li do
text $ show count
text " "
text gift
There are a few things to notice here:
- Elements are just functions like
h1
andul
. - Text content is created using the
text
function. - We can use
do
notation, and functions likefor_
to structure our code, sinceMarkup
is monadic.
Finally, we can use the render
function to turn our Markup
into a HTML string:
main = asBody $ render view
Here, the asBody
function is defined using a foreign import:
foreign import asBody
"function asBody(html) {\
\ return function() {\
\ onload = function() {\
\ document.body.innerHTML = html;\
\ };\
\ };\
\}" :: forall eff. String -> Eff (dom :: DOM | eff) Unit
One other neat thing about purescript-smolder
is that it also provides a renderer which makes use of the virtual-dom
library, in the Text.Smolder.Renderer.VTree
module. This means that you would be able to render two values of type Markup
to virtual DOM elements, and patch the DOM by applying the differences. This approach can result in some remarkable speed improvements in DOM-heavy applications.
Today, I've been looking at the yesod-purescript
library, which allows PureScript code to be compiled as part of a Yesod website.
yesod-purescript
is created by @mpietrzak, who has done a wonderful job of documenting the library, including a getting started guide in the README file, and even a fully-worked example project. I have not done a great deal of work with Yesod in the past, but I was able to get up and running in 30 minutes (not including the time to cabal install
the necessary libraries).
I started by using cabal init
to create a new Cabal project in a sandbox, followed by cabal install
ing yesod-core
and yesod-purescript
(from the cloned repository). From there, I created an empty Yesod website in Main.hs
, using the name Greeting
for my site's foundation type:
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE OverloadedStrings #-}
module Main where
import qualified Yesod.Core as Y
data Greeting = Greeting
Y.mkYesod "Greeting" [Y.parseRoutesNoCheck|
/ Home
|]
instance Y.Yesod Greeting
handleHome :: Handler ()
handleHome = return ()
main = Y.warp 8080 Greeting
This project should compile and run, opening a web server on port 8080
. The site will serve an empty page at the root /
.
From there, I simply followed the instructions in the yesod-purescript
README
file:
-
I added an import for the
Yesod.PureScript
module:import qualified Yesod.PureScript as Y
-
I modified my site's foundation type to include a value of type
PureScriptSite
, and added an instance for theYesodPureScript
type class:data Greeting = Greeting { getPureScriptSite :: Y.PureScriptSite } instance Y.YesodPureScript Greeting
-
I updated my routing table to route
/purs
to my new subsite:Y.mkYesod "Greeting" [Y.parseRoutesNoCheck| / Home /purs PureScript Y.PureScriptSite getPureScriptSite |]
-
I updated
main
to create the subsite usingcreateYesodPureScriptSite
:main = do purs <- Y.createYesodPureScriptSite def Y.warp 8080 $ Greeting purs
At this point, if you compile and run the project, you should be able to open your browser to localhost:8080/purs
and see the yesod-purescript
status page, which lists PureScript modules and their compilation status.
However, we don't have any PureScript modules yet, so let's create one.
Create purs/
and bower_components/
directories in your project's root directory, and copy the Prelude files from your PureScript distribution's share
directory into purs/
. Also create a purs/Main.purs
file, for example:
module Main where
import Control.Monad.Eff
foreign import data Alert :: !
foreign import alert
"function alert(s) {\
\ return function() {\
\ window.alert(s);\
\ };\
\}" :: forall eff. String -> Eff (alert :: Alert | eff) Unit
main = alert "Merry Christmas!"
Finally, we need to update our home page route to include the compiled PureScript code:
handleHome :: Handler Y.Html
handleHome = do
Y.defaultLayout $ do
Y.setTitle "Merry Christmas from PureScript!"
Y.addScript $ PureScript $ Y.getPureScriptRoute ["Main"]
Now, if you recompile and refresh the page at localhost:8080/purs
, you will hopefully see a list of successfully compiled modules. If you open the browser to the home page at localhost:8080/
, the compiled JavaScript code will be run, resulting in a call to window.alert
.
The neat thing about yesod-purescript
is that you can develop your PureScript code in the purs/
directory without needing to recompile or even restart your Yesod application. If your PureScript modules change, they will be recompiled on the next page load.
I think that yesod-purescript
is a great way to get started with any project which uses a Haskell backend and a PureScript frontend, and I hope to make use of it in an upcoming project.
My complete project code is available here.
Until tomorrow...
Today, I have been looking at the purescript-hatter
DOM templating library by @mechairoi.
Hatter is a very interesting library, because it solves a problem which would normally be solved using something like Template Haskell (which does not yet exist for PureScript, but which is planned): compile-time code generation.
The purescript-hatter
library itself contains a parser and code generator, but also comes with a set of companion libraries: gulp-purescript-hatter
which embeds the code generation capability into the build process using the Gulp build automation tool, and purescript-hatter-runtime
, which contains the runtime needed to support the generated code.
Hatter targets the virtual-dom
library using the purescript-virtual-dom-typed
bindings, which means that the generated templates should generally be very fast, and support partial updates.
Here, I'm just going to give a very brief overview of the functionality.
I created a new project using pulp
, and installed purescript-hatter
and purescript-virtual-dom-typed
using Bower. My Main
module uses the following imports from Hatter:
import Text.Hatter
import Text.Hatter.Parser
Based on the example in the repository, I came up with the following template:
input :: String
input =
"greet :: String -> VirtualDOM.VTree.VTree\n\
\greet name =\n\
\<p style='color: red'>\n\
\ Merry Christmas,\n\
\ <span style='font-weight: bold'>\n\
\ <% VirtualDOM.VTree.vtext name %>\n\
\ </span>\n\
\</p>"
Hopefully, when Template PureScript is functional, it will be possible to embed code like this directly into a quasiquoter.
Notice how Hatter allows us to embed HTML directly into PureScript code. After code generation, this means that our HTML will benefit from type checking, while keeping all the performance benefits of a statically-defined template.
The <% ... %>
brackets also allow us to embed virtual-dom
elements back into the HTML structure. From what I can tell, attributes and text content can also be templated in this way.
Here is my main
function:
main =
case hatter "Greeting" ["VirtualDOM.VTree"] input of
Left err -> print err
Right purs -> trace purs
I specified the module name Greeting
, and my imports list, and this was enough to get Hatter to generate some PureScript code for my template. This code can then be built as part of your application.
I had a little trouble with versions of various Bower packages, but many of these bindings are new, and hopefully we will see them stabilize along with the upstream packages like purescript-virtual-dom
.
That's it for my brief overview, but I urge you to try this excellent library out for yourselves. I think that virtual-dom
is a great match for PureScript, and I look forward to seeing more interesting integrations like this.
Today, I'm going to take a break from talking about libraries, to briefly highlight some of the options available for working with PureScript in various popular text editors.
Vim is my terminal text editor of choice, so I can say the most about it here. I use the purescript-vim
plugin by [@raichoo], which provides two neat features:
- Syntax highlighting
- Automatic indentation
The indentation levels can be configured to your liking. Also, to my knowledge, the purescript-vim
syntax highlighter is the only one which currently correctly identifies triple-quoted string literals (although I might be wrong).
Installation is simple - install Pathogen if you don't have it installed already, and then clone the purescript-vim
repository under ~/.vim/bundle/
.
Emacs users can enjoy a collection of PureScript-related plugins, thanks to the excellent work done by various contributors:
- @spion's
purscheck
providesflycheck
-compatible syntax and error checking, with errors highlighted in the buffer. - @ardumont's
emacs-psci
provides a major mode for thepsci
interactive mode. - @dysinger's
purescript-mode
repurposeshaskell-mode
for PureScript.
Alejandro Cabrera has written some useful information about his Emacs setup here.
Support is available for other editors to various degrees:
ikarienator/pure-idea
is a plugin for IntelliJ IDEA, which supports syntax highlighting and grammar checking.joneshf/sublime-purescript
provides highlighting, auto-completion and support for snippets in Sublime Text 2.darinmorrison/atom-language-purescript
supports syntax highlighting in the Atom text editor.
Did I miss any?
One of the prioritized items for the compiler is the so-called "compiler-as-a-service", which should make it possible to build richer editor integrations: type-checking on save, getting the type of the expression at the cursor, searching for possible implementation a la Hoogle, etc.
Generally, this is an area where the community can use a lot of help. Good editor integration is exactly the sort of thing which will enable PureScript to succeed, so please join the #purescript IRC channel and share your ideas.
Today, I'm going to look at the purescript-strongcheck
library by @jdegoes. Strongcheck is an "industrial-strength" version of the purescript-quickcheck
library, providing additional combinators for building more powerful tests, and new testing strategies entirely, such as statistical testing and exhaustive testing. In fact, Strongcheck actually comprises what could make up several smaller interesting libraries.
Suppose we write a library to verify Christmas gift wishlists. The library is designed to ensure that the list is not too long, and does not contain duplicate gift requests:
module Wishlist where
import Data.Array (length, nub)
type Gift = String
newtype Wishlist = Wishlist [Gift]
verify :: Wishlist -> Boolean
verify (Wishlist wl)
| length wl > 5 = false
| length (nub wl) /= length wl = false
| otherwise = true
We can test this library in psci
:
$ pulp psci
> Wishlist.verify ["foo", "bar", "bar"]
false
> Wishlist.verify ["foo", "bar", "baz", "bam", "bux"]
true
> Wishlist.verify ["foo", "bar", "baz", "bam", "bux", "xyz"]
false
And we could use purescript-quickcheck
to generate arbitrary Wishlist
s and verify their properties. The problem is that it is tricky to generate arbitrary Wishlist
s which satisfy the criteria described by the verify
function.
Strongcheck provides additional functions for generating random input data, which makes this job easier. In this case, we can use two additional functions, chooseInt
and nChooseK
to generate valid Wishlists
from a pool of 20 mock "gifts":
gifts :: [Gift]
gifts = toGift <$> (1 .. 20)
where
toGift :: Number -> Gift
toGift n = "Gift " ++ show n
instance arbWishlist :: Arbitrary Wishlist where
arbitrary = do
n <- chooseInt 0 5
Wishlist <$> nChooseK n gifts
Then we can use the quickCheck
function to validate 100 random Wishlist
s:
main = do
quickCheck Wishlist.verify
The chooseInt
generator chooses an integer from a range, and the nChooseK
generator will generate arrays of a given size without duplicates. We can use these two functions to generate randomly-selected valid Wishlist
s.
Strongcheck provides many interesting new generators like these ones. This is possible because Strongcheck's Gen
type is built on the purescript-machines
library, and can support more interesting ways of generating of pseudo-random data.
Strongcheck also provides two new test types in the form of the smallCheck
and statCheck
functions.
smallCheck
can be used to test properties exhaustively. In fact, if we want to modify our test to verify all valid Wishlist
s instead of a random sample, it is as simple as changing quickCheck
to smallCheck
:
main = do
smallCheck Wishlist.verify
statCheck
is used when you want to verify that a property holds with some probability, but cannot guarantee that it holds with certainty for any random sample.
For example, we would expect that a random sampling of Wishlist
s generated as above would contain any given gift from the list of 20 mock gifts, about a quarter of the time. We can express this with statCheck
as follows:
main = do
statCheck (1/4) $ \(Wishlist wl) -> "Gift 1" `elem` wl
Strongcheck contains some other impressive features, like the ability to resize and perturb randomly generated values to generate more meaningful test failures, but I won't cover those here.
I urge you to check out this feature-packed library. If purescript-strongcheck
doesn't meet your needs for generative testing, I would be surprised!
If you would like to see the code for this example, it is available here.
Today I spent some time looking at the purescript-d3
library, written by @pelotom. purescript-d3
provides a set of bindings to the D3 diagrams library.
I have not used D3 before, but I started by creating a new project, installing purescript-d3
using Bower, and following the excellent examples.
The data science division of Santa and co. is preparing its annual report and has decided to use D3 to create its charts. Here is their data in the form of an array:
array = [ { label: "Nice"
, count: 92
}
, { label: "Naughty"
, count: 8
}
]
I will show how to plot this as a simple bar chart, with vertical bars.
I created a HTML file, based on the example, which looked like this:
<html>
<head>
<style>
.chart rect {
fill: green;
}
.chart text {
font: 10px sans-serif;
}
</style>
</head>
<body>
<svg class="chart"></svg>
<script src="../bower_components/d3/d3.min.js"></script>
<script src="index.js"></script>
</body>
</html>
and a PureScript file in src/Main.purs
, with the following imports:
module Main where
import Graphics.D3.Util
import Graphics.D3.Selection
purescript-d3
provides a monadic interface to D3, based on the Eff
monad. D3's fluent interface is captured by the bind operation of the Eff
monad, but purescript-d3
provides a helpful ..
operator as a synonum for >>=
, making the code more readable.
In the first part of the code, I obtain a reference to the <svg>
element on the page, and bind the data in array
to a list of <g>
subnodes of that element:
main = do
g <- rootSelect ".chart"
.. selectAll "g"
.. bind array
.. enter
.. append "g"
The result is that we create one <g>
element for each entry in our array. g
is bound to this selection, and we can use this reference to append different nodes to each element.
Let's start by adding a rectangle to represent each bar:
g # append "rect"
.. attr'' "x" (\_ i -> i * 100)
.. attr' "y" (\o -> 100 - o.count)
.. attr "width" 98
.. attr' "height" (\o -> o.count)
.. style "stroke-width" "1"
.. style "stroke" "rgb(0,255,0)"
Here we are essentially only using two functions to modify the rectangle, attr
and style
. The attr'
and attr''
modifiers are variants of the attr
function, which provide access to the current element of the array, and its index in the array respectively, as function arguments.
Next, we can append a text node for each array element, to label the corresponding bar, as follows:
g # append "text"
.. attr'' "x" (\_ i -> i * 100 + 20)
.. attr "y" 120
.. text' (\o -> o.label ++ " (" ++ show o.count ++ "%)")
And that's it! The finished product can be seen here.
purescript-d3
provides much more functionality, including transitions, interpolation, and the ability to read data from TSV files. I don't have time to cover these here, but they are well worth checking out on GitHub.
Today, I've been looking at @bodil's purescript-signal
library, which is a PureScript port of part of Elm's signal implementation. You might be familiar with this library already from Bodil's excellent Strange Loop 2014 talk. I'm going to try to make a very minimal Christmas-themed game using the library.
I started by creating a new project with pulp init
, and using Bower to install purescript-signal
. I found an image of Rudolf the red-nosed reindeer online and copied it into the images/
directory, and created this simple HTML page:
<html>
<head>
<style>
#rudolf {
position: absolute;
background-image: url('../images/rudolf.png');
background-size: contain;
width: 268px;
height: 355px;
margin-left: -134px;
margin-top: -177px;
}
</style>
</head>
<body>
<div id="rudolf"></div>
<script src="index.js"></script>
</body>
</html>
The goal will be to animate the rudolf
element by using signals to change its position based on things like the mouse position.
My first attempt was very simple - I had the element simply follow the cursor. I started with a foreign import which would set the left
and top
properties on the div
, as follows:
import Control.Monad.Eff
import DOM
import Signal
import Signal.DOM
foreign import render
"function render(pos) {\
\ return function() {\
\ var rudolf = document.getElementById('rudolf');\
\ rudolf.style.left = pos.x + 'px';\
\ rudolf.style.top = pos.y + 'px';\
\ };\
\}" :: forall eff. CoordinatePair -> Eff (dom :: DOM | eff) Unit
With that, my main
action was very simple:
main = do
mouse <- mousePos
runSignal (render <~ rudolf mouse)
where
rudolf :: Signal CoordinatePair -> Signal CoordinatePair
rudolf mouse = mouse
The main abstraction in the purescript-signal
library is the Signal
type constructor, which represents time-varying values. Here, I only use the mousePos
action to get a Signal
which represents the current mouse position. I then use the <~
operator, which is a synonym for the Functor
's fmap
function, to map the render
function over the generated coordinates. Finally, I use runSignal
to listen for changes to the signal's value, and run the wrapped effects.
I can compile this file with pulp browserify
and save the resulting JavaScript in html/index.js
, and see in the browser that Rudolf does indeed follow the mouse cursor. However, to make things more interesting, let's try to make Rudolf avoid the cursor, and the aim of the game can be to try to catch him.
Let's start by making our own Signal
which will reflect the size of the window. To keep things simple, I'll just check the size of the window each second, but you might like to try using the FFI to implement this using an event handler instead:
type Dimensions = { w :: Number, h :: Number }
foreign import dimensions
"function dimensions() {\
\ return { w: document.body.offsetWidth\
\ , h: document.body.offsetHeight\
\ };\
\};" :: forall eff. Eff (dom :: DOM | eff) Dimensions
dimensionsS :: forall eff. Eff (dom :: DOM, timer :: Timer | eff) (Signal Dimensions)
dimensionsS = unwrap $ every second ~> \_ -> dimensions
I've used the every
signal to generate a tick every second, and the unwrap
function, which turns a signal of effectful computations like dimensions
into a regular signal.
Here is my new main
method:
main = do
mouse <- mousePos
dims <- dimensionsS
runSignal (render <~ rudolf mouse dims)
where
rudolf :: Signal CoordinatePair -> Signal Dimensions -> Signal CoordinatePair
rudolf = zip position
position :: CoordinatePair -> Dimensions -> CoordinatePair
position o d = { x: d.w - o.x, y: d.h - o.y }
I've used the zip
combinator to combine my two signals into a single signal by applying the two-argument function position
. Now Rudolf avoids the mouse cursor, unless the mouse is in the center of the screen.
My final version of the game has Rudolf move at a velocity which depends on his distance to the cursor, and in a direction away from the cursor. In addition, if the user manages to catch Rudolf, then he will jump to another position.
The code for the final version can be found here. It uses the sampleOn
combinator to sample the relevant events every 20 milliseconds, and then uses the foldp
combinator to modify the game state (Rudolf's position) based on these events.
The final game can be played here.
Programming with signals is a lot of fun, especially when designing interactive web pages or games like these. I suggest you try it out!