elmq - is it a worthwhile abstraction or not?

348 views
Skip to first unread message

Rupert Smith

unread,
Jun 16, 2017, 7:04:48 AM6/16/17
to Elm Discuss
By writing this post, I may answer my own question, but anyway your opinions appreciated as always.

=== What is it?

As I have posted on here before, I pulled this library:

https://github.com/rupertlssmith/elmq

Out of the elm-ui code here:


The idea is that if you have nested update functions in several modules:

Main.update
  Child1.update
  Child2.update
  ..

And you also have nested subscriptions:

Main.subscription
  ChildA.subscription
  ChildB.subscription
  ..

Where the 2 sets (1..n) and (A.._), may have an intersection. The elmq library provides a convenient way for the child modules to talk to one another, based on using named 'channels'.  Here is a little example:

module Child1 exposing (..)

import Elmq

-- To send string messages to the "chat.elm" channel.
elmTalk : String -> Cmd msg
elmTalk message =
    Elmq.sendString "chat.elm" message

module ChildA exposing (..)

-- To receive string message on the "chat.elm" channel
type Msg =
    ReceiveChat String

subscriptions : Model -> Sub Msg
subscriptions model =
    Elmq.listenString "chat.elm" ReceiveChat

It is implemented as an effects module; there is a little routing engine that processes the Cmds and turns them into Subs, where matching senders and listeners on the same channel.

=== Why?

Very early on in my learning with Elm, I did not know what an 'out message' is. People tried to point me in this direction, I was confused and there was just too much to get my head around.

I pulled out the auth code from an application I wrote, and wanted to re-use it. It is very convenient to be able to easily invoke 'unauthed : Cmd msg' from anywhere in an application without having to worry about a specific out message type, and having to implement the routing logic for the auth out messages in the Main.update of every application I write seemed like it would be a pain. I just wanted to encapsulate auth as a re-usable thing that I can drop into any application I write.

Working with something with the type 'Cmd msg' seems easier than working with an extra out message type, since update functions tend to return Cmds anyway.

Out message are considered a little awkward in Elm - we often feel we are having to write a lot of boiler plate to pass them up the chained update functions.

== What are the potential benefits?

There is a generic message router - that logic does not need to be re-implemented in every Main.update, I ever write.

The concept of channels can be useful for more dynamic routing. Channels can come and go. This feature may actually prove to be useful, but I don't actually have an application that needs it.

== Why reconsider it?

Now that I sat through Richard Feldman's talk, I know that even the generic concept of 'out messages' can be too wide. If you have a nested update function that needs to return something, just return it. If it needs to return lots of things, those things may get enumerated as a tagged union - or the design might be wrong.

Elmq does not really solve the boilerplate issue that nested update and subscriptions present. For example in my Main.update I am still using Cmd.map to lift child messages to the main Msg type, and in Main.subscription I am still Sub.map to forward the subscriptions to child modules. This made me realise that all I am really doing with elmq is hacking Cmd and Sub to work as a generic out message types - I may as well just declare my own ElmqMessage type, pull the routing logic out of the effects module and run it in the application instead.

I can't publish effects modules. Using elmq means I can't publish other things I have done that depend on it.

== How to fix my code?

As above, make my own ElmqMessage type and pull the routing logic out of the effects module. This would result in a message router that retains the dynamic routing capability, should anyone ever need such a thing.

Narrow the types of the auth messages that my applications need - that is, don't use my own generic ElmqMessage type, just narrow it to the specific things that auth needs - type AuthMsg = LogIn | LogOut | Refresh | Unauthed. Have the auth module expose an update function that is specific to this type, update : AuthMsg -> AuthState -> AuthState.

Get back within the Elm 0.18 platform, and be able to share my wondrous projects with you all. Thanks for reading, if you get this far.

Rupert Smith

unread,
Jun 16, 2017, 7:13:09 AM6/16/17
to Elm Discuss
On Friday, June 16, 2017 at 12:04:48 PM UTC+1, Rupert Smith wrote:
As above, make my own ElmqMessage type and pull the routing logic out of the effects module. This would result in a message router that retains the dynamic routing capability, should anyone ever need such a thing.

This would actually be better - one of the problems elmq has is that the internal representations of Cmd and Sub in an effects module need to be declared in it as a particular concrete type:


As the consumer of the module cannot vary this type with type parameters, it settles on passing everything as a Json.Encode.Value. This means that the message router is not as strongly typed as it could be - the compiler cannot make sure that when I pass a chat message of type String, the receiver of that message is also  expecting a String and not say an Int.

Doing it all outside of an effects module with a type of 'ElmqMessage a' would allow stronger guarantees to be enforced.

Charles Scalfani

