It’s been said many times that we don’t want to just import JavaScript libraries with “thin” wrappers to pollute the wonderful Elm ecosystem.
However, I wonder if the converse isn’t actually a great idea: we can compile Elm libraries to JavaScript and publish them to npm and use them from JavaScript.
Unlike importing JS to Elm, importing Elm to JS is a lot nicer:
- Elm is mostly a subset of JavaScript semantics*, whereas JS has many things that aren’t easily expressible in Elm
- Having Elm’s functional design in JS will feel relatively natural for many problems.
- A strict functional language will be really nice for design algorithms and other “library” sort of things.
Why I personally want this
I work in a place where most projects are in React, and so when writing a library to support a particular use case, I can’t really write it in Elm. That means Elm will be missing out on some high quality libraries.
Some design notes
I think every module that is in exposedModules
should end up as an ES6 module in the output with it’s names not mangled.
The biggest question for this project is how to handle type conversions on the boundaries. I think there are two options:
-
Manual: The functions are emitted as is, and the module author has to write a bit of custom JavaScript to provide an interface to these.
Examples
Given an elm file:
module MyLib exposing (foo) foo : Int -> List Int foo n = bar n [] bar : Int -> List Int -> List Int bar n l = ...
This would compile to the following JS at
build/elm/MyLib.js
:function F2(fun) { function wrapper(a) { return function(b) { return fun(a,b); }; } wrapper.arity = 2; wrapper.func = fun; return wrapper; } function A2(fun, a, b) { return fun.arity === 2 ? fun.func(a, b) : fun(a)(b); } var _user$project$Temp1522315140234014$bar = F2( function (n, l) { // ... }); export const foo = function (n) { return A2( _user$project$Temp1522315140234014$bar, n, {ctor: '[]'}); };
Then the user would add
build/MyLib.js
:import { foo as elmFoo } from "./elm/MyLib"; const elmListToJsArray = list => ... export const foo = num => elmListToJsArray(elmFoo(num))
-
Automatic: The compiler compiles the elm functions with their normal names and so on in a common implementation file, but also emits interace javascript files that contain the exposed functions that call the original internal ones, but also perform conversion on their inputs and outputs (perhaps in the same way that ports/flags do now). This doesn’t preclude you to also use some techniques from method 1.
Examples
Given the same elm file:
module MyLib exposing (foo) foo : Int -> List Int foo n = bar n [] bar : Int -> List Int -> List Int bar n l = ...
This would compile (with all the other stuff) to JS at
build/elm.js
:function F2(fun) { function wrapper(a) { return function(b) { return fun(a,b); }; } wrapper.arity = 2; wrapper.func = fun; return wrapper; } function A2(fun, a, b) { return fun.arity === 2 ? fun.func(a, b) : fun(a)(b); } var _user$project$Temp1522315140234014$bar = F2( function (n, l) { // ... }); var _user$project$Temp1522315140234014$foo = function (n) { return A2( _user$project$Temp1522315140234014$bar, n, {ctor: '[]'}); };
And the compiler would also export
build/MyLib.js
:import "./elm"; export const foo = num => toJsArray(_user$project$Temp1522315140234014$foo(num))
This would be even better if the output was ready to ship as a npm module (whether that means including some further build tools from the JS ecosystem I don’t know).
What do you think? Does it sound like a good idea? What is the effort required to achieve this? (I would love to build this, but I have been extremely swamped lately, so can’t actually build any of this).
* The things that elm has that JS needs some help with are:
- Auto-Currying: this can be done with a use of a helper function, but needs to be handled by the boundary code.
- ADTs: These mostly are just functions, but pattern matching on a result might be a bit tricky from the JS side. I believe a
switch
on theconstructor
field could work, but that might be an implementation detail, so some further design on this would need to be done here.