Design of Large Elm apps

6,401 views
Skip to first unread message

Ryan Erb

unread,
Aug 4, 2016, 6:15:53 PM8/4/16
to Elm Discuss
Got a few questions regarding the best way, or what options there are for scaling up to a large site.
If i have a site with lots of features what is the best way to orgainze everything?

One Elm page to rule them all? With this approach you only have to deal with one update happening, you can separate code into different modules, but the update function would be HUGE. Guess this would be the same as a single page application?

One Elm page per HTML page? This way every page deals with only what it is used for. You can write common things in reusable modules. One problem might be switching to other elm pages. The only really good way right now that i have is using a port to "window.locaion.href" to another elm page. Then pulling all the data it requires from the server using more ports.

One Elm page per feature? This way you can write separate apps for each part of your page. One for the header menu bar, one for a sidebar, and one for each main feature. This way you can mix and match and piece together for a full page. BUT you might have to do a lot of port talking between each section.

Some other way??????

Been searching around a cant seem to find any blogs or topics on this subject. Our stack is Elm/Phoenix/Elixir/Postgres.

If anyone has created a large application using Elm would happy to have your input on what worked/ didn't work for you.

Thanks,

Ryan Erb

unread,
Aug 4, 2016, 6:45:00 PM8/4/16
to Elm Discuss
Well after a bit more searching found this 


Seems like one large Elm app divided into modules seems to be the way to go?

Nick Hollon

unread,
Aug 4, 2016, 7:10:58 PM8/4/16
to elm-d...@googlegroups.com
That is the approach I would take. 

Don't forget that the elm website and package manager are written in elm, and they are open source! Recommended reading ;-)
--
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.

Peter Damoc

unread,
Aug 5, 2016, 12:56:30 AM8/5/16
to Elm Discuss
That gist is old. For current Elm Architecture, please refer to the guide. 
http://guide.elm-lang.org/

I don't know about "the best way" but I can share how my App is structured.

I have two types of pages in my app: Interactive and Informational. 
The informational pages are typically generated from markdown and are rendered all by the MainView.
The interactive pages typically live inside their own modules. 
Some of the interactive pages generate actions for the entire App. 
Those pages' update typically evaluate to a 3 element tuple: (Model, Cmd Msg, Maybe OutMsg) 

The recommendation I have is to keep your app one "component" as long as you can. 
Rethink the code structure in terms of smart types that can generate what you need with simple functions. 
Move to "components" as late as possible. 

Yes, you might end up with quite large update and view functions but, most of the time, flat is better than nested

 


--
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/

art yerkes

unread,
Aug 5, 2016, 5:55:58 AM8/5/16
to Elm Discuss
In my app, my elm code was structured like this (note that this is an SPA with no server integration ...)

/Master.elm <-- SPA host.  Hosts a header, drawer, login dialog and view stack
/Account/Login.elm <-- Login dialog
/Account/WorkoutEditor.elm <-- Views that can be accessed only by logging in
/Account/Share.elm ...
/Header/Header.elm <-- Header renderer
/Splash/Welcome.elm <-- Default view
/ShowCard/Search.elm <-- Views that can be accessed without logging in
/ShowCard/SearchResult.elm ...
/ShowCard/WorkoutInfo.elm ...
/ShowCard/WorkoutView.elm ...

It might seem like you'd need a big update function but I composed a bunch of small ones with http://package.elm-lang.org/packages/prozacchiwawa/effmodel/2.0.0/ like this:

Happily, if you do this, you can write tests that ensure that each sub-update implements the right state transitions.

effModelUpdate : Action -> EffModel Model Action -> EffModel Model Action

effModelUpdate action effmodel =

    effmodel

        |> handleMDL action

        |> handleSlugUpdate action

        |> handlePageAnimation action

        |> handlePurposeSet action

... -- Snipped

        |> handleFacebook action

        |> handleSlugArgs action

        |> serializeTimer action

        |> handleLoginAction action

        |> handleLoginProcessAction action


Each of these functions conceptually handles a self contained set of messages related to one feature.  Connecting the subcomponents via ports would be an option, but this worked ok.


I wrote about the way these are composed here: https://medium.com/@prozacchiwawa/the-i-m-stupid-elm-language-nugget-6-48229076c88e#.2fgf44mx3


For each subcomponent I had a small test main that set up some test data and ran the component on its own.  That's one reason I think that splitting up views into modules is a good idea.


Владимир Кобеляцкий

unread,
Aug 5, 2016, 10:02:29 AM8/5/16
to Elm Discuss

Jaroslaw Zabiello

unread,
Aug 5, 2016, 10:48:38 AM8/5/16
to Elm Discuss
I am not sure how Elm Architecture scales for dozens or hundreds of modules because it's tough to pass a global context object down to all submodules and components. I love the way Cerebral works. Every component is connected to it's part in the immutable main model. UI components are isolated from the state controlled by modules. A component register it's signals. Every signal execute a chain of actions. They are always sequential, sync, and asynch are executed in the order. Actions can be grouped in higher older factories as well as the signals. It's very clean and scalable. Every module has access to global context object. Every component has access to any part of one, global model object. Cerebral is like Redux-Saga but much simpler.

OvermindDL1

unread,
Aug 5, 2016, 11:08:58 AM8/5/16
to Elm Discuss
I'm unsure if this is Elm'y, but it is built on prior experiences with other languages.

I currently have a javascript app that I am slowly converting to Elm as parts gets replaced, so I am using a lot of little embedded Elm apps (in a single file so as to not duplicate the standard large library).  Each one has a main section that calls to and builds up other areas.  I have a singular Helper that can dispatch around that I implement in each of my main apps like (which I understand is similar to debois/elm-parts though I think is more generic?)
```elm
type alias Model =
    { helpers : HelpersModel Msg
    | others...

init : ProgramFlags -> Result String Location -> ( Model, Cmd Msg )
init programFlags locResult =
    let
        model' : Model
        model' =
            { helpers = Helpers.helpers_init Helpers
            , etc...


type Msg
    = Helpers (HelpersMsg Msg)
    | etc...


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
      case msg of
          Helpers helpersMsg ->
                let
                    ( helpersModel, helpersCmd ) =
                        Helpers.helpers_update Helpers helpersMsg model.helpers
                in
                    ( { model | helpers = helpersModel }
                    , helpersCmd
                    )


subscriptions : Model -> Sub Msg
subscriptions model =
    Sub.batch
        [ helpers_subscriptions Helpers model.helpers
        , etc...
```

I should probably switch to debois/elm-parts as it is an actually released library and so wrap my helpers into it for my project, but as it stands I can make new components (many counters if I want!), wrappers for a ton of port functionality (How about focus an element after the next rendered frame?  Sure!  `cmd_onNextFrame Helpers <| focusElem Helpers input_selector`), etc...

Ryan Erb

unread,
Aug 5, 2016, 11:13:09 AM8/5/16
to Elm Discuss
Thanks all for the responses, got a lot of good information here to start playing with. 
Does seem the consensus is that there is one "main" elm module that will bubble down information to other sections and modules.

Thanks!!!

Aislan de Sousa Maia

unread,
Aug 8, 2016, 5:55:49 PM8/8/16
to Elm Discuss
Since I'm comfortable with React way to componentization, I can't understand why " flat is better than nested. " while getting big update and view functions. These huge files won't hurt the maintainability (and reusability) of the project ? Someone can expand in this area ?

Thanks in advance.

Richard Feldman

unread,
Aug 9, 2016, 12:42:57 AM8/9/16
to Elm Discuss
We have over 36,000 lines of production Elm code that we've been running over a year. Before that we had a ton of React code, although Elm has since overtaken it in terms of LoC.
 
Since I'm comfortable with React way to componentization, I can't understand why " flat is better than nested. " while getting big update and view functions.
These huge files won't hurt the maintainability (and reusability) of the project ? Someone can expand in this area ?

If the files feel too big, split them into separate files and import them. That's not a big deal.

What is a big deal, and a super common beginner mistake, is to split out separate state ownership when you don't need to yet.

Our React code base is full of components that each have their own local state, because that's what React encourages.

Our Elm code base has very, very few components with their own local state. In most cases each "page" has its own state and state is not subdivided any further than that.

As far as maintainability goes, it's not remotely close. It's not just that Elm code is more maintainable, it's that it is on a different planet of maintainability. The gap between Elm and React is bigger than the gap between React and MooTools.

tl;dr I wouldn't worry about it. :)

Robert Walter

unread,
Aug 9, 2016, 5:39:42 AM8/9/16
to Elm Discuss
Hi Richard,

