Comments on the TEA Components package

777 views
Skip to first unread message

Mark Hamburg

unread,
Apr 18, 2017, 11:39:16 AM4/18/17
to Elm Discuss
Marek Fajkus's original announcement was on elm-dev but that list tends to become disapproving of discussions fast, so I'm moving over to here to discuss it. The post suggested that it was in the Elm package library but it seems to have been pulled, so I will point to the Github repository:


I've read the ReadMe but haven't looked at the code or the examples beyond the ReadMe.

Two things immmediately struck me:

1. It seems like an interesting idea to wrap the embedding up into a single structure. It doesn't really reduce the boilerplate that much but it makes it seem simpler because it isn't spread out and the number of type annotations needed is smaller. I see it as a more elaborate form of lenses — a structure I've started pushing through my code to consolidate various idioms (and to reduce copy-paste-change errors when I've got multiple record fields of the same type and hence the type system won't help me when I miss a change).

2. This isn't really about defining components — a hot button word with some people (go read the elm-dev thread) — so much as it is about defining embeddings of one TEA-shaped unit within another.

Side note on TEA: In Elm 0.16, TEA was all about being composable. The examples focused on things like going from one counter to multiple counters. This carried over into Elm 0.17 where Cmd.map and Sub.map were clearly targeted at this sort of composition and Html.map both helped with the composition and with making Html.Lazy work in an environment with composition.(*) But somewhere along the way, portions of the Elm community, including Evan, seem to have swung hard against composing TEA units and now just recommend using functions. This ignores the fact that in many cases, the units can't be encapsulated into single functions. On the other hand, the accompanying piece with these functions is generally Config arguments and what this package does is provide a useful standard Config and functions to work with that Config when embedding.

Turning back to the package in question, I would also question the use of the term "polymorphic" to describe the more generalized case in that the thing it describes can't really be used in multiple contexts as presented. Still, it's good to show that the pattern fits with variations on TEA-shaped units.

Finally, while it was also interesting to see the material on action bubbling, I think that using commands to send messages to parents is probably a dangerous choice since there are few if any guarantees regarding delivery order. Our solution has been to replace commands with out messages that subsume commands as one special case. This has given us a lot of flexibility to handle notifications — e.g., the login component can report back to the parent on login success, a modal process can report if canceled, etc — but it does involve more boilerplate. I may see whether I can adapt Marek's approach to this more general embedding mechanism.

In any event, I recommend that those interested in ways to structure large Elm programs go look at this. It doesn't radically change anything but it could be a better way to express the composition of TEA-shaped units.

Mark

(*) Or it would if it weren't for a bug where some nesting of the two leads to a type system violation and thence generally a runtime error.


Message has been deleted

Marek Fajkus

unread,
Apr 18, 2017, 12:35:23 PM4/18/17
to Elm Discuss
reposting due to nonsense in code example - Can't write without Emacs, sorry:(

I really feel you get the whole idea, brilliant!

I don't really know what I should add to that but at first - Package was taken down on my own request. At this point I agree that term component even though it might make sense from some point of view isn't really the best choice. Also in elm-dev I have described our use-case briefly which I believe makes it clear why I some people might want to structure app in terms of Parent-Child modules.

I would also question the use of the term "polymorphic" to describe the more generalized

100% agree. I'm not happy with that name either.

I think that using commands to send messages to parents is probably a dangerous choice 

In most cases this is useful for async actions anyway (like component requesting data parent knows how to get for instance). Sometimes you don't know who and if anyone is interested that you (bottom update) did something. Good example might be http://package.elm-lang.org/packages/zaboco/elm-draggable/latest which is also sending Cmd.

If you need anything else just expose Msg with constructors (Msg(..)) from children. That way you can pattern match on children's Msg in parent like:


update msg model =
   case msg of
      ChildMsg childMsg ->
          let
              newModel =
                  if childMsg == Child.Msg.Important then
                      { model | itHappend = True }
                  else
                      model
          in
              (newModel, Cmd.none)
                  |> Component.update child childMsg


(hope that code is clear)

Anyway there are some questions how to do some things for sure. I'm really happy someone have look at it and was able to understand it that well. Appreciated.

Richard Feldman

unread,
Apr 18, 2017, 1:03:59 PM4/18/17
to Elm Discuss
2. This isn't really about defining components — a hot button word with some people (go read the elm-dev thread) — so much as it is about defining embeddings of one TEA-shaped unit within another.

Side note on TEA: In Elm 0.16, TEA was all about being composable.

I really like this phrasing.

Crucially, between 0.16 and today, we learned that a Model-View-Update triplet is the wrong unit of composition for Elm applications.

It's important that people understand the historical context here: many of us have tried this, and found that composing individual functions was both simpler and consistently led to a much better experience. I've laid out my advice for specifically how to do that here.

In any event, I recommend that those interested in ways to structure large Elm programs go look at this. It doesn't radically change anything but it could be a better way to express the composition of TEA-shaped units.

I'll reiterate that thinking about application organization as "composing TEA-shaped units" is neither officially recommended nor what Elm is designed for, and many folks have tried to fit that round peg into the square hole and reported that it led to pain at scale. Specifically I've had many people tell me that refactoring according to this advice led to a much better experience.

Exploring can be fun, but if you're looking for a good way to organize your Elm applications, be aware that this particular path has been explored before - and there's a signpost next to it labeled "WARNING: PIT OF SNAKES AHEAD." ;)

Mark Hamburg

unread,
Apr 18, 2017, 4:13:58 PM4/18/17
to elm-d...@googlegroups.com
The remark that composing TEA-shaped units isn't what Elm is designed for raises the question of whether we should then be expecting Cmd.map and friends to be going away since that would seem to be exactly what they are designed for.

Having read the reddit thread about just starting big and breaking things down, it reminds of arguments I've heard for years about how monolithic programs are easier to work with. They are. That's how we end up with them. It's almost always easier to tack a little more onto an existing ball of mud. If you've never worked in a statically-typed language then maybe Elm's type checking seems like a revelation, but it isn't so revolutionary as to throw out the experience that even static type-checking only gets you so far. Everything that can change the model can violate the constraints on the model that fall beyond the capabilities of the type system. Everything that sees the full model is a potential dependency for each and every piece of the model. Abstraction barriers are what make big programs possible. I don't think there is any magic in Elm that changes this fundamental lesson of 60 years of software development. It may make you feel you can move the boundaries relative to what some other languages afford, but if your architecture comes down to saying at the surface we have these few concepts but inside that we just have a big ball of type-checked mud, that's not really an architecture.

The rest of the world has looked st TEA and seen value in it and is looking to extend it. It's sad to see Elm itself — which really is a nice language — potentially treating it as little more than a veneer. I welcome efforts like Marek's to try to figure out how to build something other than a ball of mud with Elm.

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.


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


Richard Feldman

unread,
Apr 18, 2017, 6:36:14 PM4/18/17
to Elm Discuss
Having read the reddit thread about just starting big and breaking things down, it reminds of arguments I've heard for years about how monolithic programs are easier to work with.
 
This Reddit post is about how to break up a monolith. It doesn't talk about anything else except how to break up a monolith. :)

Abstraction barriers are what make big programs possible. I don't think there is any magic in Elm that changes this fundamental lesson of 60 years of software development.

I couldn't agree more! Glad we're on the same page about that.
 
The rest of the world has looked at TEA and seen value in it and is looking to extend it.

If people look at it, see value in it, and then think "okay this works great, but how can we take it in a different direction?" - that sounds like putting the cart before the horse to me.

I think a lot of people would be happier if they gave the "use the simplest API that works" approach a shot. :)
 
The remark that composing TEA-shaped units isn't what Elm is designed for raises the question of whether we should then be expecting Cmd.map and friends to be going away since that would seem to be exactly what they are designed for.

That's a great question! They are designed to make it easier to work with Html and Cmd alongside several message constructors.

Example

Pretend there is no Html.map and I'm writing a reusable view whose entire API consists of one function:

checkbox : (Bool -> msg) -> Bool -> Html msg

The only state I care about is whether the checkbox is checked (the Bool argument), and the only user interaction is checking or unchecking the box (the (Bool -> msg) argument; the caller is free to use whatever Msg type they are already working with, no conversion necessary).

Using a higher-order function to manage messages is a great lightweight default choice, so I don't miss Html.map at all here. I wouldn't use it regardless!

Now let's say what I'm building is not a reusable checkbox, but something more complex: a reusable signup form that expands in-place when the user clicks it.

This will involve a lot more Msg constructors - for recording text entry in the username/password fields, HTTP responses for username availability checks, expanding and collapsing it, submitting it...overall, a lot more going on. Let's say it needs 10 message constructors to work properly.

Using the same API design as we did for checkbox above, we'd write something like this:

signupForm : (SignupForm.State -> GiganticRecordOfTenMsgConstructors msg) -> SignupForm.State -> Html msg

With this API, reusing this signup form entails spelling out all 10 Msg constructors as the first argument to this function. That's a lot more work than the single Bool argument needed by checkbox! This is where a more heavyweight approach can pay off: creating a new SignupForm.Msg and having signupForm return Html SignupForm.Msg instead of Html msg.

Here's how that API would look:

signupForm : SignupForm.State -> Html SignupForm.Msg

type SignupForm.Msg

