Very well put Mark.

Regarding Nicholas' advice on "The process"

> 6. Do things in life that make you happy. If it upsets you that Elm lacks 
something you think is super important, maybe take a break and come back 
later.

That's what I've done. I'm back in JS land and Getting Things Done. Elm's 
lack of a simple mechanism for interaction with external APIs (other than 
pub/sub style APIs, which it does well, but they are the minority) limits 
its practical application. The proposition that the tiny core Elm team will 
deliver built-in integration to all APIs in every JS runtime context 
(Electron? Cordova? Lambda? Chrome Extension? React Native? Arango? 
Espruino? ...) is clearly not realistic. 

On Thursday, April 20, 2017 at 4:07:47 AM UTC+10, Mark Hamburg wrote:
>
> Since the call is for concrete use cases, here is one: Reading from local 
> storage.
>
> My program wants to cache various things in local storage or for the 
> purposes of this example it wants to read from various locations in local 
> storage to get the values needed at various points in the model/UX. If we 
> didn't use local storage and just used HTTP, we would generate HTTP 
> requests at the points where we needed the information and using Elm 
> message routing get the results delivered back. We would like to do the 
> same thing with local storage as a cache — possibly followed by an HTTP 
> fallback if we found nothing in local storage but that's beyond the scope 
> here.
>
> The analogous API to the HTTP case would be something like:
>
> get : (Result Error Json.Decode.Value -> msg) -> String -> Cmd msg
>
>
> Or to make it more composable — cough, tasks v commands, cough — we might 
> have:
>
> getTask : String -> Task Error Json.Decode.Value
>
>
> Given that the Elm local storage effects manager seems to be on indefinite 
> hold, we need to use ports. So, what does it take to do this using ports?
>
> Command ports don't return results. Subscription ports don't convey 
> information to the JavaScript side. So, we have to use a pair of them. A 
> command to trigger work on the JavaScript side and a subscription to 
> receive values back. But how do we match results coming back with requests 
> coming in? How do we tag those results appropriately for delivery?
>
> There would seem to be two options:
>
> 1. For every request we need to make, create a separate pair of ports. 
> Now, we don't need to pass in the key string because the key is built into 
> the port identity. In fact, it would be bad to pass in the key string since 
> that would reintroduce the question of which response goes with which 
> request. This approach will work if we can statically enumerate ahead of 
> time all of the keys we are interested in AND we are prepared to write 
> JavaScript handler code for each and every request. This might be viable 
> for the big flat applications that some people like to advocate but it's a 
> pretty messy maintenance situation and it breaks the moment we can't 
> statically enumerate the keys of interest.
>
> 2. When we send a request in, we tag it with an identifying number and 
> maintain a dictionary mapping those id's to tagger functions. The 
> JavaScript side includes the id in its response — basically the same thing 
> Phoenix push messages do — and the subscription port feeds into logic that 
> looks up the id and tags and delivers the result. Those of you who are 
> horrified at the thought of storing functions in the model should perhaps 
> stop right here because that id to tagger function dictionary is doing 
> exactly that. But even if one gets beyond that, note that this approach 
> won't work just like HTTP because we will need at some point before this 
> becomes a command to update the delivery map. That can either happen by 
> replacing commands with requests or by allowing arbitrary update code to 
> touch the shared global id generator and delivery map. Neither approach is 
> as straightforward as the HTTP case.
>
> This could be addressed by extending command ports to allow them to return 
> responses but if we're doing that, why not just make them composable and 
> generate tasks that take a Json.Encode.Value as input and return a Task 
> PortError Json.Decode.Value (where PortError might just be another 
> Json.Decode.Value or it might be some other response structure that could 
> reflect other implementation errors TBD):
>
> port get : Json.Encode.Value -> Task Task.PortError Json.Decode.Value
>
>
> Now, I can wrap this up in code to do the encoding of strings to JSON 
> (trivial) and map the resulting task result to do any appropriate decoding 
> and delivery.
>
> One could argue that this would all be handled in the local storage effect 
> manager thereby rendering this example moot. But as noted above, that 
> effects manager seems to be on indefinite hold. Furthermore, local storage 
> was just a convenient and simple example. The exact same arguments could 
> apply to almost any storage mechanism we wanted to talk to. If the Elm 
> community were cranking out effects managers to handle these cases that 
> would clearly mitigate this issue but that hasn't been happening. What's 
> more such effects managers would almost certainly end up writing kernel 
> (née native) code to provide task support for the interaction with 
> JavaScript so instead of having a single mechanism for marshaling these 
> interactions and keeping the relevant code out of the kernel, we would 
>  instead have a growing number of pieces of code wanting kernel rights.
>
> Mark
>
> On Fri, Apr 14, 2017 at 11:39 AM, Simon <[email protected] <javascript:>> 
> wrote:
>
>> +1 for Task ports.
>>
>> On Friday, 14 April 2017 20:32:24 UTC+2, Nicholas Hollon wrote:
>>>
>>> The process:
>>>
>>> 1. Share your ideas, experience, & code on elm-discuss.
>>> 2. Accept that you have no direct influence over what Evan works on next.
>>> 3. Look at the release history <http://elm-lang.org/blog> for Elm. 
>>> Notice that changes happen slowly. Notice that improvements to the 
>>> JavaScript interface (ports, tasks, subscriptions) are spaced out by at 
>>> least a year. Notice that 0.17 has only been out for about a year.
>>> 4. Remember that things are going to get better! Just because something 
>>> isn't being worked on right this minute doesn't mean that it isn't going to 
>>> be improved in the future.
>>> 6. Do things in life that make you happy. If it upsets you that Elm 
>>> lacks something you think is super important, maybe take a break and come 
>>> back later.
>>>
>>>
>>>
>>> On Friday, April 14, 2017 at 10:24:46 AM UTC-7, Conner Ruhl wrote:
>>>>
>>>> What is the process for requesting these sort of features? Is there one?
>>>>
>>>> On Saturday, August 13, 2016 at 10:31:07 AM UTC-5, James Wilson wrote:
>>>>>
>>>>> The problem
>>>>>
>>>>> ports as they stand are fundamentally incompatible with Tasks. Being 
>>>>> backed by Cmd's, they are harder to compose. A frustration of mine is 
>>>>> that 
>>>>> often we are directed to "just use ports" when a proper interface to some 
>>>>> native API is not yet available, but this leads to our Msg types growing 
>>>>> and more significant changes being required when eventually the proper 
>>>>> interface is made available.
>>>>>
>>>>> Also, many JS interop things I find myself wanting to do are 
>>>>> fundamentally one-shot functions which I expect a result back into Elm 
>>>>> from 
>>>>> immediately, or otherwise just want to compose with other Task based 
>>>>> things. Some examples that come to mind of one-shot tasks you may want to 
>>>>> compose rather than use the streaming interface that ports provide:
>>>>>
>>>>>    - Getting items from local/sessionStorage
>>>>>    - .. really, most things involving working with the Web API that 
>>>>>    arent yet implemented in Elm.
>>>>>    - Embedding JS widgets into Elm elements
>>>>>    - Using a JS library for doing things like hashing passwords or 
>>>>>    obtaining some data back from some custom service
>>>>>    - Interacting with things like Electron for creating apps that can 
>>>>>    run in the desktop and interact with the filesystem etc.
>>>>>    
>>>>>
>>>>> The solution
>>>>>
>>>>> Task ports. The idea is that these are defined the same way that Ports 
>>>>> in elm currently are, but they return a Task type rather than a Cmd or 
>>>>> Sub 
>>>>> type. On the JS Side, we attach a function to the Elm app that returns a 
>>>>> Promise, and on the Elm side we wait for the Promise returned to reject 
>>>>> or 
>>>>> resolve, and marhsall the error or result from the promise into the error 
>>>>> or result type required by the Task type of the port.
>>>>>
>>>>> Let's see how this might work:
>>>>>
>>>>>
>>>>> *Ports.elm:*
>>>>>
>>>>> port apiSession: Task String SessionId
>>>>>
>>>>>
>>>>>
>>>>> *Main.elm:*
>>>>>
>>>>> import Ports
>>>>> import Json.Decode as Decode
>>>>> import Task exposing (andThen)
>>>>>
>>>>>
>>>>> -- get an API session from JS land and make an http request using it
>>>>> -- given some path and a decoder to decipher the result:
>>>>> apiRequest : String -> Decoder a -> Task ApiError a
>>>>> apiRequest path decoder =
>>>>>   let
>>>>>     headers sessId =
>>>>>         [ ("Content-Type", "application/json")
>>>>>         , ("MyApp-SessionId", sessId)
>>>>>         ]
>>>>>
>>>>>
>>>>>     req sessId = Http.send Http.defaultSettings
>>>>>         { verb = "POST"
>>>>>         , headers = headers sessId
>>>>>         , url = path
>>>>>         }
>>>>>
>>>>>
>>>>>     decodeResponse res = Decode.decodeString decoder -- ...handle 
>>>>> error etc
>>>>>   in
>>>>>     Ports.apiSession `andThen` req `andThen` decodeResponse
>>>>>
>>>>>
>>>>> *App.js:*
>>>>>
>>>>> Elm.Main.ports.apiSession = function(){
>>>>>     return new Promise(function(resolve,reject){
>>>>>
>>>>>
>>>>>         var sess = localStorage.getItem("sessionId");
>>>>>         if(!sess) reject("NO_SESSION");
>>>>>         else resolve(sess);
>>>>>
>>>>>
>>>>>     });
>>>>> }
>>>>>
>>>>> var app = Elm.Main.fullscreen();
>>>>>
>>>>>
>>>>>
>>>>>
>>>>> Here, we use a tiny bit of JS to access localStorage and pull out a 
>>>>> session ID. This function is used whenever the apiRequest Task is 
>>>>> performed 
>>>>> in Elm, and composes nicely into our apiRequest without the need for a 
>>>>> complicated effect manager or threading a sessionId through everywhere 
>>>>> just 
>>>>> because we need to get it from a Cmd based port.
>>>>>
>>>>> One of the nice things about this is that there is minimal refactoring 
>>>>> to do for those things that do eventually receive coverage in the Elm Web 
>>>>> API - you're just swapping out Tasks for other Tasks. As the Web API will 
>>>>> always be changing, I think that having a nice way to make JS polyfills 
>>>>> like this will always have some value, let alone for interacting with 
>>>>> libraries written in JS that haven't or won't ever be ported to Elm.
>>>>>
>>>>> Elm would continue to make the same guarantees as with other ports; if 
>>>>> the task port can't marshall the response back into Elm an error would be 
>>>>> thrown along the same lines as is currently done via ports.
>>>>>
>>>>> Summary
>>>>>
>>>>> - regular ports only let you send data off or receive data back, not 
>>>>> both.
>>>>> - Cmd's and Sub's are not composable
>>>>> - Task based ports allow you to create a new Task that is backed by JS
>>>>> - Task based ports allow for better composition and less friction when 
>>>>> the backing JS is eventually implemented in Elm.
>>>>>
>>>>> I'd love to hear what people think about this. Perhaps I'm missing 
>>>>> some big issues with the idea for instance, or maybe it's an awesome idea 
>>>>> :) What do you all think?
>>>>>
>>>>> -- 
>> 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.
>>
>
>

-- 
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