interesting information there. For me, I'm at a much smaller scale, but the scalability of Elm certainly is the deciding factor for whether or not I want to use it in production. I'm fooling around with a small but non-trivial app to get a better feeling and understanding of how Elm behaves "over time" and how easy refactorings are. I take it that, a lot of the road bumps I hit are due to my lack of fully embracing The Elm Architecture, but I would really like to get a better sense of what you mean with "state ownership". Are there any "archetypes" or "patterns" that give a bit more guidance? I can see that are common cases in different apps that appear over and over again, where we could give some direction.
Not sure if my use case is actually that common, but just to give an example what I have in mind: 
I have an app with a "collector" and "items" (to keep it abstract for the moment). I want to have a page in my app, where I show the user an overview over all typed collections (think of a dashboard). For each item type, there are specific pages that show the user details of an item. Of course, everything is interactive. The question is, should the items become components? If yes, why? If no, why? 
Maybe there is no "yes of no" answer for this specific case, but I hope I was able to illustrate my idea.   

In general, I'd prefer to work with components early on, because I feel that, the longer I wait, the bigger and more painful the refactoring will become once I have to go from monolith to components. 

Peter Damoc

unread,
Aug 9, 2016, 5:57:22 AM8/9/16
to Elm Discuss
Hi Robert, 

To better understand the whole "state ownership" situation, take a look at the implementation of elm-sortable-table
https://github.com/evancz/elm-sortable-table
and if you have the time/patience, watch this api design session 
https://www.youtube.com/watch?v=KSuCYUqY058

What I can share from my own experience is this:
Within a complex app, there are a series of business objects. Specific locations in your app deal with these business objects and do something to their data. 

If you keep these business objects in one place at the top of your app and you pass their information down to the components through the view, the way it is done in elm-sortable-table, you end up with better code.

If you pass the business objects at init down into the components and then you have to deal with the whole extraction of the information and synchronization of the information you end up with more trouble than you need to have. 






--
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.

Robert Walter

unread,
Aug 9, 2016, 6:54:11 AM8/9/16
to Elm Discuss
thank you Peter, I will try this approach and see how it works for my app.
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.

Rupert Smith

unread,
Aug 9, 2016, 9:21:13 AM8/9/16
to Elm Discuss
On Thursday, August 4, 2016 at 11:15:53 PM UTC+1, Ryan Erb wrote:
Got a few questions regarding the best way, or what options there are for scaling up to a large site.
If i have a site with lots of features what is the best way to orgainze everything?

I have been trying out the approach described here:

It has been useful to me, but I have too little experience to judge it yet. 

Wouter In t Velt

unread,
Aug 9, 2016, 1:25:19 PM8/9/16
to Elm Discuss
Thank you Peter for sharing the sortable table and especially the video link!
Video has a somewhat long winded exploration in accessibility in the early part, but I found it most helpful.

The thoughts on config and even updateConfig were really helpful. Also the idea to have an update take in 1 message type and output another message type is food for thought. Got me to rethink my own setup of child-to-parent communication.

I can definitely recommend both links!

Jaroslaw Zabiello

unread,
Aug 9, 2016, 6:15:18 PM8/9/16
to Elm Discuss
How you solve the problem with communication between two decoupled widgets? By decoupled, I mean two components which are not on the same parent-children path.

OvermindDL1

unread,
Aug 9, 2016, 6:32:39 PM8/9/16
to Elm Discuss
'How' do they need to communicate?  I've always seen such things as via some callback Message lifter, which means you can just pass the parent message type to it and the parent can pass it on as needed?

Jaroslaw Zabiello

unread,
Aug 9, 2016, 7:30:00 PM8/9/16
to Elm Discuss
But passing the callback down through the chain of nested components is ugly, fragile and may not even work if two components are not in the same parent-children chain.

Maybe I don't understand the Elm Architecture, but as far as I know every Elm module has it's own model, view and the update function. It's all simple and nice if I have just a single file. But in the complex app I have to split my code among many files. On the other side I want to have the single main main model tree combined from all those smaller ones. How the update function of the nested module can change it's model now? It's aggregated to the higher place? And because Elm is immutable the model can't keep the reference to the object.

Raoul Duke

unread,
Aug 9, 2016, 7:39:16 PM8/9/16
to elm-discuss

Generate updates as commands, in structs.

Add them to a list during the current frame. Current state remains immutable.

At the end of the frame apply all commands to generate next world.

Next step starts with new world, old one is discarded.

Lather rinse repeat burmashave

Richard Feldman

unread,
Aug 9, 2016, 7:54:24 PM8/9/16
to Elm Discuss
But passing the callback down through the chain of nested components is ugly, fragile and may not even work if two components are not in the same parent-children chain.

Not quite sure if that's what this is saying, but a good rule of thumb is that Msg should never have a function in it, and neither should Model.

Maybe I don't understand the Elm Architecture, but as far as I know every Elm module has it's own model, view and the update function.

For our (enormous) Elm code base at work, most pages (as in an individual URL an end user can visit) have one model, one view, and one update function. There is no "parent-child communication" on these pages.

We had Elm in production for months before we encountered the first time it was a good idea to have a "parent-child" relationship, and it was over 6 months (and well over 10,000 lines of Elm code) before we found the second case where it was a good idea.

Here is what I recommend:
  1. For a given page, start with one model, one view, and one update.
  2. If one of those (model, view, or update) gets too big and starts feeling unwieldy, subdivide only that thing. For example, if view feels too big, split it into some helper functions but don't touch model or update. If update feels too big, same thing - split out helper functions but don't touch the view or model. If your model feels too big, reorganize only the model. Split it from one record into some nested records, but don't rearchitect update or view.
  3. Any time you find yourself thinking "I bet this will be nicer if I split it into its own model/update/view" your next thought should be "whoops! That's a classic beginner mistake. My code will actually be worse if I do that. Glad I didn't do that!" (It's an easy trap to fall into - I fell into it myself, and fell hard, as a beginner.)
Basically, whenever I meet someone who is new to Elm and asking about parent-child communication, what I really want to say is "a parent-child relationship in an application is very rarely the right tool for the job, and is almost certainly not what you want as a beginner. How can I help you avoid falling into the same trap I fell into when I was just starting out?" :)

Erik Lott

unread,
Aug 9, 2016, 10:20:23 PM8/9/16
to Elm Discuss
Richard, just to clarify, when you say "pages", are you referring to server side rendered pages that each contain an Elm app, or a large Elm single page app that handles routing internally?

Richard Feldman

unread,
Aug 9, 2016, 10:28:33 PM8/9/16
to Elm Discuss

Either. :)


--
You received this message because you are subscribed to a topic in the Google Groups "Elm Discuss" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/elm-discuss/_cfOu88oCx4/unsubscribe.
To unsubscribe from this group and all its topics, send an email to elm-discuss...@googlegroups.com.

Erik Lott

unread,
Aug 9, 2016, 11:08:56 PM8/9/16
to Elm Discuss
Haha yeah I get your point, but when you talk about the large redink code base (no doubt it's huge), I'm curious of the granularity of your production Elm apps. Is your code base generally organized as one/a few large single page applications (routing handled client side - data via api) or is your code organized into many smaller applications, each served on a single server rendered html page (routing handled server side - maybe initial data is provided directly through Elm.fullscreen rather than hitting an api). What say you good sir?

It would be helpful to know, and would add some context to your comments. 

Richard Feldman

unread,
Aug 9, 2016, 11:26:07 PM8/9/16
to Elm Discuss
Haha yeah I get your point, but when you talk about the large redink code base (no doubt it's huge), I'm curious of the granularity of your production Elm apps. Is your code base generally organized as one/a few large single page applications (routing handled client side - data via api) or is your code organized into many smaller applications, each served on a single server rendered html page (routing handled server side - maybe initial data is provided directly through Elm.fullscreen rather than hitting an api).

The latter, although it's important to note that it's the same granularity either way. We started building our Elm code base on top of a large legacy non-SPA Rails app, so the way each individual page works is that Rails renders some JSON in a <script> which Elm then consumes and uses to build the initial Model.

This is exactly the same organization as what a SPA does for each logical "page" - when the location changes and you want to render a new "page," you pass the "page" some data (not JSON, since there's no need to serialize from Elm to Elm like there is from Rails to Elm), that data becomes the page's initial Model, and you proceed from there.

Sergey Skrynnikov

unread,
Aug 10, 2016, 2:29:36 AM8/10/16
to Elm Discuss
Oh no, just as I make a 3-deep TEA stack in my own app and start to feel good about finally sort of figuring it out, you have to go and post this :D
I don't think I've fallen into a trap.. yet.. but it wouldn't be a beginner trap if a beginner could casually avoid it, now would it?
Evan's recent video on designing a better API was very enlightening, and I think a lot of people would appreciate it if something like that was made but taking a beginner's application and explaining where the architecture could be improved, common traps and errors. Stuff an experienced person takes for granted, but not obvious for new people coming from different frameworks and conditioned to think in a specific way, etc. I'd be glad to provide my own pile of mistakes for dissection if one of our resident experts was willing to try something like this :) 

среда, 10 августа 2016 г., 2:54:24 UTC+3 пользователь Richard Feldman написал:

Rupert Smith

unread,
Aug 10, 2016, 6:54:28 AM8/10/16
to Elm Discuss
On Wednesday, August 10, 2016 at 12:54:24 AM UTC+1, Richard Feldman wrote:
We had Elm in production for months before we encountered the first time it was a good idea to have a "parent-child" relationship, and it was over 6 months (and well over 10,000 lines of Elm code) before we found the second case where it was a good idea.

Interesting read for a beginner who has just figured out how to do parent-child relationships...

What were the cases where it was a good idea? You found some genuinely re-usable component in your code base? 

OvermindDL1

unread,
Aug 10, 2016, 10:25:55 AM8/10/16
to Elm Discuss
I only use TEA with things that are both re-usable and hold internal state.  Debois elm-parts library I've not used yet but it looks like a good way to clean up boilerplate with those as well.

And yeah, I was not talking about passing functions in message/model, but rather passing functions 'into' the view, like how `onClick` works, all it does is just decorate and send out a message (a message defined by the parent).

Richard Feldman

unread,
Aug 10, 2016, 9:38:45 PM8/10/16
to Elm Discuss
What were the cases where it was a good idea? You found some genuinely re-usable component in your code base?

The first example that comes to mind is when we had a "school search" modal dialog on a dashboard that only displayed occasionally: once on signup, and then again if you wanted to edit the school associated with your account.

It had a ton of self-contained logic - 20 fields in the Model, talking back and forth to an Algolia API, etc - that involved a lot of code which had absolutely nothing to do with the rest of the page. No matter what the user did on the modal, it didn't affect any other part of the UI. So there was almost nothing to communicate to the parent (pretty much just "hey, the user clicked the Close button") and giving it its own separate Model, its own separate View, and its own separate Update moved a lot of code out of all three places for the parent.

In a lot of ways, needing a lot of parent-child communication is probably a red flag that these are too tightly coupled for a parent-child relationship to be a good idea. :)

Mark Hamburg

unread,
Aug 10, 2016, 11:50:51 PM8/10/16
to elm-d...@googlegroups.com
How big do the case statements in your update functions get? Or are your pages just not that complex?

I tend to like to keep modules in any language to a size that can be reasonably read and comprehended. That doesn't always happen. I've written some behemoths. But I tend to feel guilty when doing so.

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

Richard Feldman

unread,
Aug 11, 2016, 1:33:48 AM8/11/16
to Elm Discuss
How big do the case statements in your update functions get? Or are your pages just not that complex?

I tend to like to keep modules in any language to a size that can be reasonably read and comprehended. That doesn't always happen. I've written some behemoths. But I tend to feel guilty when doing so.

They definitely get big, but one surprising thing I've learned is that a gigantic update function basically reads like a bunch of small, decoupled functions.

For example, here are a few cases from one of our update functions at work:

        ToggleDescriptions ->
            ( { model | showDescriptions = not model.showDescriptions }, Cmd.none )

        SetGradeFilterTooltipVisibility visibility ->
            ( { model | gradeFilterTooltipVisible = visibility }, Cmd.none )

        OpenQuestionTooltip questionId ->
            ( { model | tooltipShowing = Just questionId }, Cmd.none )

        CloseTooltips ->
            ( { model | tooltipShowing = Nothing, gradeFilterTooltipVisible = False }, Cmd.none )

One way to look at this is that it's 12 lines of code inside an update function.

Another way to look at it is that it's basically four one-liner functions, in terms of how decoupled they are and how easy it is to tell what they're doing.

That's what it feels like maintaining these. If I want to know what CloseTooltips does, I go look up its implementation. As it happens, its implementation lives inside a long update function, but it wouldn't have been any easier or harder to look up if the implementation happened to be a one-liner function instead. Obviously pattern matches aren't as composable as functions, but that doesn't make it any harder to follow what they're doing.

In practice a long update function feels like a lot of short, decoupled functions: really pleasant to maintain. :)


I took these four examples from our longest update function. It's about 600 LoC, and its Msg is a union type with about 50 constructors.

One way to look at that is "that function is way too big by the heuristics I'm used to!"

Another way to look at it is "that will feel about the same as maintaining 50 small independent functions, each an average of 12 LoC in length. Sounds like it'd be really easy to tell what each of them is doing!"

And so it is. :)

Rex van der Spuy

unread,
Aug 12, 2016, 12:36:16 PM8/12/16
to Elm Discuss
Thanks, Richard, this is great advice:
  1. For a given page, start with one model, one view, and one update.
  2. If one of those (model, view, or update) gets too big and starts feeling unwieldy, subdivide only that thing. For example, if view feels too big, split it into some helper functions but don't touch model or update. If update feels too big, same thing - split out helper functions but don't touch the view or model. If your model feels too big, reorganize only the model. Split it from one record into some nested records, but don't rearchitect update or view.
  3. Any time you find yourself thinking "I bet this will be nicer if I split it into its own model/update/view" your next thought should be "whoops! That's a classic beginner mistake. My code will actually be worse if I do that. Glad I didn't do that!" (It's an easy trap to fall into - I fell into it myself, and fell hard, as a beginner.)
Basically, whenever I meet someone who is new to Elm and asking about parent-child communication, what I really want to say is "a parent-child relationship in an application is very rarely the right tool for the job, and is almost certainly not what you want as a beginner. How can I help you avoid falling into the same trap I fell into when I was just starting out?" :)

But, I have a question! :)

Are you suggesting that each self-contained Elm "app" ("page", "game" etc.) should only have one Model/View/Update and avoid using nested modules for sub-components?
For example, do you mean that something like the nested counter example from the Elm Archtecture guide is a bad idea?


If that's the case, can you suggest a better way to modularize that kind of nesting that keeps everything inside a single M/V/U?
Or, am I misunderstanding "parent-child-communication" in this context? (... that's very likely!)


Mark Hamburg

unread,
Aug 12, 2016, 2:48:11 PM8/12/16
to Elm Discuss
I get where you are coming from in saying that the update function becomes essentially fifty smaller functions. Some of the questions on modularity then stem from whether you ever expect someone to read through and consider an entire Elm module or whether you expect that people will make changes to pieces of code within a module and simply ignore the rest of it. The latter is probably common no matter what. To me, it's a question of whether the former should be viable or whether it's basically considered irrelevant as a practice.

Mark

Richard Feldman

unread,
Aug 13, 2016, 3:38:00 AM8/13/16
to Elm Discuss
Are you suggesting that each self-contained Elm "app" ("page", "game" etc.) should only have one Model/View/Update and avoid using nested modules for sub-components?
 
Yeah - 

do you mean that something like the nested counter example from the Elm Archtecture guide is a bad idea?

It's not necessarily a bad idea, just not the first thing you should reach for. Or the second thing you should reach for. Or the third thing you should reach for.

The first three things you should reach for are, in no particular order:
  1. Splitting view into smaller functions (without reorganizing Model or update)
  2. Splitting Model into smaller values (without reorganizing view or update)
  3. Splitting update into smaller functions (without reorganizing Model or view)
Do one of these 3 things, one at a time, over and over, until either you're thinking "I need to split out some of this to reuse it elsewhere" (in which case you enter the realm of API design, and there is no one-size-fits-all answer to what you should do next), or you find yourself thinking "even after these small refactors, there is a big chunk of code that still feels unwieldy, and it's self-contained enough that I could separate it out into essentially its own little application, and have it communicate with the rest of the application, and that communication overhead sounds like it would be worth it."

As the guide says:

what happens when our code starts getting big? It would be crazy to just grow your Model and update functions endlessly. This is where Elm's module system comes in.

The nested counter is a demonstration of how to do this technique once your code base grows crazy enough to make it seem worthwhile, but it's super common to build something straightforward enough that it never needs this kind of nesting at all!

I get where you are coming from in saying that the update function becomes essentially fifty smaller functions. Some of the questions on modularity then stem from whether you ever expect someone to read through and consider an entire Elm module or whether you expect that people will make changes to pieces of code within a module and simply ignore the rest of it. The latter is probably common no matter what. To me, it's a question of whether the former should be viable or whether it's basically considered irrelevant as a practice.

In my experience the latter hasn't been a problem, so it doesn't seem like looking for a solution would be a good use of anyone's time. ;) 

Rex van der Spuy

unread,
Aug 13, 2016, 8:02:49 AM8/13/16
to Elm Discuss
Thanks so much Richard, that's absolutely fascinating!

The way that I've been building my current generation of Elm apps has been modelled on the Counter/Pair example.
So it's not uncommon for me to have apps with nested M/V/U models 3 levels deep (For example, a "page" containing a "panel" which contains a "button" - that kind of structure.)
And, yup, it's a kind of a pain to manage the chain of updates through those nested structure and bubble up their effects.
So just having one structure to deal with sounds kind of..... easy and nice :)

