Structure for large Elm apps

1267 views
Skip to first unread message

clouddie

unread,
Oct 16, 2016, 5:00:59 PM10/16/16
to Elm Discuss
Hi, are there official guidelines for structuring large scale apps ? For instance, what is the community take on things like https://github.com/rogeriochaves/structured-elm-todomvc/tree/modular ?
This is quite neat although I cannot quite m'y head round the fact the Update fonctions in TaskList deal with messages for Task ans TaskList, and they do not respect thé standard (Model, Cmd Msg) signatures ?

Peter Damoc

unread,
Oct 17, 2016, 2:50:23 AM10/17/16
to Elm Discuss
update is a function that takes a message and a model and produces the updated version of the model. In some cases, update also can produce some requests for more input (http requests, requests for random numbers, ports requests) this is why the top level update has (Model, Cmd Msg) as return. 
Internal updates however, might not need these requests and can be simpler. 
What you see in the repository you linked is a pattern of nesting "components" that is currently passively discouraged.  (there use to be a set of examples around this but they are gone). 

The official recommendation around scaling is to focus on breaking the functionality into functions:
https://guide.elm-lang.org/reuse/




On Mon, Oct 17, 2016 at 12:00 AM, clouddie <contact....@gmail.com> wrote:
Hi, are there official guidelines for structuring large scale apps ? For instance, what is the community take on things like https://github.com/rogeriochaves/structured-elm-todomvc/tree/modular ?
This is quite neat although I cannot quite m'y head round the fact the Update fonctions in TaskList deal with messages for Task ans TaskList, and they do not respect thé standard (Model, Cmd Msg) signatures ?

--
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+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.



--
There is NO FATE, we are the creators.
blog: http://damoc.ro/

clouddie

unread,
Oct 17, 2016, 1:21:21 PM10/17/16
to Elm Discuss
Hi, thanks for your answer, I also got the impression that component approach was being discouraged as the notorious "dual counters" and "list of counters" relying on the component approach have been withdrawn from documentation as it seems...

To be fair I have already read the docs, but my model, messages and update functions are becoming waaaay too unwieldy (> 1,000 lines), and Evan's footnote is not that helpful (I understand he must be the busiest man on earth) :

Note: I plan to fill this section in with more examples of growing your Model and update functions. It is roughly the same ideas though. If you find yourself repeating yourself, maybe break things out. If a function gets too big, make a helper function. If you see related things, maybe move them to a module. But at the end of the day, it is not a huge deal if you let things get big. Elm is great at finding problems and making refactors easy, so it is not actually a huge deal if you have a bunch of entries in your Model because it does not seem better to break them out in some way. I will be writing more about this!

 

Le lundi 17 octobre 2016 08:50:23 UTC+2, Peter Damoc a écrit :
update is a function that takes a message and a model and produces the updated version of the model. In some cases, update also can produce some requests for more input (http requests, requests for random numbers, ports requests) this is why the top level update has (Model, Cmd Msg) as return. 
Internal updates however, might not need these requests and can be simpler. 
What you see in the repository you linked is a pattern of nesting "components" that is currently passively discouraged.  (there use to be a set of examples around this but they are gone). 

The official recommendation around scaling is to focus on breaking the functionality into functions:
https://guide.elm-lang.org/reuse/



On Mon, Oct 17, 2016 at 12:00 AM, clouddie <contact....@gmail.com> wrote:
Hi, are there official guidelines for structuring large scale apps ? For instance, what is the community take on things like https://github.com/rogeriochaves/structured-elm-todomvc/tree/modular ?
This is quite neat although I cannot quite m'y head round the fact the Update fonctions in TaskList deal with messages for Task ans TaskList, and they do not respect thé standard (Model, Cmd Msg) signatures ?

--
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...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.

Marcus Roberts

unread,
Oct 17, 2016, 2:41:23 PM10/17/16
to elm-d...@googlegroups.com
Is this article (and the one it refers to) taking a good or bad approach to program structure?


I copied the structure as a way of seeing how a program might perhaps be broken down, but it feels like a very component based approach, given each level has its own update function.  I learnt a fair bit about mapping between message types, so it was useful.

