This is a bit pedantic, but over the course of learning Elm I have sometimes found the use of magic symbols a bit confusing. This is the first I heard of the ! operator. In 2 of the three places it is used in the code [1] the ! infix operator is not really shorthand for the tuple form because it amounts to the same number of characters (surrounding a single Cmd with square brackets instead of the entire expression with round brackets, and using an exclamation mark instead of a comma), and it is only a shorthand in the other place because the empty list evaluates to Cmd.none. The tuple form would have been more familiar and readable to a relatively inexperienced Elm programmer like me - particularly in the place where it added most conciseness, as Cmd.none is surely more expressive than [].
Kudos for sharing the code example. I don't mean to be critical, just reflective. [1] https://gist.github.com/pdamoc/d492ab58023926cd4d4950f12e5e170d On Thursday, June 2, 2016 at 5:42:16 AM UTC+10, Peter Damoc wrote: > > I'm guessing you referring to: > > http://package.elm-lang.org/packages/elm-lang/core/4.0.1/Platform-Cmd#! > > This is the code : > > (!) : model -> List (Cmd msg) -> (model, Cmd msg) > (!) model commands = > (model, batch commands) > > > I use it as a short hand for joining the model and the Cmds. > It's also pretty handy when you don't have a Cmd since `batch []` > evaluates to Cmd.none. > > > > > > On Wed, Jun 1, 2016 at 10:18 PM, Simon <[email protected] <javascript:>> > wrote: > >> This is super cool. >> But what is the function: >> >> (!) : a -> List b -> (a, b) >> >> On Wednesday, 1 June 2016 09:45:05 UTC+2, James Wilson wrote: >> >> Thanks for your help; I'll have a ponder and probably end up taking an >>> effect manager route again (I started this way and then ended up with some >>> weird hybrid where my cache was in the main elm app and an effect manager >>> did the "lower level" api stuff; but I'm not so happy with it) >>> >>> As another effect manager code point if you're intrigued how they work >>> (and my comments are in any way useful, which they may not be); here's an >>> effect manager I made for sending and receiving things from a backend I'm >>> wiring my app up to (so it uses custom Cmds and Subs): >>> >>> >>> https://github.com/jsdw/tl-asset-browser/blob/3e9e8527f65ae0a0a2d5d00d01779bdfdf03701d/src/elm/Api.elm >>> >>> Not sure whether it's any help (may just be easier looking at the >>> elm-lang effect managers!) but just in case :) >>> >>> On Wednesday, 1 June 2016 08:31:11 UTC+1, Peter Damoc wrote: >>>> >>>> I haven't used an effect manager for this because I haven put in the >>>> time needed to learn how to create effect managers. :) >>>> >>>> If what I've shown here can be accomplished with an effect manager then >>>> that's the way it should be done. :) >>>> >>>> >>>> >>>> >>>> >>>> >>>> On Wed, Jun 1, 2016 at 10:21 AM, James Wilson <[email protected]> wrote: >>>> >>>>> Thanks, that looks like basically exactly what I'd have guessed :) >>>>> It's super useful seeing an actual code sample with these ideas in. >>>>> >>>>> One thing I wonder now is; why not use an effect manager for this? It >>>>> basically seems to fit the exact same space (allows you to create a >>>>> custom >>>>> Req like thing that can be mapped and batched and passed up the component >>>>> hierarchy - except that it's just a Cmd instead and plays nice with other >>>>> Cmds; allows you to maintain and update state (the cache) as you go; >>>>> allows >>>>> you to "convert" Reqs to tasks to be run - just Cmds again now). In fact, >>>>> effect managers don't really seem to help you do anything other than >>>>> what's >>>>> described here (plus a subscription side if you want it). Are there any >>>>> cons to using an effect manager here that you have in mind? >>>>> >>>>> On Tuesday, 31 May 2016 20:43:41 UTC+1, Peter Damoc wrote: >>>>>> >>>>>> The updating of the cache sounds to me like this: >>>>>> >>>>>> 1. if we have the info in cache, just supply the info without a HTTP >>>>>> GET >>>>>> 2. if we don't have the info in cache, return a different Msg that >>>>>> encapsulates the msg that requested the original information and the >>>>>> info >>>>>> required for the cache update. >>>>>> >>>>>> Here is a quick update of the code I've previously posted to include >>>>>> this caching mechanism. >>>>>> >>>>>> https://gist.github.com/pdamoc/d492ab58023926cd4d4950f12e5e170d >>>>>> >>>>>> >>>>>> >>>>>> >>>>>> On Tue, May 31, 2016 at 10:05 PM, James Wilson <[email protected]> >>>>>> wrote: >>>>>> >>>>>>> The key part that's not coded in the gist is the use of a >>>>>>> cache/global state object, however I think I see what you're getting >>>>>>> at - >>>>>>> pass back up the chain a Req object, say, and at the top we can turn it >>>>>>> into a Cmd using, say, some top level global state as well as whatever >>>>>>> other data we need. This may lead to a request being made to the server >>>>>>> or >>>>>>> it may not. >>>>>>> >>>>>>> The other part of the puzzle is actually updating the cache when a >>>>>>> request is made. Req.toCmd for instance could return an updated >>>>>>> GlobalState >>>>>>> so that it's able to cache "pending" states on values (so that we can >>>>>>> avoid >>>>>>> duplicating requests). To update the cache when the response actually >>>>>>> comes >>>>>>> in we could have toCmd return a Cmd.batch of 2 commands, one that will >>>>>>> fail/succeed and send a message to the component that initiated the >>>>>>> Req, >>>>>>> and one that will send a message aimed at the top level cache itself. >>>>>>> >>>>>>> Thanks Peter, I'll definitely mull over this! >>>>>>> >>>>>>> On Tuesday, 31 May 2016 19:45:42 UTC+1, Peter Damoc wrote: >>>>>>>> >>>>>>>> ADT in Elm is one of its most powerful weapons. >>>>>>>> >>>>>>>> You could encapsulate your requests in a type and use this type at >>>>>>>> top level to fulfill them. >>>>>>>> >>>>>>>> For example: instead of returning Cmd msg you return some Req msg >>>>>>>> that can be turned into a Cmd msg at top level based on some context >>>>>>>> information. >>>>>>>> >>>>>>>> Here is a gist with a skeleton of how I view this implemented: >>>>>>>> https://gist.github.com/pdamoc/a47090e69b75433efa60fe4f70e6a06a >>>>>>>> >>>>>>>> I've sent the base of the URL as a simple String in `Req.toCmd` but >>>>>>>> you can imagine a more complex type holding all kind of information >>>>>>>> (e.g. >>>>>>>> cache, auth, etc ) . >>>>>>>> Also, I've kept the type of the Req simple (only saved the rest of >>>>>>>> the URL based on the user and the request) but one could use it to >>>>>>>> store >>>>>>>> all the info needed when you will turn the Req into a Cmd. >>>>>>>> >>>>>>>> >>>>>>>> >>>>>>>> >>>>>>>> >>>>>>>> >>>>>>>> On Tue, May 31, 2016 at 7:29 PM, James Wilson <[email protected]> >>>>>>>> wrote: >>>>>>>> >>>>>>>>> In Elm, each component basically has its own internal state (which >>>>>>>>> is actually all just a slice of one global model). In my app, I also >>>>>>>>> want >>>>>>>>> global state that is independant of any components; for example a >>>>>>>>> clientside cache of various API responses (asset details - there >>>>>>>>> could be >>>>>>>>> many thousands, user authentication status). >>>>>>>>> >>>>>>>>> I want any component to be able to call methods that make use of >>>>>>>>> this global state. For example, a method to obtain details for items >>>>>>>>> in the >>>>>>>>> current view might first look at the global state to see if these >>>>>>>>> items are >>>>>>>>> cached. If they arent, the call would provide a Cmd to be issued that >>>>>>>>> gets >>>>>>>>> the items (and puts them in the cache), while simultaneously updating >>>>>>>>> the >>>>>>>>> state to indicate that they are being loaded (so that the same >>>>>>>>> request >>>>>>>>> again from another component doesnt trigger another call to the >>>>>>>>> backend). >>>>>>>>> If they are cached, they can be easily returned from there. A first >>>>>>>>> shot at >>>>>>>>> a signature might look something like: >>>>>>>>> >>>>>>>>> getItem : GlobalState -> ID -> Tag -> (GlobalState, Cmd msg) >>>>>>>>> >>>>>>>>> >>>>>>>>> >>>>>>>>> However we could partially apply functions that exist on some >>>>>>>>> globalState instantiation to hdie the initial state being passed in >>>>>>>>> and end >>>>>>>>> up with: >>>>>>>>> >>>>>>>>> state.items.getItem : ID -> Tag -> (GlobalState, Cmd msg) >>>>>>>>> >>>>>>>>> >>>>>>>>> >>>>>>>>> The downside of this approach is that I have to thread this state >>>>>>>>> through multiple calls that might make use of it, and thread it back >>>>>>>>> up >>>>>>>>> explicitly through the update functions to get it back to the top. At >>>>>>>>> the >>>>>>>>> top we'd then have something like (excuse any mistakes!): >>>>>>>>> >>>>>>>>> update msg model = case msg of >>>>>>>>> SubMsg m -> >>>>>>>>> let (newSubModel, subCmds, newGlobalState) = SubComponent.update >>>>>>>>> m model.subModel >>>>>>>>> in ({ model | state = newGlobalState, subModel = newSubModel >>>>>>>>> }, Sub.map SubMsg subCmds) >>>>>>>>> ... >>>>>>>>> >>>>>>>>> >>>>>>>>> An alternative approach is to hold this global state in an effect >>>>>>>>> manager, and so in the app you'd end up using the Cmd/Sub mechanism >>>>>>>>> to ask >>>>>>>>> for things from the state and internally initiate API requests to >>>>>>>>> update >>>>>>>>> the state as necessary. We'd end up with an API more like: >>>>>>>>> >>>>>>>>> getItem : ID -> Tag -> Cmd msg >>>>>>>>> >>>>>>>>> >>>>>>>>> or >>>>>>>>> >>>>>>>>> state.items.getItem : ID -> Tag -> Cmd msg >>>>>>>>> >>>>>>>>> >>>>>>>>> where the returned Cmd would either lead to an item being sent to >>>>>>>>> the component immediately via a cache (where Tag is a Msg type the >>>>>>>>> component knows about) or after it was obtained via some backend. >>>>>>>>> This >>>>>>>>> would make all retrieving of state async but seems to simplify the >>>>>>>>> interface (perhaps at the cost of more complexity in implementing the >>>>>>>>> effect manager). >>>>>>>>> >>>>>>>>> Which approach do people think is best for working with global >>>>>>>>> state (neither is an option if you have a better way!)? Do you get >>>>>>>>> away >>>>>>>>> with not needing this kind of thing (and if so, how)? I'd love to >>>>>>>>> hear >>>>>>>>> back, especially from those that have had experience building larger >>>>>>>>> apps >>>>>>>>> in Elm! >>>>>>>>> >>>>>>>>> -- >>>>>>>>> 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. >>>>>>>>> >>>>>>>> >>>>>>>> >>>>>>>> >>>>>>>> -- >>>>>>>> There is NO FATE, we are the creators. >>>>>>>> blog: http://damoc.ro/ >>>>>>>> >>>>>>> -- >>>>>>> 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. >>>>>>> >>>>>> >>>>>> >>>>>> >>>>>> -- >>>>>> There is NO FATE, we are the creators. >>>>>> blog: http://damoc.ro/ >>>>>> >>>>> -- >>>>> 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. >>>>> >>>> >>>> >>>> >>>> -- >>>> There is NO FATE, we are the creators. >>>> blog: http://damoc.ro/ >>>> >>> >> >> -- >> 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] <javascript:>. >> For more options, visit https://groups.google.com/d/optout. >> > > > > -- > There is NO FATE, we are the creators. > blog: http://damoc.ro/ > -- 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.
