Inter triplets communication.

405 views
Skip to first unread message

sergii.k...@zalando.de

unread,
Nov 28, 2016, 9:01:34 AM11/28/16
to Elm Discuss
Hi All,
I know that Elm doesn't have the concept of components but instead it has triplets. They should behave similar to components, they encapsulate internal implementations.
I use this concept in my App and I faced some architectural questions. What is the proper or better way to communicate between triplets without breaking encapsulation?
For example, let say I have two "widgets", UserInfo and Ratings. In main "update" function I have this

   Ratings act ->
      let
        ( ratings, effect ) =
          Ratings.update act model.ratings
      in
        ( { model | ratings = ratings }, Cmd.map Ratings effect )

    UserInfo act ->
      let
        ( userInfo, effect ) =
          UserInfo.update act model.app
      in
        ( { model | userInfo = userInfo }, Cmd.map UserInfo effect )



I have a button "Login" in the UserInfo widget. It will call backend API and return the user state. 
Then I want to load and update the Rating widget.
How and where do this?


Should I catch "UserInfo Loaded" event in the main "update"?
(In the UserInfo "update" function if cannot return a command(effect) "Ratings Reload" directly, because of this "Cmd.map UserInfo effect")

   Ratings act ->
      let
        ( ratings, effect ) =
          Ratings.update act model.ratings
      in
        ( { model | ratings = ratings }, Cmd.map Ratings effect )

    UserInfo act ->
      let
        ( userInfo, effect ) =
          UserInfo.update act model.app
          
          cmd =
                    if act==Loaded 
                    then Cmd.batch [ Cmd.map UserInfo effects, Cmd.message (Ratings Reload) ]
                    else  Cmd.map UserInfo effects
      in
        ( { model | userInfo = userInfo },  cmd )



I feel it will be too messy to keep all this inter-triplet logic here. Is there a better way? Maybe  something similar to pub/sub or I'm fundamentally wrong here? 

Rupert Smith

unread,
Nov 28, 2016, 9:38:13 AM11/28/16
to Elm Discuss
On Monday, November 28, 2016 at 2:01:34 PM UTC, sergii.k...@zalando.de wrote:
I feel it will be too messy to keep all this inter-triplet logic here. Is there a better way? Maybe  something similar to pub/sub or I'm fundamentally wrong here? 

