Responses from command ports

Honestly a question as opposed to a troll disguised as a question… This is a question from the perspective of a language implementation geek who also gets asked why some things are awkward in Elm and wants to give honest answers.

There are lots of arguments in favor of the way ports work that cite avoidance of runtime exceptions. I understand that having native code run synchronously with other Elm code or worse manipulating Elm’s data structures opens up risks. But what I don’t understand is why disallowing command ports from returning values and instead requiring a separate subscription — an issue that seems to come up repeatedly for some uses ports — creates runtime safety that would not be present if such things were allowed. The same Turing completeness argument that argues that nothing is impossible with ports would also seem to argue that having a command trigger JavaSript code which triggers a subscription has the same potential for runtime exceptions as code in which the command implementation gets to respond directly to the command rather than via a subscription.

Honestly, what am I missing? What runtime exceptions is the plumbing around ports on either side preventing or at least heavily discouraging that a ports approach with the ability for commands to asnchronously return values wouldn’t?

Mark

I’m not saying this is the answer, but it could also be that making it non trivial to use ports is not a technical thing, but a design goal for the language.

I am of the opinion that using any javascript at all should be avoided/last resort. And that using ports or interacting with javascript should be possible but be hard to do/non trivial.

Elm will hopefully not always compile to Javascript.
As I see it, Javascript is only chosen as a “temporary” target because it is the only thing supported properly by the browsers today.

If it is too easy to use javascript libraries, little incentive is aceived for creating a pure Elm solution to that problem.

Elm as a language is better off in the long run keeping communication to Javascript like it is, so that you really want a pure Elm solution to get rid of the ports if you can.

1 Like

There is a helpful discussion of some technical considerations here (though what you’re proposing may not be identical to what was being discussed there):

There are also some cultural considerations which are expressed here:

https://groups.google.com/forum/#!topic/elm-dev/kNKilHjUYqo

1 Like

That’s a different argument from the one I’ve seen. I’m sure I read Evan saying the argument against “Task ports” is that the Elm definition of a Task comes with a guarantee that it will always resolve to either a success or a failure. It can’t just hang forever and never resolve. All the core library Tasks are designed that way.
If you go out to JS you lose that guarantee. Technically you could implement it as a Task but then a Task becomes something that only usually resolves, rather than always.

@Brian_Carroll: In the link above Evan also writes something that to me sounds like “Calling Javascript functions should not be too easy, as it promotes wrapping JS libraries”
And that is not good for Elm in the long run…

From link above Evan writes:
A “task port” is a traditional FFI.
When you have a traditional FFI, you push people towards copying JS APIs directly because “those are the functions they gave us”. Now we have someone who does 100% copy bindings to some JS library. “It works! It is ready to share!” But you cannot publish code with ports. “Man, we should really allow that.” And now we are back to the exact same kernel code discussion.

I don’t really see how ports are less “js api wrapping” than tasks. It depends on the context, and especially the state (shared? concurrent? none?). In my mind it’s very similar to the websocket/rpc/rest opposition. I get it that tasks have to terminate, but you have the exact same thing with ports: you can expect something coming back from a port (and show a spinner) yet it never responds. Worse, having to do everything port related by hand, it’s highly tempting to only treat the successes and dismiss the errors to save some complexity and wait for it to bite.

For the sake of argument: the courageous can already fake custom tasks by wrapping the global xhr (tomorrow even service workers?) hence intercepting http requests (the drawback being that you have to serialize/deserialize your objects). You end up writing kind of the same js code (I usually have only one port each way, and encode/decode the messages), it really just is the way you use it on the elm side that change, especially chaining.

1 Like

The take away I’m getting here seems to be:

If the question is “why do we have to use ports”, the answer is “in order to prevent JavaScript code from violating compiler assumptions and language promises”.

If the question is “why are ports (and particularly command ports) awkward to use”, the answer seems to be “because we don’t want JavaScript interop to be too easy”.

The first is pretty easy to sell. The second is harder. Is this a correct read?

Mark

4 Likes

I really like the separation between data going out and data coming in, it’s never been an issue for me and I didn’t realise it was for anyone else. When people “asked why some thing are awkward in Elm”, do they give an example of what they’re doing and why it’s awkward? Or are there some example floating about elsewhere?

