Can Elm do *this* yet?

254 views
Skip to first unread message

Paul Chiusano

unread,
Jul 1, 2014, 9:18:35 PM7/1/14
to elm-d...@googlegroups.com
In this reddit thread from a while ago, someone pointed out something that is difficult or impossible to express in Elm:

> An example I suggest implementing is the following. Start with a simple counter widget: a label displaying the current count, and a pair of buttons for incrementing and decrementing the counter. Then combine two counters on a single page. Then implement a variable number of counters: there is a "add counter" button which adds a new counter, and with each counter goes a button "delete" to delete that counter. In addition, display the sum of the values of all the counters.
> That should be trivial, and it is trivial with conventional OO GUIs. If it is not trivial, that means that there is a new abstraction to be found.

I'm curious if there's been any more progress on addressing use cases like this. A large number of applications will be adding and removing page content very dynamically, based on input received from the current set of UI elements on the page. I don't see a way of writing such applications, but maybe that's due to my lack of imagination.

Related to this, I was looking through the `Graphics.Element` and was hoping to see a function like `varying : Signal Element -> Element`, but that does not exist.

Any pointers or suggestions on how to write Elm applications of this sort?

Cheers,
Paul :)

John Mayer

unread,
Jul 1, 2014, 9:53:47 PM7/1/14
to elm-d...@googlegroups.com

I have an example of something like this but it's broken due to some bugs in the Array code. I'll try to rewrite it with list and semi release it - I've been doing some widget stuff myself and I think that I have some useful commentary. I'll try to make sure I don't repeat previous discussion but in essence, yes, it becomes really difficult to encapsulate widget abstractions that deal with collections of widgets and/or nested widgets.

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

Max Goldstein

unread,
Jul 1, 2014, 10:16:34 PM7/1/14
to elm-d...@googlegroups.com, john.p....@gmail.com
Yes, it can! http://share-elm.com/sprout/53b369e4e4b07afa6f98274d

True, it looks like crud, but I'm sure we could clean it up a bit and stick it on the examples page. A few implementation tricks: the classic event datatype to homogenize different interactions. Associating each counter with a unique identifier (the u is there really to prevent collisions with id the identity functions). And Counters keep track of their button Elements. It's not the best modularity, and in fact it's probably not necessary: just create new buttons every time the counter is rendered. This seems a bit wasteful though, but maybe I'm just wary about the new Graphics.Input handle system. Ultimately, the real trick - not available when the challenge was poses, I suspect - was to dynamically add event many senders to a statically known and wired event pool.

Thanks for the challenge, that was fun! I didn't really have to think too much about how to implement it, it just kind of came to me (after many months and many programs, for sure). But hey, once you learn Elm, you'll find yourself structuring your program around a state, an event, and a transition function for foldp.

Paul Chiusano

unread,
Jul 2, 2014, 3:35:25 AM7/2/14
to elm-d...@googlegroups.com

Thanks, that gives me a good place to start!

However, I don't like that class of solution very much. I want fine grained decomposition of system state into different events and behaviors, like what I get for free when the layout structure is fixed - I don't want to be forced into using a monolithic state type and monolithic state update function. That's ugly and not modular, composable, etc.

So, is it possible to do better in Elm?

Paul

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

Jeff Smits

unread,
Jul 2, 2014, 4:53:04 AM7/2/14
to elm-discuss
I wholeheartedly agree with that Paul. The monolithic state/update always annoys me. 
My Signal Loops proposal is partly about mitigating this issue, but it's rather complicated so it's not done yet. I really need to sit down and get it done some time, but I think I'll have time to work on it in the coming weeks so we'll see. 

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

Alex Neslusan

unread,
Jul 2, 2014, 8:33:50 AM7/2/14
to elm-d...@googlegroups.com
I find it's also more composable to have your main update function be of type:

update : (State -> State) -> State -> State

and then your main state signal function is something like

state : Signal State
state = foldp update initialState updates


and then your updates function merges together different State -> State functions which trigger on your different input events. Then you don't need something like an Event type, and it becomes much easier to add more functionality without remodeling the existing structure. I did something like this here.

You still have a monolithic state type I guess. I've also come around to thinking Signal Loops would be a much more natural way to express certain complex things like this.

Max Goldstein

unread,
Jul 2, 2014, 8:51:57 AM7/2/14
to elm-d...@googlegroups.com
I actually like how Elm doesn't let you "cheat", that you have to be perfectly clear about the state of the program and what constitutes and event. Granted this likely won't scale .... I'm aware of using functions as events, first discovered (?) by Max New here ... I refrained from going that route since it's more complicated, and I thought it would make a better point to show that the task could be done without using the biggest gun in the arsenal. The challenge was more about dynamically created event handlers, made possible by the revisions to Graphics.Input a few months ago ... that said I do like the event-as-function methodology, since it (1) makes it much easier to extend than a datatype (2) unites all the logic in a particular case, rather than spreading it over a many signals which are merged (implicitly in the case of Graphics.Input) and then case analysis in the transition function (foldp's function argument). The transition function is merely function application; all the logic of how an event changes the state is pushed to event signals themselves. ... I'll give this another look over in a day or two for rendering improvements and small style concerns, and then refactor into the event signal model.

Evan Czaplicki

unread,
Jul 2, 2014, 9:24:41 AM7/2/14
to elm-d...@googlegroups.com
Working on paper to outline a kind of "nesting doll" approach to this problem in which you use the write abstraction for the level of complexity you face.

The idea is that Message Passing Concurrency is the most general. Elm's version of FRP is less general, but cuts away a ton of complexity that makes it easy to make widgets. And Self-Adjusting Code cuts away FRPs time sensitivity, making it easier to cache computations intelligently. These can all nest inside each other really nicely.

The idea is that you should use the appropriate approach. Everyone wants their thing to cover all possible problems. I think this is not wise because you'll start stretching an abstraction to the point that it is not even nice for its core competency any more.

Right now the way to wire lots of Elm widgets is to embed them in JS and communicate with them using ports. I can imagine a couple ways to do this within Elm, but it's not time yet. We can all understand the general problem, but I feel I need more data from users on exactly what happens in practice to design a good solution.

I also expect most early users will be embedding anyway. No industrial user will sit down and decides "I want to throw all of my code away and rewrite it entirely in Elm" so we have time to sort this out.


On Wed, Jul 2, 2014 at 2:51 PM, Max Goldstein <maxgol...@gmail.com> wrote:
I actually like how Elm doesn't let you "cheat", that you have to be perfectly clear about the state of the program and what constitutes and event. Granted this likely won't scale .... I'm aware of using functions as events, first discovered (?) by Max New here ... I refrained from going that route since it's more complicated, and I thought it would make a better point to show that the task could be done without using the biggest gun in the arsenal. The challenge was more about dynamically created event handlers, made possible by the revisions to Graphics.Input a few months ago ... that said I do like the event-as-function methodology, since it (1) makes it much easier to extend than a datatype (2) unites all the logic in a particular case, rather than spreading it over a many signals which are merged (implicitly in the case of Graphics.Input) and then case analysis in the transition function (foldp's function argument). The transition function is merely function application; all the logic of how an event changes the state is pushed to event signals themselves. ... I'll give this another look over in a day or two for rendering improvements and small style concerns, and then refactor into the event signal model.

--

Spiros Eliopoulos

unread,
Jul 2, 2014, 10:24:34 AM7/2/14
to elm-discuss
Here's an implementation using elm-d3, complete with clown colors:


-Spiros E.

Paul Chiusano

unread,
Jul 2, 2014, 10:27:58 AM7/2/14
to elm-d...@googlegroups.com

Thanks for the replies everyone, this gives me a lot to think about. One idea that occurs to me to improve the modularity of using a single state type is to use a Lens into the state, so that different parts of the code can be aware of only the portion of the state they care about (this combinator is called zoom in Control.Lens and has an absurdly general type, but the basic idea is pretty simple, it's zoom : Lens s0 s -> State s a -> State s0 a). I'll play around with this and report back.

Max, I'd like to see that improved code when you get the chance!

Evan, do you have an early draft of that paper, or an informal writeup somewhere? It sounds tantalizing, but I need more detail than what you just gave. :) 

> I also expect most early users will be embedding anyway. No industrial user will sit down and decides "I want to throw all of my code away and rewrite it entirely in Elm" so we have time to sort this out.

IMO, I think nailing down a good story for this sort of dynamism is pretty important, even in the short term. Reason is that there's also a class of users (like myself) that are starting new projects and are considering Elm. An existing project can just incrementally rewrite some pages that play well to Elm's current strengths, but new projects are making more 'all or nothing' tech decisions between various possible client side options. It's important therefore that new projects be able to answer the question 'Is Elm expressive enough for these use cases?'

For me, expressiveness is key with any technology evaluation - before investing in a technology, I much prefer to know, up front, if the tech can express my use case without total hacks. Once I know that, I start looking at other 'nice to haves' to make a decision.

Paul :)

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

Evan Czaplicki

unread,
Jul 2, 2014, 12:39:54 PM7/2/14
to elm-d...@googlegroups.com

Thanks for the replies everyone, this gives me a lot to think about. One idea that occurs to me to improve the modularity of using a single state type is to use a Lens into the state, so that different parts of the code can be aware of only the portion of the state they care about (this combinator is called zoom in Control.Lens and has an absurdly general type, but the basic idea is pretty simple, it's zoom : Lens s0 s -> State s a -> State s0 a). I'll play around with this and report back.

I believe Clojure's Om library has the same approach when it comes to having a large chunk of state. The use a thing called a cursor to deal with this, which I have been told is very much like a lens, but with a more accessible name.

I have definitely discussed the idea of using lenses in Elm with Spiros and a couple others. I think it's a valuable direction to explore, and my main hope would be that connecting it to a very practical problem like this would help make the API simple and clear to newcomers. I suspect we can learn from Om on this :)

That said, I find Spiros's code quite easy to understand. No one has written gmail in Elm yet, so I can't say with 100% certainty, but I have a sense that writing modular code is possible as things are. Something like this:

Counter.Model
Counter.Update
Counter.Render

So a counter is defined as a set of pure functions describing the state, how it advances, and how it is viewed. This same pattern can be used for however many widgets you want. Because it's all pure functions, you are free to build up composite widgets.

I think there is something quite nice about this, though I am curious how you feel about this kind of architecture! Once I know when/how/why it can fail it'll be clearer how to improve.

Max, I'd like to see that improved code when you get the chance!

Evan, do you have an early draft of that paper, or an informal writeup somewhere? It sounds tantalizing, but I need more detail than what you just gave. :)

Working on it! It's due soon :) The rough idea from early on was to somehow enclose modules into self-contained widgets (like in AFRP). A recent iteration of this was something like this:

component Counter where

port incoming : Signal Int
port outgoing : Signal Int
main = lift render (foldp step state inputs)

As an independent component, this is exactly like a node in a message-passing system. We have three explicit channels for sending info in and out of the node. When we hook those channels up to things, we get results. The tricky part is finding all of the implicit channels, like Mouse.position, such that the component can be entirely self contained.

This sounds cool to me, but it's a massive change that adds non-trivial complexity to the language. Add to that the fact that JS does not have real facilities for concurrency and some of the draw of this is lost. In any case, I think this could work, but I want Elm to develop with its users:
  • If we add this too early, no one knows they want it and just see that Elm is hella complex. No users.
  • If we wait, users say "man, I have problem X with the monolith approach" and we can design with those pain points in mind. This approach may not be best for the pain points that actually arise in practice.

> I also expect most early users will be embedding anyway. No industrial user will sit down and decides "I want to throw all of my code away and rewrite it entirely in Elm" so we have time to sort this out.

IMO, I think nailing down a good story for this sort of dynamism is pretty important, even in the short term. Reason is that there's also a class of users (like myself) that are starting new projects and are considering Elm. An existing project can just incrementally rewrite some pages that play well to Elm's current strengths, but new projects are making more 'all or nothing' tech decisions between various possible client side options. It's important therefore that new projects be able to answer the question 'Is Elm expressive enough for these use cases?'

Great point, I agree the story should be better. Do you think the points I have made above address this? What do you think we can do better still? 

For me, expressiveness is key with any technology evaluation - before investing in a technology, I much prefer to know, up front, if the tech can express my use case without total hacks. Once I know that, I start looking at other 'nice to haves' to make a decision.

Makes sense :) I think the paper will help with this. As a preview, I consider it an open question whether Applicative FRP and Monadic FRP are equally expressive. My feeling is that they are fundamentally the same but rely on different mechanisms for structuring applications (pure functions in Applicative FRP, flatMap in Monadic FRP). I don't know any decisive counter examples yet, though I also haven't been able to formalize what it'd really mean for them to be "equally expressive".