Rupert Smith

unread,
Aug 17, 2016, 5:19:51 AM8/17/16
to Elm Discuss
This sounds like Parna's principles of modular design and not wildly different to applying those principles in say OO design. If there is too much communication between two classes, the 'feature envy' suggests that the abstraction is not quite right. A better design needs to identify a class with enough self-contained state and/or logic to justify its existence. That said, in classic imperative OO design I reach for the modular design and re-use toolbox pretty quickly, it is almost second nature.

It is interesting what you are saying about not reaching for a modular design so quickly with elm, but to explore the other options first. For example, I am already finding that the View will fairly quickly throw up re-usable bits and pieces - perhaps a div that I wrap various UI elements in to give them some common look and feel. This can be abstracted out by writing it as a function, and if that function is particularly useful it can be pulled up into a ViewUtils.elm for re-use. Functional programming itself offers a lot of possibilities that imperative OO does not.

So I am reading your advice as 'explore what you can do with functional programming before you explore what you can do with modularizing the elm architecture'. 

Oliver Searle-Barnes

unread,
Aug 17, 2016, 7:25:45 AM8/17/16
to Elm Discuss
Thanks, this is really practical advice.


The first three things you should reach for are, in no particular order:
  1. Splitting view into smaller functions (without reorganizing Model or update)
  1. Splitting Model into smaller values (without reorganizing view or update)
  1. Splitting update into smaller functions (without reorganizing Model or view)

I can see how this fits really well for building whole pages, where reuse isn't a concern. There are a couple of scenarios where I'm still left pondering though:

1) reusabled UI components

See elm-mdl for example, what are your thoughts on the elm-parts approach?

2) services 

e.g. I'm using websockets to communicate with the server, in my previous experience I would have implemented a Store of some sort which took care of communication with the server and allowed the view layer to just worry about asking for information and sending mutations. 

Currently I've implemented a Store as a TEA component without a view. This works but I'm left with this nagging doubt that I'm simply supplanting an ember-like approach into my new Elm app :) 

Just to add some weight to my feeling that there needs to be a layer between the view and the server I'm also intending to add management of optimistic updates --- so updates will initially be applied directly to the model optimistically, then once all updates have been confirmed with the server the optimistic state is thrown away and replaced by a version which has been updated with messages received only from the websocket (which is a mix of updates from both the local user and any other users connected to the same channel --- I'm using Phoenix).

Would you agree that a layer between the view and websocket makes sense here? If so how would you organise that? Does the approach I've taken, to implement it as a TEA component make sense or perhaps there's another approach that I haven't considered?

 

OvermindDL1

unread,
Aug 17, 2016, 10:33:25 AM8/17/16
to Elm Discuss
On Wednesday, August 17, 2016 at 5:25:45 AM UTC-6, Oliver Searle-Barnes wrote:
See elm-mdl for example, what are your thoughts on the elm-parts approach?

I quite like the elm-mdl/elm-parts approach, very extensible.  Do note that elm-mdl only uses elm-parts for parts that hold any kind of state (using 'render' as its view function call).  If it is pure view then it does not use elm-parts (and uses 'view' as its view function call) in the general case.

Mark Hamburg

unread,
Aug 17, 2016, 5:50:46 PM8/17/16
to elm-d...@googlegroups.com
On Aug 17, 2016, at 4:25 AM, Oliver Searle-Barnes <oli...@opsb.co.uk> wrote:
>
> e.g. I'm using websockets to communicate with the server, in my previous experience I would have implemented a Store of some sort which took care of communication with the server and allowed the view layer to just worry about asking for information and sending mutations.

I have similar concerns about how deep down the stack Elm can go. At some point, it seems like a good thing to separate business logic concerns from UX logic concerns. Managing complexity is one of the most important parts of doing software development. (Or is if you pay heed to Dijkstra.)

These concerns aren't about Elm as a language but have to do with the monolithic architecture that Elm encourages. Types make viable larger programs than would seem reasonable with JavaScript, but at some there needs to be a way to separate concerns.

I can see two potential answers within Elm. (There may be more but these are the ones that leap out.) One is that it could be that business logic belongs in Effects Managers. The documentation around Effects Managers, however, suggests that they should not be created all that often and that would be an odd stance for what would be a standard part of a larger program. The second is to put the business logic layer in as a wrapper around the view layer. That, however, requires a rethink of commands and subscriptions to embrace something more like the more general request concept with them really representing a request for the next layer up to do something or provide some information.

Mark

Richard Feldman

unread,
Aug 17, 2016, 7:40:35 PM8/17/16
to Elm Discuss
I can see how this fits really well for building whole pages, where reuse isn't a concern. There are a couple of scenarios where I'm still left pondering though:

1) reusabled UI components

See elm-mdl for example, what are your thoughts on the elm-parts approach?

I honestly haven't used elm-mdl or elm-parts yet (both are on my list to try out!) but as far as reusable stuff goes, I would definitely follow the example of elm-sortable-table. Evan also recorded and posted an API design session for a reusable autocomplete library, which I also recommend checking out!
 
2) services 

e.g. I'm using websockets to communicate with the server, in my previous experience I would have implemented a Store of some sort which took care of communication with the server and allowed the view layer to just worry about asking for information and sending mutations. 

Currently I've implemented a Store as a TEA component without a view. This works but I'm left with this nagging doubt that I'm simply supplanting an ember-like approach into my new Elm app :) 

Yeah, that sounds like unnecessary coupling to me too!

I would start by writing a module whose purpose is to provide helper functions to assist your update function. Very often that's all you need, and if not, you can always refactor from there. :)

Would you agree that a layer between the view and websocket makes sense here?

Nah, I definitely think you want the view to know and care about the current Model, and nothing else. :) 

Richard Feldman

unread,
Aug 17, 2016, 8:18:50 PM8/17/16
to Elm Discuss
I have similar concerns about how deep down the stack Elm can go. At some point, it seems like a good thing to separate business logic concerns from UX logic concerns. 

This can already be done today, using plain old modules and organizing your data structures in a way that makes sense. Separating business logic from UX logic concerns does not require rearchitecting how your application is structured, nor should it. In this case, the way to pay heed to Dijkstra's advice is not to create complexity out of fear. :)

it could be that business logic belongs in Effects Managers. The documentation around Effects Managers, however, suggests that they should not be created all that often and that would be an odd stance for what would be a standard part of a larger program. The second is to put the business logic layer in as a wrapper around the view layer. That, however, requires a rethink of commands and subscriptions to embrace something more like the more general request concept with them really representing a request for the next layer up to do something or provide some information. 

Nah, these are both solutions in search of a problem. I sometimes wonder whether the actual root issue here is that I'm saying "you should just build things and then divide later in response to specific pain points" but that's a really boring (however correct) answer to what would otherwise be a fun problem to try and solve. ;)

I'm not the only one saying this, by the way. Erik Lott is developing a large Elm SPA and his recent post echoes the same "this is the wrong thing to spend time worrying about" sentiment I've been repeating.

tl;dr I hear you, but this concern doesn't materialize in practice. Elm has some things yet to work out (DOM APIs, how code splitting should work, etc), but architecture is not one of them.

We're all set on that front, so let's go build things! :)

Mark Hamburg

unread,
Aug 25, 2016, 4:09:45 PM8/25/16
to Elm Discuss
Richard's definition of large application (36,000 lines) and my definition (Photoshop, Lightroom, ...) are rather different in scale, so maybe that's part of the difference in perspective and my sense that one needs to find good ways to carve things apart.

With that in mind, let me put an example out. Imagine working on something akin to Slack (or any number of other chat systems). For the view, we need an updating list of channels and for any channel, we need an updating list of messages. We need ways to create channels and post messages. This first really well with the 0.17 architecture. The lists of channels and messages are subscriptions. Channel creation and message posting are commands.