unread,
Jun 16, 2017, 11:47:10 AM6/16/17
to Elm Discuss
I authored a library that handles this problem pretty simply and the abstraction is definitely worth it as I am writing backend Elm and we have complex libraries that are 6 levels deep. You can check it out at: https://github.com/panosoft/elm-parent-child-update

Mark Hamburg

unread,
Jun 16, 2017, 12:27:37 PM6/16/17
to elm-d...@googlegroups.com
The. Config parameter to generate messages is much like the out message approach in that essentially the child is listing off things that it either can't handle itself or that it doesn't actually care about but wants to let the parent know about. For example, a sign in module when finished may just want to say "We're signed in now!" or for that matter "The user canceled." Config basically is a way to say "I may need to send these messages to you, please tell me how to send them." Out messages list the things that will be returned and presumably the parent code will case over them.

As for which to choose, it probably comes down to a question of which approach does a better job of having manageable boiler plate. The out message approach does make me feel like I'm writing the same code over and over again,  but it does follow a well defined pattern so it's not like I'm having to think hard while getting the structure in place. I assume that the config approach involves similar but different "boiler plate" in that each child response needs its own parent message and implementation in the update function.

Another option for something like the sign in case is that the parent just queries the child after every update. "Are we signed in yet?" For some cases, this works well but it also means that children may need to store more state. For example, it's a lot easier to send a "close me" message to the parent in response to the close box being hit than it is to store a Boolean "I'm closed flag" and have the parent query it's status to discover that the modal child should be closed.

All that said, I think elmq is really interesting for the case where the interaction isn't hierarchical. For example, model hierarchy might reflect the presentation which might be relatively different from the model needed to talk to the server. It's a pity it can't be typed. Lacking type support, it's interesting to think about it as being analogous to talking to a web server in the encode/decode process. Following that line of reasoning, however, leads to wanting a way to send a message and get a response delivered only to the sender rather than broadcast to every part of the app talking to that internal server.

Mark

P.S. To avoid triples in return values from update functions and corresponding complexity in support libraries, I folded commands into out messages. Conceptually, they are the same thing — requests for an effect beyond the bounds of the model being updated. This does, however, mean that every update has to include command forwarding logic as part of its out message processing.
--
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.

Rupert Smith

unread,
Jun 19, 2017, 6:15:26 PM6/19/17
to Elm Discuss
On Friday, June 16, 2017 at 5:27:37 PM UTC+1, Mark Hamburg wrote:
All that said, I think elmq is really interesting for the case where the interaction isn't hierarchical.

Yes. In my case I have the Auth module, and other modules using it, which are typically structured like this:

Main
 - Auth
 - Other

So I am using it to talk more directly from Other -> Auth. Sibling rather than parent-child communication. But I came to the realization that the parent-child part is still there because Cmd.map and Sub.map are still used to plug everything together.

I think it was interesting to try this approach, and it was a quick way to get started when I was unsure of that the Auth API would look like. Now that I know that Other needs to be able to request 'login', 'logout', 'refresh' or 'unuathed' as a side-effect, or to take the current auth state providing 'isAuthed', 'permissions' and so on as inputs, I may as well just code that API directly, and move away from a more generic approach.

Rupert Smith

unread,
Jun 21, 2017, 8:20:44 AM6/21/17
to Elm Discuss
On Monday, June 19, 2017 at 11:15:26 PM UTC+1, Rupert Smith wrote:
On Friday, June 16, 2017 at 5:27:37 PM UTC+1, Mark Hamburg wrote:
All that said, I think elmq is really interesting for the case where the interaction isn't hierarchical.

Yes. In my case I have the Auth module, and other modules using it, which are typically structured like this:

Main
 - Auth
 - Other

I've now refactored my auth package to not use elmq, but instead have an API that is purely specific to what it does. There was no need to have message routing that can be runtime dynamic.

It is split into 2 public modules:

Auth - which provides the API most of an application wants to interact with. To login, logout etc, and to query a sub-set of the total auth state to know what is the current username, what are the current permissions, and so on. The remainder of the auth state is fully opaque so that applications do not depend on it and the auth package can continue to evolve whilst presenting a consistent public API.
AuthController - which is TEA structured. It has an update function which accepts the login, logout etc "commands" to update the state. I say "commands" in inverted commas because they are not of type Cmd Msg, but of type AuthCmd, which is an extra thing any update function requested a side-effect on the auth state will return.  This part gets wired in to the Elm update cycle of whatever application is consuming the auth package. If I need some helper functions to reduce boilerplate that this wiring in will entail, they will go in their too.

I still like the idea of a more general purpose 'messaging middleware' for Elm, but this just feels simpler and more direct.

I will also now be able to publish it along with the back-end auth micro-service, and also an elm-mdl UI for managing accounts, roles, permissions etc.
Reply all
Reply to author
Forward
0 new messages