Closed Escapingbug closed 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:
Gradual typing: I don't think this is going to happen for a couple of reasons. As mentioned above, it's not really clear how this would actually work with regards to the necessary (un)currying, but more importantly I don't think it's really in line with the rest of the type system. @zardyh will probably be able to provide more information.
Make binding easier: There's lots of small improvements we could do to make writing external bindings easier:
external val { sub : string -> int -> int -> string,
len : string -> int } = "string"
It's not much, but would make things that tiny bit more concise.
Binding generators: I just knocked up something which will generate a skeleton set of bindings when given a module file. It makes things a tiny bit easier I guess, but is obviously missing all the type information. It might be worth looking into parsing LDoc/EmmyLua annotations to get type information, but that's probably a whole 'nother issue to track.
Gradual typing wouldn't really work with type classes, unless you can point me to a research paper disproving that.
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.
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.