NOTE: This manual is a work in progress. Please let us know if you think something is missing by filing an issue, or join our Discord server.


Caramel is a functional language for building type-safe, scalable, and maintainable applications.

It is built in OCaml 🐫 and maintained by Abstract Machines.

Caramel leverages:

  • the OCaml compiler, to provide you with a pragmatic type system and industrial-strength type safety.

  • the Erlang VM, known for running low-latency, distributed, and fault-tolerant systems used in a wide range of industries.

Check out the CHANGELOG to see what's new.

For installation instructions see Installation.

Feature highlights

  • Excellent type-inferece, so you never need to annotate your code
  • Zero-cost type-safe interop with most existing Erlang and Elixir code
  • Has a reviewed standard library included
  • Supports sources in OCaml (and soon Reason syntax too)
  • Ships a single executable (caramel)
  • Has a built-in formatter (caramel fmt)

Philosophy & Goals

Caramel aims to make building type-safe concurrent programs a productive and fun experience.

Caramel should let anyone with existing OCaml or Reason experience be up and running without having to relearn the entire languages.

Caramel strives to integrate with the larger ecosystem of BEAM languages, like Erlang, Elixir, Gleam, Purerl, LFE, and Hamler.

Caramel should be a batteries included environment where you pay only for what you use.

Getting Started

Caramel works on macOS, Linux, and Windows. It is a single binary, and it has no external dependencies.

The only prerequisite to run the code you compile with Caramel, is Erlang or Elixir. The Elixir installation page lists many ways to install it comfortably, we suggest you to follow that step before continuing.

Manual binary installation

You can manuall install Caramel as well on Windows, macOS, or Linux by downloading the zipped release from Github:

Using a package manager

NOTE: We are working on supporting these plugins but we could use a hand with them. If you're interested in contributing please join us on Discord

If you are an Elixir programmer, Caramel can be installed with mix, include the mix plugin in your project as a dependency and enable the compiler:

def project do
    compilers: [:caramel] ++ Mix.compilers(),
    caramel_paths: ["src"],
    caramel_release: "v0.1.1"

defp deps do
  {:mix_caramel, github: "AbstractMachinesLab/mix_caramel", branch: :main}

If you are an Erlang programmer, Caramel can be installed with rebar3, but we are still working out the plugin. If you're interested in helping out please reach us out in Discord!

{plugins, [
  {rebar3_caramel, {git, "", {branch, "main"}}}

Other platforms. If you'd like Caramel to be easier to install in your favorite platform, feel free to Open a Github Issue and we can talk about making it happen.

Install from Sources

Check the Building from Source section for up to date instructions.

If you have OCaml (opam) and the source code of Caramel, you can install it by running make install.

First Steps

This section teaches you some of the fundamentals of Caramel, make sure you installed it first following the Installation guide.

It assumes you have some prior knowledge of functional programming, like Erlang or Elixir, and some knowledge of static typing, like from TypeScript or Java.



You will need a working Erlang/OTP installation. You can get one from your package manager:

# For Homebrew on OS X
brew install erlang

# For Ubuntu and Debian
apt-get install erlang

# For Fedora
yum install erlang

# For FreeBSD
pkg install erlang

Hello World

Caramel is a language that relies on the Erlang VM, just like Elixir or Gleam. This means that we need to compile our code, and run it from within Erlang.

Let's write a Hello World example:

(* file: *)
let main _ = Io.format "~s~n" ["Hello World"]

We can compile it as follows:

$ caramel compile
Compiling hello_world.erl OK

And run it with escript:

$ escript hello_world.erl
Hello World

escript is an Erlang utility to run an Erlang program as a script. It defaults to running the main/1 function of a module, and it passes all command line arguments to it as a list of strings.

We can also load and run this module from a regular Erlang shell.

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

Eshell V11.1.5  (abort with ^G)
1> c(hello_world).
2> hello_world:main([]).
Hello World

Say Hello!

For our next example, we will take a list of names and will say hello to each of them.

(* file: *)

(* This function is recursive, so we must mark it with `let rec` *)
let rec say_hello names =

  (* We can use `match ... with` to inspect values like a Switch/Case *)
  match names with

  (* If there are no names, then the list is empty! and we're done *)
  | [] ->
      (* We return a *unit* (the empty parens: ()). This is the closest we have
       * in Caramel to the usual `:ok` or `ok` atoms returned in Elixir / Erlang
       * to indicate that nothing happened, but everything's okay

  (* If there is at least one name, we can proceed... *)
  | n :: names' ->
      Io.format "Hello, ~s!\n" [n];
      say_hello names'

let main args = say_hello args

Lets run this program now:

$ escript say_hello.erl Joe Robert Mike
Hello, Joe!
Hello, Robert!
Hello, Mike!

Working with Types

We said before that Caramel is a typed language, but we haven't seen any types at work so far. How is this possible?

Caramel is a strict subset of OCaml and it has excellent type-inference. This means that Caramel has figured out the types for us.

NOTE: Type inference is great, but sometimes it makes for less clear code than it should. Consider annotating for clarity whenever some things start getting complex!

Let's write a program now that will add together 2 numbers, ignoring their decimals.

Our test cases will be:

  • 10 + 2 = 12
  • 10 + 2.7 = 12
(* file: *)
let main (a :: b :: _) =

  (* First we will try to convert our strings to integers *)
  let a_int = Erlang.list_to_integer a in
  let b_int = Erlang.list_to_integer b in

  (* Then we will add them both together *)
  let result = a_int + b_int in

  (* Finally we print out the results *)
  Io.format "~p + ~p = ~p\n" [a_int; b_int; result]

We can call it and see if it works:

$ escript calc_add.erl 10 2
10 + 2 = 12

But what happens when we try to add 10 and 2.7?

λ escript calc_add.erl 10 2.7
escript: exception error: bad argument
  in function  list_to_integer/1
     called as list_to_integer("2.7")
  in call from erl_eval:do_apply/6 (erl_eval.erl, line 680)
  in call from erl_eval:expr/5 (erl_eval.erl, line 449)
  in call from escript:eval_exprs/5 (escript.erl, line 872)
  in call from erl_eval:local_func/6 (erl_eval.erl, line 567)
  in call from escript:interpret/4 (escript.erl, line 788)
  in call from escript:start/1 (escript.erl, line 277)
  in call from init:start_em/1

The Erlang.list_to_integer/1 function tries to parse a string into an Integer, not a Float. Since Erlang makes a distinction between those at this level, we have to use another function for this: Erlang.list_to_float/1.

Lets change our second value to be parsed as a float:

(* ... *)
let b_int = Erlang.list_to_float b in
(* ... *)

When we try to compile, Caramel will show us an error message!

$ caramel compile
File "", line 8, characters 23-28:
Error: This expression has type float but an expression was expected of type

Caramel has figured out that you are trying to add a float and an integer together and its telling you that it can't just do that. We'll have to turn our float into an integer, or turn our integer into a float, to be able to add them together.

NOTE: Errors at the moment have the exact same copy and information as errors from the OCaml compiler. Over time this will be changed.

Custom Types

Processes and Message Passing

A Glimpse into OTP

v0.1.1: Improvements to Docs, Better operator support, Cleaning up Stdlib

You can download this release from Github

📙 Manual

  • The manual is now automatically released in CI through Github Actions.
  • We've got an open invite to Discord available on every page.
  • Erlang as a runtime dependency has been clarified.
  • Fixed several SEO issues.

🧰 Compiler

  • Better support for bit-shift, unary float, and keyword operators
  • Now support for floating point numbers without trailing digits (1. == 1.0)

📚 Standard Library

  • Introduced the Math module
  • Clean up several unimplemented bindings that were leftovers from the OCaml standard library

v0.1.0: Shaping up!

You can download this release from Github

🌎 From a Backend to Language

Caramel is starting to take shape, and we'll now refer to it as a language rather than an OCaml backend to make it a little easier to talk about it.

After all, it isn't really 100% OCaml but a strict subset of it. It doesn't ship the same standard library or tools.

We also have a new website at:

🗫 Community

We've updated our Code of Conduct to the Contributor Covenant v2.0.

We've opened the Github Discussions -- drop by and say hi or grab a Discord invite link! 👋

📙 Manual

I've started work on the Caramel Manual where we have an installation guide, an introduction, a small syntax cheatsheet, some examples, and a small guide on writing Caramel code that talks to existing Erlang code.

This is a big work in progress, so if you'd like to help, please reach out!

In the future I'm planning to write guides for how to use Caramel with existing build systems like Rebar3 or Mix, and include a reference guide to the standard library.

💅 Caramel Formatter

Caramel now ships a formatter that you can run to make sure your code is always stylish and you have no more bikesheds. It supports ZERO configuration, so what you see is what you get.

This is currently just a wrapper around ocamlformat, and all the kudos go to the amazing team putting that together.

You can use it by calling caramel fmt *.ml and it should work on both .ml and .mli sources.

It only does in-place formatting.

🧰 Compiler

The compiler has dropped support for several targets, including Core Erlang to Native, and the default OCaml compilation modes.

It will from now on focus only on taking Caramel sources into runnable Erlang and Core Erlang code. Over time we will move from Erlang to only Core Erlang support, to make it easier to reuse Caramel code from Erlang, Elixir, Gleam, and Hamler.

The additions to the compiler are:

  • Replaced unbound vars in specs with wildcards (thanks @ilya-klyuchnikov, PR)
  • Keyword atoms are automatically quoted now
  • Removed unnecessary compilation steps (thanks @Drup)
  • Removed unused object file generation (.cmo)
  • Better support for operators like &&, ||, and >=

📚 Standard Library

The standard library has been simplified for contributing to it, and you can now find it at the top of the repository.


  • String concatenation now possible with the ^ operator: "yay" ^ "!!!"
  • Binary.split/3 to split binary strings
  • Erlang.floor/1 to round down floats to integers
  • Erlang.list_to_float/1 to parse strings to floats
  • Erlang.float_to_list/1 to turn floats into strings
  • Erlang.list_to_integer/1 to parse strings to integers
  • Erlang.integer_to_list/1 to turn integers into strings
  • Lists.foldl/foldr signature has been fixed

v0.0.14: Website, split Erlang library, proper exit codes, and better runtime support

  • erlang: the Erlang library included, with a lexer/parser/AST/printer for Standard Erlang, is now completely split from the Caramel code and will be published to opam shortly.

  • caramelc: will return exit code 0 if everything went well. Otherwise expect a non-zero status!

  • stdlib: remove dependency on the Erlang AST printer for parts of the runtime (like the recv function), and instead include the relevant .erl sources as part of the packed stdlib.

  • docs: better contribution notes, documenting the release flow and saying a word about the rationale behind it. I've also put together a small website for Caramel here:

  • examples: the echo tcp server has been refactored to make it harder to accidentally override the gen_tcp module that is shipped with Erlang. We'll have to figure out a nice way to prevent these things from happening, which may just mean using all the modules on the Stdlib to avoid redefinition.

  • ci: several changes to CI to ensure we can release the erlang library to opam.


  • erlang: prepare erlang library for initial release to opam.

  • erlang: add a new erldump binary that can be used to dump the parsed ast. This is currently being used for the tests.

  • caramelc: binary now exits with status code 0 only if everything went well.

  • ci: include necessary erlang artifacts in releases so we can publish this library to opam.

  • ci: prefix cache names with the secret version so breaking them is more effective.

  • examples: add new gen_tcp example.

  • docs: publish small website at


  • compiler: match expressions with cascading cases are not that straighforward to translate to Erlang and my gut tells me that doing the juggling here will get in the way of type-checking Erlang in the upcoming milestone, so this is forbidden as it is right now.


  • compiler: preliminary support for guards is added in this release. They are "safe" as long as you don't redefine any of the expected functions. There is no good way at the moment to prevent this from happening, but we could achieve it by essentially forbidding the use of the module name Erlang and requiring all guards to be fully qualified names.

    This still leaves us with the issue of compounded guard expressions.

    At the end of the day, we want a function is_valid_guard : expression -> bool that can traverse an arbitrary expression and tell us if it is or is not a valid guard based on the specification found at the Erlang docs.

  • caramelc: the parse subcommand now can take a --lang and --tree parameters to print out the parse and typed trees for OCaml, as well as the parse tree of Erlang, and the parse tree of the result of compiling OCaml to Erlang.

    This is particularly useful for testing and understanding the AST translation, and will likely be used later on to see if the compile -> typecheck -> compile cycle yields the same inputs, and thus is an isomorphism.


  • Add support for record updates (see #23):
{ my_record with field = value }
My_record#{ field := value }
  • Add tests showing that lambda function calls are now working as expected and will be called with the right number of parameters:
let f () =
  let g () = 1 in
  g ()
f() ->
  G = fun () -> 1 end,


  • compiler(#12): the compiler will now let you know when you're redefining a function on the OCaml side, which is not allowed on the Erlang side and stop compilation.

  • compiler(#16): shadowing bindings with let are (for) now unsupported on the OCaml side, which makes translation runtime safe. We won't see any more X = X + 1 on the Erlang side.

  • compiler(#15): to help with #16, priming of variables is now supported and translated to valid Erlang. We can write x' = x + 1 and it will translate to X_prime = X + 1.

  • compiler(#13): recursive let bindings within a function definition are now not supported since they don't have a direct Erlang equivalent and require runtime overhead.

  • error messages have been created for all of the above


  • stdlib: fix name of Io module so Merlin can pick it up properly.


Better releases

  • compiler: automatic function reference arities! As see in #10

  • compiler: We're ignoring all fresh type variables when translating to Dialyzer specs for now. More work will be done in #20

  • caramelc: caramelc compile now supports multiple --target flags, so you can compile both archives and Erlang sources at once.

  • caramelc: standard library will now by default be in the respective installation directory (respecting dune install conventions)

  • stdlib: Process.spawn/1 has been renamed to Process.make/1 until we have support for module attributes (see #21)

  • stdlib: Dropped top-level namespacing until we figure out how it can work best with .merlin

  • ci: several changes to release flow, including a nicer folder structure in the tarball

  • ci: entire codebase is instrumentabled by bisect_ppx now to start gathering coverage reports

  • erlang: removed an unused helper


  • Lots of work on the Stdlib, as visible in issue #8.

  • External definitions now can override the name they will use in the generated source code with their string value

  • Parser supports arbitrarily parenthesized expressions

The idea for this pre-release is to start testing out how the Stdlib feels to write some small programs and scan 1) whether some obvious FFIs are missing, and 2) how straightforward it is to write new ones.


The internal modules for the compiler and the typing experiments are split now, and the Erlang support is unified in a single library that only depends on the standard library and the Sexplib library for deriving Sexp representations.

The goals here are:

  • make the codebase easier to hack on in general -- there's still work to do here, as I think the ideal structure would be flatter than it is right now.

  • prepare the Erlang library for continued work -- extending the lexer with positioning information on every read token will help parametrize the AST with contextual information. This would aid in error reporting.

  • establish clearer compilation and type checking paths:

    • from OCaml to Erlang,
    • from OCaml Lambda to Core Erlang,
    • from Erlang to Native binaries.


dist: use valid gcc host triples in release names


First release shipping with a tiny Stdlib!

Now you don't really need OCaml installed for Caramel to be able to compile some ML and typecheck some Erlang


New release naming and a new linux+musl binary


  • compilation should have much nicer errors when dealing with unsupported features and expression at the value and type level
  • new command for type-checking erlang: caramelc check
  • --target flag is now available, and supports core, erl and native targets
  • --dump-ast flag is now documented and hooked up to core erlang backend
  • string concatenation should support arbitrarily complex expressions now
  • license was updated in binary file to point to BSD 3-clause too


First release with caramelc binaries.


First release.

Syntax Cheatsheet

Here is a comparison between common Erlang and Elixir syntaxes and the OCaml and Reason syntaxes that are supported by Caramel.


Variants and Unions


-type bool() :: true | false.
-type foo() :: bar() | baz().


@type bool() :: true | false
@type foo() :: bar() | baz()


type bool = True | False
type foo = Bar of bar | Baz of baz
type foo = [ `Bar of bar | `Baz of baz ]
type foo = [ bar | baz ] (* for when bar and baz are polymorphic variants *)



-type pair(A, B) :: #{ fst => A, snd => B }.


@type pair(a, b) :: %{ :fst => a(), :snd => b() }


type ('a, 'b) pair = { fst : 'a; snd : 'b }

Expressions and Values


ok:ok`ok`okAtoms in Caramel are treated as polymorphic variants.
'ok':'ok'------Quoted atoms are unsupported in Caramel.


% comment# comment(* comment *)// comment


A = 1a = 1let a = 1let a = 1
A2 = A + 1a = 1let a' = a + 1let a' = a + 1

Binary Strings, and Charlists

"string"'binary'['b'; 'i'; 'n'; 'a'; 'r'; 'y']['b', 'i', 'n', 'a', 'r', 'y']

Function Calls

Lambda()lambda.()lambda ()lambda()
local()local()local ()local()
mod:fn()Mod.fn()Mod.fn ()Mod.fn()
A:F()apply(A, F, [])------

Dynamically dispatched calles are not supported in Caramel, because we can't know the type of the arguments they will have, or the type of the value they would return.

If expressions


  Foo -> Bar;
  _ -> Baz


if(foo, do: bar, else: baz)

if foo do


if foo then bar else baz


foo ? bar : baz

Match / Case / Switch expression


case Foo of
  Bar -> Baz


case foo do
  bar -> baz


match foo with
| bar -> baz


switch foo {
| bar => baz

Calling Erlang code

One of the goals of Caramel is to make it easy and free to call Erlang code in a type-safe way.

To do this, we have a special language feature inherited from OCaml to create external bindings, or simply bindings, using the external keyword.

We can define a binding with this pattern:

external <name> : <type-signature> = "<foreign name>"

If the "<foreign name>" string is empty, Caramel will fallback to using "<name>".

When Caramel compiles your code, it will make sure that any calls to Module.<name> end up as calls to Module:<foreign name>.

WARNING: This is a safety escape hatch and Caramel DOES NOT guarantee that foreign code will be safe, or will not crash your program.

There are 4 possible combinations of external bindings:

  • Bindings to functions with the same name
  • Bindings to functions with differet names
  • Bindings for functions with many input types
  • Bindings for functions with many return types

Bindings to functions with the same name

Let's look at an example and bind the erlang:is_pid/1 function.

(* file: *)

external is_pid : 'a -> bool = ""
(* We don't care what the input type is, so we can use a type variable like
 * `'a` but we know that we will always return a boolean.
 * We leave the foreign name as empty, because the actual function is called
 * exactly the same: is_pid

Now from our Caramel code we can call it as:

Erlang.is_pid 1 (* <-- this will be true or false *)

Bindings to functions with different names

Some bindings however, have other foreign names, or have foreign names that look or feel unidiomatic for Caramel. For those we can replace that string and still be able to call them.

For example, the new keyword is inherited from OCaml as a reserved word, so we can't use it directly. Instead Caramel uses the make word as a convention for creating new values of a given type.

Here we make ets:new/2 callable as Ets.make/2:

(* file: *)

external make : 'a -> make_opt list -> ('k, 'v) t = "new"

Bindings for functions with many input types

Some functions can take many different input types, such as tuples or lists, or single values, and work just the same. This is thanks to the BEAM's run-time support for pattern matching on values of different kinds.

When doing static typing, we say these functions work on Union Types.

Caramel does not support Union Types, so functions like ets:insert/2 that work both with a single values and a list of values can't be mapped by a single function type.

Instead, we split them in several bindings that all map to the same foreign function:

(*file: *)

external insert_one : ('k, 'v) t -> 'k * 'v -> unit = "insert"

external insert_many : ('k, 'v) t -> ('k * 'v) list -> unit = "insert"

Bindings for functions with many output types

Some function can return many different output types, such as tuples or lists, or different single values, and they expect you to handle them just the same. This works thanks to the BEAM's run-time support for pattern-matching on values of different kinds.

When doing static typing, we say these functions return Union Types.

Caramel does not support Union Types, so functions like this one:

-spec f(integer()) :: none | {integer(), integer(), integer()} | binary().
f(0) -> none;
f(1) -> {1234, 5678, 9};
f(2) -> <<"TEN!">>.

That can return either an atom, like none, or a triple of integers, or a binary string, can not be called directly.

Instead we have to choose to pay a small cost for runtime typing, or to restrict the values coming from Erlang.

Restricting Values

If we know that the returned values from the Erlang code are tagged tuples, on any level of nesting, and we can map them to a type in Caramel, then we can simplify the process by declaring the type on the Caramel side and letting the compilation process match the structures.

For example, if the function from before returned:

-spec f(integer()) :: {atom, none}
                    | {triple, {integer(), integer(), integer()}}
                    | {string, binary()}.
f(0) -> none;
f(1) -> {1234, 5678, 9};
f(2) -> <<"TEN!">>.

We can bind to it by writing out the type first:

type none = None
type f_return =
  | Atom of None
  | Triple of int * int * int
  | String of string

external f : int -> f_return = ""

Fortunately plenty of Erlang code heavily relies on tagged tuples for runtime pattern matching.

When it doesn't, we have to do the typing at runtime ourselves.

Runtime Typing

Paying a small cost means that we have to add some runtime inspection of values to decide what types they actually have. This is not a big deal because it is something we do in Erlang all the time anyways, but it certainly means that calling these Erlang functions is not a zero cost operation.

For example, we can use the functions in the Erlang module to check if the return value is of some type:

type unknown
external __unsafe_f : int -> unknown = "f"

type f_values = 
  | Binary of string
  | Atom of string
  | Triple of (int * int * int)

let f : int -> f_values =
 fun i ->
  let x = __unsafe_f i in
  if Erlang.is_binary x then Binary (unsafe_cast x)
  else if Erlang.is_atom x then Atom (unsafe_cast x)
  else if Erlang.is_tuple x 3 then Triple (unsafe_cast x)
  else panic

At this point, we could add a lot more validation logic to our f function to try to make 100% sure that the value we have is of the type we expect.

This process can be error prone, and certainly relies on the Erlang code never returning any value that we didn't account for.

It is worth noting that this can also be done on the Erlang side, perhaps even more idiomatically, by tagging the values:

tagged_f(X) ->
  case f(X) of
    none -> {atom, none};
    {_, _, _} -> {triple, X};
    Y when is_binary(Y) -> {string, Y}

After this we can use the Restricting Values approach.

Hello, Joe!

The BEAM community has had a tradition of writing Hello World programs where we say Hello to Joe Armstrong, one of the creators or Erlang.

We can do this by creating a new file, called with the following contents:

let hello () = Io.format "~p" ["Hello, Joe!"]

Now we can compile this by calling caramel compile

$ ls
$ caramel compile
$ ls  hello_joe.erl

Our new compiled file, hello_joe.erl should looks like this:

% Source code Generated by Caramel


hello() -> io:format(<<"~p">>, [<<"Hello, Joe!">>]).

Now we can compile and run this Erlang code

Simple HTTP Echo Server

(* file: *)

let handle conn =
  Gen_tcp.send conn "hello world";
  Gen_tcp.close conn

(* this function will actually spawn new processes per connection, so we
   can easily handle millions of concurrent connections *)
let rec loop socket =
  let Ok conn = Gen_tcp.accept socket in
  let handler = Process.make (fun _self _recv -> handle conn ) in
  Gen_tcp.controlling_process conn handler;
  loop socket

let start port = Process.make (fun _self _recv ->
  let Ok socket = Gen_tcp.listen port [Active false; Packet Http] in
  loop socket)



       caramel - Caramel compiler

       caramel COMMAND ...

       Caramel is a functional language for building type-safe, scalable, and
       maintainable applications.

           Compile Caramel code to run on the Erlang VM.

       fmt Format Caramel code.

           (UNSTABLE) Helper command to parse sources and dump ASTs

           Sort OCaml files by their dependencies on each other.

           Show version information.

       --help[=FMT] (default=auto)
           Show this help in format FMT. The value FMT must be one of `auto',
           `pager', `groff' or `plain'. With `auto', the format is `pager` or
           `plain' whenever the TERM env var is `dumb' or undefined.

           Show version information.

       ocaml(1) erlang

       Leandro Ostera.

       Copyright (C) 2020-present, Abstract Machines Lab Sweden AB

       Caramel is licensed under Apache License 2.0

caramel compile

       caramel-compile - Compile Caramel code to run on the Erlang VM.

       caramel compile [OPTION]... SOURCES...

        The Caramel takes as input OCaml sources and compiles them to Erlang

       SOURCES (required)
           A list of source files to compile

       -d, --dump-ast
           Use this flag to print out to standard output the ASTs of the
           different representations being used during compilation. This is
           NOT suitable for programmatic usage, and its mostly used for
           debugging the compiler itself.

       --help[=FMT] (default=auto)
           Show this help in format FMT. The value FMT must be one of `auto',
           `pager', `groff' or `plain'. With `auto', the format is `pager` or
           `plain' whenever the TERM env var is `dumb' or undefined.

           Use this flag to compile sources without opening the Standard
           Library by default.

       --stdlib-path=VAL (absent=/home/ostera/.opam/4.11.1/lib/caramel/stdlib
       or CARAMEL_STDLIB_PATH env)

           Show version information.

       These environment variables affect the execution of compile:

           See option --stdlib-path.

       ocaml(1) erlang

       Leandro Ostera.

       Copyright (C) 2020-present, Abstract Machines Lab Sweden AB

       Caramel is licensed under Apache License 2.0

caramel fmt

       caramel-fmt - Format Caramel code.

       caramel fmt [OPTION]... SOURCES...

        Reformats Caramel source code. 

       SOURCES (required)
           A list of source files to format

       --help[=FMT] (default=auto)
           Show this help in format FMT. The value FMT must be one of `auto',
           `pager', `groff' or `plain'. With `auto', the format is `pager` or
           `plain' whenever the TERM env var is `dumb' or undefined.

           Show version information.

       ocaml(1) erlang

       Leandro Ostera.

       Copyright (C) 2020-present, Abstract Machines Lab Sweden AB

       Caramel is licensed under Apache License 2.0

caramel sort-deps

       caramel-sort-deps - Sort OCaml files by their dependencies on each

       caramel sort-deps [OPTION]... SOURCES...


       --help[=FMT] (default=auto)
           Show this help in format FMT. The value FMT must be one of `auto',
           `pager', `groff' or `plain'. With `auto', the format is `pager` or
           `plain' whenever the TERM env var is `dumb' or undefined.

           Show version information.

       ocaml(1) erlang

       Leandro Ostera.

       Copyright (C) 2020-present, Abstract Machines Lab Sweden AB

       Caramel is licensed under Apache License 2.0

caramel version

       caramel-version - Show version information.

       caramel version [OPTION]... 


       --help[=FMT] (default=auto)
           Show this help in format FMT. The value FMT must be one of `auto',
           `pager', `groff' or `plain'. With `auto', the format is `pager` or
           `plain' whenever the TERM env var is `dumb' or undefined.

           Show version information.

       ocaml(1) erlang

       Leandro Ostera.

       Copyright (C) 2020-present, Abstract Machines Lab Sweden AB

       Caramel is licensed under Apache License 2.0

Contributing to the Manual

Thanks for contributing to the 📙 Caramel Manual!

We'd love this manual to be useful, inclusive, and friendly, so make sure your tone matches this.

The manual sources live in <root>/manual/src, and it is written in Markdown. It is published automatically once a PR is merged, so you don't have to worry about that.


Manual Structure

The structure of the manual is reflected in the file, so that the links that go under the same heading are within the same folder.

Here you can see that everything under the "Getting Started" section is under the ./getting_started folder.

# Summary

- [Introduction](./
- [Getting Started](./getting-started/
  - [Installation](./getting-started/
  - [First Steps](./getting-started/
  - [v0.1.1](./changelog/
  - [v0.1.0](./changelog/
  - [v0.0.14 and older](./changelog/


- [Guides]()
  - [Syntax Cheatsheet](./guides/
  - [Using Caramel with Rebar3]()
  - [Using Caramel with Mix]()
  - [Calling Erlang code](./guides/
  - [Calling Elixir code]()
- [Examples]()
  - [Hello, Joe!](./examples/
  - [Simple HTTP Echo Server](./examples/


- [Reference](./reference/
  - [Caramel CLI](./reference/cli/
    - [`caramel compile`](./reference/cli/
    - [`caramel fmt`](./reference/cli/
    - [`caramel sort-deps`](./reference/cli/
    - [`caramel version`](./reference/cli/
  - [Language Features]()
  - [Standard Library]()
    - [`Binary`]()
    - [`Calendar`]()
    - [`Erlang`]()
    - [`Ets`]()
    - [`Io`]()
    - [`Lists`]()
    - [`Maps`]()
    - [`Process`]()
    - [`Timer`]()
  - [Typed OTP]()


- [Contributing]()
  - [Contributing to the Manual](./contrib/
  - [Building from Source](./contrib/
  - [Architecture](./contrib/

Editing Existing Pages

If you find a typo in some page, you can look into the to see which markdown file to modify.

You can edit the markdown files with any editor you choose, and submit a Pull Request.

Try to stick to 80 characters per line or less.

Adding New Pages

Start the server (see Previewing Your Changes) and add a new page in

Once you save it, the server will have created the right folders and files for you.

Now you can edit the new empty Markdown files as if they were pre-existing pages.

If you want help writing a section, please draft your ideas and open a Draft Pull Request so we can work on it together.

Previewing Your Changes Locally

If you want to work on the manual locally, you will need to install mdbook.

mdbook comes with a built in server with hot-reloading. This lets you see the results automatically in the browser.

Start the server with mdbook serve and open the URL it lists. If your port 3000 is already taken, you can call mdbook serve -p PORT to use a different one.

Building from Source

Caramel is built with OCaml, so it needs a working opam installation.

Additionally, we use make, since it makes our CI steps pretty minimal.

Getting Started

The next scripts show how to set up Caramel and create a release:

# install ocaml first
sh <(curl -sL

# clone the repository
git clone
cd caramel

# install all required dependencies
make setup

# compile projects and runs the tests
make test

# installs project from sources
make install

Common Tasks

To bootstrap the repository, you can run make setup build.

To compile the manual, you can run make manual.

To run all the tests, you can run make test.

To install the local version, you can run make install.

To format all the code, you can run make fmt.

To create a release of Caramel, you can run make release, or make on Windows.



The Caramel compiler is composed of 2 main components: a Frontend and a Backend.

Frontend: Parsing and Type Checking

The Frontend takes care parsing the OCaml sources and doing the type-checking. You can find the entrypoint for the frontend here: ./caramel/compiler/

For the most part, the frontend takes care of picking the right modules to parse the sources into the OCaml Parsetree, and it reuses most of the OCaml compiler frontend to type-check the Parsetree into a Typedtree.

Once we have a Typedtree, we know for certain that the sources are well-typed, and we can hand it over to the backend.

Backend: Code Generation

The Backend takes care of generating the Erlang sources.

It has for input a Typedtree that is then translated into an Erlang AST, to be pretty-printed into .erl sources.

The bulk of this translation happens in the ./caramel/compiler/ocaml_to_erlang/ modules.