I've broken my app down into pages using this structure, and so each page has a view, model, etc.  Then the top level model has a model record for each page.  This seems to work as it means the page doesn't need to know how it's being used at the level above.  But the general conversations I see suggests this is not the right way to go and to break things down more into modules of related code.    So I guess back to the original question, is this structure a good way to work? 



Eric G

unread,
Oct 17, 2016, 3:38:14 PM10/17/16
to Elm Discuss
I'm no expert, and would like clarification on this as well, but "pages of an app" seems like the main place where using nested modules makes sense -- unless your pages can be modeled more easily as variants of each other, as sometimes happens. 

My experience* has been that just about everywhere else it's easier to have flat modules, using helper update functions, view functions with generic msg types, config records, etc., and extracting common bits as needed into separate modules. Sometimes I have nested msg types, and do the Html.App.map / Cmd.map  internally, within one module. Richard Feldman had a list of "steps to take before breaking code out into separate modules" awhile back that I thought was helpful.

As for that article you linked to, it seems overkill, esp. for what is mostly a static site. All of the static pages can just be functions in Main, just split out the ones that have actual actions like the Contact page, and then only if it seems unwieldy to handle those in Main too.  

Basically my advice is start with Main, or Main and one page module, and see how far you get, rather than planning ahead what your file structure is going to look like.

(*My experience is limited to business-y company-internal applications, someone else will have to say what works for games and other kinds of applications.)



On Monday, October 17, 2016 at 2:41:23 PM UTC-4, Marcus Roberts wrote:
Is this article (and the one it refers to) taking a good or bad approach to program structure?


I copied the structure as a way of seeing how a program might perhaps be broken down, but it feels like a very component based approach, given each level has its own update function.  I learnt a fair bit about mapping between message types, so it was useful.

I've broken my app down into pages using this structure, and so each page has a view, model, etc.  Then the top level model has a model record for each page.  This seems to work as it means the page doesn't need to know how it's being used at the level above.  But the general conversations I see suggests this is not the right way to go and to break things down more into modules of related code.    So I guess back to the original question, is this structure a good way to work? 



On Mon, Oct 17, 2016 at 7:50 AM, Peter Damoc <pda...@gmail.com> wrote:
update is a function that takes a message and a model and produces the updated version of the model. In some cases, update also can produce some requests for more input (http requests, requests for random numbers, ports requests) this is why the top level update has (Model, Cmd Msg) as return. 
Internal updates however, might not need these requests and can be simpler. 
What you see in the repository you linked is a pattern of nesting "components" that is currently passively discouraged.  (there use to be a set of examples around this but they are gone). 

The official recommendation around scaling is to focus on breaking the functionality into functions:
https://guide.elm-lang.org/reuse/



On Mon, Oct 17, 2016 at 12:00 AM, clouddie <contact....@gmail.com> wrote:
Hi, are there official guidelines for structuring large scale apps ? For instance, what is the community take on things like https://github.com/rogeriochaves/structured-elm-todomvc/tree/modular ?
This is quite neat although I cannot quite m'y head round the fact the Update fonctions in TaskList deal with messages for Task ans TaskList, and they do not respect thé standard (Model, Cmd Msg) signatures ?

--
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...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.
--
There is NO FATE, we are the creators.
blog: http://damoc.ro/

--
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...@googlegroups.com.

clouddie