Now, if these were working with a RESTful interface, we might generate the commands by generating HTTP tasks and converting those to commands and we would handle the subscriptions again by generating HTTP tasks coupled with timer tasks for when to re-query.

But in the interests of performance and to avoid polling, the service is built using web sockets and speaks a particular message protocol to do so. Now, we really want to use the web sockets effects manager. But the commands and subscriptions it provides — particularly the subscriptions — don't really fit with the needs of the view. We want to maintain some form of local data model that interacts with the server via web sockets. That would be pretty easy but that data model then doesn't fit as neatly with the commands and subscriptions needed by the view. At least not when we essentially have to query it for "subscribed" values after every update — e.g., after every update to the data model, we need to fetch the list of forums and see whether it changed or the update function for the data model has to provide this information in a form that allows it to be routed to the subscriptions from the view model side.

I want the UX developer and the client-side backend developer to get together and specify an API for what the UX needs and the abstraction provided by the backend. In this case, the commands and subscriptions listed above. I then want the UX developer to be able to go off and build using this API and I want the backend developer to similarly be able to go off and build the implementation of this API in as straightforward a way as possible.

What it feels like we need is a way to build effects managers based on other effects managers and an embrace of effects managers as a more general part of the architecture rather than a piece used for a limited range of platform API interfacing to be written by a much smaller number of people. Commands and subscriptions are great ways to specify the communication between layers and to allow those layers to be built and maintained independently but they aren't really available for that purpose as far as I can tell from the 0.17 documentation.

Or are there other patterns that allow the developers to agree on what needs to be done and get out of each others way as effectively as commands and subscriptions seem to do?

Mark

Richard Feldman

unread,
Aug 25, 2016, 5:51:14 PM8/25/16
to Elm Discuss
Richard's definition of large application (36,000 lines) and my definition (Photoshop, Lightroom, ...) are rather different in scale

OP said "If i have a site with lots of features what is the best way to orgainze everything?" and our code base precisely fits the bill for what he asked for advice about. :)

To repeat myself from earlier: "It's not necessarily a bad idea, just not the first thing you should reach for. Or the second thing you should reach for. Or the third thing you should reach for.
not the first thing you should reach for. Or the second thing you should reach for. Or the third thing you should reach for."

If you are at Slack's scale, sure, maybe you have reached the point where this makes sense.

But even Slack was not Slack on day one. What I'm saying is that if you aspire to become Slack someday, you should start simple, like I'm recommending, and then refactor as needed on a case-by-case basis. I'm claiming that "don't divide until you need to" scales nicely. That's been my experience, and I think it's reasonable to expect others will have the same experience.

I think trying to organize your code base on day one for "the scale you hope to have in year five" is a huge mistake, and I strongly recommend against it. :)

Mark Hamburg

unread,
Aug 25, 2016, 7:53:01 PM8/25/16
to elm-d...@googlegroups.com
On Aug 25, 2016, at 2:51 PM, Richard Feldman <richard....@gmail.com> wrote:

Richard's definition of large application (36,000 lines) and my definition (Photoshop, Lightroom, ...) are rather different in scale

OP said "If i have a site with lots of features what is the best way to orgainze everything?" and our code base precisely fits the bill for what he asked for advice about. :)

Point taken.

However, I fear that your argument to worry about scaling later dances around the fact that some scaling issues arrive the moment you would like to have two people work on something. At that point, they can generally both be more productive if you can subdivide the work, put an API in place in between, and then let each proceed in a separate file. This avoids lots of opportunities for toes to get stepped on either in actual coding or in a source code merge.

Breaking things out into new utility types and functions is pretty clear, but what are the useful patterns to take an app and subdivide it so that two people can work on it at once? What are the API design principles one should apply?

Mark

Richard Feldman

unread,
Aug 25, 2016, 8:11:05 PM8/25/16
to Elm Discuss
some scaling issues arrive the moment you would like to have two people work on something. At that point, they can generally both be more productive if you can subdivide the work, put an API in place in between, and then let each proceed in a separate file.

I understand this concept, and my recommendation takes it into account.

Having multiple people working on the same part of the code base comes up for us all the time!

It's totally fine. Nobody wants to reorganize our code base afterwards. :)

I get that you are worried about this in theory, but what I'm saying is that in practice we frequently do the thing you're saying is should be painful, and we've found that it actually just isn't.

Nick H

unread,
Aug 25, 2016, 8:22:48 PM8/25/16
to elm-d...@googlegroups.com
some scaling issues arrive the moment you would like to have two people work on something

Well, those two people are going to be pair programming, right? ;-P <omg i am winking so hard right now>

--
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.

Mark Hamburg

unread,
Aug 25, 2016, 8:24:27 PM8/25/16
to elm-d...@googlegroups.com
And when we go to four programmers, we use mob programming... :-P

Rupert Smith

unread,
Aug 26, 2016, 10:49:35 AM8/26/16
to Elm Discuss
On Friday, August 26, 2016 at 12:53:01 AM UTC+1, Mark Hamburg wrote:
On Aug 25, 2016, at 2:51 PM, Richard Feldman <richard....@gmail.com> wrote:
Richard's definition of large application (36,000 lines) and my definition (Photoshop, Lightroom, ...) are rather different in scale

OP said "If i have a site with lots of features what is the best way to orgainze everything?" and our code base precisely fits the bill for what he asked for advice about. :)

Point taken.

However, I fear that your argument to worry about scaling later dances around the fact that some scaling issues arrive the moment you would like to have two people work on something.

And it should also be added that sometimes the need to scale comes after you have already hacked out a non-scalable code base and is accompanied by pressure from a business that needs scalability yesterday. Before committing to a new framework like Elm, one would first like to know that it can be scaled, how it can be, what will be necessary to refactor code to make it scalable and what pitfalls one could have avoided in the first place that may become big problems later. I feel more comfortable about the start simple approach once I've got that figured out.

Josh Adams

unread,
Aug 26, 2016, 3:02:15 PM8/26/16
to Elm Discuss
 Before committing to a new framework like Elm, one would first like to know that it can be scaled, how it can be, what will be necessary to refactor code to make it scalable and what pitfalls one could have avoided in the first place that may become big problems later. I feel more comfortable about the start simple approach once I've got that figured out.

So...it's super nice and scalable.  Use us as arguments from authority (not that I'm a huge authority on this or anything, but I've built enough large systems (billions of dollars in transactions per year, etc) in other languages and done enough elm that I'm entirely convinced.  The problem stems from people really wanting to invent a problem before they've hit it.  I did it too early on in Elm.  And I was mad when I got responses like this.

Then I breathed a bit and though "huh these people have been doing it for a while, why don't I just take their advice and see how it goes".  And it was glorious.

-Josh

Mark Hamburg

unread,
Aug 26, 2016, 3:59:40 PM8/26/16
to elm-d...@googlegroups.com
I've already hit the problems.

I'm trying to rebuild the successor to some past systems in Elm and one thing we learned on those systems was that to get good performance it was critical that we prioritize HTTP fetches so that we didn't ask for the low priority items at the same time as the high priority items. From a correctness standpoint, the particular fetches are a local problem and the Elm architecture provides a reasonable way to route those. But the prioritization is a global problem. To go with that, I need cancelation support because that's the other key piece in getting responsiveness and what that means in practice is that if something hasn't left the queue yet, I need a way to cancel it. What are the Elm design patterns to deal with this sort of local v global situation?

What I'm hearing is, "just build a big program and worry about it later". It may be that Elm manages to let things scale further than in the past but that wouldn't really be anything new. Wirth's Pascal had no modularity. But we've learned as an industry that the ability to modularize code helps with reasoning about it — a small module can be subjected to the sort of thorough code review that a large one can't — and helps with keeping programmers out of each others way. Team scale is a problem that I'm also already looking at. While Elm may scale up to monolithic code bases better than some languages, I strongly doubt that it's brought some new magic to the table that makes modularization and separation of concerns irrelevant.

So, the question is, what practices should we be keeping in mind so that the codebase doesn't eventually just become so huge that it can't move and adapt? What patterns and architectures should apply to code written now so that it can be modularized down the road?

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.

Richard Feldman

unread,
Aug 26, 2016, 5:18:34 PM8/26/16
to Elm Discuss
I'm trying to rebuild the successor to some past systems in Elm and one thing we learned on those systems was that to get good performance it was critical that we prioritize HTTP fetches so that we didn't ask for the low priority items at the same time as the high priority items. From a correctness standpoint, the particular fetches are a local problem and the Elm architecture provides a reasonable way to route those. But the prioritization is a global problem. To go with that, I need cancelation support because that's the other key piece in getting responsiveness and what that means in practice is that if something hasn't left the queue yet, I need a way to cancel it.