I've pulled out a message channels pub/sub implementation, adapted from Gusztáv Szikszai's elm-ui (https://github.com/gdotdesign/elm-ui). It can be found here:


So far I have only had to do 'inter-triplet communication' for the same reason as you: handling user logins. For changing the 'auth state' of the application, I set up message channels (called login, logout, refresh and unauth). These are all fire and forget operations; they do not need a reply from the auth module. This makes them well suited to using this style.

I originally started out doing this with ports but have now switched to a pure effect module. This effect module is not part of the official packages - nor have I tried to get it accepted. Still evaluating, but generally I think it is a reasonable idea.

For modules that need to know what the current 'auth state' is, I implemented different views that get selected at the top-level depending on what the current users logged in state is. Like this:

        if authenticated && hasPermission then
            app model
        else if authenticated && not hasPermission then
            notPermitted model
        else if not authenticated && logonAttempted then
            notPermitted model
        else
            welcome model

That is basically the top-level of my applications view. When authenticated with the right permissions, show the app. If not having the right permission, or a login attempt just failed, show the 'not permitted please retry' view, otherwise show the welcome screen.

Works for the auth scenario, but feels a bit hacky to me. Certainly not a generalizable solution for building components.

Nick H

unread,
Nov 28, 2016, 11:40:21 AM11/28/16
to elm-d...@googlegroups.com
(In the UserInfo "update" function if cannot return a command(effect) "Ratings Reload" directly, because of this "Cmd.map UserInfo effect")

It seems to me that you should not be wrapping "Loaded" in a "UserInfo" action. Judging from your update function, I am guess your main action type looks like:

type Action = UserInfo UserInfo.Action | Ratings Ratings.Action

Here, these are wrappers for actions that are dealt with in one module of your program or another. I suggest that you modify this to be

type Action = Loaded | UserInfo UserInfo.Action | Ratings Ratings.Action

Because "Loaded" has consequences in both of your components, it ought to be dealt with in your main update function.


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

sergii.k...@zalando.de

unread,
Nov 29, 2016, 9:45:36 AM11/29/16
to Elm Discuss
@Rupert Smith
Your module for pub/sub looks great! I will be useful for a lot of people like me. I think you should publish it.
Can you add a README or provide few examples how to use it properly?  

@Nick H
I also thought about this
type Action = Loaded | UserInfo UserInfo.Action | Ratings Ratings.Action
But then I should also change this
 Cmd.map UserInfo effect
to
 Cmd.map effect  
and in UserInfo.update use global actions everywhere. And this also brakes encapsulation, so I cannot use 
this triplet in my other apps with different main action type. 


Rupert Smith

unread,
Nov 29, 2016, 10:27:18 AM11/29/16
to Elm Discuss
On Tuesday, November 29, 2016 at 2:45:36 PM UTC, sergii.k...@zalando.de wrote:
@Rupert Smith
Your module for pub/sub looks great! I will be useful for a lot of people like me. I think you should publish it.
Can you add a README or provide few examples how to use it properly?  

It can't be published as it is an 'effects' module. There are other posts on this list detailing the reasons why this is the case. I think it requires special permission of some kind from the elm devs to publish it. Part of the reason for this restriction, is so that native code in an effects module can be fully reviewed and tested for browser compatability etc. Part of the reason is to maintain control over how the core Elm language evolves. 

In this case there is no native code as it is also a 'pure' effects module.

I think there is a case to be made for a pub/sub message effects module to become part of the core language though.

For the time being you can install it with elm-github-install, which will download it directly from GitHub. There is a 0.5.0 version for Elm 0.17, and just today I pushed a new 1.0.0 version for Elm 0.18.

Rupert Smith

unread,
Nov 29, 2016, 10:31:59 AM11/29/16
to Elm Discuss
On Tuesday, November 29, 2016 at 2:45:36 PM UTC, sergii.k...@zalando.de wrote:
@Rupert Smith
Can you add a README or provide few examples how to use it properly? 

That I can do. 

Rupert Smith

unread,
Nov 29, 2016, 10:47:16 AM11/29/16
to Elm Discuss
On Tuesday, November 29, 2016 at 3:27:18 PM UTC, Rupert Smith wrote:
I think there is a case to be made for a pub/sub message effects module to become part of the core language though.

I also think Elmq is not ready for publishing yet. Would like to get some input on what features it should support.

For example, if two listeners are on the same channel to they both get all the messages (pub/sub style), or does each listener get each message uniquely (point-to-point style). Would likely want to support both models.

Should there be some significance to the channel names when subscribing? For example fi channels are named with dot notation like newsgroups "comp.lang.elm", then can you create subscriptions spanning multiple channels like "comp.lang.*"?

When the type of data sent to a channel does not match the expected type being listened for, a default value is passed instead. Could there be a better way, which would be to not trigger a listener event at all when this happens?

Richard Feldman

unread,
Nov 29, 2016, 1:05:23 PM11/29/16
to Elm Discuss
Yikes, this is a frightening thread to read. I hope this response isn't too late!

I feel it will be too messy to keep all this inter-triplet logic here. Is there a better way? Maybe  something similar to pub/sub or I'm fundamentally wrong here? 

Well, yes, this approach is fundamentally wrong—but please don't feel bad! This is such a common beginner mistake that Evan wrote an entire section of the official guide to try and prevent this from happening. :)

I know that Elm doesn't have the concept of components but instead it has triplets. They should behave similar to components, they encapsulate internal implementations. I use this concept in my App and I faced some architectural questions. 

Emphasis mine. Here's what the official guide has to say about this:


If you are coming from JavaScript, you are probably wondering “where are my reusable components?” and “how do I do parent-child communication between them?” A great deal of time and effort is spent on these questions in JavaScript, but it just works different in Elm. We do not think in terms of reusable components. Instead, we focus on reusable functions. It is a functional language after all!


Put another way, Elm does not have anything similar to components!

It's not about the word, it's about the concept. Thinking in terms of "triplets" will lead to worse code!

What is the proper or better way to communicate between triplets without breaking encapsulation?

Emphasis mine again. The question presumes that breaking encapsulation is a bad idea, which implies that encapsulating state is a good idea. The answer to this question is:

In Elm, by default encapsulating state is a cost, not a benefit.

State encapsulation is super common in object-oriented languages where state is mutable. When we can't easily grant read access to some part of our state without granting write access as well, encapsulating mutable state lets us share code safely.

However, since everything is immutable in Elm, code is safe to share by default—meaning state encapsulation has no benefit here. It's just creating more work for us! The pursuit of state encapsulation leads us to think in terms of "parent-child communication" when we could be thinking in terms of simple function calls instead.

If we adjust our default thinking from "state encapsulation is a benefit" to the correct "state encapsulation is a cost," it completely reframes how we approach these situations.

The wording in the official guide was chosen very deliberately:

“how do I do parent-child communication between them?” A great deal of time and effort is spent on these questions in JavaScript, but it just works different in Elm. 

If we are asking about how to do "parent-child communication" (again—between "triplets" or "components" or "widgets" or whatever term you choose), we are heading down the wrong road!

The answer is not "here is how to do this in Elm," the answer is "we should not do this in Elm." :)