unread,
Oct 20, 2016, 5:15:58 PM10/20/16
to Elm Discuss
Actually, just to testify on nested components : got severely burnt by this pattern as I spent my Sunday refactoring my app before discovering that one nested component had to update the state of an other one, and realized I just did not want to go through the whole sibling or child whatever pattern (https://www.brianthicks.com/post/2016/06/27/candy-and-allowances-part-ii-child-child-communication-in-elm/) which seems way too complicated.

So I just refactored out my main file into Model, Msg, Update, View, and used helper functions, and now my mind is clear again. "Better flat than nested" might be a good idea after all...

Wouter In t Velt

unread,
Oct 21, 2016, 1:22:32 PM10/21/16
to Elm Discuss
+1 for the "better flat than nested " approach.
I think it was the second elm town podcast where Brian admitted that he actually regretted his post.

From personal experience (admittedly limited) I definitely agree to the "flat is better than nested". Having one update function with helpers, and not having any components with local state has made debugging at least 10x faster for me.

Adding features like an undo function was way easier with a flat structure.

Ed Ilyin

unread,
Oct 21, 2016, 3:01:29 PM10/21/16
to Elm Discuss
Can you, please, provide an example?
When I do a component (like one page of my SPA), I dedicate a module for it. So, how can look a flat structure? Just Shared.elm, Main.elm, Update.elm and View.elm?

Wouter In t Velt

unread,
Oct 22, 2016, 12:12:05 PM10/22/16
to Elm Discuss
Op vrijdag 21 oktober 2016 21:01:29 UTC+2 schreef Ed Ilyin:
Can you, please, provide an example?

In a SPA I am developing, I use a structure like:
Helpers.elm
Helpers -- folder with more helpers
 
Nav.elm -- helpers for navigation
 
Exam.elm -- helpers for updating the exam
 
Summaries.elm -- helpers (for view) to create a summary string from details
Main.elm
Model.elm
Msg.elm
Route.elm -- with routes the user can navigate to
Types.elm -- details of the stuff in the model
Update.elm
View.elm
Views -- folder with elm file per page, imported by view.elm
 
Today.elm
 
Exams.elm
 
EditExam.elm
 
PickExamDate.elm

Whenever I create a new page (let's say a page to see the entire calendar), I usually modify stuff in the following order:
  • add a route in Route.elm
  • add a type in Types.elm (if needed)
  • modify my Msg (if needed)
  • modify update.elm, 
  • add another view file in the view folder, 
  • and import this file in my main view.elm file.
Flat helps me to only change what is needed: Often a new page I want is just another view of the same data. So I do not need a new model, and I do not need to change the model, I only need a new view function. And a very simple change to the update function, to enable navigating to the new page.

I was very much inspired by the Time Tracker SPA (see here), which may also be a good place to look at a flat structure.

Previously, I had one exam component, with its own model, update and view. Each exam had a list of dates.
When I wanted to create a page that showed a calendar, and for each date, show the exams for which the user plans to study, I realised my component structure broke down.
-- before (nested)

type
alias Model =
 
{ exams : List Exam }

type
alias Exam =
 
{ subject : String
 
, studyDates : List Date
 
}

-- now (flat)

type
alias Model =
 
{ exams : List Exam
 
, studyDates : List StudyDate
 
}

type
alias Exam =
 
{ id : Int
 
, subject : String
 
}

type
alias StudyDate =
 
{ studyDate : Date
 
, exam : Int -- id only
 
}

Hope this helps!

Dave Thomas

unread,
Oct 22, 2016, 12:26:02 PM10/22/16
to Elm Discuss
Hi I wrote that article as a general overview of how I structured a single page app after reviewing different approaches, and relying on some of my knowledge of what worked for me in F# too.  Having the code structured in functions residing in modules made it really easy to update a specific part of the app, and also to refactor parts that needed it.  The hardest part was actually getting webpack to work with bootstrap4!


On Monday, October 17, 2016 at 7:41:23 PM UTC+1, Marcus Roberts wrote:
Is this article (and the one it refers to) taking a good or bad approach to program structure?


I copied the structure as a way of seeing how a program might perhaps be broken down, but it feels like a very component based approach, given each level has its own update function.  I learnt a fair bit about mapping between message types, so it was useful.

I've broken my app down into pages using this structure, and so each page has a view, model, etc.  Then the top level model has a model record for each page.  This seems to work as it means the page doesn't need to know how it's being used at the level above.  But the general conversations I see suggests this is not the right way to go and to break things down more into modules of related code.    So I guess back to the original question, is this structure a good way to work? 



On Mon, Oct 17, 2016 at 7:50 AM, Peter Damoc <pda...@gmail.com> wrote:
update is a function that takes a message and a model and produces the updated version of the model. In some cases, update also can produce some requests for more input (http requests, requests for random numbers, ports requests) this is why the top level update has (Model, Cmd Msg) as return. 
Internal updates however, might not need these requests and can be simpler. 
What you see in the repository you linked is a pattern of nesting "components" that is currently passively discouraged.  (there use to be a set of examples around this but they are gone). 

The official recommendation around scaling is to focus on breaking the functionality into functions:
https://guide.elm-lang.org/reuse/



On Mon, Oct 17, 2016 at 12:00 AM, clouddie <contact....@gmail.com> wrote:
Hi, are there official guidelines for structuring large scale apps ? For instance, what is the community take on things like https://github.com/rogeriochaves/structured-elm-todomvc/tree/modular ?
This is quite neat although I cannot quite m'y head round the fact the Update fonctions in TaskList deal with messages for Task ans TaskList, and they do not respect thé standard (Model, Cmd Msg) signatures ?

--
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...@googlegroups.com.

For more options, visit https://groups.google.com/d/optout.
--
There is NO FATE, we are the creators.
blog: http://damoc.ro/

--
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...@googlegroups.com.

Francois

unread,
Oct 23, 2016, 2:49:01 PM10/23/16
to Elm Discuss
Hi,

I'm really new to Elm. I'm not good enough to propose a guideline about Elm app structure but as a novice a guideline can help lots of people.

I worked / works on a large angular application and John Papa Styleguide has been really helpful at the beginning.
So writing a community guideline can be a great resource. (there is another thread inter component that seems related)

For example, on the server side, structuring domain core by features is good thing (for me). Help define some boundaries and help maintability. Grouping all Java classes by technical aspect at the project root : all services in one package, all models in one package can be tedious.

On the front, if I correctly understood this thread :
- a unique Msg / Model inside Main.elm can be a good starting point and easy to refactor later. 
- separating the view per feature (only view functions) : advice taken from Dave
- break Model / Msg when too big (definition of "too big" is perhaps not easy, or someone can give some advices to detect "a too big Msg"). At this point, break Msg / Model / Update by feature. It is not all or nothing, just extract at first one feature and put them inside a file. Then the Main module wraps the feature (using map inside the Update).

Can we imagine starting a community guideline somewhere ? adding examples later.
Really easy for me to say that.


Mark Hamburg

unread,
Oct 23, 2016, 3:29:23 PM10/23/16
to elm-d...@googlegroups.com
We're just getting into having an app where this is likely to matter, but if you have multiple views onto the same data, then the pattern that would seem to apply is to have a single data model and multiple view models. The data model handles the shared part. The view models handle things specific to the particular view — e.g., is a spin down open, what's the scroll position, etc. The view functions would take the data model and their particular view model as parameters and would generate appropriately tagged messages back. This avoids significant nesting while still providing some structure to keep pieces that can be separated separate.

There are then a couple ways to structure the multiple view models. One is to have a view selector but keep all of the view models alive all the time. The benefit of this is that when you switch back to a view, it will remember where you were. The downside is that one is carrying around — and potentially updating if the view model depends on the data model — an increasing number of view models if you have an increasing number of views. The alternative is to put the view models into a union type and have one model entry that serves as both view selector and view model storage. This keeps things lighter weight but means that you have nowhere to maintain context when switching back and forth.

Above all of this, the other key subdivision we are making in our SPA and that I would recommend having seen where other programmers often first head, is having a top layer that handles overall app state — e.g., logged in v not logged in — as a type union. This can also often be where the routing logic plugs in. People who haven't spent a lot of time thinking in terms of type unions tend to load their model up with a lot of fields that only have meaning in certain states. This then leads to more invariants about the state that can't be enforced by the compiler. Breaking things up into different type cases for different states allows one to make more bad states simply impossible. For example, our logging in state has a RemoteData.WebData User reflecting whether we've gotten valid user data back from the server but our logged in state simply has a User. The top level event dispatcher works with the following type signature on the log in state update:

update : Login.Msg -> Login.Model -> ( Login.Model, Cmd Login.Msg, Maybe User )

After each call to update, it can check to see whether we now have a valid user and if so switch to the logged in state, initializing it with the supplied user.

This isn't as flat as some people go, but it doesn't spread things out into lots of components and the top layer(s) are pretty simple.

Mark


--
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+unsubscribe@googlegroups.com.

Oliver Searle-Barnes

unread,
Oct 24, 2016, 7:29:05 AM10/24/16
to Elm Discuss
@Mark this is a pattern I've been exploring recently. An area that I haven't found a solution to is keeping the view model in sync with the shared models. For instance, let's say I have a view with a list of checkboxes next to each user. The state for the checkboxes would be kept in the view model, but then I need to manage adding/removing checkboxes as the list of users changes. Currently I handle this by having a default state for the checkbox which is used until a user actually clicks on a checkbox at which point the state is added to the view model. This works but is semantically awkward and doesn't take care of removing the checkbox state if a user is removed from the shared list. https://github.com/elm-lang/html/issues/19 provides a potential avenue for cleanup, but even then the lifecycle seems a little awkward. 

I wonder if you have a different approach for this problem?
To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss...@googlegroups.com.

Erik Lott

unread,
Oct 24, 2016, 3:04:44 PM10/24/16
to Elm Discuss
Above all of this, the other key subdivision we are making in our SPA and that I would recommend having seen where other programmers often first head, is having a top layer that handles overall app state — e.g., logged in v not logged in — as a type union. This can also often be where the routing logic plugs in. 

This is exactly what we do throughout our entire SPA app. We use union types to manage overall state and protect our invariants, making it impossible for the app to be in an invalid state at any time. The union types may get a little deep at times, but the payoff is more than worth it. 
To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss...@googlegroups.com.

Mark Hamburg

unread,
Oct 24, 2016, 3:37:02 PM10/24/16
to elm-d...@googlegroups.com
So, your "data model" is a list of users but your "view model" is a boolean state for each user? I'm working on thinking about how that would come up but I guess the checkboxes could be for selection and the selection could be view specific.

Anyway, here is my quick effort at breaking this out. I'm going to assume that we can represent users with strings. If they need more data, then there is presumably a way to convert a user into a user-id for internal use and a string for display. So, our data model is:

type alias User = String

type alias DataModel = List User

-- In model:
dataModel : DataModel

The view model consists of the information for each checkbox which assuming it's a checkbox really is just a boolean state. We maintain this as a dictionary indexed by user:

type alias ViewModel = Dict User Bool

-- In model:
viewModel : ViewModel

The view function can now map over the data model (list of users) and extract the extra view model information by consulting the dictionary.

Changes to the checkbox state, just write values back into the view model dictionary.

Changes to the user list need to update the data model but then they also need to update the view model to remove any users no longer on the list and add entries for any users newly on the list. (Alternatively, one could use the initial state for users as a default and use Maybe.withDefault to handle new users. Then one just needs to remove the view model entries for users no longer in the data model.)

The key thing in structuring this is that you can build all of the logic that works on the data model independent of the checkbox states needed by the view model. For example, if these checkboxes represent selected users, you could have the changes to the data model be driven by a set of users. Events would flow into the view model collecting up this selected set and then turn into a message or operation for the data model. That change to the data model would then result in an update to the view model.

Potential type signatures could be:

view : DataModel -> ViewModel -> Html ViewMsg

updateView : ViewMsg -> ViewModel -> (ViewModel, Cmd ViewMsg, Maybe DataMsg)

updateData: DataMsg -> DataModel -> (DataModel, Cmd DataMsg)

updateViewForData: DataModel -> ViewModel -> (ViewModel, Cmd ViewMsg)

Then we get something like the following at the model level:

type alias Model = { dataModel : DataModel, viewModel : ViewModel }

type Msg = ToData DataMsg | ToView ViewMsg

update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
case msg of
ToData dataMsg ->
let
(newData, dataCmd ) = updateData dataMsg model.dataModel
(newView, viewCmd ) = updateViewForData newData model.viewModel
in
( { model | dataModel = newData, viewModel = newView }
, Cmd.batch [ Cmd.map ToData dataCmd, Cmd.map ToView viewCmd ]
)
ToView viewMsg ->
updateView viewMsg model.viewModel
|> OutMessage.mapComponent (\newView -> { model | viewModel = newView })
|> OutMessage.mapCmd ToView
|> OutMessage.evaluateMaybe (\dataMsg m -> update (ToData dataMsg) m)


This does have the potential for pinging back and forth between data model updates and view updates but that's a hazard in any two component system. The key thing is that the model can be built and in operation can evolve independent of the view or views. The potentially more ugly problem comes if it is difficult to factor the response to a view message into logic that can just update the current view model and then generic logic that can respond to a data model change.

Mark

To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss+unsubscribe@googlegroups.com.

Mark Hamburg

unread,
Oct 24, 2016, 3:48:50 PM10/24/16
to elm-d...@googlegroups.com
I don't know what I was thinking when I wrote that. The code I just provided can't actually cycle. We can go from updateView to updateData to updateViewForData, but then we're done.

Mark
Reply all
Reply to author
Forward
0 new messages