New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide a more accessible alternative to subscriptions #127

Closed
pkese opened this Issue Dec 13, 2017 · 8 comments

Comments

Projects
None yet
4 participants
@pkese

pkese commented Dec 13, 2017

When having to deal with external sources of events, I (subjectively) find the current model of subscriptions to be rather difficult and cumbersome.

Where I'd simply need to get access to a dispatch Msg -> unit function, subscriptions force me to find a way to refactor my code so that I can provide a callback that will be given this dispatch

I'm not sure if this whole topic is even a valid one - maybe I just don't understand something or I don't know enough of the advanced functional design patterns (hell, it took Haskell folks many years to come up with I/O monad, so why should I understand it all in one day).
Although it is indeed a bit comforting, that even bigger minds than me sometimes stumble at these issues (see fable-compiler/samples-pixi#19 (comment) or #113 (comment))

Maybe there's a valid reason that it must be done this way and I don't understand it. My simplistic view though is that there already is a MailBox inside Elmish process. Why not just expose access to post to that mailbox when the process is created?

let dispatch =
    Program.mkProgram init update (fun model _ -> printf "%A\n" model)
    |> Program.startAndGiveMeDispatch

myMsg |> dispatch

This ticket is not necessarily a proposal for the above code,
but rather an invite to the discussion about whether subscriptions can or should be improved.

@pkese

This comment has been minimized.

pkese commented Dec 13, 2017

Also the terminology in the documentation kept hinting me that I was maybe on a wrong page. I need to dispatch events at random times, but the page is telling me how to e.g. Aggregate multiple subscribers. The cognitive divide here is "No, I'm not trying to subscribe to anything, I just need to dispatch my events." Maybe the documentation could in some cases use words like publishers instead of subscribers?

@MangelMaxime

This comment has been minimized.

Member

MangelMaxime commented Dec 13, 2017

Can't you create a custom command to handle your case ? Some examples are in #123

@et1975

This comment has been minimized.

Member

et1975 commented Dec 13, 2017

I think subscriptions/publishers duality is a matter of perspective. You say

need to dispatch events at random times

But the way I'm looking at it, you have some source of events and you want to subscribe the dispatch loop to process the messages arising from those events.
Publishing of messages, on the other hand happens from all over an Elmish program, there are just different ways for every context.
Another thing to consider is that while run returns in its current implementation, I have been looking at rewriting the dispatch loop to eliminate async for potential performance gains and running the dispatch loop synchronously will never return!

@Zaid-Ajaj

This comment has been minimized.

Member

Zaid-Ajaj commented Dec 13, 2017

@pkese This might help you if you want to dispatch events at any point in time:

open System
open Elmish

type MapEventType =
    | AddZoneCard 

let mapEvent = Event<MapEventType>()

let mapEventSubscription initial =
    let sub dispatch =
        let msgSender msg = 
            msg
            |> Home.Types.MapEvent
            |> HomeMsg
            |> disptach
            
        mapEvent.Publish.Add(msgSender)

    Cmd.ofSub sub

// App
Program.mkProgram init update root
|> Program.withSubscription mapEventSubscription

// at some point in time, trigger a message into the dispatch loop
mapEvent.Trigger AddZoneCard

Credit goes to @MangelMaxime for the sample 😄

@pkese

This comment has been minimized.

pkese commented Dec 13, 2017

@Zaid-Ajaj thank you. Yours (or actually @MangelMaxime 's) is a really nice solution to my problem.

Maybe we should just put this pattern somewhere in documentation or even introduce a sort of Program.withEvent

I still didn't get though, how I could solve this using a 'custom command' from examples in #123

Thank you all.

@et1975

This comment has been minimized.

Member

et1975 commented Dec 13, 2017

IMO what makes this workaround useful is the global, publicly-observable mutable state, which is precisely what Elmish works hard to avoid, so I don't see it becoming a part of Elmish core.

@MangelMaxime and @Zaid-Ajaj, thanks for chiming in! I think it's a valuable workaround to have, but I want everyone to carefully consider what

at some point in time

really means.

I see just a few scenarios:

  • You are integrating with a 3rd party - it doesn't play nice with stateless architectures and you don't have much control over it, but you really-really-really want to use it.
  • You have a source of events and a simple subscription just might do the trick, you just need to figure out what it is.
  • You are running some code outside/after the dispatch loop started, 'cause that's how you are used to do it and it seems like a good idea.

If it's the last one, you may want to stop and rethink your life choices.
Correct me if I'm wrong... closing in the meantime.

@et1975 et1975 closed this Dec 13, 2017

@MangelMaxime

This comment has been minimized.

Member

MangelMaxime commented Dec 13, 2017

I do agree with you @et1975 and should have warn users about this usage.

Their is some few cases where this is needed but most of the time users need to rethink his application workflow.

@pkese

This comment has been minimized.

pkese commented Dec 14, 2017

@et1975
Thanks.
The more I look at it, the more I agree on your point 3 (rethink architecture).

What I'm having trouble with is your first sentence:
"IMO what makes this workaround useful is the global, publicly-observable mutable state"

I find it hard to understand, why you think that this is introducing a mutable state?
It is just exposing posting messages to the mailbox from the outside. The state is still encapsulated and can only be modified through message processing.

What I see is a 1) state machine with its encapsulated state and 2) a mailbox with a queue of inbound messages to process. Technically a state machine wrapped into an Actor.

Except that for whatever reason, we provide access to posting to the mailbox only to our Truman show's encapsulated world that we create ourselves (either the HTML we render can dispatch or the HTTP requests we post can Cmd.ofAsync).

I don't see how this is any better (or safer or more functional) than if we just admitted that if it walks like an Actor and it squeeks like an Actor, then it maybe is an Actor.
There would be no harm if the outside world could post messages to this Actor. That still wouldn't mean that we'd expose any global mutable state.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment