-
Notifications
You must be signed in to change notification settings - Fork 5
Dependency Injection via 'Lens Configs'
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