leostera / caramel

:candy: a functional language for building type-safe, scalable, and maintainable applications
https://caramel.run
Apache License 2.0
1.06k stars 25 forks source link

Partial function application compiles to wrong erlang #44

Open michallepicki opened 3 years ago

michallepicki commented 3 years ago

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).
michallepicki commented 3 years ago

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.

?

michallepicki commented 3 years ago

Also tried on 0fe81e7, same result

leostera commented 3 years ago

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 ).

leostera commented 3 years ago
-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.

leostera commented 3 years ago
-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.

nicobao commented 3 years ago

I look into this.

nicobao commented 3 years ago
-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?

nicobao commented 3 years ago

I tried:

nicolas@localhost:~/test-caramel$ caramel --version
0.1.0+git-23d695b

_testhello.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

_testocaml.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?

leostera commented 3 years ago

@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 🙌🏽