We had to do more work to set this up, of course (the checkbox function didn't need the Msg implementation this signupForm function requires), but we successfully eliminated the need for the gigantic record argument. Great!

In exchange, though, not only have we needed a new Msg type on the SignupForm side, but now we also need our caller to convert from SignupForm.Msg to whatever their local flavor of Msg is. (Hence why this is a more heavyweight approach.)

Since our application uses FooApp.Msg, but signupForm returns Html parameterized on a SignupForm.Msg, we'll have a type mismatch if our application's view tries to call signupForm directly. In the absence of Html.map, we're stuck - this thing is no longer reusable, it's become a nonfunctional art piece that we can't actually plug in anywhere.

If we have Html.map, problem solved! We can add this approach to our toolbox, right alongside the simpler "one higher-order function and you're done" API we used for checkbox.

Overengineering

Now granted, Html.map does introduce the potential for overengineering. We wrote this earlier:

checkbox : (Bool -> msg) -> Bool -> Html msg

This was an unremarkably simple API, but Html.map gives us the ability to replace this single function with a full-blown Checkbox module, complete with:
  • type alias Model = { checked : Bool }
  • type Msg = SetChecked Bool
  • update : Msg -> Model -> ( Model, Cmd Msg) [gotta return Cmd Msg in addition to Model, because what if someday our checkbox wants to send HTTP requests?!]
  • view : Model -> Html Msg
  • a chauffeured Rolls-Royce to deliver the single Boolean worth of state and interaction from one function to another.
To implement this massively overengineered API (remember, we achieved the same functionality using one small function before) in a world without Html.map and Cmd.map, we'd have to pass those hulking 10-field records between even more functions. In such a world, this API would be so outrageously cumbersome to use that I doubt anyone would attempt it. The fact that Html.map makes this appear deceptively okay as an API choice is an unfortunate downside of Html.map.

And sure, given that Html.map and Cmd.map exist, it's a reasonable question to ponder - "if I was correct to give my Reusable Signup Form this API, shouldn't One Size Fit All, meaning it would also be correct to choose that exact same API for every other use case I can think of?" - but there is an answer to this question, and it is "no; my code will be simpler if I fit the API to the use case."

Despite the fact that Html.map introduces this potential overengineering pitfall, I think it's clearly useful enough for the Reusable Signup Forms of the world to be worth including. I wouldn't (and don't) worry about it going anywhere! :)

Rupert Smith

unread,
Apr 18, 2017, 6:39:25 PM4/18/17
to Elm Discuss
On Tuesday, April 18, 2017 at 9:13:58 PM UTC+1, Mark Hamburg wrote:
Having read the reddit thread about just starting big and breaking things down, it reminds of arguments I've heard for years about how monolithic programs are easier to work with. They are. That's how we end up with them. It's almost always easier to tack a little more onto an existing ball of mud. If you've never worked in a statically-typed language then maybe Elm's type checking seems like a revelation, but it isn't so revolutionary as to throw out the experience that even static type-checking only gets you so far. Everything that can change the model can violate the constraints on the model that fall beyond the capabilities of the type system. Everything that sees the full model is a potential dependency for each and every piece of the model. Abstraction barriers are what make big programs possible. I don't think there is any magic in Elm that changes this fundamental lesson of 60 years of software development. It may make you feel you can move the boundaries relative to what some other languages afford, but if your architecture comes down to saying at the surface we have these few concepts but inside that we just have a big ball of type-checked mud, that's not really an architecture.

Well said. I kind of wanted to say something along these lines but you articulated better than I could have. Parnas' modular design principles are my principal guide.

There is room for the mud-ball and the pristine encapsulated architecture. I usually do some mud-ball hacking then figure out how I can tidy it up a bit better with a view to encapsulating data models and operations so that they are both well documented and can only be manipulated to produce legal states, as well as how to improve a data model using the type system to eliminate representations of illegal states.

Learn both (or all) approaches, I guess. Nested TEA is working very nicely for me and feels appropriate in many situations, but just not as a blanket approach for everything.

Mark Hamburg

unread,
Apr 18, 2017, 7:45:10 PM4/18/17
to elm-d...@googlegroups.com
It's certainly reasonable to say that there is a point where pursuing fractal TEA is overkill and not buying one much but extra plumbing work. But it is also exactly the case of things like the sign up form where being able to say "Here is a sign up form. It has a model, messages, update, and view. You embed it in the rest of your app like this." The goal in a composable architecture is to have "like this" fit enough of a pattern that a programmer coming into an embedding situation knows what to expect. TEA works pretty well for this though it can feel a bit boilerplate heavy. I would put it as:

It's often easiest to work in a monolithic program and there are things you can do to mitigate the effects of doing so and thereby build a bigger monolith. But when a project gets truly large, the ease of building a monolithic ball of mud gives way to the problems of figuring out what interacts with what and while the type system will have your back at some level, there are limits to what it can achieve. But TEA is fractal. You can build more isolated components using the same architecture and embed them within your larger program. Do you have multiple pages? It may be natural to build each of them using the TEA pattern. You can then fit them together in the parent app as follows... The caveat here is that while this is both straightforward and robust, it is a noticeable amount of code and translation at the glue layer, so taking the TEA hammer and smashing pieces down until you have tiny nuggets is going to leave you with a lot of glue holding those nuggets together.

Mark

P.S. The checkbox could also have the signature: `checkbox : Bool -> Html Bool` and one could use `Html.map` to apply the translation into some other message space. I don't know that that's better.


Mark Hamburg

unread,
Apr 18, 2017, 7:56:14 PM4/18/17
to elm-d...@googlegroups.com
If you buy my premise, then one could say that the old examples using counters were useful because they allowed one to focus on the plumbing without distraction of use case specific details but problematic because they also suggested that something the size of the counter model was a good target to break things down to.

Mark

Richard Feldman

unread,
Apr 18, 2017, 7:58:03 PM4/18/17
to Elm Discuss
It's certainly reasonable to say that there is a point where pursuing fractal TEA is overkill and not buying one much but extra plumbing work.

I would say that pursuing it as the end itself, rather than using it when appropriate as the means to an end (the end being a code base that scales nicely), is overkill from the start. :)

But it is also exactly the case of things like the sign up form where being able to say "Here is a sign up form. It has a model, messages, update, and view. You embed it in the rest of your app like this."

Sure. You can already do that; that is the signup form's API. That doesn't imply the checkbox ought to have the same API!
 
The goal in a composable architecture is to have "like this" fit enough of a pattern that a programmer coming into an embedding situation knows what to expect.

That is the purpose of an API; programmers seeking to reuse code know what to expect by looking at a clear API.

"Everything ought to have the same API" is a much harder claim to defend. It sounds wrong at face value, and I haven't seen any evidence (in this thread or elsewhere) to convince me that it's a wise goal to pursue. :)

Nicholas Hollon

unread,
Apr 18, 2017, 8:05:00 PM4/18/17
to Elm Discuss
Nobody is advocating for a ball of mud. Richard is describing incremental design via refactoring. This is a standard component of agile/extreme programming methodologies. I have seen it work. And it can be done using modules and functions as your only abstraction tools.

Disagree about what abstractions are the most useful. But throwing around the term "ball of mud" is nothing but name-calling.

Mark Hamburg

unread,
Apr 18, 2017, 11:22:10 PM4/18/17
to elm-d...@googlegroups.com

To say, the architectural guidance is to decompose things with functions is essentially no guidance (architecture) at all since functions are what one would be building with anyway in a functional language. TEA did outline a way to build Elm programs. If you discard the nested aspects, then it's just the API to the program function.

That said, I used the term being also aware of its more positive usage with regard to LISP.

Mark

Peter Damoc

unread,
Apr 19, 2017, 12:56:23 AM4/19/17
to Elm Discuss
On Wed, Apr 19, 2017 at 2:58 AM, Richard Feldman <richard....@gmail.com> wrote:
"Everything ought to have the same API" is a much harder claim to defend. It sounds wrong at face value, and I haven't seen any evidence (in this thread or elsewhere) to convince me that it's a wise goal to pursue. :)

But isn't the entirety of the html package and actual example of an unified API? 
All the widgets there have the same API: widget : List (Attribute msg) -> List (Html msg) -> Html msg


checkbox : (Bool -> msg) -> Bool -> Html msg


How about the case where one has calls to a server involved? for example a weather widget or some kind of "Quote of the day" widget, a stocks ticker, etc. 
How does one handles side-effects (http, random, time etc) with this pattern?  






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

Richard Feldman

unread,
Apr 19, 2017, 2:20:09 AM4/19/17
to Elm Discuss

My point that there's a simple way to scale Elm applications by abstracting at the function level has gone uncontested for awhile in this thread. I think at this point I've said my piece and might as well move on.

It's cool for people to have philosophical goals separate from the goal of a nice scaling experience, but I honestly don't think I have much to contribute to those discussions. :)


--
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/Lo6bG96zotI/unsubscribe.
To unsubscribe from this group and all its topics, send an email to elm-discuss...@googlegroups.com.

Peter Damoc

unread,
Apr 19, 2017, 2:54:17 AM4/19/17
to Elm Discuss
On Wed, Apr 19, 2017 at 9:19 AM, Richard Feldman <richard....@gmail.com> wrote:

My point that there's a simple way to scale Elm applications by abstracting at the function level has gone uncontested for awhile in this thread. I think at this point I've said my piece and might as well move on.

