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.
