I'm hoping someone might be able to help me out here with Signals and Effects.
I'm looking into Signals/Effects and have been studying the-elm-architecture, particularly example 8.
In this example (as I understand it), if you click on the shape(s) :
SPIN
action is fired on updateI'm trying to replicate the exact same flow (using similar code) but instead of clicking on a button and using the HTML package, I want to just send a signal using the spacebar.
In my code, the spacebar sends a Signal off to the Punch
action. Just like example 8, I want to then also call Tick
and update the debounceState
in my model.
You'll see in my comments that I can reach my Punch
action, but I don't seem to ever get to Tick
.
I have looked at this question, but because I'm using a Keyboard Signal and not the elm StartApp, I don't think it applies.
If someone could explain why I'm not able to reach Tick
in my example, I'd super appreciate it.
module Main (..) where
import Graphics.Element exposing (..)
import Time exposing (Time, second)
import Effects exposing (Effects)
import Keyboard
type alias DebounceState =
Maybe
{ prevClockTime : Time
, elapsedTime : Time
}
type alias Model =
{ punching : Bool
, count : Int
, randomString: String
, debounceState : DebounceState
}
duration = second
-- MODEL
initialModel : ( Model, Effects Action )
initialModel =
( { punching = False
, count = 0
, randomString = ""
, debounceState = Nothing
}
, Effects.none
)
model : Signal ( Model, Effects Action )
model =
Signal.foldp update initialModel (punch Keyboard.space)
-- ACTIONS
type Action
= NoOp
| Punch Bool
| Tick Time
-- UPDATE
update : Action -> ( Model, Effects Action ) -> ( Model, Effects Action )
update action ( model, fx ) =
case action of
NoOp ->
( model, Effects.none )
Punch isPunching ->
case model.debounceState of
Nothing ->
( { model |
punching = isPunching
, count = model.count + 1 }, Effects.tick Tick )
Just _ ->
( { model |
punching = isPunching
, count = model.count + 2 }, Effects.none )
-- I don't seem to reach `Tick` at all
-- You'll notice that I try to apply a value to `randomString` in the
-- conditional to indicate where I end up
Tick clockTime ->
let
newElapsedTime =
case model.debounceState of
Nothing ->
0
Just {elapsedTime, prevClockTime} ->
elapsedTime + (clockTime - prevClockTime)
in
if newElapsedTime > duration then
( {model | randomString = "foo"} , Effects.none )
else
( {model | randomString = "bar"} , Effects.tick Tick )
-- SIGNAL
punch : Signal Bool -> Signal Action
punch input =
Signal.map Punch input
-- VIEW
view : ( Model, Effects Action ) -> Element
view model =
show model
main : Signal Element
main =
Signal.map view model
Paste that straight into Try Elm.
The Tick
action is never fired because there is no port transferring Effects
into Tasks
. In the related Stack Overflow answer you posted, StartApp
is being used, so wiring up a port is easy.
Since you are not using StartApp
, there's a few things you'll have to do manually. Right now, your model
is only listening to the keyboard space signal. You'll need to wire up signal handling so your Effects go all the way back to firing your update
function.
You'll need a mailbox to coordinate the signals.
actions : Signal.Mailbox (List Action)
actions =
Signal.mailbox []
Notice that this mailbox is defined in terms of a List of Actions. That's one of the complicated parts about translating Effects to Tasks, and it is explained here.
Now you'll need a port that takes the second part of your (Model, Effects Action)
tuple and translates that to a Task
.
port effects : Signal (Task.Task Effects.Never ())
port effects =
Signal.map (Effects.toTask actions.address << snd) model
That port will now send your Tick
action to the mailbox, but we still haven't wired up your model to listen to both the keyboard space key and this new mailbox. To do that, we'll use Signal.mergeMany
.
signals : Signal Action
signals =
let
singleAction list =
case list of
[] -> NoOp
(a::_) -> a
in
Signal.mergeMany
[ punch Keyboard.space
, Signal.map singleAction actions.signal
]
The funny business going on in the singleAction
function just gets us around the fact that Effects.toTask
forces us to use a list of Actions
rather that a single Action
. Looking at the documentation, it is clear that we are safe just taking the first element off the list.
So now we have a signal that is triggered both by the keyboard space bar as well as the Tick action. The last part is to make the model
listen to that signal instead of just the keyboard.
model : Signal ( Model, Effects Action )
model =
Signal.foldp update initialModel signals