Code Smells in the Elm Architecture

647 views
Skip to first unread message

Richard Gebbia

unread,
Jan 13, 2016, 11:17:53 AM1/13/16
to Elm Discuss
I'd like to preface this by saying that I still really like the Elm Architecture and the experience of coding in Elm. With that said, however, there are three aspects of the architecture that have been bugging me.

1. Calling update and view on the whole application any time one thing changes

I realize that this is the pattern you get when you're using a virtual DOM implementation, but it screams "unnecessary calculation" to me. You can mitigate this somewhat with the "lazy" functions, but it still has to do a check to make sure nothing has changed, which wastes precious cycles. (I could be totally wrong about this.)

It would be nice to have a way to specify that a change is local to a particular component. Though this would also mean that local side effects are effectively hidden, which might go against the principles of the Elm Architecture.

2. Bubbling events all the way up and back down again 

From what I understand, the way Signal.forwardTo works is it sends a Message to original Mailbox mapped over the provided function. Almost 100% of the time this is used just to wrap a child component's Action into its parent component's Action. This goes all the way up to the application root and then back down to where it's processed. 

This is probably tied to #1, but it really seems extraneous. Again, I don't want to pay for architecture in cycles if I don't have to. My background is primarily in games, where cycles are of the utmost importance.

3. MOST IMPORTANTLY: Separation of Update and View/Effects as second-class citizens

It really hurts that the only accepted outputs of the Elm compiler are (Signal) Element and Html. The Elm Architecture can easily apply to more than just view code. 

What if I want to make a web server in Elm? The "Actions" would be HTTP requests and the "view" would be HTTP responses. 
What if I want to make a command-line utility in Elm? The "Actions" would be text and the "view" would be text.
What if I want to make a robot in Elm? The "Actions" would be sensor input and the "view" would be actions that the robot could take.

Currently, if I wanted to make any of these, I'd have to do so using ports (which is a lot better than having no options at all, but still suboptimal).

This ties directly into (what I believe to be) the unnecessary separation of the update and view functions. Html is output and so are Effects, so why not just have one function with type (Signal.Address Context -> Action -> Model -> (Model, Output))?

I fully realize that, in practice, if I was making UIs for the web, I would probably have my own view function, but that shouldn't be tied to the architecture itself.


Overall, I'd love to see Elm move forward, and I think addressing these three issues will be important in doing so. Unfortunately, I don't have solutions for issues 1 and 2, and my solution for issue 3 might very well be incorrect. I'd like to know what you all think.

Peter Damoc

unread,
Jan 13, 2016, 12:30:02 PM1/13/16
to Elm Discuss
Hi Richard, 

Thank you for your input. 

I don't mind paying with CPU/GPU cycles for a clean and easy to understand architecture BUT I do agree that there is such a thing as "too high of a price". I would however want to see benchmarks that show this price because I know that my intuition is bad around this stuff. I don't know enough about Elm to know how it optimises stuff and as such I cannot diagnose stuff like this without numbers. :) 

Regarding main output, I think that it is related more to the current focus of Elm on the web. Other outputs are in the works. 
Take a look at elm-console
https://github.com/laszlopandy/elm-console
for an approach to creating console applications. 

Regarding the update - view separation, I too had thoughts about that kind of stuff starting from the foldp interface. 
I thought about using a mega type that encapsulates both state and output and have an initial version of this type called `init` and a function `next` that takes actions and moves this type to the next state. `next` would be the equivalent of `update`. 

What I've discovered is that the best way to criticise the Elm architecture is with code. 

Reimplement the functionality of the eight Elm Architecture examples and present the alternative. 
If your approach is better, this would be obvious in comparison OR maybe it will start a discussion about using some of the ideas from your implementation and producing a hybrid.

I too believe that Elm Architecture might not be the end of the road but from what I've seen so far, it is the clearest and most viable approach. 

I would love to see alternatives. Again, with the eight use-cases implemented as a showcase. 



 

--
You received this message because you are subscribed to the Google Groups "Elm Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.



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

Alexandre Galays

unread,
Jan 13, 2016, 3:44:53 PM1/13/16
to Elm Discuss
It may not be intuitive, but 1) is not a problem at all. The DOM is so much slower than both creating an entire vdom structure + diffing it that you still get good performances. There exists a very simple optimization however: lazy. By wrapping some of your view function with lazy, their virtual-dom output won't be recalculated unless the function arguments actually changed; and last time I checked, virtual-dom skipped diffing completely if node trees were equal by reference. So you actually end up creating the DOM and diffing it for a small part of the app that actually changed.
it's a much saner model than trying to keep track of what exactly should be redrawn after every single action. 
Message has been deleted

