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 elm-discuss+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.