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.

Reply via email to