Skip to content

Instantly share code, notes, and snippets.

@alpacaaa
Last active December 2, 2021 08:00
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alpacaaa/13335246234042395813d97af029b10f to your computer and use it in GitHub Desktop.
Save alpacaaa/13335246234042395813d97af029b10f to your computer and use it in GitHub Desktop.
Hard things about ports being Tasks in Elm

evancz Mar 23, 2017 00:43
Just so folks are aware, one of the hard things about having ports just be a Task is the following. Right now, a Task is guaranteed to terminate with an error or a result. The only way it could be otherwise is if you have something of type Task Never Never Now, if you are calling out to random JS that is written by anyone, that guarantee goes away. You have to call some callback to give the value back to Elm, but what if that is never called? Maybe there's an error, maybe there is a weird code path. Now Elm code can "leak" tasks that never get completed because of problems in JS code. One way to protect against this is to have timeouts, such that there is some guaranteed end. My point here is just that it is more complicated than "what if it was a task?" and then everything would be nice.

rtfeldman Mar 23, 2017 00:46
https://elmlang.slack.com/archives/C13L7S5GR/p1490220408385708 👈 this seems sweet though - seems like it would solve the problem without using Task

evancz Mar 23, 2017 00:48
Doesn't it still have the "identity" problem. You need to know that the User you get from JS was related to a certain thing. That seems to be the part that's difficult. The "one port = two ports" doesn't seem to address that It is just syntactic sugar as far as I can tell

eeue56 Mar 23, 2017 00:50
That's true. there would need to be some other way to link the two

evancz Mar 23, 2017 00:50
So all I'm trying to say here is that there isn't a quick fix to the design. A coherent exploration may explore the connection between Cmd and Task at some point, but that's not going to be an insight you have casually.

rtfeldman Mar 23, 2017 00:50
I'd expect it to set up a callback on the JS side, which could be fired more than once, and would appear on the Elm side as a subscription so not just syntactic sugar but yes, nontrivial design, fair enough 🙂

evancz Mar 23, 2017 00:52
(To clarify, the whole point of wanting a Task is that you can use andThen. That implicitly clarifies the "identity" of the result value. So in a database setting, you could chain, get this, then get that, then get the other thing as one Task. As long as you are using commands and subscriptions, you do not have access to this particular chaining mechanism.)

xarvh Mar 23, 2017 00:54
Would it make sense to have andThen for Cmds?

eeue56 Mar 23, 2017 00:56
The majority of cases where I see people complaining about subs/cmds not being linked rarely need andThen, though - the majority of processing happens inside the update function, rather than during cmd construction

evancz Mar 23, 2017 00:56
If I was hooking up to some complex thing with ports, like in @splodingsocks case, my intuition would be this: Have one port out where I can specify what I want as data. That goes to some JS that does whatever. Chains whatever. It interprets the data. I'd also have ports for when things got loaded. So I hear about that in a normal way. To say it another way, I would not make each JS function its own port. I would create an interpreter that does stuff in JS @xarvh, it's crucial that you do not have map2 or andThen for commands. Commands only have map and batch so that they can be transformed and combined. If you had andThen, you get order dependency I mean, look, if you want to chain, Task is designed for that case

eeue56 Mar 23, 2017 00:58
I have a blog post in the works explaining exactly that - keep ports small and keep the processing of things in the JS side. That's how people who are using Elm for logic (e.g within a larger react application) should be doing things, because they end up frustrated making a million ports - when they should be just making one for data going out, then subscribe to data coming in.

evancz Mar 23, 2017 00:58
If you want to chain Cmd, the way to go is to express it as a Task somehow (I'm speaking abstractly, that's not concrete advice for Elm today. That's an overall design intuition.) @eeue56, that'd be great to have! Anyway, just wanted to share some subtle points about port theories

xarvh Mar 23, 2017 01:05
Agreed. At cost of sounding stupid: why don't we want order dependency for Cmds? Further, my understanding was that we want to bring as much code and logic as possible into Elm, and leave the minimum possible JS code. Wouldn't having a lot of JS logic behind a port go against this?

rtfeldman Mar 23, 2017 01:06
ports are a question of interop though you reach for them when you already determined you want to get some JS involved

xarvh Mar 23, 2017 01:12
Indeed. But as I understand the idea (which makes a lot of sense to me) was to try and minimise the JS code. "Getting little JS involved is better than getting a lot of JS involved".

rtfeldman Mar 23, 2017 01:14
that's true, all else being equal but all else is not equal in this case 😉

xarvh Mar 23, 2017 01:17
@rtfeldman fair enough.

evancz Mar 23, 2017 01:18
@xarvh, a Cmd never fails. So chaining them somehow needs to account for that. But most effects can fail. So you probably want to capture the possible errors somehow. So now when you chain a Cmd you'd have to handle a Result or something at every single step To go back to designs (1) and (2) I mentioned before, going with (2) just makes more sense. Task is for chaining. Cmd is for telling the runtime "I have a chunk of work for you"

xarvh Mar 23, 2017 01:19
@evancz Ok, makes sense now. Thanks.

splodingsocks Mar 23, 2017 02:59
evancz: I’d love to see what this looks like. I’d be very interested in the post you mentioned, @eeue56. I’m not sure I understand at this point how this would work. In my case I’m saving and getting multiple things to/from a (client-side) database. I’m trying to image how that’d fit into one port / sub, but my imagination is failing. Are you saying that I’d make a data type like “DbRequest” that somehow describes what I want to do in the database, and another for “DbResponse” that would describe what it would look like upon return? But I’ve got to stop thinking of the connected request/response cycle It’d be more like notifying the DB that I want to save something, or that I need an update on some certain kind of information and then the subscription would provide that information at a later time, not as discreet request / response pair, but a notification, or update. Maybe I can only think when I’m also talking at the same time 😉 ‘cause that makes more sense and is pretty intriguing If only there were a reliable Task data type available in JS other than promises 😉

evancz Mar 23, 2017 03:05
Yeah, I checked the key ideas. The idea is that you'd write an "interpreter" in JS and have it communicate with Elm via higher-level structured data (This may require using Json.Encode to make things flexible enough on each side. Not ideal, but probably better than tons of ports!)

splodingsocks Mar 23, 2017 03:14
Okay, thanks for the insight! This is a really interesting idea

jadams Mar 23, 2017 06:52
the way that you describe declaratively your channel connections for elm-phoenix (the effects manager version) is very enlightening on this front. Being able to just have all of that javascript goings-on happen by tweaking my model is amazing not particularly Cmd related but seemed relevant RE: having an interpreter on the js side

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment