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.

Reply via email to