Discussion:
[elm-discuss] elmq - is it a worthwhile abstraction or not?
'Rupert Smith' via Elm Discuss
2017-06-16 11:04:48 UTC
Permalink
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:

https://github.com/gdotdesign/elm-ui

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.
--
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' via Elm Discuss
2017-06-16 11:13:09 UTC
Permalink
Post by 'Rupert Smith' via Elm Discuss
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:

https://github.com/rupertlssmith/elmq/blob/master/src/Elmq.elm#L2

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.
--
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.
Charles Scalfani
2017-06-16 15:47:10 UTC
Permalink
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
Post by 'Rupert Smith' via Elm Discuss
Post by 'Rupert Smith' via Elm Discuss
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
https://github.com/rupertlssmith/elmq/blob/master/src/Elmq.elm#L2
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.
--
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.
Mark Hamburg
2017-06-16 16:27:32 UTC
Permalink
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.
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
Post by 'Rupert Smith' via Elm Discuss
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.
https://github.com/rupertlssmith/elmq/blob/master/src/Elmq.elm#L2
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.
--
You received this message because you are subscribed to the Google Groups "Elm Discuss" group.
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.
For more options, visit https://groups.google.com/d/optout.
'Rupert Smith' via Elm Discuss
2017-06-19 22:15:26 UTC
Permalink
Post by Mark Hamburg
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.
--
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' via Elm Discuss
2017-06-21 12:20:44 UTC
Permalink
Post by 'Rupert Smith' via Elm Discuss
Post by Mark Hamburg
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
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.
--
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.
Loading...