Project Proposal: Compiler for libraries

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:

  1. 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))
    
  2. 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 the constructor field could work, but that might be an implementation detail, so some further design on this would need to be done here.
5 Likes

I agree that this sounds like a great proposition in theory.

Can you (or others) point to concrete examples where you wanted to use an elm package but couldn’t?
I could imagine that parsing is a good candidate, but for most calculation-based tasks, existing JS implementations exist that

  • don’t have decoding overhead
  • can use more lowlevel constructs (like typed arrays)

We could also look at purescript to see how this might work. This post contains
a simple example, but I could not find a lot of material on people really using purescript packages from JS. They can do it, it doesn’t seem to happen much
(based on a brief google search, of course).

So, how would you actually use this, for what use cases does it make sense?

2 Likes

In my case, oddly enough, for elm-visualization. It seems to me that for a react app, it’s api is easier than d3.

The other use case is that I need to write a library for transitions between geojson frames, which is something I need both in a react app and in an elm app.

1 Like

I’m thinking of presenting some work I’ve done on image annotations for an open source track in a conference. I will probably present the library and an application made with it. The issue I have is that reusability in one of the major points of the track. And as enjoyable as Elm is to me, and most of you I guess, the library is not very reusable to JS users as is. So having it interfaced nicely to JS is something I would be interested in.

Unfortunately, I don’t have much time to spend on this either, and I’m not familiar with JS and it’s frameworks.

I think the biggest challenge to implementing this is the fact that Elm has a runtime that independently compiled modules would still be dependent on. Would each library “statically link” the Elm runtime, inflating the size of each artifact rather significantly? Would the runtime be included separately and “dynamically linked,” requiring manual inclusion/version reconciliation? Would the runtime need to expose hooks into the update loop to be properly integrated with things like React/angular?

Well, the “runtime” is only needed for side-effects.

My initial idea here was to only deal with pure code.

How to deal with effectfull code is an entirely different can of worms.

2 Likes

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