It's cool for people to have philosophical goals separate from the goal of a nice scaling experience, but I honestly don't think I have much to contribute to those discussions. :)

I am genuinely interested in a reasonable answer to the encapsulation of side-effects components/widgets.
I have tried to think about this when I tried to reimplement the old tutorial and failed to find a proper solution. 
I remember asking about it here (or maybe on Slack) and have no memory of an answer.

I don't know how to encapsulate the kind of functionality that was demonstrated in the RandomGif or the SVG Animation with the currently recommended approach.

It's fine to say, "we don't do it like we used to do it" but there was no functionally equivalent solution offered. 

Also, one could use the old approach to scale by splitting an app into pages. 
I have no idea how to avoid Model-Update-View when implementing the idea of Pages.

You say: 
we learned that a Model-View-Update triplet is the wrong unit of composition for Elm applications.

How do you propose to split the functionality one has in a highly complex app with a lot of pages without using those triplets? 

Richard Feldman

unread,
Apr 19, 2017, 3:03:24 AM4/19/17
to Elm Discuss
> How do you propose to split the functionality one has in a highly complex app with a lot of pages without using those triplets?

I don't haha...I just defended their use a few posts ago, complete with the specific example of the reusable signup form.

Peter Damoc

unread,
Apr 19, 2017, 3:07:34 AM4/19/17
to Elm Discuss
so, the Model-View-Update triplet is NOT the wrong unit of composition for Elm applications? :) 

Don't you see how people starting up with Elm could get confused by this kind of messages? 

On Wed, Apr 19, 2017 at 10:03 AM, Richard Feldman <richard....@gmail.com> wrote:
> How do you propose to split the functionality one has in a highly complex app with a lot of pages without using those triplets?

I don't haha...I just defended their use a few posts ago, complete with the specific example of the reusable signup form.

--

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.

Marek Fajkus

unread,
Apr 19, 2017, 10:52:07 AM4/19/17
to Elm Discuss
First of all I have to say I'm really glad we have this discussion. I know it's sometimes frustrated to face different opinions but outcome is definitely worth it. It looks like we're really not on same page and can learn something from other here. Also it seems that groups are good place to do that.

I have quite some points to ongoing discussion but I really have no time to put them down now since I have to work on things with colleagues.

Anyway since nobody reacted on @peter's comment and I feel it really needs to be addressed let me add my opinion just on that for now:

How about the case where one has calls to a server involved? for example a weather widget or some kind of "Quote of the day" widget, a stocks ticker, etc. 
How does one handles side-effects (http, random, time etc) with this pattern? 

Creating "components" that  do any kind of HTTP is probably way to hell. The only exception are really simple things like let's say jQuery plugins or anything which is really really really super isolated.

Generally networking should be on real surface of whole system. There are many things that can go wrong. This is the ultimate side-effect since one system talking to another over "Maybe Connection" doesn't have any guarantees about what that other system does. And you have to assume the other system will fail in one way or another. Dealing with failure is difficult itself and is hard to do in isolation of system since it pretty often has some impact of other things. Api is the only real "mutable object" you have in TEA (and you can write it in Haskell and do not change anything about this fundamental truth). Api is actually global (your domain) mutable (even hello word "pure" can return error 500) singleton. People with some Erlang experience know costs of fault tolerance. I remember in some Evan's comment in Elm's either stdlib or compiler there was some note about OTPish (Open Telecom Platform) message passing. Anyway even thought Actor model is really great interesting model for concurrency it's crucial to understand it leads to some inherited complexity. We surly don't want to implement actors in Elm. Rich Hickey (author of Clojure) would probably said something like "Adding networking stuff to component is completing concerns of how to work with A and how and where to get things A needs".

Also getting data is usually not the only thing you need to think of. Say you have Blog with posts and you're building admin. There is list of posts on left and post detail on right. Now you want to add "delete" function to post list so you can click on "delete" button and delete post. The right way to do it is emit some action from UI on click (This is what Html.Events do for you) and handle it on some very top/surface level - usually update but in case of composed updates (like "tea-component") on the very top one. Why? Because in actions like this you need to really look at system as whole. What should happen when user is trying to delete post which is actually opened? How can I tell if it's open (probably from route) and so on. Only on most top level you can control whole system (even though you might use higher lever interfaces of self contain units that if you have them).

Of course there are exception but I would rather avoid examples of "component that can do http" since this is really where people can hurt themselves.

Actually this is precisely why "tea-component" was using that "Action Bubbling" and and sending messages to upper component. Component might knows how to propagate "delete" action but has no idea about how to proceed delete. That is really concern for system as a whole. This is sort of same as Html.Events just on higher level. Child just asks "Hey this just happened and you might be interested" and system as whole knows how, when or if at all to do with it.

This is at least what I thing based on my experience. Hope it makes sense.

Mark Hamburg

unread,
Apr 19, 2017, 12:51:24 PM4/19/17
to Elm Discuss
Here is where our app is migrating after starting as an intermixture of view-side code and data-side code.

The high level split is a store and a display. The store knows about things like how to talk to our server. As I summarized it, the UX code should know nothing about Phoenix. The display manages the user interaction. Both sides have state. The store, well, stores information — generally a reflection of what is on the server or should be on the server. The display, however, also stores information like whether panels are open, the state of animations, etc. Just as knowledge of Phoenix doesn't belong in the UX, knowledge of animations does not belong in the store.

This isn't a radical design by any means. A lot of code is built this way. It's perhaps more interesting to this discussion to observe that the Elm community doesn't encourage even this sort of factoring and in fact naive use of MUV/TEA does lead to a deep intermingling of store and display concerns. (I guess the "just write one monster model" approach deals with this by saying that code can choose to only interact with parts of the model.)

What I've been working through while engineering this split is how to manage coordination between the two sides.

Sending messages from the display to the store is pretty simple. We've replaced commands in the update signature with a list of "out messages". These messages include internal notifications — e.g., canceled, finished, please shift the target somewhere else, etc — and operations for the world beyond the display which includes commands to flow out the top of the app, requests for work by the store, and requests for random number generation using a shared generator. (This last is much like the effects manager random logic but it uses the Pcg generator.) The operations are all subject to command style mapping to adjust the tagging for their responses back to the display. Notifications are just notifications. The current implementation involves more boilerplate at every nesting within the display than I would really like but I'm working on that.

Getting data from the store to the display is more interesting and is an area I continue to work through.

One naive approach we looked at would be to just hand the store into the view functions and let them query the store as needed to do their work. There are two problems with this.

The first is that some display side concepts such as which item is currently targeted/selected — it's a display-side concept since the server should never know — need to update in response to store-side changes. For example, if the selected item goes away, we may want to automatically select something else. Since we can't do updates during view calls, we need to provide a way for the display to react to store changes. Our solution here has been to treat this as one would treat the data if it were entirely stored on the server: we store a local copy on the display side and perform a fetch operation to get it from the store. This means that the display gets to validate its copy and the problem of keeping that copy up to date is exactly the same as the problem one would have if there were no store and we were just talking over the wire to a server.(*)

This leads to the second problem: A fetch operation is great for getting the initial information and replacing commands with operations makes that pretty easy to implement but how do we keep the data up to date and relatedly how do we keep track of when the display side loses interest? Here I'm looking at building a subscription equivalent (an observation?) but I'm not wild about what it would take to make it efficient in the presence of say scrolling a grid of several thousand items starting and stopping observations as the visible items change. But efficiency with large sets of subscriptions seems like something the Elm implementation hasn't really worried about either since I think it regenerates and collects up subscriptions after every mutation — if you adhere to the "no functions in the model" advice(**), you can't cache your subscriptions because they hold tagger functions — and has the effects managers diff the new subscriptions against the old. Maybe things are just efficient enough as it is but I suspect that it's more likely that with so few effects managers for Elm, few applications generate large number of subscriptions at any one time. But lacking any better ideas, we will probably start with mirroring subscriptions and see whether it blows up the time complexity in a noticeable way.

Moving on, there's one other bit of code structuring that we've only recently adopted but which has clarified a lot of code and should help with writing unit tests. The display side models for the overall experience or pieces of the experience or particular modal portions of the experience (e.g, sign in) tend to have a model that hold display specific information like the state of panels or animations and a logic submodel that holds all of the business logic including figuring out whether the data is valid and if not what errors to display. This logic submodel is also the part that talks to the store thereby further keeping the display out of the loop beyond needing to forward messages on behalf of the logic. Logic submodels follow the standard model-update-view pattern but here view is replaced with a toViewData function that returns the contents of the submodel in a form designed for use in driving the display. These logic submodels have also proven useful for code reuse because we've been able to build a couple of different sign in experiences (needed for contextual reasons) while using the same underlying logic that understands signing in or creating an account.

It should be noted that while we apply the MUV/TEA paradigm through the app, we only push it through where it makes sense. The app UX has a certain structure and particular modalities. That results in just a couple of layers of TEA nesting. The logic submodels result in one more level of nesting. But within a major view modality or within the logic, the code can get as ball-of-mud-like as seems appropriate. The abstraction barriers exist not to make everything architecturally pristine but to keep one ball from getting overly entangled with another ball and mean that if we want to throw out a piece and replace it, it's boundaries are clear. Similarly, the store basically presents a single API though we've observed that requests can be structured in much the same way that the tagging works for message delivery if we wanted to nest the code.

