Elm
Signals and Suggestions
Working online
In general, I found working online at http://elm-lang.org/try to be the best way to program Elm Instant feedback, so you can try one change at a time and see if it worked No good way to save your work; I did an occasional copy-all and paste
into Sublime Text The images used in the examples are on the server, not locally, so you
can’t readily use them in your own program I installed Elm and used the elm repl to test single expressions and very
small bits of code The REPL is especially useful for entering expressions to find out their
type > Signal.map
<function: map> : (a -> b) -> Signal.Signal a -> Signal.Signal b
2
Program structure Elm programs have a structure very similar to MVC (Model-View-Controller) The structure is Model - Update - View, or sometimes Model - Update - View -
Signals In Elm
The Model is typically just data, describing the current state of the program; typically this is in the form of a record named model
The Update is a set of pure functions; typically there is a function named update with a signature similar to Input -> Model -> Model
The View is a set of functions whose purpose is to display the current state; typically there is a function named view with a signature something like Input -> Model -> Element
The Signals part includes a function named main; it collects signals together and bundles them up, sends them to update to get a new program state, and sends the result to view
3
Mouse signals
import Mouse exposing (..) position : Signal ( Int, Int ) x : Signal Int y : Signal Int isDown : Signal Bool clicks : Signal ()
4
Keyboard signals
import Keyboard exposing (..) Mouse.position : Signal (Int, Int) arrows : Signal { x : Int, y : Int } wasd : Signal { x : Int, y : Int } enter, space, ctrl, shift, alt, meta are all Signal Bool
type alias KeyCode = Int isDown : KeyCode -> Signal Bool keysDown : Signal (Set KeyCode) presses : Signal KeyCode -- most recent key
pressed5
Time signals
import Time exposing (..) fps : number -> Signal Time
fps (frames per second) will produce a signal the given number of times every second
fpsWhen : number -> Signal Bool -> Signal Time Same as the fps function, but you can turn it on and off
every : Time -> Signal Time Takes a time interval t and produces a signal updated every t
delay : Time -> Signal a -> Signal a Delays a time signal
There a few additional functions6
Window signals
import Window exposing (..) dimensions : Signal ( Int, Int ) width : Signal Int height : Signal Int
When you embed Elm in a <div> it gives the dimensions of the container, not the whole window
7
Monads
Signal is a monad type Signals, by definition, vary over time Clearly, this is inappropriate for a pure functional language Monads “isolate” impure operations
The main function in the Signals part of an Elm program:• Collects signals together and bundles them up• Extracts the data from the signals and sends them to the
pure function update to get a new program state• Sends the new program state to view
8
Bind
Remember “bind” (>>=) in Haskell? bind takes a value out of a monad, applies a function to it, and puts
the result back into a monad:(>>=) :: Monad m => m a -> (a -> m b) -> m b
Elm has something similar but not identical Signal.map : (a -> b) -> Signal a -> Signal b
This allows us to take a Signal a, use it to call a pure function (a -> b), and get the result as a Signal b
The arguments are reversed from those of >>= but in the same order as those of List.map List.map : (a -> b) -> List a -> List b
<~ is an alias for Signal.map9
Signal.mapn Signal.map applies a pure function to the value inside a Signal, producing
another Signal Signal.map : (a -> b) -> Signal a -> Signal b
However, you often have pure functions that take more than a single argument map2 : (a -> b -> result) -> Signal a -> Signal b -> Signal result The result type is, of course, up to the supplied function, but is often a tuple
map3 : (a -> b -> c -> result) -> Signal a -> Signal b -> Signal c -> Signal result
map4 : (a -> b -> c -> d -> result) -> Signal a -> Signal b -> Signal c -> Signal d -> Signal result
map5 -- the obvious type is omitted for reasons of space
10
Warning! lift
Elm is a new and rapidly changing language If you find examples using the functions lift, lift2, lift3, etc., these functions no longer exist
They have been replaced by map, map2, map3, etc. The old functions have the same signature as the new,
and as far as I know, is just a name change
11
Sampling
import Signal exposing (..) sampleOn : Signal a -> Signal b -> Signal b
Sample from the second input every time an event occurs on the first input. For example, (sampleOn clicks (every second)) will give the approximate time of the latest click
The value of the first signal (Signal a) is discarded
I believe that the purpose of sampleOn is to restrict the number of events that must be handled by the update and view parts of the program Mouse.position can probably produce hundreds of events
a second
12
foldp
foldp folds signals “over time”
From the documentation:foldp : (a -> state -> state) -> state -> Signal a -> Signal stateCreate a past-dependent signal. Each update from the incoming signals will be used to step the state forward. The outgoing signal represents the current state.
13
Mario example I
main : Signal Elementmain = Signal.map2 view Window.dimensions (Signal.foldp update mario input)
view : (Int, Int) -> Model -> Element
update : (Float, Keys) -> Model -> Model
14
Mario example II
main : Signal Elementmain = Signal.map2 view Window.dimensions (Signal.foldp update mario input)
input : Signal (Float, Keys)input = let delta = Signal.map (\t -> t/20) (fps 30) in Signal.sampleOn delta (Signal.map2 (,) delta Keyboard.arrows)
15
Modified Mario I
I modified the Mario example to also accept mouse position signals
main : Signal Elementmain = Signal.map3 view Window.dimension Mouse.position (Signal.foldp update mario input)
view : (Int, Int) -> (Int, Int) -> Model -> Element
update : (Float, Keys) -> Model -> Model16
Modified Mario II
main : Signal Elementmain = Signal.map3 view Window.dimension Mouse.position (Signal.foldp update mario input)
input : Signal (Float, Keys, Location)input = let delta = Signal.map (\t -> t/20) (fps 30) in Signal.sampleOn delta (Signal.map3 (,,) delta Keyboard.arrows Mouse.position)
17
Modified Mario III type alias Model = { x : Float , y : Float , vx : Float , vy : Float , dir : Direction , mouse : (Int, Int) }
update : (Float, Keys, Location) -> Model -> Modelupdate (dt, keys, position) mario = mario |> gravity dt |> jump keys |> walk keys |> physics dt |> attract position -- defined as type alias Location = (Int, Int)
18
Questions main : Signal Elementmain = Signal.map3 view Window.dimension Mouse.position (Signal.foldp update mario input)
input : Signal (Float, Keys, Location)input = let delta = Signal.map (\t -> t/20) (fps 30) in Signal.sampleOn delta (Signal.map3 (,,) delta Keyboard.arrows Mouse.position)
Why isn’t Window.dimension handled in input? Why do I need to mention Mouse.position twice?
19
Collage example A Collage is used to display things on the screen view : (Int, Int) -> (Int, Int) -> Model -> Elementview (w',h') mario = let -- many definitions omitted: w, h, marioImage, etc. in collage w' h' [ rect w h |> filled (rgb 174 238 238) , rect w 50 |> filled (rgb 74 167 43) |> move (0, 24 - h/2) , marioImage |> toForm |> move position ]
20
21
The End