Okay, so what should we do instead?

For example, let say I have two "widgets", UserInfo and Ratings.
In main "update" function I have this

I have a button "Login" in the UserInfo widget. It will call backend API and return the user state. 
Then I want to load and update the Rating widget.
How and where do this?

Let's take a step back. I'd like to know more.
  1. Where does this Login button get reused in the application? Is it always in the same place in the header? Is it scattered throughout every page?
  2. Is this a single-page application with multiple routes?
These questions matter to figuring out how best to make it reusable. :)

Nick H

unread,
Nov 29, 2016, 2:27:28 PM11/29/16
to elm-d...@googlegroups.com
Sergii, not every interaction with the "UserInfo" module needs to go through a function called "update". Just because the top-level Elm architecture uses a single update function and a single view function does not mean that you should automatically mirror this setup throughout your program. As Richard points out, trying to do this leads to awkward design problems that don't need to exist.

Also... I don't know what a triplet is. The only place I've heard that word is referring to a three-member tuple. What do you mean when you use it?



sergii.k...@zalando.de

unread,
Nov 29, 2016, 9:41:21 PM11/29/16
to Elm Discuss

@Nick "triplet" is commonly used shortcut for model, update, view functions. https://www.youtube.com/watch?v=LCNs92YQjhw&feature=youtu.be&t=21m09s

@Richard 
Thank you for your response, I really appreciate it! As I said before, I can be totally wrong.

Put another way, Elm does not have anything similar to components!
But how about elm-mdl, elm-autocomplete, elm-ui, elm-datepicker, elm-form, elm-sortable-table even spinner elm-spinner? Thay all use theirs  model, update, view "triplet" to be reusable and hide implementation details.
And it is differently similar to components and pretty abstracted and reusable.
I think it is good that I don't need to know how a Datepicker stores his view-state(open/close popup, scroll position etc) and his event flows, I just need the selected date. 
Even more, it is exactly like described here https://gist.github.com/evancz/2b2ba366cae1887fe621 by the author of Elm 

Back to my example.

Let's take a step back. I'd like to know more.
  1. Where does this Login button get reused in the application? Is it always in the same place in the header? Is it scattered throughout every page?
  1. Is this a single-page application with multiple routes?
These questions matter to figuring out how best to make it reusable. :)


 The UserInfo is more than a button, it will contain tabs, drop-down  with checkboxes and buttons. So the model I will contain some user data (name, preferenses) and some view-state info (like current tab and open/close popup).
Actions(Messages) will be also some global (like the "preferences changed") and the local (like tab switched).
For login/logout and  "preferences changed" I need to reload some stored data and send data to store preferences on our server.

