A statically typed functional language
for the Erlang VM

Leandro  

staff software engineer

goals

  • An ML
  • Expressive and sound type-system
  • Embraces Erlang and OTP Patterns:
    • Functional
    • Immutable Data
    • Type-Safe Concurrency
  • Leverage the Ecosystem:
    • Zero-cost Interop with other BEAM languages 
  • Free and Open Source
  • Batteries Included

why an OCaml?

  • Pragmatic, sound, proven type-system
  • Small core functional language
    • Caramel has been mainly a solo effort
  • Good scaling properties
    • Functors for type-safe code reuse
    • Great compilation times
    • Type-safe macro support
  • Extensible compiler design
  • Elegant syntax

why the Erlang VM?

  • Most scalable runtime for functional languages
  • Supports massive concurrency & parallelism
  • Optimized for low latencies
  • Designed for reliability
    • Reliability by embracing failure
    • Supervision patterns
      (think built-in Kubernetes)
  • New support for JIT compilation
  • A good compilation target platform
    (think Elixir, Gleam, Clojerl, Purerl, LFE)

why a sound type system?

  • Rule out a large number of errors
  • Make illegal states unrepresentable
  • Put domain constraints at compile-time
  • Makes fearless refactoring easier
  • Better autocomplete and developer experience
    • Look at what TypeScript did for JS
    • Look at what IntelliJ does for Java
  • "Typing by convention" is not good enough

why not just write OCaml?

  • OCaml runtime is still single threaded
    but multi-core is coming soon
  • Concurrency story:
    • cooperative scheduling of promises
      on a single thread
    • IPC-style concurrency
  • OCaml Community is very strong but
    rather small compared to e.g Elixir
  • OCaml Libraries are normally excellent but
    rather few compared to Erlang / Elixir

Caramel is an OCaml Dialect

  • No mutability, No Object Oriented System
  • Statically Typed with Algebraic Data Types
    • Tuples, Records
    • Variants, Polymorphic Variants
    • GADTs
  • Full Hindley-Milner Type Inference
    • Chances are you'll never *have*
      to annotate your code 
  • Structurally typed modules
  • Lightweight FFI syntax
  • Type-safe macros (ppx)

status

Latest version is v0.1, so everything is still a WIP!
 

  • Language Features
  • Tooling
  • Standard Library
  • Documentation & Manual

language

  • Lists and Tuples
  • Binary Strings
  • Arbitrary Precision Integers
  • Side-effects, not pure
let rec fib_aux n b a = 
  if n <= 0 then a
  else fib_aux (n-1) (a+b) b

let fib n = fib_aux n 1 0

let main _ =
  let fib_nth = [
    (10, fib 10);
    (1000, fib 1000);
    (100000, fib 10000)
  ]
  in Io.format "~p\n" [numbers]

language

  • Records and Variants
  • Opaque/Abstract Types
  • Type-directed pattern matching
    with exhaustiveness checks
  • Type-inference finds out
    that handle_action takes a
    value of type action based on
    pattern matching
(* action can be one of these 2 *)
type action = Login | Logout

(* this is opaque and can't be inspected! *)
type id 

(* session must have both of these fields *)
type session = {
  logged_in_at: DateTime.t;
  user_id: id;
}

let handle_action next state =
  match next with
  | Login -> (* begin session *)
  | Logout -> (* end session *)

language

  • Flexible module system
  • Partial application and currying 
  • Tail-call optimization
  • Functions with
    • named arguments
    • pattern matching
    • default arguments
module Hello = struct
  (* print a name! *)
  let say ?(name="Joe") =
    Io.format "Hello, ~p!\n" [name]
end

let rec run ~args:(name :: args) =
  Hello.say ~name;
  run ~args

let main () = 
  un ~args:["Joe";"Robert";"Mike"]

language

Zero-cost Interop 

module Logger = struct
  external info : string -> unit = "" 
end

let main () = info "hello world"
-module(presentation).

-export([main/0]).

% Calls the actual logger module directly!
main() -> logger:info(<<"hello world">>).

tooling

  • Single binary with no external dependencies
    (makes it super easy to bootstrap)
  • Code formatter built-in
  • Coming soon:
    • Language server built-in
    • Documentation generator built-in
    • Type-safe language extension preprocessor
      (think embedded GraphQL syntax compiled to type-safe modules and types)
  • WIP:
    • plugins for rebar3 and mix
    • vs-code extension

stdlib

  • Current library is a sketch, modeled after the Erlang Stdlib
     
  • Next standard library being designed inspired by Rust, OCaml, and Golang
    • Focus on reusing high quality Erlang/Elixir libraries
    • Focus on type-safety and developer ergonomics
    • Batteries included!
      • Core, Async, Collections, Num
      • OS, FileSystem, Net, Crypto,
      • Common serde (json, toml, yaml, s-expr)
      • Typed OTP, Agents

manual

  • Introduction to the Language
  • Getting Started & Installation
  • Guides for Common Tasks
    • Calling Erlang/Elixir libs from Caramel
    • Calling Caramel code from Erlang/Elixir
    • Using with rebar3 or mix
  • References
    • CLI tool
    • Language features
    • Standard Library
  • How to Contribute
    • Building from Source
    • Architecture

future

  • 2021 goal is to sort of end the year being able
    to write the core of a Phoenix app in Caramel 🌟
     
  • There's tons of work to be done across the board
    we're still scoping the breadth of it 🗺
     
  • Now 12 supporters on Github Sponsors 🙌🏽
    All goes straight into helping Caramel succeed
    First goal is underway! 🚀
     
  • Starting monthly meetings with 2 contributors
    Primarily communicating on Discord, join us! 📅

thank you!

Caramel: bringing an OCaml to the Erlang VM

By Leandro Ostera

Caramel: bringing an OCaml to the Erlang VM

Here I present Caramel v0.1, answering several common questions like why an OCaml, why on the Erlang VM, why a sound type system, and how is Caramel an OCaml. We'll also go over the status of Caramel v0.1, including language and tooling features, and what's ahead in the roadmap.

  • 917