Proposal: directly calling JS functions

This is a random idea that might not be useful but I will give it anyway.

It might be useful to be able to conveniently call a JS function from Elm. Ports kinda do this, but it does not give Elm a lot of control over how the function gets called. This might be preventing some Elm package from being simple to set up, such as this one.

Defining this function in a core library seems like an obvious solution (first argument is function name, second is the argument list, and third makes a msg from the return value):

callFunction : String -> List Value -> ( Value -> msg ) -> Cmd msg

But I think the first argument here is a problem. It causes a security risk since any Elm package could call any function without you providing that function. And it’s better to explicitly control how a JS function becomes accessible from the Elm application.

A better solution would be to allow Json Value to represent a JS function since it is a first class value in JS. The only way for this function to enter the Elm application would be through flags (the best way) or through a port. Then you can use these things which would be defined in some core elm/* package:

callFunction : Value -> List Value -> (Result FuncError Value -> msg) -> Cmd msg

type FuncError
  = Exception Value

There also needs to be a way to construct a new object when given a constructor

The question of ports vs FFI is a common topic that comes up. Ports are a pretty fundamental part of Elm’s design. It’s worth doing some searching through discourse and YouTube, and other resources like the Elm guide, for some more background on the design considerations behind not allowing FFI in Elm. Here’s one good discussion where it comes up:

2 Likes

You can find discussions about this kind of functionality by searching for “task ports”. In short, it has been discussed multiple times and it was decided against it.

The most recent position of Evan is here and I expect that this is the current position too.

If you look into the previous discussions you will understand more of the concerns around this kind of functionality.

Yeah this idea was really a shower thought that I posted just in case it’s a good idea. The long term solution will be to have Elm be able to do more things that reduces the need for ports (such as creation of elm/websockets package).

Implementation of more of the web api would reduce the needs for ports but, to be honest, ports get an unfair bad reputation. It is not all that hard to work with ports, especially if you use an Actor model-like port architecture. Murphy’s talk has more details about this approach.

4 Likes

Yes. Ports work perfect for me and will always be useful if Elm is only suitable for part of an application. Completely replacing ports with something else in Elm would be such an Apple move. Just like removing something every darn update :)

I had already implemented a similar API trough Elm’s ports named SimpleJs and discussed here

I was also thinking of such an API for new, but so far I just call JS-functions, and can not create objects without using helper functions, like in here for a JS Date. This works for me.

I do not expect, that JS-objects are equal to Elms Json.Decode.Value, because the do not include the functions / object methods, or at least this is an implementation detail, which can change with every new version, so I have not tried it. I do not expecting the compiler, when copying a variable of Json.Decode.Value type to copy also the included functions. So a special new type additional to Value is necessary, let call it JsObject. Without extending the compiler to support JsObject, JsObject can be a reference, like a Elm Int to a real JS-object. So the parameters of the function need to change from List Value to List JsObject. But then you need the encoders creating a JsObject, which need call JS trough ports again. For me, it seems to difficult to implement using Elm ports, and programming with (references to) objects this way in Elm would not help me to create clean code.

1 Like

Crazy idea:

Introduce JsParam:

type alias JsObject
  = Int

type JsParam
  = Obj JsObject
  | Val Json.Decode.Value

and define

callFunction : List String -> List JsParam -> ( Json.Decode.Value -> msg ) -> Cmd msg
callMethod: (JsObject, String) -> List JsParam -> ( Json.Decode.Value -> msg ) -> Cmd msg
new: List String -> List JsParam -> ( JsObject -> msg ) -> Cmd msg
delete: JsObject -> msg -> Cmd msg

As the JS-objects must be stored in a JS-array, so delete is necessary, buy you can easy forgot to call it, causing memory leaks on JS side. Reminds me to some old JNI (Java native interface, calling “C” from Java) implementation. It is not a nice solution, but could be implemented using Elm-ports.

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