This "not component" will be used here only once (or more but not now) but should be used in several similar projects.They all will be using the same Identity Service so userInfo(and login/logout) should look and feel similar.
The rest of application is a set of pages with the tabs navigation and multiple master/details grids and graphs.
How I see my architecture for now. I have base service layer(folder). It contains modules for big data models, like user, cached data stores, etc. Plus some update functions to work with them (loader and reset, sync). 
Then I have pages models. Each page contains a model with sets of views-states(sorting order, tab states, scroll positions, etc) and their views and "update" function.

I understand your point "don't do this", but the missing part is "do that". :)
So Richard, How do you suggest to organize this differently? Can you point me to any good example of a big Elm application (with a user login and a multi-page navigation)?


Richard Feldman

unread,
Nov 30, 2016, 3:08:02 AM11/30/16
to Elm Discuss
Put another way, Elm does not have anything similar to components!
But how about elm-mdl, elm-autocomplete, elm-ui, elm-datepicker, elm-form, elm-sortable-table even spinner elm-spinner? Thay all use theirs  model, update, view "triplet" to be reusable and hide implementation details.
And it is differently similar to components and pretty abstracted and reusable.

In libraries that have component systems, components are used as a common building block. They're encouraged as a good default choice, a method of reuse to reach for as a matter of course.

When making something reusable in Elm, we always want to start with data if we can get away with it. If we can't get away with data, then we use a function. If a function still won't do the trick, we try upgrading it to a higher-order function. Only after all else fails do we resort to encapsulating a separate model, update, and view. It's the most expensive way to reuse anything, not something we should reach for as a matter of course!

That's the difference. Components are something you use at the drop of a hat. In Elm, encapsulated state is something you use when you tried all the simpler ways to reuse something first, and none of them worked out.

Hence my objection to "I know that Elm doesn't have the concept of components but instead it has triplets."

What I'd say instead is "I know Elm doesn't have the concept of components but instead it discourages encapsulating state except as a last resort." 

Like Evan said, "it just works different in Elm. We do not think in terms of reusable components." :)

This "not component" will be used here only once (or more but not now) but should be used in several similar projects.They all will be using the same Identity Service so userInfo(and login/logout) should look and feel similar.

That greatly clarifies things, thank you!

Since this is going to be used in multiple projects, it will want to be a separate package. I would look at thebritician/elm-autocomplete as a starting point.

I'd give UserInfo its own model, view, and update (since it is clearly doing a bunch of complicated state logic, all of which needs to be reusable) and expose a UserInfo.update function like this:

update : Maybe User -> UserInfo.Msg -> UserInfo.State -> ( UserInfo.State, Cmd UserInfo.Msg, Maybe User )

The idea is that the caller is responsible for storing the logged-in (or not, hence the Maybe) user - we don't store that user inside UserInfo.State. If the user logs in, we inform the caller by passing Just loggedInUser as the third element in the tuple we return. If the user logged out, we return Nothing. If neither happened, we return untouched whatever user the caller passed us. Regardless, it's up to the caller to store that information.

So the caller's update might look something like this:

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        UserInfoMsg subMsg ->
            let
                ( userInfoState, subCmd, user ) =
                    UserInfo.update model.user subMsg model.userInfoState
            in
                ( { model | userInfoState = userInfoState, user = user }
                , Cmd.map UserInfoMsg subCmd
                )

Similarly, this would mean UserInfo.view would take the user separately as well, which the caller would be responsible for passing in:

view : Maybe User -> UserInfo.State -> Html UserInfo.Msg

Hope that helps!

Peter Damoc

unread,
Nov 30, 2016, 4:12:50 AM11/30/16
to Elm Discuss
On Wed, Nov 30, 2016 at 10:08 AM, Richard Feldman <richard....@gmail.com> wrote:
Like Evan said, "it just works different in Elm. We do not think in terms of reusable components." :)

Does this mean that Elm will never support web-components natively? (i.e. web components implemented in Elm) 
Some web components require encapsulating state.




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

Richard Feldman