Okay, now we're getting somewhere!

So you have a specific problem where you need a global queue for dispatching HTTP requests according to your applications' particular business logic.

Here's how I would approach this if I had these same design constraints. I would add something like this to my model:

requestQueue : Dict RequestId { order : Int, request : Task Never Msg }

Then I'd introduce a function to enqueue messages with a particular priority level that determines the order value.

I'd also introduce a Msg called Dispatch that pulls the next thing off the queue (sorting Dict.values by the order field) and uses Task.perform to run the HTTP request.

To allow canceling, I'd add this to my top-level Msg:

| CancelRequest RequestId

This is what I would do if I had your particular business need. Seems pretty reasonable!

I also think it would be no problem to introduce this to an existing Elm application after the fact. Just start by deleting your dependency on elm-http, and the compiler will immediately tell you every single place you were doing HTTP "the old way." Go through and transition those direct HTTP calls to use a stubbed-out implementation of "the new way," and then finally reintroduce the elm-http dependency to make the queue work for real!

I'll also note that this use case seems very specific to your application. I would avoid trying to overgeneralize this into any kind of pattern that others should follow. :)

Mark Hamburg

unread,
Aug 26, 2016, 6:03:51 PM8/26/16
to elm-d...@googlegroups.com
Thanks, Richard. That all works if everything is essentially sitting together at the top level and talk to the model. As I said, writing the queue isn't really the hard part. I know you just like to let stuff accumulate in one monolithic piece, but if you wanted to subdivide some of the major sections into sub-modules, would you have them stop using Platform.Cmd to communicate upward and instead say use something like:

type AppCommand msg
    = Immediate (Cmd msg)
    | Queued Int (Cmd Msg)
    | Batch (List (AppCommand msg))

with appropriate implementations for AppCommand.batch, AppCommand.map, and AppCommand.none?

The general guidelines for code development would then perhaps be:

• Structure in terms of models, messages, init, update, subscriptions, and view (leaving some pieces out where they aren't appropriate). 
• Be prepared to replace the standard platform types for Cmd and Sub if you need to bridge between what your application logic needs and what your server (or other backend) provides but when doing so follow the same API patterns so that the shift is easy to make.
• More generally document what you need and what you deliver via types and use transformations to go from one type to another. The tagging pattern in the standard Elm examples is just a particularly simple form of transformation.
• Don't subdivide too much.

If these are the guidelines, then it might be good if Elm did not auto import pieces of Platform like Platform.Cmd and if Task.perform were replaced with Platform.Cmd.fromTask.  That way the guide could talk in terms of building applications based on "commands" and "subscriptions" but could also say that Platform.Cmd and Platform.Sub were just examples of such types that were used at the boundary layer between the application, the runtime system, and the effect managers and were also for most simple and many not so simple programs but were in no way sacrosanct beyond their role at the boundary.

Mark

--

Mark Hamburg

unread,
Aug 26, 2016, 6:04:46 PM8/26/16
to elm-d...@googlegroups.com
Oops. Typo. That should be

| Queued Int (Cmd msg)

Richard Feldman

unread,
Aug 26, 2016, 6:34:03 PM8/26/16
to Elm Discuss
I know you just like to let stuff accumulate in one monolithic piece, but if you wanted to subdivide some of the major sections into sub-modules

Just to be clear, at work we split things up into modules at the drop of a hat. If a particular file is too long, pulling some of it out into a different module is typically trivial and uncontroversial.

You're talking about entire model/view/update triplets that have different Msg types and are responsible for their own isolated state, though, which is a totally different thing. :)

if you wanted to subdivide some of the major sections into sub-modules, would you have them stop using Platform.Cmd to communicate upward

Oh I definitely would only use Cmd to communicate directly to the Elm Runtime for things like effects and JS interop. I don't think it's the right tool for organizing internal Elm code in an application. I would use Msg for that, and things like Html.App.map and Cmd.map to facilitate that if necessary. :)

AppCommand seems like overkill. We need to prioritize our HTTP requests. Do we really need a drop-in replacement an entire foundational core library to do that? Why not just say "we have a business need to do HTTP requests in a certain way, so everybody on the team, use this way instead of making vanilla elm-http calls" instead?

Mark Hamburg

unread,
Aug 26, 2016, 7:19:42 PM8/26/16
to elm-d...@googlegroups.com
On Aug 26, 2016, at 3:34 PM, Richard Feldman <richard....@gmail.com> wrote:

AppCommand seems like overkill. We need to prioritize our HTTP requests. Do we really need a drop-in replacement an entire foundational core library to do that? Why not just say "we have a business need to do HTTP requests in a certain way, so everybody on the team, use this way instead of making vanilla elm-http calls" instead?

Because everyone who reads the Elm guide will have learned that the way to make an HTTP request is to construct a Task and convert it into a Cmd and return that in the second part of the update result. So, I'm looking for a way to say "same routing pattern, different call to create the prioritized request". Keeping code to the same code flow keeps different programmers from finding different ways to update the priority queue in the root model with their request. Instead, they just code mostly like they would otherwise and the root model takes care of the actual priority queue management.

Now, like you, I find the notion of rebuilding a core type off putting even if it isn't much code. That's why I've been searching for an alternative. But with no way to extend Platform.Cmd and no way to look inside batch commands, it doesn't feel like there is much choice without having the more disruptive plumbing of making sure that all the necessary pieces of code can not only access but modify the priority queue and hence the root model.

Mark

Richard Feldman

unread,
Aug 26, 2016, 7:28:12 PM8/26/16
to Elm Discuss
AppCommand seems like overkill. We need to prioritize our HTTP requests. Do we really need a drop-in replacement an entire foundational core library to do that? Why not just say "we have a business need to do HTTP requests in a certain way, so everybody on the team, use this way instead of making vanilla elm-http calls" instead?

Because everyone who reads the Elm guide will have learned that the way to make an HTTP request is to construct a Task and convert it into a Cmd and return that in the second part of the update result. So, I'm looking for a way to say "same routing pattern, different call to create the prioritized request". Keeping code to the same code flow keeps different programmers from finding different ways to update the priority queue in the root model with their request. Instead, they just code mostly like they would otherwise and the root model takes care of the actual priority queue management.

I see where you're coming from on that. I don't personally think that's the way to go - personally I would say "we are doing this part differently, here is why we're doing it differently, here is how to use it properly" - but now I understand the motivation for the drop-in replacement. :)

I haven't personally tried doing anything like that, and I'm not honestly sure how it would feel in practice. I'd be curious to know, though!

Richard Feldman

unread,
Aug 26, 2016, 8:18:14 PM8/26/16
to Elm Discuss
Keeping code to the same code flow keeps different programmers from finding different ways to update the priority queue in the root model with their request.

Almost forgot to mention: I think the best way to accomplish this is with opaque types. You make a module that holds the "enqueue a Prioritized HTTP Request" union type, but which does not expose any constructors.

Instead it exposes specific functions which let people build and/or access these from scratch - but only in the ways you want!

Charles-Edouard Cady

unread,
Aug 27, 2016, 2:55:28 AM8/27/16
to Elm Discuss
Would it be possible in your architecture to perform server requests at the highest level of your application? You would then send a a->Msg to each sub component. That way server requests would only be performed in one place where you could fine-tune how you want them performed.
This comes from Richard's answer to a question I had on composability. Components should only update their own private non-shared state that other components don't need. Shared states such as your server queue are updated at the highest level and each sub component merely receives a function to run the update.
Does that make sense in your context?

Mark Hamburg

unread,
Aug 27, 2016, 6:14:41 PM8/27/16
to elm-d...@googlegroups.com
I'm working on some example code. I'm varying between a moderately fleshed out example of the plumbing but with little backing it up and a fully worked but trivial to the point of not being interesting example. The general model, however, works as follows:

• We have a service model and a client model. The idea is that after agreeing on an API, these can then undergo independent development. Furthermore, we should be able to do things like mock the service rapidly and then fill it in with a version that talks to the web service or whatever is needed. Similarly, we could mock the client side quickly if we just wanted to test the service APIs.

• The API is represented by a union type: APICommand clientMsg. A typical entry would be something like:

| PostMessage (Error -> clientMsg) (() -> clientMsg) ForumId String


