I'm not annoyed at you, Nick. I'm sorry if I came across that way. The
queue logic is attractive because it's straightforward but it's also
contrived so seeing the discussion veer toward how to write the queue feels
like looking for ones keys under the streetlight because that's where the
light is. I'm trying to focus the discussion on to the problem of how to
generate a serialized command in one place, have it end up the queue, and
then have the result get delivered back to the point where we tagged the
command result to go.

I will admit that your more detailed queue code caused me to miss your
generation of new commands in doAThing — which could readily generalize
into being a call to an update function for an inner model — but it has the
problem that then everything gets shoved into the queue. What if we only
want to serialize some of the commands? For example, maybe what we're
really running is a priority queue for HTTP fetches (a less contrived
example) but we need commands that are trying to obtain the window size to
run without queueing.

And to make matters worse, if we keep with the standard Elm architecture
constructs, we get back a single command from an update and this is likely
a batch command and we have no way to break that down into smaller
commands. That may tell us that we need to route the commands on up to the
app runtime and then have the serialized commands somehow send their
payload back down without execution to get put into the queue. What are the
type signatures that make that work particularly together with full
compatibility with the tagging patterns in the Elm architecture?

Following the doAThing pattern, we could require the update function for an
inner model making use of the queueing service to return not a command but
rather a list of Queued/NotQueued commands:

type QNQ msg
    = Queued (Cmd msg)
    | NotQueued (Cmd msg)

type alias InnerUpdate innerModel innerMsg =
    innerMsg -> innerModel -> (innerModel, List (QNQ innerMsg))


Given that type signature, then we can readily figure out what needs to be
delayed through the queue and what doesn't. That, however, moves away from
the traditional Elm architecture code in the way update functions are
written and it could run afoul of usage of Cmd.batch unless that was
strongly discouraged in the codebase.

We could probably emulate the whole of the command architecture adding
batch, none, and map functionality to QNQ. This would allow the code to
look much the same as before but would force the widespread replacement of
Cmd with our new QNQCmd. That's feasible but what if we have more than one
of these sorts of services to support?

So, again, sorry for slamming your work on the queue. It's just that that's
the part that is entirely contrived and hence is basically a matter of
searching for a problem. A priority queue would have been less contrived
but would also be more code and would have been an even bigger distraction.
But the real meat here is the question of what it does to the general code
structure to support this type of functionality.

Mark

On Fri, Aug 26, 2016 at 11:37 AM, Nick H <[email protected]> wrote:

