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 <hotbe...@gmail.com> 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 elm-discuss+unsubscr...@googlegroups.com.
> 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 elm-discuss+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to