Finally, if writing monolithic applications and then adjusting them on an ad hoc basis is the way one prefers to work, I don't want to stop you. I just know that I've seen what that tends to do to codebases that last multiple years and have developers coming and going or that have core components one would like to reuse in new ways. (I've dealt with a C++ codebase where the central class had a huge number of public methods because they were used by various internal parts of the overall initial use for the codebase. When trying to turn this into a library for use elsewhere, there was a big question of what the real external API was.) I could argue about whether architecture is premature because code probably won't endure or whether lack of architecture contributes to the premature death of code, but people should generally know their use cases and which things matter. You don't need pretty architecture for a shell script to get a specific job done. What concerns me in the seeming push back against architecture in Elm apps is that it may lead to decisions in Elm's future that make it harder to build structured applications — e.g., if hierarchy is discouraged, why keep Cmd.map? So, on the one hand, we have some of us in the community asking, how best should we apply structure to an Elm app believing that structure is a good thing for enduring codebases and on the other hand simply saying "please don't make it harder to apply structure to an Elm app".

Mark

(*) In my dream implementation, the store and the display could actually run on separate threads. That's not going to happen in any browser environment at the moment.)

(**) If the store can't respond to a request immediately, it is going to have to hold onto the tagger function for its eventual reply. I'm not sweating that though it means that testing for store equality is not an option.

(***) Okay, that's a normative statement. When we started this, we were pushing it deeper and we're now unwinding that as we unwind the store/display entanglement.



--
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,
Apr 19, 2017, 1:27:09 PM4/19/17
to Elm Discuss
so, the Model-View-Update triplet is NOT the wrong unit of composition for Elm applications? :) 
> How do you propose to split the functionality one has in a highly complex app with a lot of pages without using those triplets?

I don't haha...I just defended their use a few posts ago, complete with the specific example of the reusable signup form.

To recap:
  1. Earlier I said "between 0.16 and today, we learned that a Model-View-Update triplet is the wrong unit of composition for Elm applications...composing individual functions was both simpler and consistently led to a much better experience. I've laid out my advice for specifically how to do that here."
  2. Later I pointed out an example of when it would be useful to use Html.map and Cmd.map to make a reusable signup form, and for that use case it happened to make sense to have a separate model, view, and update.
In (1) I am saying that I expect you'll have a better time if you think about building Elm applications in terms of composing individual functions, not in terms of composing Model/View/Update triplets.

In (2) I am giving an example of a very specific set of circumstances in which a separate Model/View/Update makes sense.

In summary: "Here is a useful but complex tool. Always reach for a simpler one first." 

Richard Feldman

unread,
Apr 19, 2017, 2:00:08 PM4/19/17
to Elm Discuss
"just write one monster model" approach

Mark, this is about the third time you've insinuated that my explanation of how to split things up somehow amounts to not splitting things up at all. Someone called you out on it, and then you switched insults from "ball of mud" to "monster model." If you genuinely want to have a discussion, it's important not to misrepresent the other side.

It's also weird to me that you keep responding to my point of "this approach has worked really well for lots of Elm programmers, in practice, in real life, already" with "I bet that won't work well in practice, in real life, based on my experiences in other languages." We're past the point of theoretical predictions here. The experiment has already been done, and then replicated successfully many times.

Your comment that "this approach didn't work for us in C++" seems particularly weird. I'd say there are "a few" differences between Elm and C++, refactoring experience included. ;)

Yosuke Torii

unread,
Apr 19, 2017, 2:46:45 PM4/19/17
to Elm Discuss
Richard,

I'm curious how your product uses (not makes) external UI library. You are right if everyone make their UI from scratch, but how about others who wants to use existing library? For instance, date picker is a popular widget. We expect this widget to do lot of complex things behind the scene. But it requires state management (e.g. selected month). Without nested TEA, how can we use this library? I think TEA gives the best user experience for those widgets.

If I understand correctly, Marek's library is trying to reduce the cost of using them. I agree with your opinion about breaking the monolith or "don't make needless things for saving time". It's the best practice of agile development. But does "scale" mean just "my app become big"? I expect that word to mean "easy to use external library, custom widgets can be easily made and they can be used across pages".

I don't think the exploration of TEA is finished. elm-sortable-table is a good example (I wonder why nobody mention it, btw). Calling update function is not needed. You can use it just like <input> element. I hope UI libraries that uses this pattern will increase.

Terms like "component is proven to be an anti-pattern" sounds too negative for me. It sounds like "Elm proposes DIY every time, no reusable UI ever!", even if you mean "no need to make everything a component for building app, there are actually not so many reusable UI parts in practice".



2017年4月20日木曜日 2時27分09秒 UTC+9 Richard Feldman:

Mark Hamburg

unread,
Apr 19, 2017, 2:50:24 PM4/19/17
to Elm Discuss
The monster model design is one aspect that leads to the ball of mud code. Let's look at the source quote for the term big ball of mud:

A Big Ball of Mud is a haphazardly structured, sprawling, sloppy, duct-tape-and-baling-wire, spaghetti-code jungle. These systems show unmistakable signs of unregulated growth, and repeated, expedient repair. Information is shared promiscuously among distant elements of the system, often to the point where nearly all the important information becomes global or duplicated. The overall structure of the system may never have been well defined. If it was, it may have eroded beyond recognition. Programmers with a shred of architectural sensibility shun these quagmires. Only those who are unconcerned about architecture, and, perhaps, are comfortable with the inertia of the day-to-day chore of patching the holes in these failing dikes, are content to work on such systems.

— Brian Foote and Joseph Yoder, Big Ball of Mud. Fourth Conference on Patterns Languages of Programs (PLoP '97/EuroPLoP '97) Monticello, Illinois, September 1997

When everything starts out in one place with access to everything else, it's natural to end up with everything talking to and depending on everything else — or at least the dependencies are less likely to be well regulated because there is no mechanism doing the regulation. Now, as conservative politicians will love to tell you, regulations get in the way and we can be more productive without them, so yes, this unregulated environment is seemingly more productive. The type system will help in detecting out and out errors in making changes but once things get intertwined, they become less amenable to significant change. That's often fine for an agile development model in which one focuses on small changes but I've seen agile teams that could generate lots of small changes but when faced with needing to do something big found themselves profoundly stuck. An approach which basically seems to advocate building a monolithic, intertwined codebase and if that gets to be too much, here are some techniques to break it down after the fact will work to an extent but will be fighting to apply order to chaos and almost all the time it will be easier for a developer trying to get a task done to just add a little more chaos.

