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.
Introduction
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: github.com/AbstractMachines/caramel/releases.
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"
]
end
defp deps do
{:mix_caramel, github: "AbstractMachinesLab/mix_caramel", branch: :main}
end
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, "https://github.com/AbstractMachinesLab/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.
Examples:
- Hello World
- Say Hello
- Working with Types
- Custom Types
- Processes and Message Passing
Prerequisites
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: hello_world.ml *)
let main _ = Io.format "~s~n" ["Hello World"]
We can compile it as follows:
$ caramel compile hello_world.ml
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).
{ok,hello_world}
2> hello_world:main([]).
Hello World
ok
Say Hello!
For our next example, we will take a list of names and will say hello to each of them.
(* file: say_hello.ml *)
(* 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: calc_add.ml *)
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 calc_add.ml
File "fs3_calc_add_tagged.ml", line 8, characters 23-28:
Error: This expression has type float but an expression was expected of type
int
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: caramel.run
🗫 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.
Changes:
- String concatenation now possible with the
^
operator:"yay" ^ "!!!"
Binary.split/3
to split binary stringsErlang.floor/1
to round down floats to integersErlang.list_to_float/1
to parse strings to floatsErlang.float_to_list/1
to turn floats into stringsErlang.list_to_integer/1
to parse strings to integersErlang.integer_to_list/1
to turn integers into stringsLists.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: https://caramel.abstractmachines.dev
-
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.
v0.0.13
-
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
caramel.abstractmachines.dev
v0.0.12
- 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.
v0.0.11
-
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.
v0.0.10
- 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,
G().
v0.0.9
-
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 toX_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
v0.0.8
- stdlib: fix name of Io module so Merlin can pick it up properly.
v0.0.7
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 toProcess.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
v0.0.6
-
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.
v0.0.5
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.
v0.0.4-triples
dist: use valid gcc host triples in release names
v0.0.4
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
v0.0.3-musl
New release naming and a new linux+musl binary
v0.0.3
- 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 supportscore
,erl
andnative
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
v0.0.2-bin
First release with caramelc
binaries.
v0.0.2
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.
Types
Variants and Unions
Erlang:
-type bool() :: true | false.
-type foo() :: bar() | baz().
Elixir
@type bool() :: true | false
@type foo() :: bar() | baz()
OCaml
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 *)
Records
Erlang:
-type pair(A, B) :: #{ fst => A, snd => B }.
Elixir
@type pair(a, b) :: %{ :fst => a(), :snd => b() }
OCaml
type ('a, 'b) pair = { fst : 'a; snd : 'b }
Expressions and Values
Atoms
Erlang | Elixir | OCaml | Reason | Description |
---|---|---|---|---|
ok | :ok | `ok | `ok | Atoms in Caramel are treated as polymorphic variants. |
'ok' | :'ok' | --- | --- | Quoted atoms are unsupported in Caramel. |
Comments
Erlang | Elixir | OCaml | Reason |
---|---|---|---|
% comment | # comment | (* comment *) | // comment |
Variables
Erlang | Elixir | OCaml | Reason |
---|---|---|---|
A = 1 | a = 1 | let a = 1 | let a = 1 |
A2 = A + 1 | a = 1 | let a' = a + 1 | let a' = a + 1 |
Binary Strings, and Charlists
Erlang | Elixir | OCaml | Reason |
---|---|---|---|
<<"binary">> | "binary" | "binary" | "binary" |
"string" | 'binary' | ['b'; 'i'; 'n'; 'a'; 'r'; 'y'] | ['b', 'i', 'n', 'a', 'r', 'y'] |
Function Calls
Erlang | Elixir | OCaml | Reason |
---|---|---|---|
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
Erlang:
if
Foo -> Bar;
_ -> Baz
end.
Elixir
if(foo, do: bar, else: baz)
if foo do
bar
else
baz
end
OCaml
if foo then bar else baz
Reason
foo ? bar : baz
Match / Case / Switch expression
Erlang:
case Foo of
Bar -> Baz
end.
Elixir
case foo do
bar -> baz
end
OCaml
match foo with
| bar -> baz
Reason
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: Erlang.ml *)
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: Ets.ml *)
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: Ets.ml *)
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}
end.
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 hello_joe.ml
with the following
contents:
let hello () = Io.format "~p" ["Hello, Joe!"]
Now we can compile this by calling caramel compile hello_joe.ml
:
$ ls
hello_joe.ml
$ caramel compile hello_joe.ml
$ ls
hello_joe.ml hello_joe.erl
Our new compiled file, hello_joe.erl
should looks like this:
% Source code Generated by Caramel
-module(hello_joe).
-export([hello/0]).
hello() -> io:format(<<"~p">>, [<<"Hello, Joe!">>]).
Now we can compile and run this Erlang code
Simple HTTP Echo Server
(* file: http_echo.ml *)
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)
Reference
caramel
NAME
caramel - Caramel compiler
SYNOPSIS
caramel COMMAND ...
DESCRIPTION
Caramel is a functional language for building type-safe, scalable, and
maintainable applications.
COMMANDS
compile
Compile Caramel code to run on the Erlang VM.
fmt Format Caramel code.
parse
(UNSTABLE) Helper command to parse sources and dump ASTs
sort-deps
Sort OCaml files by their dependencies on each other.
version
Show version information.
OPTIONS
--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.
--version
Show version information.
SEE ALSO
ocaml(1) erlang
AUTHORS
Leandro Ostera.
LICENSE
Copyright (C) 2020-present, Abstract Machines Lab Sweden AB
Caramel is licensed under Apache License 2.0
caramel compile
NAME
caramel-compile - Compile Caramel code to run on the Erlang VM.
SYNOPSIS
caramel compile [OPTION]... SOURCES...
DESCRIPTION
The Caramel takes as input OCaml sources and compiles them to Erlang
code.
ARGUMENTS
SOURCES (required)
A list of source files to compile
OPTIONS
-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.
--no-stdlib
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)
--version
Show version information.
ENVIRONMENT
These environment variables affect the execution of compile:
CARAMEL_STDLIB_PATH
See option --stdlib-path.
SEE ALSO
ocaml(1) erlang
AUTHORS
Leandro Ostera.
LICENSE
Copyright (C) 2020-present, Abstract Machines Lab Sweden AB
Caramel is licensed under Apache License 2.0
caramel fmt
NAME
caramel-fmt - Format Caramel code.
SYNOPSIS
caramel fmt [OPTION]... SOURCES...
DESCRIPTION
Reformats Caramel source code.
ARGUMENTS
SOURCES (required)
A list of source files to format
OPTIONS
--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.
--version
Show version information.
SEE ALSO
ocaml(1) erlang
AUTHORS
Leandro Ostera.
LICENSE
Copyright (C) 2020-present, Abstract Machines Lab Sweden AB
Caramel is licensed under Apache License 2.0
caramel sort-deps
NAME
caramel-sort-deps - Sort OCaml files by their dependencies on each
other.
SYNOPSIS
caramel sort-deps [OPTION]... SOURCES...
DESCRIPTION
OPTIONS
--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.
--version
Show version information.
SEE ALSO
ocaml(1) erlang
AUTHORS
Leandro Ostera.
LICENSE
Copyright (C) 2020-present, Abstract Machines Lab Sweden AB
Caramel is licensed under Apache License 2.0
caramel version
NAME
caramel-version - Show version information.
SYNOPSIS
caramel version [OPTION]...
DESCRIPTION
OPTIONS
--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.
--version
Show version information.
SEE ALSO
ocaml(1) erlang
AUTHORS
Leandro Ostera.
LICENSE
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.
Topics
Manual Structure
The structure of the manual is reflected in the SUMMARY.md
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](./introduction.md)
- [Getting Started](./getting-started/index.md)
- [Installation](./getting-started/installation.md)
- [First Steps](./getting-started/first-steps.md)
- [CHANGELOG]()
- [v0.1.1](./changelog/v0.1.1.md)
- [v0.1.0](./changelog/v0.1.0.md)
- [v0.0.14 and older](./changelog/v0.0.14.md)
--------------------------------------------------------------------------------
- [Guides]()
- [Syntax Cheatsheet](./guides/syntax-cheatsheet.md)
- [Using Caramel with Rebar3]()
- [Using Caramel with Mix]()
- [Calling Erlang code](./guides/erlang-ffi.md)
- [Calling Elixir code]()
- [Examples]()
- [Hello, Joe!](./examples/hello-joe.md)
- [Simple HTTP Echo Server](./examples/simple-http-echo-server.md)
--------------------------------------------------------------------------------
- [Reference](./reference/index.md)
- [Caramel CLI](./reference/cli/default.md)
- [`caramel compile`](./reference/cli/compile.md)
- [`caramel fmt`](./reference/cli/fmt.md)
- [`caramel sort-deps`](./reference/cli/sort-deps.md)
- [`caramel version`](./reference/cli/version.md)
- [Language Features]()
- [Standard Library]()
- [`Binary`]()
- [`Calendar`]()
- [`Erlang`]()
- [`Ets`]()
- [`Io`]()
- [`Lists`]()
- [`Maps`]()
- [`Process`]()
- [`Timer`]()
- [Typed OTP]()
--------------------------------------------------------------------------------
- [Contributing]()
- [Contributing to the Manual](./contrib/manual.md)
- [Building from Source](./contrib/building.md)
- [Architecture](./contrib/architecture.md)
Editing Existing Pages
If you find a typo in some page, you can look into the SUMMARY.md
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 SUMMARY.md
.
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 https://raw.githubusercontent.com/ocaml/opam/master/shell/install.sh)
# clone the repository
git clone git@github.com:AbstractMachinesLab/caramel.git
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 release.win
on
Windows.
Architecture
Compiler
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/compiler.ml.
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.