Is it correct that your issue could be addressed by not bringing back
arbitrary record extension in expressions via the pre-0.16 syntax { ... |
... = ... }, but *only* bringing back constructor functions for extensible
records? That is, if from the table at https://github.com/elm-lang/
elm-platform/blob/master/upgrade-docs/0.16.md#updating-syntax only language
change in the row “record constructors that add fields” were reverted, but
not the language changes in the other rows?Also relevant in this context, then: https://github.com/elm-lang/ elm-compiler/issues/1308. 2016-08-23 10:23 GMT+02:00 Charles-Edouard Cady < [email protected]>: > Greetings! > > I originally posted this on https://github.com/elm-lang/el > m-compiler/issues/985 which, as Richard Feldman pointed out, is probably > not the best place to put it. I'll follow Richard's suggestion & be a bit > less abstract. > > I'm building an application to help ship captains create routes. My > question is about the organization of the model of this application. > > The application currently has one page containing three widgets: > > 1. A map showing the routes > 2. A table showing the performances (eg. fuel consumption, > duration...) of each route > 3. A profile widget showing the speed profile on the selected route > > > It is structured in three layers of decreasing size (and increasing > versatility): > > > 1. The top layer, providing synchronization between the widgets and > the layout of the page > 2. The widget layer (all widgets are independent) > 3. The elementary widget layer (containing sliders, date pickers...) > ie. reusable components > > > The reusable components have their own model, which is independent from > the rest of the application: their model is initialized by their containing > widget. > The widgets, however, must share some information (eg. the list of routes) > but not all (for example, the profile widget couldn't care less which route > is hovered in the table widget, but the map widget does) because it > complicates refactoring (changing the model one widget impacts the others) > and makes it difficult to reuse widgets (eg. use the profile widget in a > context where I do not need/have the fuel consumption). > > Now in Elm's architecture tutorial, there are two extreme cases: > > - The same model is shared by all widgets > - Each widget has its own independent model > > > I find myself somewhere in between: part of the model is shared by all > widgets and is application-independent (eg. the waypoints of each route), > part of it is shared only by two widgets and mostly concerns the layout > (eg. route hovering) and part of it is not shared (and should not be) (eg. > the sliders' state). > > At the top-level (Page): > > > type alias Model a = > { a > | routes : List Route > , hovered : Maybe Int > , selected : Maybe Int > , map : Map.InternalModel > , table : Table.InternalModel > , profile : Profile.InternalModel > } > > The Profile widget might use a part of this model: > > type alias Model a = > { a > | route : List Route > , selected : Maybe Int > , profile : InternalModel > } > > while the Table widget uses another: > > type alias Model a = > { a > | route : List Route > , selected : Maybe Int > , hovered : Maybe Int > , table : InternalModel > } > > > Just like @rgrempel in https://github.com/elm-lang/elm-compiler/issues/985, > I want each module also provides its init function to initialize its part > of Page's model. With extensible records, I could simply do (eg. in Table > ): > > init : a -> Model a > init foo = > {foo | table = initInternal} > > and for Page I would have the very clean and composible chain: > > init : Model > init = > {routes = []} > |> Table.init > |> Profile.init > > So the extensible records were an easy way for me to build composable > applications. With the removal of this feature, the init function can no > longer be type-parametrized. This is really important so let me emphasize a > bit: *no extensible records means init must know the full record it > operates on*. > > So I decided to use @rgrempel's strategy, but in his case where all parts > of his model were independent. The only workaround I found is to do the > following for Page (top-level): > > type alias Model a = > { a > | shared : Shared.Shared > , map : Map.InternalModel > , table : Table.InternalModel > , profile : Profile.InternalModel > } > > init : Model > init = > { shared = Shared.init > , map = Map.init > , table = Table.init > , profile = Profile.init > } > > When you loose extensible records you have to put all shared parts in a > Shared record, which essentially means you know in advance how your > widget will be used. For instance, the data shared by the Profile and > the Table widgets is not the same as that used by the Profile and the Map > widgets and if I add another widget, chances are I'll have to modify the > Shared record. This makes me sad because it breaks separation of concern. > With extensible records, I could simply add the fields I need to Page's > model & in the specific widgets & they would simply be ignored by the other > widgets. Whenever I modify what is shared, I'm modifying the Shared > record that all widgets depend on &o if I decide to use eg. the Profile > widget in another application, it will quickly become unmanageable. > > As previously stated, as soon as you define an init function, the record > it returns (or operates on) can no longer be type-parametrized (i.e. > extensible), which means that if init returns the shared part, Shared is > no longer extensible. If Shared is not extensible & you want to include > Page in a bigger application, Page's model will have to be completely > independent from the other widgets' model at the same level, ie. it will > not be able to share part of its model with the other widgets. > > With extensible records, you could apply the same pattern to any number of > levels, but as soon as you put shared data in a shared field you're > basically stating once and for all what is shared by all possible widgets: > that information is only needed at the application level (at the Page > level), but it dribbles down to all widgets (which shouldn't care whether > they're being used in isolation or not). > > Sorry to ramble on about this, but it's been a thorn in my side for a long > time now. > > I would really appreciate any thoughts on this. > > -- > 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. > -- 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.
