Charlie Harvey

Seven More Languages: Elm Day Two

As it turns out quite a lot has changed in Elm between the release in the book and the current 0.14 version. Which made things more challenging than perhaps I may have liked. Still, it has been a good way of getting to know the documentation. Virtue of necessity and all that.

You can find a port of the examples from day 2 in my associated gitorious repository if you’re using 0.14 version and up, I’ve tested on the online editor as of just now, when it seems to be running 0.14.1.

So after taking longer than usual to get through the chapter, I am still enjoying Elm. It seems a very natural way to pipe together events. Whether it scales to more complex problems, well I guess we find out on day two. But so far everything has been rather fun. We have already made what looks very like a pong bat that will move across the screen and whatnot. And in the hard exercises we make an animated car. Like this.

Exercises

Easy exercises

I did all the sets of exercises in a single file. Here is the import block for the easy ones.

import Graphics.Element (..) import Graphics.Collage (..) import Color (..) import Signal (Signal, channel, map, map2, foldp, sampleOn, send, subscribe) import Signal as Signal import Keyboard import Mouse import Window import String import Graphics.Input as Input import Graphics.Input.Field as F import Text (..)

Write a signal to display the current mouse position, including whether a mouse button is pressed.

My initial crack at this showed the mouse position or whether the button was pressed. But really I wanted both at the same time, so I ended up with this. main = map2 asTx Mouse.position Mouse.isDown asTx p d = let a = toString (fst p) b = toString (snd p) c = if d then " Mouse is down." else " Mouse is up." in asText ("Mouse is at: (" ++ a ++ ", " ++ b ++ ")" ++ c)

Write a signal to display the y position of the moouse when the button is pressed.

Following on from one of the examples in the text, this seems adequate. main = yOnClick yOnClick = Signal.map asText (Signal.sampleOn Mouse.clicks Mouse.y)

Medium exercises

Here is the import block for the medium exercises. state of the car in the

import Graphics.Element (..) import Graphics.Collage (..) import Color (..) import Signal import Text (asText) import Time (every, second) import Mouse import Window

Use lift and signals to draw your own picture at the current mouse position. Change the picture when the mouse button is pressed.

lift is one of the bits of Elm that has changed. You map now as it turns out. There is quite a lot going on here. But it is mostly work to stop the picture going off the edge of the screen. We map show over the window dimensions, whether the mouse is pressed and the current mouse position. Show calls drawCircle to do soe maths to keep the image within the frame and to swap the big circle for the small one. main = Signal.map3 show Window.dimensions Mouse.isDown Mouse.position up = filled red (circle 24) down = filled red (circle 44) drawCircle : Int -> Int -> Bool -> Int -> Int -> Form drawCircle w h d x y = case d of True -> down |> moveX (normalizeX x w) |> moveY (normalizeY y h) False -> up |> moveX (normalizeX x w) |> moveY (normalizeY y h) normalizeX : Int -> Int -> Float normalizeX x w = (minmax x w) - toFloat w / 2 normalizeY : Int -> Int -> Float normalizeY y h = 0 - ((minmax y h) - toFloat h / 2) minmax : Int -> Int -> Float minmax a d = if | a < 44 -> 44.0 | a > d-44 -> toFloat (d - 44) | otherwise -> toFloat a show : (Int,Int) -> Bool -> (Int,Int) -> Element show (w,h) d (x,y) = collage w h [ drawCircle w h d x y ]

Because that was rather a lot of code and because it makes the post a little more interesting to look at, here is a video of that.

Write a program that counts up, from zero, with one count per second.

This was very elegant and straightforward to implement. main = Signal.map asText count count : Signal.Signal Int count = Signal.foldp (\click count -> count + 1) 0 (every second)

Hard exercises

I have just provided a single bit of code for the hard exercises as they are so closely related. Once I had a solution for the first one the second was trivial.

Use foldp to make the car move from left to right, and then right to left, across the bottom of the screen.

Make the car move faster when the mouse is farther to the right, and slower when the mouse is farther to the left.

import Graphics.Element (..) import Graphics.Collage (..) import Color (..) import Signal (Signal, map, foldp, (~), (<~)) import Signal as Signal import Mouse import Window import String import Time (Time, fps) import Graphics.Input as Input import Graphics.Input.Field as F import Text (..) moveBy = 1 -- number of pixels to ove by in each frame frameRate = 48 -- frame rate type alias World = { w:Int, mx:Int, delta:Time } -- collection of signals type alias Car = { x:Int, ltr:Bool, vx:Int} -- a model of a car main : Signal Element main = map showcar (foldp drive defaultcar world) world : Signal World world = World <~ Window.width ~ Mouse.x ~ (fps frameRate) defaultcar : Car defaultcar = {x=150, ltr=True, vx=moveBy} drive : World -> Car -> Car drive world car = let ltr' = if (car.x > ((world.w * 2) - 150)) || (car.x < 150) then not (car.ltr) else car.ltr vx' = moveBy + round (toFloat (world.mx) / 100.00) in if ltr' then { car | ltr<-ltr', x<-car.x+vx', vx<-vx' } else { car | ltr<-ltr', x<-car.x-vx', vx<-vx' } showcar : Car -> Element showcar car = collage (car.x) 500 [ carBottom , carTop |> moveY 30 , tire |> move (-40, -28) , tire |> move (40, -28) ] carBottom = filled black (rect 160 50) carTop = filled black (rect 100 60) tire = filled red (circle 24)

That is a reasonable amount of code, but tolerably brief given that we are moving some graphics around a screen. The main insight I had here came from googling about and finding the <~ syntax for mapping, which to my eye is rather easier to read.

We construct a ‘world’ and a ‘car’ to hold the things we are interested in namely how wide the screen is, where the mouse is, hw time is changing and the deets of the car: x position, direction (True if left to right, False otherwise), and velocity. Then we fold p over the car and world making the state of the world create a list of cars. Which we can then map over and show.

It took a couple of hours and some swearing to get this far, but the resulting code reads quite well and it is clear what is going on. Once I had the types right and a model of how things should work in my head, the code sort of fell out of Elm. A good experience all in all.


Comments

  • Be respectful. You may want to read the comment guidelines before posting.
  • You can use Markdown syntax to format your comments. You can only use level 5 and 6 headings.
  • You can add class="your language" to code blocks to help highlight.js highlight them correctly.

Privacy note: This form will forward your IP address, user agent and referrer to the Akismet, StopForumSpam and Botscout spam filtering services. I don’t log these details. Those services will. I do log everything you type into the form. Full privacy statement.