Paul Chiusano

unread,
Jul 2, 2014, 1:20:10 PM7/2/14
to elm-d...@googlegroups.com
Thanks for the reply. I think I need to play with these ideas a bit and see how it works out. I can see how it is possible to express these use cases using some combination of the approaches mentioned, it's more that I wonder how nicely it will work out for a complex application (Gmail in Elm, for example).

What are the types of Counter.Model, Counter.Update, and Counter.Render? And is there some more general Widget type you could create to encapsulate this sort of pattern? What would its API be?

One of the concerns I have with going down this road is that you end up with more complexity in user code, where user code now has to deal with separate Widget and Signal types, and lots of additional plumbing, when the problem could be solved at the framework / language level by adding something like dynamic event switching.

Evan Czaplicki

unread,
Jul 2, 2014, 1:50:54 PM7/2/14
to elm-d...@googlegroups.com
Counter.Model and friends would be modules. Something like the architecture used in this and this, though just repeated to represent many kinds of things. Composition becomes something like this:

module Model where

import Counter.Model as Counter
import Task.Model as Task

type State =
    { counters = [Counter.State]
    , tasks = [Task.State]
    }

Modules are the encapsulation method in this world, so there's no extra conceptual stuff to deal with. Does that make it clearer? Does someone else have an app written in this style they can share?