APICommand clientMsg is wrapped up inside a ClientCommand clientMsg type that provides standard Cmd-style functionality (batch, map). It also provides a way to embed non-API commands of type Cmd clientMsg. I would have liked to just use a different type of message within Cmd to do this rather than reconstructing map and batch — trivial though they may be — but mapping for platform commands is built around tagging results as they come back and we need to adjust the tagging functions on API commands before they get to the service.

• The update function for the client has the signature:

clientMsg -> clientModel -> (clientModel, ClientCommand clientMsg) 

This gets applied at the "root" level when processing messages destined for the client. On the way back, it can turn the client commands into platform commands of type Cmd (RootMsg serviceMsg clientMsg) where the RootMsg type provides for messages bound for the client, messages bound for the service, and API commands bound for the service.

• The service processes both its own messages and API commands (in separate update functions in my latest code). Service updates return (serviceModel, ServiceCommand serviceMsg clientMsg) where ServiceCommand again mirrors Cmd in terms of providing batch and map support. (This could actually be represented as a Cmd (Either serviceMsg clientMsg) but I'm not sure that leveraging Cmd in that case provides more clarity.)

• The root update function again maps the service commands appropriately to generate commands of type Cmd (RootMsg serviceMsg clientMsg) which route to the service and client as appropriate.

The amount of code is actually pretty small and as discussed above, it provides a very nice separation of concerns between the client side and the server side and allows each to be coded following standard Elm architecture patterns with the caveat that one doesn't actually use Cmd during propagation.

So, with that, I would be happy were it not for the fact that it's dawned on me that subscriptions are going to be more difficult because I can't simply wait for the runtime to ask the root model for subscriptions and then plumb it appropriately. This is because the service model will almost certainly need to adjust its state based on the client subscription requests — see some effects managers for examples — and when asked for subscriptions we can't update the root model. So, I will need to perform an internal subscription update after every client model update. This isn't fatal but I could imagine it being inefficient. I could also send a strobe to do this but that might introduce more delay than I'd like. That said, I've wondered whether subscriptions need lazy support akin to what we have for views.

Mark

P.S. The heart of this work was in making it possible to write an API like the following:

type Error
    = NoPermission
    | NoConnection
    | ServiceFailure


type ForumID = ForumID String


type APICommand msg
    = AddForum (Error -> msg) (ForumID -> msg) String
    | DeleteForum (Error -> msg) (() -> msg) ForumID
    | PostMessage (Error -> msg) (() -> msg) ForumID String


type APISubscription msg
    = ForumList (List (ForumID, String) -> msg)
    | MessageList (Array.Array String -> msg) ForumID

Everything else was just the work to allow the client side and the service side to hew as closely as possible to the Elm architecture while getting the appropriate interaction between the two.
 


Mark Hamburg

unread,
Aug 27, 2016, 6:26:28 PM8/27/16
to elm-d...@googlegroups.com
Not using Cmd does make (!) far less useful.

Mark

Richard Feldman

unread,
Aug 28, 2016, 4:14:43 AM8/28/16
to Elm Discuss
Components should only update their own private non-shared state that other components don't need. Shared states such as your server queue are updated at the highest level and each sub component merely receives a function to run the update.

Apologies, but I have read and re-read this and I'm still not sure what it's saying. :x

That's totally on me, but I'm quickly becoming convinced that Evan's view of the term "components" is dead-on: our speaking in terms of "components" seems to result in way more confusion than positive outcomes. I'm on board with the idea that we should stop using it to describe portions of Elm programs, and instead focus discussions on terms whose definitions are clearly applicable: modules, functions, data, and the first-class Elm Architecture building blocks of Model, Msg, update, and view.

I gather that this is has to do with an interaction between two update functions...is there some way to rephrase it in terms of how those two update functions interact?

Rex van der Spuy

unread,
Aug 28, 2016, 2:23:58 PM8/28/16
to Elm Discuss


On Sunday, August 28, 2016 at 4:14:43 AM UTC-4, Richard Feldman wrote:

I gather that this is has to do with an interaction between two update functions...is there some way to rephrase it in terms of how those two update functions interact?

Richard, this has been a huge area of confusion for me in the child-parent discussions that I've been following over the past few months! What's a "module", what's a "child", what's a "component"? Everyone seems to have different mental models for what these things are.... include me :) ... resulting in some muddy and inconclusive discussions.

... just a observation from someone in the Peanut Gallery.
Carry on! This is a fun thread :)

Nick H

unread,
Aug 28, 2016, 2:58:58 PM8/28/16
to elm-d...@googlegroups.com
Well, "module" is a keyword in Elm, so I really hope everybody who knows Elm knows exactly what this is!

--

Richard Feldman

unread,
Aug 28, 2016, 3:02:52 PM8/28/16
to elm-d...@googlegroups.com

Here's a primer on Elm modules: https://dennisreimann.de/articles/elm-modules-import.html

Indeed, "component" and "child" are not first-class constructs in Elm, and so can mean different things to different people. Sorry for the confusion!

I'm going to make a point to start discussing things using terms that have first-class language support, and see how that goes. :)


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.

--
You received this message because you are subscribed to a topic in the Google Groups "Elm Discuss" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/elm-discuss/_cfOu88oCx4/unsubscribe.
To unsubscribe from this group and all its topics, send an email to elm-discuss...@googlegroups.com.

Mark Hamburg

unread,
Aug 28, 2016, 6:09:01 PM8/28/16
to elm-d...@googlegroups.com
I think the problem is that "components" in other communities tends to mean "reusable components" and while reuse is certainly valuable, what is of more concern is how to subdivide and partition state/logic/functionality etc and how to do so in a sufficiently general pattern that people don't need to figure everything out with each new program they look at. For example, view functions are a simple concept and they compose well. If we have one piece nested inside the other, the outer view function will call the inner view function. If the type of the inner piece is known, it can do so directly. If it's handled in a type parameter, then the inner view function needs to be supplied as a parameter. TEA also provides a standard pattern for building update functions though that tends to be more complicated than view functions because of information propagation between pieces. We are also now seeing advisory patterns like "don't store state in more than one place and here is how to avoid doing so". So, maybe components sound too much like objects and should be avoided but what are needed are patterns of partitioning and coordination.

Mark

P.S. We also need performance-oriented patterns. For example, I'm concerned that a complex model — regardless of how it is partitioned or not — could require many subscriptions and if these are re-computed and effects managers are updated on every change to the model, there is likely to be a lot of repeated busy work. Are there patterns for managing subscriptions that avoid that?
--
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.

Richard Feldman

unread,
Aug 28, 2016, 10:51:07 PM8/28/16
to Elm Discuss
So, maybe components sound too much like objects and should be avoided but what are needed are patterns of partitioning and coordination.

100% agree!

P.S. We also need performance-oriented patterns. For example, I'm concerned that a complex model — regardless of how it is partitioned or not — could require many subscriptions and if these are re-computed and effects managers are updated on every change to the model, there is likely to be a lot of repeated busy work. Are there patterns for managing subscriptions that avoid that?

Not at the moment, but presumably an analog to Html.lazy would suffice.

Erik Lott

unread,
Aug 28, 2016, 10:52:04 PM8/28/16
to Elm Discuss
Mark, tell me about this code:

type APICommand msg
   
= AddForum (Error -> msg) (ForumID -> msg) String
   
| DeleteForum (Error -> msg) (() -> msg) ForumID
   
| PostMessage (Error -> msg) (() -> msg) ForumID String


type
APISubscription msg
   
= ForumList (List (ForumID, String) -> msg)
   
| MessageList (Array.Array String -> msg) ForumID


Conceptually, I appreciate what you're trying to do here (create a simple system driven by domain messages - AddForum, DeleteForum, etc) , but the actual implementation worries me. I'm wondering why you're not simply creating an API module instead:

module API exposing(addForum, deleteForum, postMessage)

addForum
: String -> Task Http.Error Forum
// code here...

deleteForum
: String -> Task Http.Error Never
// code here

etc
 

I know that you mentioned that you have some additional complexity (task queue, websockets,etc), but I haven't actually seen anything that would cause me to abandon the built-in command/subscriptions system. What if you need to do something in the page that is not necessarily domain related, but requires a task/command - e.g. generate a random number? Are these types of situations factored into your design?



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

Mark Hamburg

unread,
Aug 29, 2016, 12:33:37 AM8/29/16
to elm-d...@googlegroups.com
Why does Elm work in terms of commands rather than tasks? That's somewhat rhetorical, but in the case of effect managers it allows an effect manager to do things like maintain additional state, batch together operations, etc.

