scary, but has plenty of usability improvements Features simple and useful Foreign Function Interface Meaning hand-written JS can be integrated first-class Statically typed with a powerful type system And much more!
mechanical correctness of your program Use the type system to encode more information Powerful abstractions make for simpler problem solving Mature JS backend Use with browsers, Node, or anything that runs JS!
used to avoid writing parentheses until the end of the line Because balancing multiple parens visually is hard What about all those other weird symbols/operators? Like normal math operators, they’re just easier to read and delimit operands E.g. add 1 1 vs 1 + 1, map increment [1,2,3] vs increment <$> [1,2,3] In JS, I use array.map(project), not _.map(array, project)
architecture Action → State → State Write effectful code in Purescript instead of passing off to magic runtime or partially-typed ports Also comes with type-safe CSS and routing Can use or be used in existing React code
be drawn on using a cursor 2-dimensional Some collection of points A cursor Cursor moves UDLR Collection of points built up of cursor’s previous positions
{ cursor :: Coords , points :: Set Coords , width :: Int , height :: Int , increment :: Int } What are coords? data Coords = Coords Int Int derive instance eqCoords :: Eq Coords derive instance ordCoords :: Ord Coords Derivable instances Eq and Ord Instances used so that Coords can be used in a Set
class Eq a where eq :: a -> a -> Boolean Example instance: instance eqCoords :: Eq Coords where eq (Coords ax ay) (Coords bx by) = ax == bx && ay == by Constraints are set on the set items: data Set a instance eqSet :: Eq a => Eq (Set a) where eq (Set m1) (Set m2) = m1 == m2 insert :: forall a. Ord a => a -> Set a -> Set a
Mainly, the movement of the cursor data Action = MoveCursor Direction | ClearScreen | NoOp The cursor can move in one of four directions data Direction = Up | Down | Left | Right The update function uses an action and the old state to produce a new state update :: Action -> State -> State update (MoveCursor direction) state = moveCursor direction state update ClearScreen state = state { points = mempty } update NoOp state = state
moveCursor direction state@{cursor: (Coords x y)} = if isInvalidPoint state cursor' then state else state {cursor = cursor', points = points'} where points' = insert state.cursor state.points cursor' = case direction of Up -> Coords x (y - 1) Down -> Coords x (y + 1) Left -> Coords (x - 1) y Right -> Coords (x + 1) y Attempt to move the cursor by calculating the new cursor position (remember SVG grids originate at the top left). If the new cursor position is invalid, return the state as-is. Otherwise, replace the old cursor and update the points by inserting the old cursor into our set of points.
-> Html Action pointView increment color (Coords x y) = rect [ key $ color <> show x <> "x" <> show y <> "y" , width $ show increment , height $ show increment , HA.fill $ color , HA.x $ show (x * increment) , HA.y $ show (y * increment) ] [] First, we need a point view (aka “pixel”). Must have the increment size, and then be positioned accordingly.
view state = let pointView' = pointView state.increment points = pointView' "black" <$> fromFoldable state.points cursor = pointView' "grey" state.cursor in [...] fromFoldable: Foldable → Array, <$>: infix map For our view, we first need to prepare the point views of our cursor and our points. We partially apply arguments here as needed. To create an array of elements that we’ll need for our rendering, we use fromFoldable to create an array out of our foldable set, and then map over our point view function.
[ button [ onClick (const ClearScreen) ] [ text "Clear" ] ] , div [] [ svg [ style do border solid (px $ toNumber 1) black , width $ show state.width , height $ show state.height ] $ snoc points cursor ] ] Then we construct our app view. The Pux Html DSL works that we have a node, an array of node properties, and then an array of child nodes. We use our point views from before and construct our points accordingly here.
:: DOM | e) (Signal Action) getKeyDirections = do ups <- map (actions $ MoveCursor Up) <$> keyPressed 38 downs <- map (actions $ MoveCursor Down) <$> keyPressed 40 lefts <- map (actions $ MoveCursor Left) <$> keyPressed 37 rights <- map (actions $ MoveCursor Right) <$> keyPressed 39 pure $ ups <> downs <> lefts <> rights where actions x = if _ then x else NoOp Next, we have this seemingly scary block for how we can run an effectful function of DOM effects and get back a signal of actions. We’ll go into detail to see that it’s actually quite simple.
→ Eff (dom :: DOM | e) (Signal Boolean) actions :: Action → Boolean → Action getKeyDirections = do map: (a -> b) -> f a -> f b (Boolean -> Action) -> Signal Boolean -> Signal Action ups <- map (actions $ MoveCursor Up) <$> keyPressed 38 Eff _ (Signal Boolean) <$>: (a -> b) -> f a -> f b (Signal Boolean -> Signal Action) -> Eff _ (Signal Boolean) -> Eff _ (Signal Action)
f a (<>) / append :: a -> a -> a -- associative! (x <> y) <> z = x <> (y <> z) getKeyDirections :: forall e. Eff (dom :: DOM | e) (Signal Action) getKeyDirections = do ups :: Signal Action [...] pure $ ups <> downs <> lefts <> rights Signal Action -> Signal Action -> Signal Action Signal Action -> Eff _ (Signal Action) ** Note that you will almost never solve this by hand as this is the compiler’s job
our main function of Eff _ Unit to. We first run our getKeyDirections function through our effect to get the signal of actions. We then initialize the Pux application. Finally, we render the result to the DOM. main :: forall e. Eff _ Unit main = do keyDirections <- getKeyDirections app <- start { initialState , update: fromSimple update , view , inputs: [ keyDirections ] } renderToDOM "#app" app.html
problem AND get solutions easily Writing code with controlled effects is easy Complicated parts can be simplified down mathematically We didn’t need to know what “Monads” were to do this Repo available here: https://github.com/justinwoo/purescript-etch-sketch