--

Zinggi

unread,
Jul 2, 2014, 1:58:35 PM7/2/14
to elm-d...@googlegroups.com
How about this? I merged three examples from the elm website into one elm program.
Would this be a reasonable way of doing it? It seems to work just fine.
However, I guess once you try to communicate between different parts it gets trickier. Also It takes a bit of manual work. Plus, if you use foldp somewhere it seems to get really tricky.
Ideally, we could just compose each program's main method together.

Jeff Smits

unread,
Jul 2, 2014, 3:03:07 PM7/2/14
to elm-discuss
I've edited your merged examples thing, so there are two easy extension points (for more inputs and for more programs): http://www.share-elm.com/sprout/53b456a8e4b07afa6f9827ec
I think if you use Playgrounds it'll be easier to compose these programs.

Sean Corfield

unread,
Jul 2, 2014, 6:45:36 PM7/2/14
to elm-d...@googlegroups.com
On Jul 2, 2014, at 9:39 AM, Evan Czaplicki <eva...@gmail.com> wrote:
> I believe Clojure's Om library has the same approach when it comes to having a large chunk of state. The use a thing called a cursor to deal with this, which I have been told is very much like a lens, but with a more accessible name.

Yes. There's global "application state" and then cursors can be applied to focus the state for each (child) component. Updates applied in a child (to a cursor) are propagated back into the overall application state. This allows each component to be concerned only with the piece of the (global) state that it is given access to. Components also have local state (entirely encapsulated) for managing their own behaviors.

For the counter problem in Om, you'd use local state for the counter value and each counter component would have the + / - buttons. The application would have global state to track the counter components and you'd most likely use a core.async channel (in the application component) to communicate delete actions up from the counter components.

If anyone's really interested in seeing this in ClojureScript / Om / core.async, I could probably rustle it up fairly quickly...

Sean Corfield -- (904) 302-SEAN
An Architect's View -- http://corfield.org/

"Perfection is the enemy of the good."
-- Gustave Flaubert, French realist novelist (1821-1880)



signature.asc

Jeff Smits

unread,
Jul 3, 2014, 3:51:42 AM7/3/14
to elm-discuss
I remember watching a video from NDC Oslo that used clojurescript and om to create a minesweeper UI. There they also use one of those core.async channels. I'm not sure a full-hour video is a good replacement of a simple example and explanation, but I thought I'd share it anyway.

Max Goldstein

unread,
Jul 5, 2014, 4:44:35 PM7/5/14
to elm-d...@googlegroups.com
Okay, so here is the first revised version: http://share-elm.com/sprout/53b8583be4b07afa6f982ab6
Specifically, I use custom buttons with some basic Forms to make it look prettier. Except text centering seems to be perceptibly off, and supplying the same form with minor variation (color) wound up being quite verbose. I also took the button elements out of the Counter record type and regenerate them on render.

Here is the "signal of functions" version: http://share-elm.com/sprout/53b85aa0e4b07afa6f982ab7
Not storing the button elements in the record backfired from a modularity standpoint: I had to modify renderButton in the Display section. I guess it's not a trivial question: should the code that knows how a counter is rendered also know how its buttons are wired? The type signature of customButton seems to force these two things together.

Furthermore, button presses work by triggering a transition that maps over all counters trying to find the counter that was responsible for putting the button there in the first place. This makes update O(n), and although we could use a Dict to get O(log n), it just seems like a silly thing to do. In a mutable OO language we'd just change the count on the counter, done. But since the counters have to be stored in an immutable collection of some kind, I don't suppose we can get constant time.

Okay, I couldn't resist doing a Dict version: http://share-elm.com/sprout/53b862f2e4b07afa6f982abb
We are saved by the fact that the UIDs are always increasing, so D.values cntrs will always return them in the order we want. If we added the buttons back to the Counter record, we could eliminate the need to store the UIDs explicitly (which would be good because we store it in the key as well as the value). If you really wanted render/update modularity, you could keep the partially applied custom button function in the record.

Hope that's useful to someone!
Reply all
Reply to author
Forward
0 new messages