In the case of this system, this allows for the API service provider to again do caching and batching, to manage login status, etc.. It also allows for more general translation of the way the UI interacts with the backend service. For example, the cloud service might just communicate via a web socket connection spewing out notifications across all of the forums for which the user was a member. This isn't going to map directly to the activity that the UI is currently interested in displaying.

The "problem" with commands and subscriptions is that the mapping really only applies to the tagging that happens on the way back down from the cloud (or wherever) and can't alter the commands and subscriptions on the way up to address any mismatch between the needs of the UI and the facilities provided by the backend service.

Mark

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

Mark Hamburg

unread,
Aug 29, 2016, 12:55:32 AM8/29/16
to elm-d...@googlegroups.com
I have started thinking about whether there would be a useful refactoring of Cmd and Sub to support some shared structure that could also work with the sort of emulations that I've been building. It's not that the emulations are hard — writing batch, map, and none just aren't that much code — but rather that I'd like to write a standard operator to do the following:

(:>) (model, Batchable ops)
-> (model -> (model, Batchable ops))
-> (model, Batchable ops)

Where Batchable would cover Cmd and my Cmd emulations. This then allows update sequences to look like:

(model, Batchable.none)
:> firstUpdate
:> secondUpdate

This would make the generalization of the Elm architecture that keeps the "command pattern" but doesn't use the "Cmd type" fit in perfectly well with the simpler Cmd patterns.

Mark

Mark Hamburg

unread,
Aug 29, 2016, 12:59:34 AM8/29/16
to elm-d...@googlegroups.com
More specifically:

(:>) (model, Batchable op)
    -> (model -> (model, Batchable op))
    -> (model, Batchable op)
(:>) (oldModel, oldOps) update =
    let
        (newModel, newOps) = update oldModel
    in
        (newModel, Batchable.batch [oldOps, newOps])

Charles-Edouard Cady

unread,
Aug 29, 2016, 2:49:03 AM8/29/16
to Elm Discuss


On Sunday, August 28, 2016 at 10:14:43 AM UTC+2, Richard Feldman wrote:
Components should only update their own private non-shared state that other components don't need. Shared states such as your server queue are updated at the highest level and each sub component merely receives a function to run the update.

Apologies, but I have read and re-read this and I'm still not sure what it's saying. :x

Oh dear.. Given this was supposed to be a summary of what I understood from your reply to a question I had about extensible records, it's rather embarrassing for me! Looks like I'm not on the path to enlightenment after all!

Still, I think the idea was worth sharing so I'll clarify.
By "component" I mean any elm module you wish to use in several applications.
The elm architecture tutorials covers two extreme cases:
(1) the application has completely independent components (eg. two independent counter widgets)
(2) all parts of the application share the same state

If I take the example of a clock widget (time picker) used in several places of an application, it has both an external state (the time) and an internal state (eg. which part of the widget is hovered) that the rest of the application neither knows nor cares about.
All I'm saying is that the clock's update should only make changes to the internal state & not change external state which will be updated by passing a (Date -> msg) function to the clock's view: it almost seems as if, in Elm, you delegate upwards...

In the case of the server requests, I'm simply suggesting to give each "update" function an extra (Request -> msg) parameter so that the ordering of the request & actual execution is done at a single place in the application higher up where more context is available.

I think using all of Msg+update+view+Model in a component is not always necessary & maybe the elm architecture tutorial could have an extra example or two with, eg. a date picker to illustrate the case where you have reusable parts that maintain a private state & use a shared state.
 

Oliver Searle-Barnes

unread,
Aug 30, 2016, 6:50:58 AM8/30/16
to Elm Discuss
This sounds interesting, I'm not clear on how communication works between the child `update` and the model of the opaque type.

What form would those functions take?

*a)* `addRequest : RequestDetails -> RequestMsg` 
A RequestMsg is created in the child and is passed back up the tree until `update` can be called on `requestsModel` at the correct level in the hierarchy.

or

*b)* `addRequest : RequestDetails -> RequestsModel -> (RequestsModel, Cmd Msg)`
The RequestsModel is passed down the tree to the child model and it's updated immediately using the api function and then returned back up the tree.

*c)* something else

Erik Lott

unread,
Aug 30, 2016, 9:14:11 AM8/30/16
to Elm Discuss
@JoshAdams has been kind enough to share a simple single level SPA example on another thread that might be of use to some folks arriving here.

Here's what I've been working on.  The recent git history is all about refactoring.  Haven't introduced 'sub-components with state' or w/e and don't see it coming soon.  It's an Elm-SPA with a Phoenix backend: https://github.com/knewter/time-tracker



On Thursday, August 4, 2016 at 6:15:53 PM UTC-4, Ryan Erb wrote:
Got a few questions regarding the best way, or what options there are for scaling up to a large site.
If i have a site with lots of features what is the best way to orgainze everything?

One Elm page to rule them all? With this approach you only have to deal with one update happening, you can separate code into different modules, but the update function would be HUGE. Guess this would be the same as a single page application?

One Elm page per HTML page? This way every page deals with only what it is used for. You can write common things in reusable modules. One problem might be switching to other elm pages. The only really good way right now that i have is using a port to "window.locaion.href" to another elm page. Then pulling all the data it requires from the server using more ports.

One Elm page per feature? This way you can write separate apps for each part of your page. One for the header menu bar, one for a sidebar, and one for each main feature. This way you can mix and match and piece together for a full page. BUT you might have to do a lot of port talking between each section.

Some other way??????

Been searching around a cant seem to find any blogs or topics on this subject. Our stack is Elm/Phoenix/Elixir/Postgres.

If anyone has created a large application using Elm would happy to have your input on what worked/ didn't work for you.

Thanks,

Mark Hamburg

unread,
Aug 31, 2016, 4:32:13 PM8/31/16
to elm-d...@googlegroups.com
I re-read your message, Erik, and my reply and realized that I hadn't answered all of the questions. The routing I've been working with handles both commands created by the client side and handled by the service side and commands created by the client and handled by "normal" external means. This gets addressed through the type declaration:

type ClientCommand msg
    = Batch (List (ClientCommand msg))
    | PlatformCmd (Platform.Cmd msg)
    | APIFromClient (APICommand msg)

The key point here is in having the platform and API commands split out so that they can be mapped differently. From the client standpoint, if you can make a Cmd, then you can call ClientCommand.fromCmd and turn it into a ClientCommand which can then be batched with API commands. At the point where the "client" meets the "service" in the model, these get turned back into Platform.Cmd's (through Task.succeed to set up the delivery of the the APICommand value to the service).

If I could figure out how to do this with tasks, that might well be simpler and easier to plug in. I may need to take a pass at that again.

Mark

P.S. I don't really like the words "client" and "service" but they seemed better at least than "client" and "server" since the latter would suggest code running on another machine which while this separation enables that possibility is not something that is happening here. I thought about "viewModel" and "dataModel" but that seemed to have a certain amount of verbal noise in saying "model" everywhere.

On Sun, Aug 28, 2016 at 7:52 PM, Erik Lott <mreri...@gmail.com> wrote:
To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss+unsubscribe@googlegroups.com.

Erik Lott

unread,
Sep 1, 2016, 3:21:30 PM9/1/16
to Elm Discuss
Mark: I spent some time last night looking at a simplified scenario.

Here's a quick explanation for anyone who cares: An app has an API module which contains all of the task/command related logic needed to communicate with the server. The API module also manages some internal state related to these tasks/commands (e.g. task queue, data cache, etc), so it is a "stateful" module that sends Msg's and has an update function. This state must live near the top level of the application, since it exists for the lifespan of the application. Question: How can other deeply nested modules make use of the API module?

After I attacked this from a bunch of different angles, I think I agree with you Mark: I don't really see another way to make this work without swapping the existing Cmd in the update cycle for a custom implementation (or cheating by using javascript/native/ports).

Original update cycle
update : Msg -> Model -> (Model, Cmd)

New update cycle
update : Msg -> Model -> (Model, ClientCommand)

This adds some additional layers of abstraction at the top of the app where everything is wired together, but aside from that, this isn't so bad. 

Mark Hamburg

unread,
Sep 1, 2016, 5:24:26 PM9/1/16
to elm-d...@googlegroups.com
One nice thing that comes out of this architecture is that if one could run pieces on separate threads, it would split very naturally between the service side and the client side. The only ugly part is that in addition to moving data over whatever bridge we're using, we also need to move tagger functions and I can imagine that being harder to do than pure data.

We're looking at how much code sharing we can do between an iOS app written in Swift and a web app written in Elm. One of the potential answers is that the service side code could run in a JavaScript context while leaving the UI itself in native code.

Mark
Reply all
Reply to author
Forward
0 new messages