The “I’m stupid” elm language nugget #2:
I found myself destructuring and re-composing a tuple of (model,effect) quite a bit in a container that has other models inside it. Passing through certain events (in my case touch events) started to become a royal pain because the model from the contained update function needed to be wrapped in my model type, the effect from the foreign update needed to be mapped on, and the whole lot then batched with any previous effects.
Like this:
Carousel ca ->
let (newModel, effects) = C.update ca m.carousel in
({ m | carousel = newModel }, Effects.map Carousel effects)
I am certainly not the first person to come up with a similar idiom (see Environment here for part of the inspiration. I wanted to turn it into an object whose state could be evolved a part at a time and then converted back to the expected type, getting all category-theory and topology in the process.
What I came up with was this:
module Signals.EffModel whereimport Effects exposing (Effects(..), none, batch, tick)
import Tasktype alias EffModel model action = {
model : model
, eff : Effects action
}wrap : model -> EffModel model action
wrap model = {
model = model
, eff = Effects.none
}wrap2 : (model, Effects action) -> EffModel model action
wrap2 (model, effects) = {
model = model
, eff = effects
}unwrap : EffModel model action -> (model, Effects action)
unwrap effmodel =
(effmodel.model, effmodel.eff)model : EffModel model action -> model
model effmodel =
effmodel.modelupdate : model -> EffModel model action -> EffModel model action
update model effmodel =
{ model = model, eff = effmodel.eff }map : (modelA -> modelB) -> EffModel modelA action -> EffModel modelB action
map f effmodel =
{ model = f effmodel.model, eff = effmodel.eff }eff : Effects action -> EffModel model action -> EffModel model action
eff eff effmodel =
{ model = effmodel.model, eff = batch [effmodel.eff, eff] }effMap : (actionA -> actionB) -> EffModel model actionA -> EffModel model actionB
effMap f effmodel =
{ effmodel | eff = Effects.map f effmodel.eff }effMessage : action -> EffModel model action -> EffModel model action
effMessage action effmodel =
effmodel |> eff (Effects.task (Task.succeed action))
Letting me turn that code into the more vertical but cleaner (IMO):
Carousel ca ->
m.carousel
|> C.update ca
|> E.wrap2
|> E.effMap Carousel
|> E.map (\c -> updateCarousel m c)
|> E.unwrap
And also helping to make other longer chains better and more composable. If you want to see what it does without rest animation, you can just comment a line of code :-)
update : Action -> Model -> (Model, Effects Action)
update action model =
model
|> wrap
|> handleInputMessage action
|> handleRestAnimation action
|> touchBind action
|> unwrap
If like me, you’re writing some code at 3am and haven’t thought to apply a like pattern to similar code, then maybe this post will be of some use.