As for your specific advice, it includes several useful points and I've refactored code using those same techniques, but the distillation of the don't use nested TEA argument when people have asked what to do instead has tended to be "use functions" which as I've noted doesn't mean much in a functional language. (I guess it means use functions rather than types but since types are what allow us to make illegal states impossible, I don't think it really comes down to just use functions either.) What TEA and nested TEA provided was architectural guidance on how to structure things for long term evolution. Saying, if your update function gets too big you can handle individual cases with separate functions is I guess useful advice but something that I would hope would be obvious to most people working in a functional language. What sort of calling conventions work well? Which ones don't work well and should be avoided? That's the sort of architectural advice that can be put into practice early on and expect it to pay off later when changes need to be made. For example, to buttress the non-component view of the world, one could push on the fact that many views do not need to own their own state but can simply be functions that render state provided from somewhere else. One could phrase that as: "You have a view function but ask yourself whether you really need a model and an update or whether those are better handled somewhere else. If you do so, then you can take a big pile of view code and move it somewhere else and not need to look at it when trying to figure out what is going on in your model."

As for C++ example I cited, there is nothing Elm is bringing to the table that makes it superior to C++ in the case I described. C++ is fully type-checked. Mutability v immutability was not a concern here. Of concern was that question "which parts of this are visible to other code for the benefit of code within the would be library and which parts are visible because they are expected to be stable interfaces for other code". And the fact it was a question arose exactly because the code base started as an entity that only needed to serve itself and for which the refactorings were all just adjustments within those bounds. The choices were expedient for immediate development but detrimental to long-term reuse.

Should one build everything to be reusable/replaceable? Almost certainly not because doing so does come with overhead. But identifying likely break points where code may need to be either reused or replaced and structure those break points up front to enable that reduces the effort when you do need it to be separate. And if you've got that separation, then you can potentially frustrate Foote and Yoder by building balls of mud through iterative development but they will be smaller balls of mud for which the strengths and weaknesses of the code can be assessed separately.

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.

Noah Hall

unread,
Apr 19, 2017, 3:30:17 PM4/19/17
to elm-d...@googlegroups.com
Hey folks,

I just want to say, it's really really really hard to reply to these
posts which are both really long and really dense. It's better to
answer in a terse, short format. This is a conversation - not a blog
post. Longer comments and responses are usually better in a blog post,
because it's quite hard to keep all the context in our heads for each
mail on a mailing list.

Regarding how to structure code for large apps, I really recommend
looking at the NRI elm-style-guide.
https://github.com/noredink/elm-style-guide#nri

The "reusable" parts are those prefixed by `Nri`. Here's an example of
what some type signatures might look like:

-- Button.elm
view : msg -> Html msg
view onClick = ...

-- Emoji.elm
type Emoji = Smile | SadFace
view : Emoji -> Html msg
view emoji = ...

-- Leaderboard.elm

type LeaderSettings field = { numberOfPlayers : Int, fields: List field }
view : LeaderSettings field -> (field -> msg) -> Html msg
view settings onFieldChange = ...


I recommend trying out this approach, and see how you like it. It
makes implementing things _so_ simple. I've tried every approach under
the sun, and seen more codebases than I can count. This is the one
that wins for me.

If anyone tries this approach, but fails to get their head around how
to refactor their code to work with it, or fails to see how it's
simpler, hit me up on Slack (eeue56) or at elm-europe or
https://osloelmday.no, and I will guarantee your code will walk away
looking nicer than it did before.
>> 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 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,
Apr 19, 2017, 3:32:29 PM4/19/17
to Elm Discuss
I've seen agile teams that could generate lots of small changes but when faced with needing to do something big found themselves profoundly stuck.

In Elm?

the distillation of the don't use nested TEA argument when people have asked what to do instead has tended to be "use functions" which as I've noted doesn't mean much in a functional language.

It means YAGNI. Don't overengineer it. :)

As for C++ example I cited, there is nothing Elm is bringing to the table that makes it superior to C++ in the case I described. C++ is fully type-checked.

In Elm, if I refactor something, my typical experience is "once it compiles, it works." It's been a long time since I did C++, but my memory of that experience was a far cry from that.

It sounds like your C++ refactoring experience has been close to your Elm refactoring experience, which surprises me.

Richard Feldman

unread,
Apr 19, 2017, 3:39:14 PM4/19/17
to Elm Discuss
You are right if everyone make their UI from scratch, but how about others who wants to use existing library? For instance, date picker is a popular widget. We expect this widget to do lot of complex things behind the scene. But it requires state management (e.g. selected month). Without nested TEA, how can we use this library? [...] elm-sortable-table is a good example (I wonder why nobody mention it, btw). Calling update function is not needed. You can use it just like <input> element. I hope UI libraries that uses this pattern will increase.

I think Sortable Table is a perfect example of this!

How do we use the library? The same way we use any library: we read the docs and plug into the API it provides.

It doesn't need to be any more complicated than that. :)

Terms like "component is proven to be an anti-pattern" sounds too negative for me.

I appreciate the sentiment, but I think it's appropriate to be negative when exploring something has yielded an overall negative result. :)
 
It sounds like "Elm proposes DIY every time, no reusable UI ever!", even if you mean "no need to make everything a component for building app, there are actually not so many reusable UI parts in practice".

I'm curious what makes it sound that way, since as you noted, that is not the point I'm making.

Any feedback on how I could be clearer would be appreciated!

Yosuke Torii

unread,
Apr 19, 2017, 4:19:12 PM4/19/17
to elm-d...@googlegroups.com
I'm curious what makes it sound that way, since as you noted, that is not the point I'm making.

I don't know if others feels like me or not. But at least for me, "no components" sounds a bit confusing (it is in official guide too). As you explained the context behind the term "component" is quite huge. I use the word "component" just to say "reusable UI", so "no component" sounds like "no reusable UI". But isn't sortable-table a component? For those who understand the context, it is not a component, but I don't know how others feel (especially who come from JS world).

Also, "no components, no nesting TEA" does not answer the problem discussed here. So how can we do instead? Maybe introducing sortable-table pattern is more constructive for this discussion. I think it is a variant of TEA, managing its own state, but still keeping the form of "reusable view". So great!



--
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/Lo6bG96zotI/unsubscribe.
To unsubscribe from this group and all its topics, send an email to elm-discuss+unsubscribe@googlegroups.com.

Richard Feldman

unread,
Apr 19, 2017, 4:38:46 PM4/19/17
to Elm Discuss
I don't know if others feels like me or not. But at least for me, "no components" sounds a bit confusing (it is in official guide too). As you explained the context behind the term "component" is quite huge. I use the word "component" just to say "reusable UI", so "no component" sounds like "no reusable UI". But isn't sortable-table a component? For those who understand the context, it is not a component, but I don't know how others feel (especially who come from JS world).

I can see that, but given that the problem is "this word means many different things to many different people," the only way to have useful discussion seems to be insisting on using less vague terminology. I recognize the problem that people are used to using this word [to mean different things], but I don't see a better solution.

Can you think of a way to use this overloaded word in a way where the people in the discussion are not confused by it, even though they think it means different things?

Also, "no components, no nesting TEA" does not answer the problem discussed here. So how can we do instead?

I covered how to grow Elm application code bases, and Evan covered reuse.

What is the remaining problem? :)

Peter Damoc

unread,
Apr 19, 2017, 4:40:12 PM4/19/17
to Elm Discuss
On Wed, Apr 19, 2017 at 11:19 PM, Yosuke Torii <jinj...@gmail.com> wrote:
I'm curious what makes it sound that way, since as you noted, that is not the point I'm making.

I don't know if others feels like me or not. But at least for me, "no components" sounds a bit confusing (it is in official guide too).

I view it as destructive so, you're not alone in seeing something not OK with that. 
 
Also, "no components, no nesting TEA" does not answer the problem discussed here. So how can we do instead? Maybe introducing sortable-table pattern is more constructive for this discussion. I think it is a variant of TEA, managing its own state, but still keeping the form of "reusable view". So great!
 
the sortable table is a clever component that pushed the external call to update inside the view.
You still need what used to be the ChildMsg tag but now instead of calling the Child.update and saving the state it receives the updated state and just saves it. 

Other than the update trick, it's more or less the same thing as the old MUV triplets.
You can even use the pattern to do nesting. 

The problem is that this approach makes it impossible (as far as I could see) to implement the components that need side effects (like the old RandomGif list) 





Richard Feldman

unread,
Apr 19, 2017, 4:58:00 PM4/19/17
to Elm Discuss
 
I'm curious what makes it sound that way, since as you noted, that is not the point I'm making.

I don't know if others feels like me or not. But at least for me, "no components" sounds a bit confusing (it is in official guide too).

I view it as destructive so, you're not alone in seeing something not OK with that. 

It's responsible to call out alluring antipatterns that consistently lead people to pain.

I'm sorry if you dislike that, but it's important. :)

Other than the update trick, it's more or less the same thing as the old MUV triplets. 
You can even use the pattern to do nesting.

"You literally can do this and it will compile" is not much of a pitch for a more complex alternative to the simpler approach that already works well. ;)

Yosuke Torii

unread,
Apr 19, 2017, 5:30:14 PM4/19/17
to elm-d...@googlegroups.com
Richard,
 
Can you think of a way to use this overloaded word in a way where the people in the discussion are not confused by it, even though they think it means different things?

I don't know. I just answered the question why I received your message as "DIY every time". I'm not looking for the alternative for "component" . 

I covered how to grow Elm application code bases, and Evan covered reuse.
What is the remaining problem? :)

Again I agree to how to break the monolith and when to do it. But is any of those reusable things non-TEA? I find UI libraries at packages.elm-lang.com but they are often written in TEA manner. If it is not simple, how can they be instead?

Sorry if you have already explained enough. Honestly it is hard to read whole of messages, as Noah said.


Peter,

The problem is that this approach makes it impossible (as far as I could see) to implement the components that need side effects (like the old RandomGif list) 

Yeah, I know. But at least, some of the UI widgets that does not have side-effects can be solved. As far as I can see, the discussion here is not on this stage yet.

I think something like `mapWithCmd : (a -> Cmd msg) -> Html a -> Html msg` would solve it. The problem is that it allows any kind of side effects including HTTP. But I don't find good explanation of why HTTP via view is bad while random (or time stamp too?) is OK.


--

Mark Hamburg

unread,
Apr 19, 2017, 6:18:14 PM4/19/17
to Elm Discuss
I think we have two general trains of thought regarding development here.

One is YAGNI. Let things grow organically. Refactor when it gets messy.

The other is Build for Success. Over engineer because you will probably end up shipping the prototype and once you do it will be too late to go back and do major restructuring.

Each has its place. Each builds for particular outcomes. Each can succeed or fail in spectacular and unique ways. With great effort, each can emulate the advantages of the other — ignoring the fact that the advantages are often in terms of not needing as much effort.

To the extent that we live in a world where we all say "you're free to develop software however you see fit", we can often take the attitude. "That's interesting. It wouldn't work for me but I see why you are making those choices."

Where this turns ugly is when it ceases to be about approaches we individually find valuable but about how everyone should work.

Let's look at this thread. It was instigated by Marek's "components" package which got some discussion on elm-dev and which I moved over here to elm-discuss. The structure camp was essentially looking at this and trying to judge whether it would help with their own structural work. The YAGNI camp joined in and said in bold text: "a Model-View-Update triplet is the wrong unit of composition for Elm applications." Not, "I (or we) haven't found it useful" but "it is wrong". So, now we're off to the races. The structure camp says (in many more words) "I've seen your alternative and it isn't a real alternative. It's a pathway that decades of software industry experience indicates leads to creating big balls of mud." The YAGNI camp can argue that its statements are strongly worded in the interest of keeping beginners away from excessive complexity. The structure camp can argue that it needs to be vocal so that beginners understand the mess that they may get themselves into on a refactor only as needed path.

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.

Marek Fajkus

unread,
Apr 19, 2017, 6:41:50 PM4/19/17
to Elm Discuss
Richard, 

this is what you said about composing update functions:
 
I'll reiterate that thinking about application organization as "composing TEA-shaped units" is neither officially recommended nor what Elm is designed for...
 
> How do you propose to split the functionality one has in a highly complex app with a lot of pages without using those triplets?
I don't haha...I just defended their use a few posts ago, complete with the specific example of the reusable signup form.
  1. Later I pointed out an example of when it would be useful to use Html.map and Cmd.map to make a reusable signup form, and for that use case it happened to make sense to have a separate model, view, and update.
I'll use your own words:
 
this level of doublespeak is really uncool

I thought I've understood this but I'm more and more confused by what you're saying:  
Crucially, between 0.16 and today, we learned that a Model-View-Update triplet is the wrong unit of composition for Elm applications.
repeating one more time:
  1. Earlier I said "between 0.16 and today, we learned that a Model-View-Update triplet is the wrong unit of composition for Elm applications...composing individual functions was both simpler and consistently led to a much better experience. I've laid out my advice for specifically how to do that here."
Well 0.16 -> 0.17 is switch from Effects to Html.program with pure individual functions init, update, subscribe, view. Going back to my first quote of you. And changing words "TEA-shaped units" with "individual functions" we get:  I'll reiterate that thinking about application organization as individual functions is neither officially recommended nor what Elm is designed for...

My point that there's a simple way to scale Elm applications by abstracting at the function level has gone uncontested for awhile in this thread

It did indeed... init update and subscribe are actually function. Looks like I still miss something. Are you trying to say that Cmd.map is not function or what? 
 
I'll use your own words:
 
this level of doublespeak is really uncool
 
on different topic:

 many of us have tried this, and found that composing individual functions was both simpler and consistently led to a much better experience.

Not even pointing out all nonsense: I just don't see any of them here just yet. I hope they will shop up. Please don't start sending links to same Reddit thread once again.

I hope you don't think anyone will actually comment this one at all:

Richard Feldman

unread,
Apr 19, 2017, 8:21:41 PM4/19/17
to Elm Discuss

I thought I've understood this but I'm more and more confused by what you're saying:  
Crucially, between 0.16 and today, we learned that a Model-View-Update triplet is the wrong unit of composition for Elm applications.
init update and subscribe are actually function. Looks like I still miss something. Are you trying to say that Cmd.map is not function or what?
 
What I'm saying is that individual functions, as opposed to a group of functions and data structures (model, view, update, msg, etc) is the right unit of composition.

In the example earlier in this thread, I showed an API for a reusable checkbox using a single function. I then showed an alternate API that resulted from the mindset that a group of functions and data structures (model, view, update, msg, etc) should be the atomic unit of composition - the notion that "you should build Elm applications by composing together model/view/update triplets" which I've seen cause pain. I pointed out that this mindset led to an overengineered checkbox API that was unnecessarily complex.

Does that clarify?

on different topic:

 many of us have tried this, and found that composing individual functions was both simpler and consistently led to a much better experience.

Not even pointing out all nonsense: I just don't see any of them here just yet. I hope they will shop up.

I'm not going to dig up a bunch of peoples' names and pester them to testify that this actually happens. :P

Please don't start sending links to same Reddit thread once again.

I'm doing that so that if beginners stumble on this thread and skim through it, they don't have to dig for what I'm recommending. :)

Yosuke Torii

unread,
Apr 19, 2017, 9:08:51 PM4/19/17
to elm-d...@googlegroups.com
I just read the whole conversation on Slack and realized what I said here was somewhat pointless. I apologize for not reading the context.

So Marek's library is for organizing entire app, not about using external library. If so, Richard's answer on reddit seems best fitted. About the external libraries that have their own state, it is already answered well.

rtfeldman [3 days ago] 
the difference is that `thebritician/elm-autocomplete` is managing its own state because there is absolutely no possible other way to implement that library
it's the last resort, not the default

I agree. It seems true from my experience too. Sometimes model, update, msg and view are all together. But it is a special case. elm-component's API is telling beginners as if they should always be together. Okay I completely understand the context. It must be an anti-pattern!

Sorry again for confusing.


--

Richard Feldman

unread,
Apr 19, 2017, 9:09:47 PM4/19/17
to Elm Discuss
The structure camp says (in many more words) "I've seen your alternative and it isn't a real alternative. It's a pathway that decades of software industry experience indicates leads to creating big balls of mud."
 
This idea would hold more water if people hadn't tried what you're telling them Decades Of Experience Dictates Can't Possibly Work and found that it actually worked great when they tried it.

The old "who are you gonna believe, me or your lying eyes?" argument does not have a great track record.
 
To the extent that we live in a world where we all say "you're free to develop software however you see fit", we can often take the attitude. "That's interesting. It wouldn't work for me but I see why you are making those choices."

It seems that from your perspective, this is an intellectual exercise with low stakes. I see this as a discussion that has great potential to cause pain, because I've seen a lot of pain result from past discussions on this topic.

A lot of people follow what you're advocating and then end up reporting that they had a really bad experience with it. I ask where they got the idea that this would be a good approach to follow, and they point to a blog post someone wrote, or a discussion thread where nobody stood up for the simpler alternative. Then I point them to advice like this, and I get a lot of people coming back to thank me afterwards, saying they got out of the "component mindset" and now they're happy with how their code scales.

I have not seen the reverse happening. I don't see people saying "I decided to refactor everything to be based around model/view/update triplets just because, and I was much happier with my code afterwards." Obviously it's not impossible to follow your advice and have a fine time anyway, but I can confirm at least one case - myself - where someone consciously shifted towards the "component mindset" (before it had that name) and hated working with the result so much, he halted work on the project until he could refactor it back to the old way.

You keep acting like this is unexplored territory even though it has been explored by many people.

I'm not going to pretend discussing things in a public form has no consequences for beginners who come across it later. Call me a bold-text-using verbal ruffian if you like. If I can pull people away from a cliff, I intend to. :)

Richard Feldman

unread,
Apr 19, 2017, 9:11:39 PM4/19/17
to Elm Discuss
Sorry again for confusing.

It's all good! :)

You were super polite and respectful throughout.

Oliver Searle-Barnes

unread,
Apr 19, 2017, 11:54:40 PM4/19/17
to Elm Discuss
> I have not seen the reverse happening.

We did spend the first 6 months or so of our project following the advice to not use nested TEA components. Our experience was that the perceived complexity of the app grew exponentially to the point where it was difficult to make progress. We refactored into a nested TEA structure and are far happier since (the change was made at the start of the year). I'll try to summarise the areas where we saw an improvement. (note I've used the term TEA component for lack an alternative -- triplet doesn't really cover it as we've added additional functions into what we think of as mini-apps).

Msgs
The Msgs for a part of the UI were only valid once that part of the UI was actually loaded. Prior to nesting we had to handle the possibility of Msgs being created that in fact couldn't exist at a particular point in time. 

Models
Similar to Msgs, the state for a particular part of the UI was only present if that part of the UI was actually initialised. Using a flattened structure this led to a lot of Maybes that could be replaced by a single Maybe or Dict (in the case that multiple versions of that part of the UI could be loaded at the same tie). While it isn't necessary to introduce a TEA component to do this, you are already another step down the road to a TEA component once you do this. 

Declarative data loading 
Each TEA component can now specify which data it requires and it's made available/kept updated when that component is active. Active here meaning that the component has been initialised but may or may not currently be visible. Having this consistent approach to data synchronisation has made it easier to understand the data lifecycle for a particular part of the UI.

Synchronisation of scroll state and focus 
Each of our nested components now has a syncDom hook which is used to synchronise aspects of the DOM which can't be handled declaratively. Being able to target a TEA component rather than the whole app or e.g. a particular element's scroll state has proven to be a practical level of granularity.

Reusable view components 
We view these as distinct from the mini-apps that I mentioned earlier. The approach elm-sortable-table uses shifts some of the burden of state management into the client of the package. In it's case this is a good tradeoff. In other cases, when a component has several pieces of view state for instance, pushing this out to the consumer ends up with copy/pasted code that has all the pain that you'd expect. Sometimes the client has no business touching the state at all, e.g. the open/closed state of a dropdown menu for instance. In that case the menu is responsible for when it's open/closed and it needs to manage this when a user for instance clicks on a link in the menu (the menu needs to be closed before handling the click).

Code comprehension 
Developers are finding it far easier to navigate the codebase. They're now able to work on a particular TEA component and the amount of code they have to grok before they can get started is far smaller. Prior to introducing the nested structure the feedback from new developers was that they felt lost at sea and overwhelmed by the amount of code they had to wade through. Generally excuses were made and they avoided working on the Elm app. Now that we've moved to a nested structure they're able to concentrate on a small part of the app and get up to speed/make changes easily.

Our experience has been that TEA component concept that was encouraged with 0.17 has it's place in SPAs. It appears other teams building SPAs have had similar experiences (although I'm not sure whether they've experimented with the flat approach). As Mark has mentioned it does at least provide an architecture which new Elm developers building SPAs can get started with. There may well be a flat approach that addresses some of the concerns that I've mentioned. Currently though it seems a somewhat illusive approach, if it's to be encouraged then there needs to be some more concrete advice regarding how to implement it. That said, aside from a little boilerplate (which is minimal by comparison to the actual app code) we really haven't felt any desire to move away from the nested TEA structure.

Richard Feldman

