Partial function application compiles to wrong erlang
Describe the bug
A simple function that taking no arguments in Caramel, that only runs Io.format, compiles to invalid erlang that says it is taking two arguments, which erlang/OTP cannot compile.
To Reproduce
$ caramelc --version
0.0.14+git-27bc02d
$ cat sweetness.ml
let text () = "Hello, world!"
let hello () = Io.format (text ())
$ caramelc compile sweetness.ml
Compiling sweetness.erl OK
$ cat sweetness.erl
% Source code generated with Caramel.
-module(sweetness).
-export([hello/2]).
-export([text/0]).
-spec text() -> binary().
text() -> <<"Hello, world!">>.
-spec hello(ok, list(any())) -> ok.
hello() -> io:format(text()).
$ erl
Erlang/OTP 23 [erts-11.1.6] [source] [64-bit] [smp:6:6] [ds:6:6:10] [async-threads:1] [hipe]
Eshell V11.1.6 (abort with ^G)
1> c(sweetness).
sweetness.erl:4: function hello/2 undefined
sweetness.erl:10: spec for undefined function hello/2
sweetness.erl:11: Warning: function hello/0 is unused
error
Expected behaviour
.erl gets created with
-export([hello/0]).
-spec hello() -> fun((list(any())) -> ok).
I think it may be because I thought there is an Io.format function that takes one argument but there's only a two argument version? Still I think this should not be the result, I don't know where the ok in spec comes from and why spec args do not align with function args. I don't know much Ocaml but should that compile to something like
-export([hello/1]).
-spec hello(list(any())) -> ok.
hello(args) -> io:format(text(), args).
? Or
-export([hello/0]).
-spec hello() -> fun((list(any())) -> ok).
hello() -> fun
args -> io:format(text(), args)
end.
?
Also tried on 0fe81e7, same result
Thanks for filing this!
Since Caramel is an ML, we do partial application by default. When you called Io.format x what you get back is a function 'a list -> unit.
You're correct that this is a bug because the signature of hello/0 is incorrect.
The right signature should be hello() -> fun( (list(_)) -> ok ).
-export([hello/1]).
-spec hello(list(any())) -> ok.
hello(Args) -> io:format(text(), Args).
This doesn't quite track because in the Caramel source you still have to call hello () args, and that unit is being translated as an ok. Empty tuples {} in Erlang just don't aren't a convention.
-export([hello/0]).
-spec hello() -> fun((list(any())) -> ok).
hello() -> fun (Args) -> io:format(text(), Args) end.
This would be the Right Thing ™️, but is highly unidiomatic Erlang code.
I look into this.
-export([hello/0]). -spec hello() -> fun((list(any())) -> ok). hello() -> fun (Args) -> io:format(text(), Args) end.This would be the Right Thing tm, but is highly unidiomatic Erlang code.
So I suppose I should implement the Right Thing, even if it is highly unidiomatic?
I tried:
nicolas@localhost:~/test-caramel$ caramel --version
0.1.0+git-23d695b
test_hello.ml
let text () = "Hello World" in
let hello () = Io.format (text ()) in hello ()
then
caramel compile test_hello.ml
which leads to no error, a test_hello.cmi file but no erl file.
If I try with
test_ocaml.ml
let text () = "Hello World" in
let hello () = print_endline (text ()) in hello ()
and I do ocaml test_ocaml.ml, it returns Hello World.
Is that normal that in operator is not accepted top-level?
@nicobao yes, I think they're ignored but they probably should fail with an error (just like let rec within a let).
The automatic uncurrying can be done (as shown by BuckleScript), but that's a larger task than undoing the work I did for it. Let's start there, translating the functions as they are, and we can optimize later 🙌🏽