> I now need knowledge of the sequencer to flow through the program in a way
>> that effects managers avoid.
>>
>
> I disagree with this statement. I was hoping that by iterating through
> this solution, we would eventually come to an agreement re: the claim
> quoted above. But you are annoyed at me for treating your problem
> seriously, and you are annoyed at Richard for dismissing your problem, so I
> don't really know where to go from here.
>
> On Fri, Aug 26, 2016 at 11:14 AM, Mark Hamburg <[email protected]>
> wrote:
>
>> As I said, I know how to write the queue if that's what I really want.
>>
>> My problem is that when a module uses the WebSocket.send function to
>> create a command, I don't have to do anything special in my program to
>> arrange for that command to make its way to the web socket effects manager.
>> I just need to route it to my top level update function up through however
>> many layers of the Elm architecture my app uses. When I want to use my HTTP
>> fetch sequencer (again a contrived example that I had hoped would be simple
>> enough not to get buried in discussions of how to build the sequencer), I
>> now need knowledge of the sequencer to flow through the program in a way
>> that effects managers avoid. I'm looking for a pattern that allows me to
>> construct service APIs using the same sort of conventions used by effects
>> managers and with the same sort of ability to not muck up the code with,
>> for example, needing to change lots of uses of Cmd.map tagger to
>> something more like Cmd.map (Wrapped.map tagger).
>>
>> Mark
>>
>> On Thu, Aug 25, 2016 at 10:37 PM, Nick H <[email protected]>
>> wrote:
>>
>>> OK, here I am going for the obvious implementation again. Sorry the
>>> formatting is a little nutty.
>>>
>>> type Action
>>>     = HTTPResponse String
>>> | SomethingElse
>>>
>>> type alias Model =
>>>   { pendingFetches : List (Cmd msg)
>>>   , waitingForResponse : Bool
>>>   }
>>>
>>>
>>> doAThing : Model -> List (Cmd Action)
>>>
>>>
>>> update : Action -> Model -> (Model, Cmd Action)
>>> update action model =
>>>   case action of
>>>     SomethingElse ->
>>>  case (model.waitingForResponse, doAThing model) of
>>>  (True, newFetches) ->
>>>    ( { model | pendingFetches = model.pendingFetches ++ newFetches }
>>> , Cmd.none )
>>>      (False, head :: tail) ->
>>>    ( { model
>>>    | pendingFetches = model.pendingFetches ++ tail
>>>    , waitingForResponse = True
>>> }
>>> , head )
>>>  (False, []) ->
>>>    ( model, Cmd.none )
>>>
>>>     HTTPResponse value ->
>>>       let
>>>         newModel = processResponse value model
>>>       in
>>>         case newModel.pendingFetches of
>>>           head :: tail ->
>>>             ( { newModel | pendingFetches = tail }, head )
>>>
>>>           [] ->
>>>             ( { newModel | waitingForResponse = False }, Cmd.none )
>>>
>>> On Thu, Aug 25, 2016 at 9:39 PM, Mark Hamburg <[email protected]>
>>> wrote:
>>>
>>>> Yes, a command queue is the obvious implementation for what I
>>>> identified as a contrived example. The core problem, however, is how items
>>>> get into the command queue through the normal command routing mechanisms.
>>>>
>>>> Mark
>>>>
>>>> On Aug 25, 2016, at 8:18 PM, Nick H <[email protected]> wrote:
>>>>
>>>> We might generate any number of these commands during a single update call,
>>>>> but the mechanics of their execution demand that we not start the HTTP
>>>>> fetch for one until the HTTP fetch for the previous has finished.
>>>>
>>>>
>>>> One solution that comes to mind is adding a command queue to your
>>>> model. Something along these lines:
>>>>
>>>> type alias Model =
>>>>   { pendingFetches : List (Cmd msg) }
>>>>
>>>> update action model =
>>>>   case action of
>>>>     HTTPResponse value ->
>>>>       let
>>>>         newModel = processResponse value model
>>>>       in
>>>>         case newModel.pendingFetches of
>>>>           head :: tail ->
>>>>             ( { newModel | pendingFetches = tail }, head )
>>>>
>>>>           [] ->
>>>>             ( newModel, Cmd.none )
>>>>
>>>> On Thu, Aug 25, 2016 at 5:51 PM, Mark Hamburg <[email protected]>
>>>> wrote:
>>>>
>>>>> I'm going to try to take the large app design questions and focus them
>>>>> on a more narrow and admittedly contrived example.
>>>>>
>>>>> Say the people doing the client coding needed to be able to take a URL
>>>>> string and fetch a string via HTTP. (Yes, this is covered in the HTTL
>>>>> module. Bear with me. I'm trying to keep the example simple.) Dealing with
>>>>> tasks all over the place muddies up the client architecture that would
>>>>> otherwise focus on commands for external operations. So, we define:
>>>>>
>>>>> getStringCommand :
>>>>>     (Http.Error -> msg)
>>>>>     -> (String -> msg)
>>>>>     -> String
>>>>>     -> Cmd msg
>>>>>
>>>>>
>>>>> This is easy to write given the standard libraries.
>>>>>
>>>>> But now it turns out we would like to execute these one at a time. We
>>>>> might generate any number of these commands during a single update
>>>>> call, but the mechanics of their execution demand that we not start the
>>>>> HTTP fetch for one until the HTTP fetch for the previous has finished. (I
>>>>> said it was contrived. Maybe we want to automatically fail subsequent
>>>>> commands if the first one fails.)
>>>>>
>>>>> From what I understand of effect managers, we could write an effect
>>>>> manager to do this but the documentation around effect managers 
>>>>> discourages
>>>>> reaching for them as a solution. They are identified as being for library
>>>>> writers and though this serialized string-fetcher seems a bit like a
>>>>> library in its usage, it also feels like a chunk of general app
>>>>> functionality. Or maybe the backend needs to use web sockets instead of
>>>>> HTTP and we would like to use the web sockets effects manager as part of
>>>>> the implementation.
>>>>>
>>>>> One way to address this is to replace commands with requests,
>>>>> recognize string fetch requests when we reach a certain point in the model
>>>>> hierarchy, and process them accordingly generating commands as we move up
>>>>> the rest of the hierarchy. This has been covered in previous posts to the
>>>>> discussion list. The downside to this is that it doesn't interoperate well
>>>>> with code that wants to speak in terms of commands. One nice thing about
>>>>> effects managers is that the addressing of a command to a particular 
>>>>> effect
>>>>> manager is essentially unseen by everything that handles it until we get 
>>>>> to
>>>>> the app runner. Having lots of code need to switch from returning commands
>>>>> to returning requests is a very visible consequence of using this service
>>>>> that speaks via requests.
>>>>>
>>>>> Another way to handle this is by changing update functions so that
>>>>> they still speak commands, but they now have a signature like:
>>>>>
>>>>> update : Msg -> Model -> (Model, Cmd (Wrapped Msg))
>>>>>
>>>>>
>>>>> We can then watch for wrapped commands and somehow unwrap the ones
>>>>> that really are looking for work by the sequencer code. That said, I'm
>>>>> waving my hands somewhat fast here and while we now continue to use
>>>>> commands, we don't use them in the way we're used to so I don't know that
>>>>> it's a big win over the requests approach.
>>>>>
>>>>> Is there a better way to do this that I'm not seeing? The example is
>>>>> contrived but so are most examples. It feels like it gets at the sort of
>>>>> problem for which there ought to be a design pattern — i.e., structure 
>>>>> your
>>>>> types and functions like this to solve this sort of problem.
>>>>>
>>>>> Mark
>>>>>
>>>>> --
>>>>> 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 [email protected].
>>>>> 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 [email protected].
>>>> 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 [email protected].
>>>> 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 [email protected].
>>> 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 [email protected].
>> 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 [email protected].
> 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 [email protected].
For more options, visit https://groups.google.com/d/optout.

Reply via email to