unread,
Apr 20, 2017, 12:49:22 AM4/20/17
to Elm Discuss
We did spend the first 6 months or so of our project following the advice to not use nested TEA components. Our experience was that the perceived complexity of the app grew exponentially to the point where it was difficult to make progress. We refactored into a nested TEA structure and are far happier since (the change was made at the start of the year).

Fair enough!

Reusable view components 
We view these as distinct from the mini-apps that I mentioned earlier.  

Ahh, right, this "mini-apps" design sounds familiar to me. I think I remember talking to you about this awhile back - as I recall, you had some really interesting business requirements in terms of how and when parts of the page were loaded.

If you have time, would you mind describing how the app works?

Oliver Searle-Barnes

unread,
Apr 20, 2017, 3:58:33 AM4/20/17
to Elm Discuss
Yes we spoke about how the idea of having "sections" within a page that function as independent units. The idea of mini-apps seems to encompass the approach of splitting out the app into Pages and page Sections. I don't have time now but I'll try to put down a description of our app later on today. ICYMI I posted a gist of the rough folder/file structure we use at https://gist.github.com/opsb/d0977bcb30b42302f3f2dc3daf0befec

Marek Fajkus

unread,
Apr 20, 2017, 10:22:11 AM4/20/17
to Elm Discuss
Richard,

What I'm saying is that individual functions, as opposed to a group of functions and data structures (model, view, update, msg, etc) is the right unit of composition.

I struggle to understand to see where is a difference. Html.program constructor is record of functions as well. Sometimes it's just nicer to have arguments named. Also as docs and code clearly says it's just composing functions, nothing more. init with init, update with update... I think saying that pure function inside record is by any mean different than pure function living in module's namespace is really misleading. For instance this using elm-decode-pipeline you're lifting (operation called pure in Json.Decode it's succeed) function (usually type constructor) to `Decoder` context. And it's fine because this is how functional programming work.

You have mention experience with teaching beginners on many occasions. That's fair point I don't have that much experience teaching beginners however I have quite a lot experience with folks doing side project in elm being scared of complex apps.
For instance our HR have contacted Tomas just few days ago. Since he knows me from elm community meetups he DM. We're both czech (and was speaking czech) so I have to translate it (adding original and translation:

czech:
No na to zatím nemám :) jsem Funkcionalni lama :)
 
english:
 I can't do this :) I'm not that skilled in functional programming

Than I spend 2 hours trying to convince him that there is nothing to be afraid of.

Just on this Tuesday he wrote me again without previous warning (this is before interview which he will have next week):

cze:
Třeba se to vyřeší samo a nevezmete mě. Alespoň pak naboucham nějaké zkušenosti do zásoby na příští kolo ;)

eng: 
 Maybe it will resolve itself and will not hire me. At least I will have time to gain more experience on next round.

THIS IS COMPLETELY CRAZY!

We've already lost one talented guy because he was afraid that he won't be able to do anything on such scale! From my experience it's easier to hire js developer and teach him elm than to hire folks from community because they are so afraid when we show them js implementation we're porting.

Yosuke,

So Marek's library is for organizing entire app, not about using external library.

Maybe you've noticed that I haven't commented your example with reusability. That was on purpose because telling when and on what you should use it is completely different topic. For sure it can be used for integrating community components. It can be used anytime you're using Cmd.map Html.map and Sub.map. That's actually beauty of abstraction that it can be applied regardless of your domain. Same way s you can List.map view over List of data to turn them into view you can use this to map date picker's api to app and anything else. My intention is not telling anyone when and on what to use it. It's just super thin helper for doing (a, Cmd b) -> (c, Cmd e) with the use of functions.

What I think...

Richard
is building software for learning kids (which is actually ethically super important mission in my opinion) where initial idea, first prototype and final feature might looks really really different. I can imagine this might be a lot about exploration and validating and reevaluating ideas. I also think you did mention this in one of your talks but I can't remember which one it was. Logically there you see value of system that is easy to reshape and many language features helps you there. Honestly this is really building ball of mud but the point sometimes there is nothing wrong with that.. Mud can be reshaped in few minutes. The really crazy idea in your field would be to do DDD since that domain is so loosely specified.

Mark
on the other hand values design patterns and architectural thinking. He has experiences with identifying critical concerns up front by analyzing obviously pretty deterministic domain. In his view of development there're is really not time involved. His problem didn't change over night he is analyzing problem which is observably same each time you look at it. I also think he might be into DDD (Domain Driven Design). Personally I'm most of the time pretty skeptical about DDD and has many arguments with our CTO who is also fan of DDD.

Me
For me the important thing is separation of concerns. This is actually why we're reimplementing that app in first place. The biggest cost in our case is when any new feature breaks anything existing. By that cost I mean we will rather send for times that much on new feature rather then being able to break existing one. This is due to the fact we're working with data where slight bug in sorting in case of some weird state in analytics tool can resolve with switched order in exported data which is then big issue for many other people like data scientists which are using that data for testing machine learning modeling and have unpredictable results (that actually happened in past). I would go even further and intentionally make it hard to reshape system any any way that might lead to some unexpected side-effects. Anyway I don't think this is real point. Real point is that there is value of separating concerns.

I don't know what's the point of Richards way of argumentation. I have a feeling that he is trying to convince me about something he's not able to explain.

Claim composing function is good, composing init/update/view is bad is odd because:
  • obviously using functions is good there's no even need to say that
  • init/update/view are functions too
  • right from beginning I made it 100x clear that this is tiny abstraction you can use to simplify composition of update and init. That's assuming you want to do it in first place.
I don't see any real value about continuing in this direction. Anyone is trying to convince me about something he can't even name. Also I want to point out that I've found usage of term agile quite to be quite odd. I really don't see point in bringing that to this conversation.

With that I really apologize if this sounds mean in any way but I'm not willing to comment anything in this thread anymore. But head ups everyone. I hope we can still tolerate and respect each other and rather focus on what we can do for community beside flaming.


Oliver Searle-Barnes

unread,
Apr 20, 2017, 2:17:56 PM4/20/17
to Elm Discuss

If you have time, would you mind describing how the app works?

This is a fairly high view of our app and how it's architected but hopefully it's a useful level of detail (I've mentioned it a couple of times already but here's a rough gist of the folder/file structure we use https://gist.github.com/opsb/d0977bcb30b42302f3f2dc3daf0befec). 

To give you a bit of our context our product is an embeddable web app that adds discussion rooms and news feeds to your website. We have Phoenix on the backend and we're using elm-phoenix for integration in the client. As well as providing a fully integrated UI which includes all of the features, we will also allow individual features to be embedded within particular pages e.g. an article might have a discussion embedded at the end, but that same discussion mini-app is also  included in a view more closely resembling Slack with a sidenav etc. 

These requirements led us to an architecture where we have a single Store which handles data synchronisation and then several mini-apps which use this Store. The Store responsibilities include: subscribing to data feeds from the backend, caching results, mutating data in the backend and latency compensation (otherwise known as optimistic UI). At the top level these mini-apps are organised into complete pages. The pages themselves don't do much, they're mapped to urls and combine a set of mini-apps. Each mini-app is then responsible for it's own lifecycle (e.g. Loading/Loaded/Error). 

While the requirements have forced have led us towards the mini-apps design we've found that they're a great way to organise the codebase, developers can concentrate on a particular mini-app and get up to speed very quickly. 

Yosuke Torii

unread,
Apr 20, 2017, 2:53:29 PM4/20/17
to elm-d...@googlegroups.com
Marek

This reply is only to my part. Did not read other parts.
 
Maybe you've noticed that I haven't commented your example with reusability. That was on purpose because telling when and on what you should use it is completely different topic. For sure it can be used for integrating community components. It can be used anytime you're using Cmd.map Html.map and Sub.map. That's actually beauty of abstraction that it can be applied regardless of your domain. Same way s you can List.map view over List of data to turn them into view you can use this to map date picker's api to app and anything else. My intention is not telling anyone when and on what to use it. It's just super thin helper for doing (a, Cmd b) -> (c, Cmd e) with the use of functions.

I'm strongly against this way of thinking.

Telling nothing about who to use, when to use or what to use for is the easiest way. But does it make anyone happy? As you say, we can use Cmd.map, Sub.map and Html.map everywhere but we are happy with the fact `view` function does not return `Cmd msg` so you can say there must be no side effects in view.

"That's actually beauty of abstraction that it can be applied regardless of your domain." 

Yes it can be, but should not be. We (...not every one but at least many of us including me) found NOT everything is a component. The old TEA document was doing just like you; "There is a pattern that is sometimes useful". I thought "oh cool, everywhere in my app should be organized like this" ...and I regretted it later. Things got needlessly complicated. For example, some components required many options like { onSelect, onChange, ... } and the parent component just translated to that layer's msg. But actually that component was used only once. I refactored that component to have some knowledge about the application and the code got significantly simpler than before. After that, I've get that better habit to think like "okay, this is just a view, no need to have state for now".

It's just super thin helper for doing (a, Cmd b) -> (c, Cmd e) with the use of functions.

Maybe you need this function? This is thinner.

andThen : (model -> (model, Cmd msg)) -> (model, Cmd msg) -> (model, Cmd msg)
andThen update (model, cmd) =
  let
    (newModel, newCmd) =
      update model
  in
    newModel ! [ cmd, newCmd ]

This function enables separating concerns so you can apply many operations one by one. I'm recently using this and now `update` function is cleaner than ever. If your core problem is this, this might be the answer, not components. So I wanted to know what concrete problem you are really want to solve.

 

Richard Feldman

unread,
Apr 20, 2017, 3:20:47 PM4/20/17
to Elm Discuss
Thanks Oliver! That's super helpful. <3

as well as providing a fully integrated UI which includes all of the features, we will also allow individual features to be embedded within particular pages e.g. an article might have a discussion embedded at the end, but that same discussion mini-app is also  included in a view more closely resembling Slack with a sidenav etc...Each mini-app is then responsible for it's own lifecycle (e.g. Loading/Loaded/Error).

I apologize for giving you advice that led to a design that didn't work well for the mini-app business requirements, but I'm really glad to hear you were able to refactor it into a happy place! I can also appreciate that you liked how this worked in broader contexts. :)

