amuletml / amulet

An ML-like functional programming language
https://amulet.works/
BSD 3-Clause "New" or "Revised" License
324 stars 14 forks source link

Lua module and amulet module interoperability #255

Closed Escapingbug closed 4 years ago

Escapingbug commented 4 years ago

This is a quite interesting project which I eagerly want to use in my project. But the problem is I currently have no idea how to cooperate lua module and amulet module, like how can we "require" anything in lua's ecosystem into amulet's module?

Another problem we may need to consider is that since lua's code has no type information, if we want to get as quick as we can to make this actually usable, maybe we should consider gradual typing or something alike to avoid writing a typed interface for every function from lua's dynamically typed world?

Also, I have noticed #195 , but seems the solution is about generating lua's module without considering amulet using lua's module. If this is considered a dup, we can discuss this in that issue and close this one.

SquidDev commented 4 years ago

The correct way to intero with Lua is by declaring an external function. There's more complete examples in the stdlib, but this roughly looks like:

external val to_json : 'a -> string = "require('cjson').encode"

Obviously, as you mention, this does require explicit types and declarations everywhere. One cheaty solution would be to just declare something as having the type 'a:

external val json : 'a = require('cjson')

let x : string = json.encode { a = 1 }

The issue with this, and I suspect any gradual typing system, is that it's not clear how to handle functions taking multiple arguments. Amulet functions are curried by default and we do a bit of compiler magic to uncurry them for external functions. Basically

external val substring : string -> int -> int -> string = "string.sub"

becomes

external val do_substring : (# string, int, int #) -> string = "string.sub"
(* (# ... #) is how we display uncurried function calls within the IR. It's
   not part of the main syntax. *)

let substring a b c = do_substring (# a, b, c #)

This transformation isn't possible without typing information.


Anyway, sorry for the long preamble, just thought it would be good to describe the current system and maybe why it is like it is. We've never found a nice way to make it simpler, which is why it's still kinda terrible. There's a couple of things we can do to improve, but none alleviate the problem:

plt-amy commented 4 years ago

Gradual typing wouldn't really work with type classes, unless you can point me to a research paper disproving that.

SquidDev commented 4 years ago

I'm going to close this for the time being, as I don't think there's any immediate actions which can be taken.

We definitely need to do some work on making writing external bindings easier, and I think it's worth borrowing some of the work done by Bucklescript. That said, I need to do some planning and prototyping before creating an issue.