Skip to content

Dependency Injection via 'Lens Configs'

ThomasOrtner edited this page Aug 24, 2017 · 5 revisions

Dependency Injection

As applications grow larger and nested through composition of apps we run into problems when multiple subapps rely on, for instance a global config in the model. Global variables or passing in the config as reference to the different sub apps do not fit well into the functional world relying on immutable data. Copying the config of the parent app into the models of the subapps leads to inconsistencies and scales horribly. Further, it destroys the composability of subapps since their models are then bound to a certain parent app config type.

Instead of passing actual data into a subapp we just give it a function telling it how to retrieve a value from a generic config. By using lenses, which are basically a getter and a setter function (see Lensesn BLA), the subapp can even change values of the parrent app's model in its update function.

module LittleConfigTest
    type InnerConfig<'a> =
        {
            arrowSize : Lens<'a,double>
        }

    let update<'a> (bigConfig : 'a) (innerConfig : InnerConfig<'a>) (a : Action) : 'a =
            match a with
                | SetArrowSize d -> innerConfig.arrowSize.Set(bigConfig, d)

In contrast to our typical app, the update and consequently the view function become generic. So the calling parent app determines the actual type of bigconfig and howto retrieve the arrowSize from its model m.

module BigConfigTest
    let innerConfig = { arrowSize = BigConfig.Lens.arrowSize }

    let update (a : Action) (m : BigConfig) =
        match a with
            | InnerAction(inner)-> LittleConfigTest.update m innerConfig inner

Full compiling code example:

module LittleConfigTest =
   
    type Action = SetArrowSize of double

    type InnerConfig<'a> =  
        {
            arrowSize : Lens<'a,double>
        }

    type MInnerConfig<'ma> =
        {
            getArrow : 'ma -> IMod<double>
        }
    
    let update<'a> (bigConfig : 'a) (innerConfig : InnerConfig<'a>) (a : Action) : 'a =
        match a with
            | SetArrowSize d -> innerConfig.arrowSize.Set(bigConfig, d)

    let view<'ma> (mbigConfig : 'ma) (minnerConfig : MInnerConfig<'ma>) : DomNode<Action> =
        let arrowSize = minnerConfig.getArrow mbigConfig
        let button =
            button [onClick (fun _ -> SetArrowSize (Mod.force arrowSize + 1.0))] [text "increase arrowsize"]
        button

module BigConfigTest =

   open ConfigLensTest
   open LittleConfigTest
   
   type Action = InnerAction of LittleConfigTest.Action


   let innerConfig = { arrowSize = BigConfig.Lens.arrowSize }

   let update (a : Action) (m : BigConfig) =
        match a with
            | InnerAction(inner)-> LittleConfigTest.update m innerConfig inner

   let view (m : MBigConfig) : DomNode<Action> =

        let c : MInnerConfig<MBigConfig> =
            {
                getArrow = fun (x:MBigConfig) -> x.arrowSize
            }
        
        LittleConfigTest.view m c |> UI.map InnerAction