Transcript Elm 2
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
pressed
5
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 functions
6
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:
1. Collects signals together and bundles them up
2. Extracts the data from the signals and sends them to the
pure function update to get a new program state
3. 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.map
9
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 state
Create 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 Element
main =
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 Element
main =
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 Element
main =
Signal.map3 view Window.dimension
Mouse.position (Signal.foldp update
mario input)
view : (Int, Int) -> (Int, Int) -> Model ->
Element
update : (Float, Keys) -> Model -> Model
16
Modified Mario II
main : Signal Element
main =
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 -> Model
update (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 Element
main =
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 -> Element
view (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
The End
21