That said, based on your note of "reusable view modules, may include any combination of view/update/Model but few have their own update/Model" - it sounds like we are approximately on the same page with regards to reuse. ;)

Oliver Searle-Barnes

unread,
Apr 20, 2017, 4:08:02 PM4/20/17
to Elm Discuss
You've definitely given me some great advice along the way, I'm very grateful!

I feel like this discussion has covered a few different scaling techniques and I feel like there's a good time to use all of them. Perhaps it would be good to just catalogue the different approaches along with some examples where people have found them useful in their projects.

Richard Feldman

unread,
Apr 20, 2017, 4:19:20 PM4/20/17
to Elm Discuss
I feel like this discussion has covered a few different scaling techniques and I feel like there's a good time to use all of them. Perhaps it would be good to just catalogue the different approaches along with some examples where people have found them useful in their projects.

That is a fantastic idea and I totally want to do that now!

Chad Woolley

unread,
Apr 20, 2017, 7:56:53 PM4/20/17
to elm-d...@googlegroups.com
I feel like this discussion has covered a few different scaling techniques and I feel like there's a good time to use all of them. Perhaps it would be good to just catalogue the different approaches along with some examples where people have found them useful in their projects.

That is a fantastic idea and I totally want to do that now!

Please do!  When we were learning Elm, our team spent many cumulative days of debating and discussing what is the "right" high-level architecture as we developed and evolved more SPAs.

This thread shows that the answer is a definitive "it depends".

It would be great to have a summary of these approaches, with code examples, in an easily-discoverable, "official" (referenced somewhere from Elm docs or website) place.  Or maybe in a book on Elm ;)

-- Chad

Rehno Lindeque

unread,
Apr 21, 2017, 10:41:55 AM4/21/17
to Elm Discuss
I have not seen the reverse happening.

I usually try not to get involved in long threads like this nowadays, but I feel obligated to quickly mention that we were also recently pretty happy with a recent refactor that reduced the boilerplate in an internal documentation page. (We haven't published this as a package, we're still building some experience with it and mostly using the inverted control that folks generally recommend).

doc =
    layout6
        Symbol.component
        Footprint.component
        PackageOverlay.component
        (SourceEditor.Form.component (SourceEditor.configure Example.sourceEditorConfig))
        (PartEditor.Form.component Example.partEditorConfig)
        ConsignEditor.Form.component
        (\symbol footprint packageOverlay sourceEditor partEditor consignEditor ->
            docBody []
                [ div []
                    [ docSection "components"
                        "Components"
                        [ docSubSection "symbol" "Symbol" [ symbol ]
                        , docSubSection "footprint" "Footprint" [ footprint ]
                        , docSubSection "packageoverlay" "PackageOverlay" [ packageOverlay ]
                        , docSubSection "sourceeditor" "SourceEditor" [ sourceEditor ]
                        , docSubSection "parteditor" "PartEditor" [ partEditor ]
                        , docSubSection "consigneditor" "ConsignEditor" [ consignEditor ]
                        ]
                    ]
                ]
        )

main =
    program
        { init =
            doc.init
                ( force Example.symbol
                , force Example.footprintFlags
                , force Example.packageOverlayFlags
                , force Example.sourceEditor
                , force Example.partEditor
                , {}
                )
        , subscriptions = doc.subscriptions
        , update = doc.update
        , view = doc.view
        }

This was a fantastic reduction from the previous boilerplate and I'm really pretty happy with how it turned out (but I don't want to hold this particular example up as canonical, this is still an experimental approach with its own caveats, like any other architecture).

Erik Lott

unread,
Apr 21, 2017, 4:51:41 PM4/21/17
to Elm Discuss
To recap:
  1. Earlier I said "between 0.16 and today, we learned that a Model-View-Update triplet is the wrong unit of composition for Elm applications...composing individual functions was both simpler and consistently led to a much better experience. I've laid out my advice for specifically how to do that here."
  1. Later I pointed out an example of when it would be useful to use Html.map and Cmd.map to make a reusable signup form, and for that use case it happened to make sense to have a separate model, view, and update.

Richard, I think it's only your broad use of the word "Elm Applications" that is a little misleading for some folks.  When you say "Elm Application", maybe you mean: Elm applications like we write at NoRedInk (non-SPA applications). ie. each page of the web app is served from a ruby on rails server, and that page may include an Elm app(s). In your context, I agree with your advice.

When other folks are talking about "Elm Applications", they're talking about Single Page Applications. In the context of an SPA, the Model-View-Update triplet will indeed be a unit of composition for an app - each logical page within the app will likely be a triplet. Of course, your advice still applies to the individual "pages" within an SPA, but not necessarily the overall architecture itself.

The distinction is important, because it's obviously confusing folks. This isn't the first time I've seen a dev misunderstand your statement - Model-View-Update triplet is the wrong unit of composition for Elm applications - and attempt to write an entire SPA without using a triplet. Very painful stuff. 

I know that's not what you're advising, but without stating that explicitly, devs are left to fill in the blanks themselves.



On Wednesday, April 19, 2017 at 1:27:09 PM UTC-4, Richard Feldman wrote:
so, the Model-View-Update triplet is NOT the wrong unit of composition for Elm applications? :) 

> How do you propose to split the functionality one has in a highly complex app with a lot of pages without using those triplets?

I don't haha...I just defended their use a few posts ago, complete with the specific example of the reusable signup form.

To recap:
  1. Earlier I said "between 0.16 and today, we learned that a Model-View-Update triplet is the wrong unit of composition for Elm applications...composing individual functions was both simpler and consistently led to a much better experience. I've laid out my advice for specifically how to do that here."
  1. Later I pointed out an example of when it would be useful to use Html.map and Cmd.map to make a reusable signup form, and for that use case it happened to make sense to have a separate model, view, and update.
    In (1) I am saying that I expect you'll have a better time if you think about building Elm applications in terms of composing individual functions, not in terms of composing Model/View/Update triplets.

    In (2) I am giving an example of a very specific set of circumstances in which a separate Model/View/Update makes sense.

    In summary: "Here is a useful but complex tool. Always reach for a simpler one first." 

    Richard Feldman

    unread,
    Apr 21, 2017, 7:59:44 PM4/21/17
    to Elm Discuss
    This isn't the first time I've seen a dev misunderstand your statement - Model-View-Update triplet is the wrong unit of composition for Elm applications - and attempt to write an entire SPA without using a triplet. Very painful stuff. 
    I know that's not what you're advising, but without stating that explicitly, devs are left to fill in the blanks themselves.

    Gah, I wonder if that's what happened in Oliver's case. 😬

    Let me try to clarify:
    1. Nesting Model-View-Update is a useful technique under the right circumstances. One example is reusing complex things like signup forms. Another example is a network of independent mini-apps like what Oliver is building. And yes, absolutely, a third example - most likely the most common example in use today - is going from the top level of a SPA to a logical "page" within that SPA.

      In practice, pages almost always want their own model, view, and update, and this technique is absolutely the right choice for having the top-level application delegate to one of these "pages" depending on the current route. (Note that this is true in Elm 0.18, but there may be an easier way in 0.19 once asset management opens some new doors. However, even if that happens, this will still be a useful technique to know about and use under the right circumstances!)
    2. My objection is specifically to what I read in the OP, which suggests that this situationally useful technique ought to be used pervasively:

      In Elm 0.16, TEA was all about being composable. The examples focused on things like going from one counter to multiple counters. [...] But somewhere along the way, portions of the Elm community, including Evan, seem to have swung hard against composing TEA units and now just recommend using functions

      Composing together Model/view/update triplets to build your application has never been what the Elm Architecture was "all about."

      The reason "The examples focused on things like going from one counter to multiple counters" is no longer true is that Evan took those examples down once he realized they'd been giving people this wrong impression. He'd intended to convey "check it out - here is a useful tool to include in your toolbox" but a lot of readers (quite reasonably) read it as "this is the right technique to use whenever I want to build something reusable, even something as simple as a counter" - which was never a message Evan intended to send.

      To be super clear, even though I think this technique is useful in the right circumstances, I disagree with the notion that "composing TEA units" is what the Elm Architecture is "all about," and I again disagree that the right way to think about organizing the content of "pages" within an application is in terms of composing model/view/update triplets. It really is all about the individual functions!
    I hope this clarifies...I totally appreciate that my attempts to share what I've learned over these past few years do not always come across as I intended, and I am extremely grateful for the direct and honest feedback. :)
    Reply all
    Reply to author
    Forward
    0 new messages