Agreed.

Tasks actually were the core way to do things in 0.16 — though ports then
were more focused on reactive programming models as I recall. Effects (what
essentially became commands) were largely a shim on top of tasks. But then
along came Elm 0.17 and it's emphasis on effects managers accessed via
commands and subscriptions and we started to see less functionality
delivered via tasks. But it feels like effects managers have stalled out —
e.g., Evan's local storage effects manager, documentation, etc — leaving
few clear answers here.

Ports are interesting but as noted don't really scale well for any cases
where you need to send in messages and get back matched responses —
particularly if those responses need to result in different messages to the
app.

So, is there a reason for why Elm doesn't place more emphasis on tasks and
provide something like task ports as a way to leverage JavaScript
functionality?

Mark

On Thu, Mar 9, 2017 at 6:47 PM, Jeff Schomay <jscho...@gmail.com> wrote:

> Just want to add my desires for this functionality and bump this topic.
>
> My current need is to add a new `li` item to a `ul`, find its offsetTop
> and scroll to it (so that the top of the new item is at the top of the
> window).  I can't find a more "Elm-y" way of doing this besides hitting a
> port to get the offsetTop and then use Dom.Scroll.toY to get there (maybe
> with some animation).  It would be great to chain all of that in a series
> of tasks, but currently I have to make a new message for the port and
> respond to that with my scroll code :-/.
>
> In general, after using Elm a lot over the many months, I'm learning that
> the monadic properties of Tasks (ie. chaining with `andThen`) are extremely
> powerful and expressive and composable, not to mention easier to test with
> libraries like arborist.  I'm just sad that there aren't more api's that
> return Tasks.
>
>
>
> On Saturday, August 13, 2016 at 9:31:07 AM UTC-6, 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