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.

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:

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