Richard Gebbia

unread,
Jan 19, 2016, 11:08:10 AM1/19/16
to Elm Discuss
Thanks for the responses, guys. As for Problem #3, I took your advice, Peter, and I re-implemented start-app and the 8 examples here: https://github.com/richard-gebbia/start-app-alt.

I'm curious to hear what you all think. As is usual with this kind of thing, there are benefits and drawbacks. I made note of what I came across in the readme.

Irakli Gozalishvili

unread,
Jan 21, 2016, 4:21:48 PM1/21/16
to Elm Discuss


On Wednesday, January 13, 2016 at 8:17:53 AM UTC-8, Richard Gebbia wrote:
I'd like to preface this by saying that I still really like the Elm Architecture and the experience of coding in Elm. With that said, however, there are three aspects of the architecture that have been bugging me.

1. Calling update and view on the whole application any time one thing changes

I realize that this is the pattern you get when you're using a virtual DOM implementation, but it screams "unnecessary calculation" to me. You can mitigate this somewhat with the "lazy" functions, but it still has to do a check to make sure nothing has changed, which wastes precious cycles. (I could be totally wrong
about this.)
 
Please note that this is also how React for example deals with it. lazy creates a thunk object and during virtual DOM diff few identity checks are enough for it to decide if anything needs to re-run or not. But yes sometimes even those identity checks may be waste of cycles that is why `lazy` is separate and depending on case you can decide to use it or not. 



It would be nice to have a way to specify that a change is local to a particular component. Though this would also mean that local side effects are effectively hidden, which might go against the principles of the Elm Architecture.


The thing is any particular change may have affect on the embedder of the component which may decide to do a different update or update more components it is supervising. In fact Elm architecture proves itself over time that not allowing local changes or state is a huge win as in practice as your application grows more uses of components require alternative embedding requiring exposing more internals etc.. This is not a problem & requires no changes in elm specifically because this architectural decision.

 

2. Bubbling events all the way up and back down again 

From what I understand, the way Signal.forwardTo works is it sends a Message to original Mailbox mapped over the provided function. Almost 100% of the time this is used just to wrap a child component's Action into its parent component's Action. This goes all the way up to the application root and then back down to where it's processed. 

This is probably tied to #1, but it really seems extraneous. Again, I don't want to pay for architecture in cycles if I don't have to. My background is primarily in games, where cycles are of the utmost importance.

You are absolutely right that it ties to #1 and my answer is also ties to my previous answer. The thing is in larger applications simple actions do trigger changes in multiple components not just the one that triggers the action. In some cases cascade starts one level above the component triggering action in other cases it can be few levels above it. For example hitting a simple button in my browser UI may cause changes in completely different parts of app, for example create a new tab & place cursor in the address bar. 

I think good way of thinking about it is:

Each module defines it's own vocabulary in terms of actions. Each embedder of the module is responsible to provide translation between it's children vocabularies to it's own or across children which is effectively what `Signal.forwardTo` and `Effects.map` are there for.


 

3. MOST IMPORTANTLY: Separation of Update and View/Effects as second-class citizens

It really hurts that the only accepted outputs of the Elm compiler are (Signal) Element and Html. The Elm Architecture can easily apply to more than just view code. 

What if I want to make a web server in Elm? The "Actions" would be HTTP requests and the "view" would be HTTP responses. 
What if I want to make a command-line utility in Elm? The "Actions" would be text and the "view" would be text.
What if I want to make a robot in Elm? The "Actions" would be sensor input and the "view" would be actions that the robot could take.

Currently, if I wanted to make any of these, I'd have to do so using ports (which is a lot better than having no options at all, but still suboptimal).

This ties directly into (what I believe to be) the unnecessary separation of the update and view functions. Html is output and so are Effects, so why not just have one function with type (Signal.Address Context -> Action -> Model -> (Model, Output))?

I fully realize that, in practice, if I was making UIs for the web, I would probably have my own view function, but that shouldn't be tied to the architecture itself.

I do think the separation of Update and Output is in fact useful. But I do agree that View is just a specific case of an Output. I can imagine that over time as Elm will find itself in more contexts like you've mentioned it's architecture could be generalized towards "model, update, output" vs "model, update, view" but given Elm's current focus I don't think that specialization to "View" as an "Output" is surprising.
Reply all
Reply to author
Forward
0 new messages