Elm2node: transform an elm module to a synchronous node module

Here is a tool to convert your elm functions into pure synchronous JS function: https://github.com/sebsheep/elm2node

It is as simple as:

-- src/Main.elm
module Main exposing (answer, sum)

answer : Int
answer = 42

sum : { a: Float, b: Float} -> Float
sum data =
    data.a + data.b

and:

elm2node src/Main.elm

The tool still is in alpha and only available for linux for now. I’m seeking for people helping me compile it for the other platforms and of course feedback!

Enjoy!

17 Likes

This is really neat! I strongly recommend looking into PureScript if making JS modules in a functional language is a desire of yours.

2 Likes

Whoops! This is neat. I see a use case for writing isomorphic code (meteor-like) for data that needs validation on the server or should be hidden from the user but is tightly integrated with the data structures prevalent in your client-side code.

1 Like

This is really neat. I’ve essentially asked for this tool a while back and extremely pleased that my wishes have come true! Well done.

3 Likes

Publish this as JS lib on npm would be great but I’m afraid it suffers some serious performance hit. All the parameters are encoded with a Json.parse(Json.stringify(parameter)) which is the way elm deals with parameter coming from JS; in the context of UI this is not a big since most of the time is spent on redrawing.

But for lib called server side, it might take some time.

A solution would be to emit typescript annotation and get rid of the Json.parse(Json.stringify(...)). (up to the user to use TS instead of JS).

1 Like

I didn’t know JSON.parse(JSON.stringify(parameter)) was used for ports! Do you know why this is – what does this do?

I guess the main reasons are:

  1. we want copy of the JS args so even if a user do
arg= {b : 5};
app.ports.myport.send(arg);
arg.b = 42;

the copy on elm-side will stay as it.

  1. prevent some weird edge cases like JS objects making side effect at properties read.

Thinking about this, if we want 1. to be still true, we can not prevent the copy of arg.

That said, I think both methods are really fast and optimized (hey, they are used everywhere in JS), maybe it is the fatest way to have 1. and 2. hold.

Doesn’t JSON.parse(JSON.stringify(...)) transform the object? https://medium.com/@pmzubar/why-json-parse-json-stringify-is-a-bad-practice-to-clone-an-object-in-javascript-b28ac5e36521

1 Like

Elm definitely doesn’t do this by default for ports. You can pass in any javascript value you like (stringifiable or not) and elm can handle it fine.

1 Like

Well…



Maybe there are more steps to prevent issues pointed by @Laurent but at some point, data are strigified/parsed.

Neither of these lines are invoked when passing a value through a port. The first is called only when trying to parse a String, like from the body of an HTTP response. There is also Json.Decode.decodeValue which deals only with Values. The second line similarly is only invoked if you need to render a Value to a String by explicitly calling Json.Encode.encode.

When the compiler generates code for ports it checks the type and sets up the appropriate converter from JS to Elm for that type. Lists and tuples, for example, get special treatment by getting converted to and from JS Arrays, but in the case of a Value, the converter is identity or Json.Decode.value (the Decoder analogue to identity). No stringifying.

Generated port code for a Value looks like this:

var $author$project$Module$portName = _Platform_outgoingPort('portName', $elm$core$Basics$identity);
var $author$project$Module$portName = _Platform_incomingPort('portName', $elm$json$Json$Decode$value);

Those generated arguments are used here https://github.com/elm/core/blob/master/src/Elm/Kernel/Platform.js#L363-L471

Flags are treated in the same manner as an incoming port.

6 Likes

Is this documented somewhere? Looks like a missing piece for Elms (Ports) documentation :smile:

Ok, my bad! Thank you @luke for correcting me.

I don’t know why I thought those functions was used during port process. I’ve checked what I’ve done and I don’t use these functions neither.

So my claim was wrong.

In the case we would want to use typescript to get rid of the encoding part, I wonder if we should care about “copy” the function argument in order to prevent mutation by the caller. If we do:


arg = { b: 5};
res = elmModule.f(arg);
arg.b = 4;

this should not bother f . But is it possible for arg to be modified in another place (say it is a global variable), during the execution of f?

It would not be possible for another piece of code to modify arg during the execution of elmModule.f since JavaScript is single-threaded. However, if elm code keeps a reference to the json Value, it is possible for two decodings of the value to produce different results if some non-elm code runs in between.

In a normal elm program, this is a possibility (and is actually sometimes useful), but I don’t know enough about your code to know whether it is a possibility in your library. Does your library ever keep any state, or in other words does your library allow wrapping Program types? If not, then it should be safe.

Ok cool @dta! Thanks.

There are no way to guarantee that a given value x returned by an “elm-converted-to-js” function cannot be modified by JS code. So I’ll never try do allow things like:

// js file:
var someOpaqueElmValue = elmModule.f(42);
var result = elmModule.g(someOpaqueElmValue);

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