Right now, Native doesn't play well with dead-code elmination. Additionally, Native review is a big bottleneck for producing and publishing new librairies. Finally, there is no way to unclude JS code which calls ports using the Elm compiler.
/**
--The top of the file declares the structure of the Native module
--and must be a valid Elm syntax declaration
module Native.SomeModule where
--Used Native modules must be explicitly imported
import Native.Utils
--We can import pre-existing library functions
--this includes them in the final output
--but they can't be explicitly called from Elm.
--A Native module must act as an interface.
import raw "Bootstrap.js"
-every used non-Native function must be explicitly imported
import Signal exposing (constant)
--Each exposed Native function is declared as a String literal in Elm
--with the name of the JS function that defines it
--This lets us statically check that no top-level functions are missing
nativeFun = "nativeFun"
otherFun = "otherFun"
**/
//The rest of the JS file is just a series of JS statements
var nativeFun = function(x)
{
if (Elm.Utils.constructor(x) == "Nothing" )
{
Elm.Utils.construct("Nothing");
}
//We construct "type" (algebraic data) values using a library function
return Elm.Utils.construct("Just", 3);
};
var otherFun = function(y)
{
return nonExposedFunction(y);
};
//We are allowed to declare other functions
//that can't be imported by Elm
var nonExposedFunction(y)
{
var ret = [];
//We build records out of arrays
//and access fields using a library function
ret["field"] = Elm.Utils.field(y, "elmField");
ret["field2] = 3;
//a library function constructs the record out of the array
return Elm.Signal.constant(Elm.Utils.record(ret));
};
- The structure of the module is declared at the top, using normal Elm syntax
- The user doesn't write a .make() function, or return a .values object. That's all done mechanically, we would be guaranteed those functions/values would always be well formed.
- The imported Native modules, and imported and exported Elm functions, must be explicitly declared
- Elm ADT and Record values are not manipulated directly, but using a library function, so Native modules would not break if Elm changed its internal representations.
- There's new syntax for including a JS file in compilation, such as pre-existing libraries.
- The module structure is defined using Elm syntax, so we can use existing parsers, and it flows nicely with the rest of the project.
- Much less Native review needed to ensure a module is well formed.
- More resistant to breaking changes.
- The
import raw
syntax can be used to include the JS side of Ports code.
- Could be slower, since we're calling library functions to access Elm values. (Could be solved by inlining).
- Requires pre-processing of comments (could be solved by putting the structure Elm declaration in its own file).
- There are now 3 types of JS ffi (Native, Ports, import raw)
This proposal makes a lot of sense.
However, I wonder whether it might be better to turn it inside-out -- that is, instead of embedding Elm in Javascript comments, why not embed Javascript in Elm? That is, for instance, the general approach that GWT uses for "native" javascript (embedding Javascript in Java comments). But we can make it even nicer than GWT, because we control the compiler -- it wouldn't even have to be 'comments', since we can invent new language-level keywords.
Here's how your example file might look in such a syntax, with some comments:
Note that the @{} syntax would need to be converted by the compiler to refer to the actual Elm reference, and use A2, A3 or whatever, depending on the number of arguments supplied. So, the Elm compiler would have to parse the Javascript, but there must be a Javascript parser available in Haskell. And, you could actually guarantee that the Javascript is reasonably well-formed, since the parser would have to understand it.