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

Compile let bindings to IIFEs #81

Closed erszcz closed 2 years ago

erszcz commented 3 years ago

I followed the idea outlined in https://github.com/AbstractMachinesLab/caramel/issues/68#issuecomment-795916884 and modified erl_printer.ml to output immediately invoked funs for let ... in ... bindings.

The indentation of the generated code is not very nice (i.e. none), but https://github.com/AbstractMachinesLab/caramel/blob/d5128fce81b08a8007729f4c60f03297a8aa4b20/erlang/src/erl_printer.ml#L288 makes me wonder if it's possible not to indent the first line of a nested fun (in case of let a = ... in let b = ... in ...). I guess I'd have to introduce a new pp_something for that purpose.

leostera commented 3 years ago

Thanks so much for taking the time to contribute @erszcz! 🙌🏽

I think this work should happen instead in the caramel/compiler/ocaml_to_erlang/fun.ml module, so we translate the let bindings into IIFEs when doing the translation instead of the printing. This would mean that not all let expressions will have to be IIEFs (such as let a = 1 in ...) and keeps the erlang library separate from Caramel logic.

As a pointer, the Erlang.Ast_helpers module can help you write something like this to wrap any expression in an IIEF:

open Erlang.Ast_helpers
let wrap_in_iief exp = Expr.apply (Expr.fun_ [ FunDecl.case ~lhs:[] ~rhs:exp ]) []
erszcz commented 3 years ago

Hi @ostera!

Thanks for the welcome and the initial pointers. Indeed, my first attempt in the printer was an undeniable false start 😅

I think this time it looks a bit better. tests/compiler/expressions.t/let_shadowing.ml now compiles to proper Erlang which evaluates to the expected value. The sequencing problem from https://github.com/AbstractMachinesLab/caramel/issues/68#issuecomment-795916884 also compiles and works fine:

$ cat seq.ml
let f () =
  let a = 1 in Io.format "printing ~p\n" [a];
  let a = 5 in a + 2
$ cat seq.erl
% Source code generated with Caramel.
-module(seq).

-export([f/0]).

-spec f() -> integer().
f() ->
  A = 1,
  io:format(<<"printing ~p\n">>, [A | []]),
  fun
  (A) -> erlang:'+'(A, 2)
end(5).

$ erl
Erlang/OTP 23 [erts-11.1.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe]

Eshell V11.1.3  (abort with ^G)
1> seq:f().
printing 1
7
2>

If you consider this the right path forward, I'll fix the tests. What do you think?

leostera commented 3 years ago

Hola @erszcz 👋🏽

After giving this some thought, we should take a stab at variable prefixing/enumeration to avoid the closure altogether, as proposed by @michallepicki here: https://github.com/AbstractMachinesLab/caramel/issues/68#issuecomment-796258253

If you'd like to give this a shot, let me know. Otherwise, I think the IIFE wrapping would make a fine first contribution even if we choose to change it later 😃

erszcz commented 3 years ago

If @michallepicki is interested in giving a try to the variable renaming solution, I won't interfere. If that's the case, we can just close this PR as a proof of concept - if need be it's easy enough to get back to this idea.

michallepicki commented 3 years ago

I'll focus on closing #59 via #80 , and then I can look into this. In any case I'm happy to collaborate, @erszcz you definitely wouldn't be interfering. We can discuss on the issue in more detail what would be needed to generate those unique names.

michallepicki commented 3 years ago

This can be closed now I think, the nested ones will be expression sequences now.

leostera commented 2 years ago

Hi! 👋🏽 since I'm currently not working on and have no plans to continue working on this version of the compiler, I'll close this PR. Thanks a lot for your contribution 🙏🏽