unread,
Nov 30, 2016, 4:24:53 AM11/30/16
to Elm Discuss

No idea long-term, but I know Evan wants to focus on elm-package and asset management for the next release, so I wouldn't say "never" but I would say "not soon." :)


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

Rupert Smith

unread,
Nov 30, 2016, 5:04:19 AM11/30/16
to Elm Discuss
On Monday, November 28, 2016 at 2:38:13 PM UTC, Rupert Smith wrote:
On Monday, November 28, 2016 at 2:01:34 PM UTC, sergii.k...@zalando.de wrote:
I feel it will be too messy to keep all this inter-triplet logic here. Is there a better way? Maybe  something similar to pub/sub or I'm fundamentally wrong here? 

I've pulled out a message channels pub/sub implementation.

In view of Richard's comments, you should ignore my pub/sub suggestions for now. I just assumed you had a genuine need for 'out messages' and communication between child modules in a nested TEA - rather than that you should consider restructuring your code to avoid overly modularizing it in the first place.

You may be wondering how your login 'widget' can be re-used, as it not only has to display a button but also produce events in reaction to the user clicking the button:

loginButton : msg -> Html msg
loginButton tagger =
  button [ onClick tagger ] [ text "Login" ]

Is a re-usable button that can be set up to trigger whatever Msg you pass in as an argument - usually called a 'tagger'. The tagger can also be a function if your Msg type takes an argument, then it is 'tagging' its argument as an event to pass to your update function.

So a re-usable 'component' in Elm does not need to encapsulate all of events, state and visual elements as Richard describes. But you can very easily build re-usable view elements. Do this as and when you need to re-use stuff by harvesting from your existing view code by introducing parameters.


sergii.k...@zalando.de

unread,
Nov 30, 2016, 5:18:50 AM11/30/16
to Elm Discuss
@Richard
Thank you for this example. But I don't see how to properly trigger my "ratings" storage reload action if user state changed.

Another question, What do you think about pages separation?
For example, I have several independent tabs and one of the tabs called "Rating" and It has some grids with sorting and filtering, so the page has some local view-state and local actions related only to this page.
In the most examples with Navigation, I see something like this in the main update function.
 
 Ratings act ->
      let
        ( ratings, effect ) =
          Ratings.update act model.ratings
      in
        ( { model | ratings = ratings }, Cmd.map Ratings effect )
Each page modules in separate folder like /Pages/Ratings/ /Pages/Reports/ etc. Each folder contains minimum this files. 

Command.elm - Cmds like fetchAll
Messages.elm 
Models.elm
Update.elm
View.elm

How would you do this differently? 

Rupert Smith

unread,
Nov 30, 2016, 6:44:38 AM11/30/16
to Elm Discuss
On Wednesday, November 30, 2016 at 2:41:21 AM UTC, sergii.k...@zalando.de wrote:
Even more, it is exactly like described here https://gist.github.com/evancz/2b2ba366cae1887fe621 by the author of Elm 

This is a gem. Not seen it before but there are some really useful ideas in there. 

It had not occurred to me that we can create record types with a 'hole' in them, which can then be used to compose them together to build larger records, but at the same time we can write functions that just operate on whatever components of the record we choose as the level of abstraction to re-use. I'm talking about the "Reusable Actions" section.

sergii.k...@zalando.de

unread,
Nov 30, 2016, 9:17:14 AM11/30/16
to Elm Discuss

Wouter In t Velt

unread,
Nov 30, 2016, 10:29:55 AM11/30/16
to Elm Discuss
This is a very educational discussion! Thank you for sharing links and examples.

As perhaps a minor point, I believe the elm-sortable-table does NOT belong in the list of "encapsulated state", "components", "triplets".
It does have a State type defined. But a notable difference with various other examples is that sortable-table does NOT have its own Msg or update function.

I've always seen the sortable-table as an example of how customizable and feature-rich you can get, without encapsulating an inaccessible local state.
React + flux lingo would be a pure component.

Richard Feldman

unread,
Nov 30, 2016, 11:58:31 AM11/30/16
to Elm Discuss
Thank you for this example. But I don't see how to properly trigger my "ratings" storage reload action if user state changed.
 
However you please! The caller has the necessary User info now, so UserInfo is out of the picture and you can do whatever would normally make sense.

Here's one way you could modify the earlier example to do this:

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        UserInfoMsg subMsg ->
            let
                ( userInfoState, subCmd, user ) =
                    UserInfo.update model.user subMsg model.userInfoState
                
                fetchRatingsCmd =
                    if model.user == Nothing && user /= Nothing then
                        -- user was previously Nothing, but now isn't; we just logged in.
                        -- that means we should fetch new ratings from storage!

                        fetchRatingsCmdGoesHere
                    else
                        Cmd.none
            in
                ( { model | userInfoState = userInfoState, user = user }
                , Cmd.batch [ fetchRatingsCmd, Cmd.map UserInfoMsg subCmd ]
                )

Even more, it is exactly like described here https://gist.github.com/evancz/2b2ba366cae1887fe621 by the author of Elm 
This is a gem. Not seen it before but there are some really useful ideas in there. 

It had not occurred to me that we can create record types with a 'hole' in them, which can then be used to compose them together to build larger records, but at the same time we can write functions that just operate on whatever components of the record we choose as the level of abstraction to re-use. I'm talking about the "Reusable Actions" section.

Yikes, definitely don't do that! That link is from Elm 0.13 - back when everything was based around Signals, the Elm Architecture did not exist yet, and Evan was still trying out different approaches to figure out what worked best.

That section is one of the experiments that did not work out in practice, and was rejected in favor of the approach he recommends today in the Guide. (There's a very good reason he didn't talk about this pattern in the Guide - and it's not that it was awesome but he somehow forgot about it!)

I never use these libraries. If installing one ever sounds appealing, I have almost certainly overcomplicated my state management and a better course of action would be to refactor. ;)

How would you do this differently?

I don't have strong feelings about module organization conventions in general. I basically feel like if I'm not happy with how my application's modules are organized, it's easy to reorganize them, so it's not something I spend a lot of time thinking about. I'm more concerned with which values a given module does or does not expose (especially in libraries) than file and folder structure. :)

Hope this helps!

Rupert Smith

unread,
Dec 1, 2016, 6:23:54 AM12/1/16
to Elm Discuss
On Wednesday, November 30, 2016 at 4:58:31 PM UTC, Richard Feldman wrote:
Even more, it is exactly like described here https://gist.github.com/evancz/2b2ba366cae1887fe621 by the author of Elm 
This is a gem. Not seen it before but there are some really useful ideas in there. 
It had not occurred to me that we can create record types with a 'hole' in them, which can then be used to compose them together to build larger records, but at the same time we can write functions that just operate on whatever components of the record we choose as the level of abstraction to re-use. I'm talking about the "Reusable Actions" section.

Yikes, definitely don't do that! That link is from Elm 0.13 - back when everything was based around Signals, the Elm Architecture did not exist yet, and Evan was still trying out different approaches to figure out what worked best.

Tried it and it seems to work ok in 0.18. Only thing was the need to add the 'alias' keyword to the record definitions. Its not a solution I would reach for quickly, but decomposing record types and functions over them in this way does seem entirely permissable within the language. 

Janis Voigtländer

unread,
Dec 1, 2016, 6:36:05 AM12/1/16
to elm-d...@googlegroups.com

2016-12-01 12:23 GMT+01:00 ‘Rupert Smith’ via Elm Discuss <elm-d...@googlegroups.com>:

Its not a solution I would reach for quickly, but decomposing record types and functions over them in this way does seem entirely permissable within the language.

It’s certainly doable/permissible in the language Elm, but it is strongly discouraged by the designer of Elm. There have been several discussions revolving around this in the past, on GitHub and the mailing list(s). Search for something like “record nesting is better than record extension” (that’s Evan’s claim). For example: https://groups.google.com/d/msg/elm-discuss/AaL8iLjhEdU/pBe29vQdCgAJ.

Reply all
Reply to author
Forward
0 new messages