Hello elm-discuss!
I've been thinking about the **.program* pattern. Currently one has to
choose one of the many different *.program functions
<http://klaftertief.github.io/elm-search/?q=program> and implement the rest
of the functionality themselves, ie. they can't mix and match, say,
*Navigation.program* and *TimeTravel.Html.program*.
A way to solve that would be to specify a list of "middlewares" that each
do a specific task, and have a "clean" program they all augment:
main =
combineMiddleware
Navigation.middleware
TimeTravel.middleware
DropboxAuth.middleware
App.program
I have tried to implement such a pattern and want to share it and ask for
opinions. *This could, after all, be a very bad idea!*
----
Some links:
- GitHub <https://github.com/Janiczek/middleware> (I didn't want to
publish that as a package just yet, but if it helps somebody, tell me and I
can do that)
- Tour of the code:
- The *main* function
<https://github.com/Janiczek/middleware/blob/master/src/Main.elm>
- The *program* (business logic)
<https://github.com/Janiczek/middleware/blob/master/src/ExampleProgram.elm>
(a counter)
- A *middleware* "talking" to the program
<https://github.com/Janiczek/middleware/blob/master/src/Middleware/ResetByMsg.elm>
(returns
it from update)
- A *middleware* logging "all the Msgs under it"
<https://github.com/Janiczek/middleware/blob/master/src/Middleware/History.elm>
(this
+ the Reset middleware could, with a bit of effort, become a Debugger
middleware)
- A *middleware* using its own Subs
<https://github.com/Janiczek/middleware/blob/master/src/Middleware/SubsTest.elm>
(the
middlewares can use Subs, Cmds, have their own model)
----
This allows people to combine multiple behaviours instead of being limited
to just one.
*Questions I'm pondering are:*
1. Is this a good idea at all?
2. Comparison to the "fractal TEA" that we generally shun now. (Hiding
of behaviour; in fractal TEA one sees the extra functionality in the model,
here it's almost invisible; does one need to see it? This approach avoids
some boilerplate -- the only thing end user needs to supply is a Msg
constructor, similar to *.program)
3. Does this encourage some bad practices, code smell, OOP in a FP
language, componentization? Good/bad? (If bad, is the *.program pattern we
currently somehow embrace OK then? I'd say it does things /hides behaviour/
basically the same way.)
----
And, for the interested, some implementation details:
- Each middleware knows about the next model in the chain (they're
nested), but can't inspect it. (If it used concrete type instead of a type
variable, it would limit what other middlewares/programs can be next to it.)
- The Msgs are also nested: each middleware has to have one Msg for
wrapping the Msgs of the next middleware/program in the chain.
- All middlewares can send messages *to the program* (but not to each
other):
- the program exposes a record with Msg constructors it offers
alongside update, init, etc.
- each middleware declares what Msg constructors it needs (through an
extensible record) -- very similar to how Navigation.program needs a
Msg constructor for the location changes
<http://package.elm-lang.org/packages/elm-lang/navigation/2.1.0/Navigation#program>
.
- the compiler makes sure all middleware Msg needs are satisfied
- middleware's update gets the record with the constructors as an
argument, and returns a Maybe Program.Msg
- the Msg gets threaded through the Elm Runtime as any other, and the
user gets a nice clean Msg in their update.
And some API:
middleware :
{ init : (innerModel, Cmd innerMsg) -> (ownModel, Cmd ownMsg)
, update : ownMsg -> ownModel -> programMsgs -> (ownModel, Cmd ownMsg,
Maybe programMsg)
, subscriptions : ownModel -> Sub ownMsg
, view : ownModel -> innerHtml as Html ownMsg -> Html ownMsg
, wrapMsg : innerMsg -> ownMsg
, unwrapMsg : ownMsg -> Maybe innerMsg
}
where
ownModel = { ownFields | innerModel : innerModel }
program :
{ init : (model, Cmd msg)
, update : msg -> model -> (model, Cmd msg)
, subscriptions : model -> Sub msg
, view : model -> Html msg
, programMsgs : programMsgs
}
where
programMsgs = (eg.) { locationChanged : Location -> Msg }
--
You received this message because you are subscribed to the Google Groups "Elm
Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
For more options, visit https://groups.google.com/d/optout.