See the recent discussion about how to generate UUIDs. Elm’s random number support doesn’t use a large enough seed so one needs to call out to JavaScript. Doing that involves poking a command port and subscribing to the response port and, if you need UUIDs from multiple points in your code, figuring out which values coming back from the subscription port are meant for you. Compare this to generating random values within Elm where the command goes to the effects manager and returns via message routing to the piece of the code from whence it was issued.

More conceptually, think about what it would be like if HTTP interactions had to work this way.

Or concretely for the early days of Elm 0.19, think about building a web sockets replacement with ports particularly if you need to connect to multiple sockets. You can do it but it isn’t pretty since you need to filter responses to see whether they are coming from the port you are interested in.

Mark

I agree that ports are harder to use this way. And that is great. It should be a bit messy and not feel right using ports… :slight_smile:

This creates incentive/demand to build a proper websocket and random library in elm. (Core or exploration)
People will probably switch to the elm library immediatly when available.
I guess If you already have a superclean and simple solution with ports, you dont care about a pure elm version, and will probably just continue using your ports when a proper library is published in Elm.

Ports is not a language feature comparable to http or other supported effects, I consider ports a (temporary) escape hatch for stuff that cannot be done in elm today.
And there is no problem that this last resort thing is not great to work with. Just possible is enough. But that is just my opinion.

I’m not sure I understand the issue. I made an ellie app that gets random numbers from Elm’s builtin package as well as from a custom javascript port. Two of each type of number are stored in the model. Here’s the link: https://ellie-app.com/3xfNpXrGjkya1

On lines 53-56, we have to go through some JSON decoding and string matching. That replaces the native elm random numbers, which are able to use two distinct members of a union type instead of string constants to differentiate between the two incoming streams of random numbers. The native solution is more type-safe and concise. But on the other hand, since the port’s javascript can do anything, it’s a good idea to make sure that the Elm side is robust. (I haven’t implemented any error handling in this example, but it’s pretty easy to imagine how one would do this, I think). Is this the issue? I can see how it seems a bit verbose, but that seems entirely consistent with elm’s design philosophy of rejecting advanced features in the interest of simplicity, even if it makes for occasionally plodding boilerplate (no typeclasses, no monadic do notation, no automatic generation of JSON en/decoders for ADTs, …)

@MarkHamburg is referring to this post Cryptographically-strong random UUIDs v4 with Elm 0.19?. Elm’s random numbers are 32bits, UUIDs typically have 128bits. So right now you need to use a port - or a custom element that produces a CustomEvent :slight_smile:

@mfeineis: maybe a silly question, but is it not possible to create a pure elm 128bit random uuid library instead of using ports?

A naive solution could be calling elm’s 32bit random 4 times in a row and append the results in a string? :slight_smile:

@Atlewee that solution only works if you want something that looks random but is not cryptographically strong.

The problem there is that the 4 random numbers have exactly the same amount of actual “randomness” (entropy) as the first one on its own. That’s because the sequence is only “pseudo-random”. The last 3 numbers are determined deterministically from the first. You’re generating fake randomness that looks right but is much easier to break from a security point of view.

If security doesn’t matter in a particular application then your solution works. But if it does, then unfortunately it only looks like it works!

Okey, I understand.
So should be 100% fine for a unique Id like in this example above.
Or temporary security number that is only valid for a short period of time.

But not good for a permanent code that someone could use time to bruteforce.

But there is probably ways of doing this safely in a pure Elm library as well. I found this link that talks about combining multiple random numbers to a larger one…
Maybe Method #2: Cryptographic Mixing can be acheived in a pure Elm library, eliminating ports for this usecase ?

https://software.intel.com/en-us/blogs/2014/03/07/to-concatenate-or-not-concatenate-rdrand

As for a practical approach that lets you create something similar to ‘Task Ports’ in current versions of Elm: Check out the Porter library.

But what if you used four separate seeds? Wouldn’t that then give you enough entropy to generate 128-bit numbers?

You would have to track each of the seeds for yourself in your Model and then use Random.step to generate the UUIDs from them as required, but it would overcome the problem of using ports when you wanted several successive UUIDs.

You could initialize the four 32-bit seeds from a 128-bit random number generated on the javascript side before initialization and passed in via flags.

Yes, I understand that. My question was an attempt to understand/delineate what’s so bad about using ports in this situation.

Actually not great for unique IDs either if you care about birthday paradox problems.

Mark

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.