Skip to content
Volume O · The TensorCore.si Library

OCaml

Fast, safe functional programming.

OCaml is a functional language with a powerful type system and a fast native compiler. It is used for compilers, static analysis tools, formal verification and finance (Jane Street runs much of its trading on OCaml). Rust's first compiler was written in OCaml, and Meta's Hack and Flow type checkers use it.

Prologue

Why learn OCaml?

OCaml shows how far a type system can take you: fast programs that are very hard to break.

  • Fast: native code with a quick compiler.
  • Safe: type inference and pattern matching catch bugs at compile time.
  • Multicore: OCaml 5 adds parallel domains and effect handlers.
Prologue

Set up and run your first program

Install opam (the OCaml package manager), then the Dune build tool and the utop interactive shell.

terminal
opam init
opam install dune utop
ocaml hello.ml
hello.ml
let () =
  let name = "Ada" in
  Printf.printf "Hello, AI! Welcome to OCaml, %s.\n" name
TipUse utop for a friendly interactive shell; end expressions with ;; there.
Chapter 1

Values and types

let binds immutable values. Integers and floats are separate types with separate operators: + for ints and +. for floats.

values.ml
let layers = 32
let lr = 3e-4
let model = "llama3.1"
let fp16_gb params = params *. 2.0 /. 1e9

let () =
  Printf.printf "%s: %d layers, lr %g, %.1f GB\n" model layers lr (fp16_gb 8.03e9)
Chapter 2

Functions and recursion

Functions are curried and can be partially applied. let rec defines recursive functions, the usual way to loop.

functions.ml
let square x = x * x
let rec sum = function
  | [] -> 0
  | h :: t -> h + sum t

let params layers width = 12 * layers * width * width
let params_4096 = params 32        (* partial application *)

let () =
  Printf.printf "%d %d %d\n" (square 12) (sum [1; 2; 3]) (params_4096 4096)
Chapter 3

Lists and higher-order functions

List.map, List.filter and List.fold_left handle most collection work. The |> pipe keeps chains readable.

lists.ml
let vram = [ ("H100", 80); ("H200", 141); ("RTX 5090", 32) ]

let () =
  vram
  |> List.filter (fun (_, gb) -> gb >= 80)
  |> List.map fst
  |> String.concat ", "
  |> print_endline;
  let total = List.fold_left (fun acc (_, gb) -> acc + gb) 0 vram in
  Printf.printf "total %d GB\n" total
Chapter 4

Variants, records and pattern matching

Variant types model choices, records hold named fields, and the compiler warns if a match misses a case.

types.ml
type gpu = { name : string; vram : int }
type device = Cpu | Gpu of gpu | Tpu of int

let describe = function
  | Cpu -> "CPU"
  | Gpu { name; vram } when vram >= 80 -> "data center GPU " ^ name
  | Gpu { name; vram } -> Printf.sprintf "GPU %s (%d GB)" name vram
  | Tpu v -> Printf.sprintf "TPU v%d" v

let () = List.iter (fun d -> print_endline (describe d))
  [ Cpu; Gpu { name = "H100"; vram = 80 }; Tpu 5 ]
Chapter 5

Modules, options and errors

Modules group code; signatures hide details. option replaces null, and result represents success or error.

modules.ml
module Vram : sig
  val fit : model_gb:int -> vram_gb:int -> (int, string) result
end = struct
  let fit ~model_gb ~vram_gb =
    if model_gb <= vram_gb then Ok (vram_gb - model_gb)
    else Error "model does not fit"
end

let () =
  match Vram.fit ~model_gb:16 ~vram_gb:24 with
  | Ok free -> Printf.printf "free: %d GB\n" free
  | Error e -> print_endline e;
  print_endline (Option.value (List.assoc_opt "B200" [ ("H100", "80") ]) ~default:"unknown")
Part II · AI with OCaml

AI with OCaml

OCaml is used in AI mainly for tooling and safe infrastructure: ML compilers and DSLs, verified code, and trading systems that use models. Libraries include OCaml bindings to PyTorch (ocaml-torch) and Owl for scientific computing, and LLM APIs are easy to call with cohttp and yojson.

The essential libraries

OwlScientific computing and neural networks.
ocaml-torchBindings to libtorch (PyTorch C++).
cohttp-lwt-unixHTTP client and server.
yojsonJSON parsing and printing.

Gradient descent from scratch

Fit y = 3x + 0.5 using arrays and a for loop.

gradient.ml
let () =
  let n = 100 in
  let xs = Array.init n (fun i -> -1.0 +. 2.0 *. float i /. float (n - 1)) in
  let ys = Array.map (fun x -> 3.0 *. x +. 0.5) xs in
  let w = ref 0.0 and b = ref 0.0 in
  for _ = 1 to 500 do
    let gw = ref 0.0 and gb = ref 0.0 in
    Array.iteri (fun i x ->
      let err = !w *. x +. !b -. ys.(i) in
      gw := !gw +. 2.0 *. err *. x /. float n;
      gb := !gb +. 2.0 *. err /. float n) xs;
    w := !w -. 0.1 *. !gw;
    b := !b -. 0.1 *. !gb
  done;
  Printf.printf "w = %.2f, b = %.2f\n" !w !b
Part III · Build an AI agent

Build an AI agent in OCaml

With cohttp-lwt-unix for HTTP and yojson for JSON (install both with opam), OCaml can run the tool-calling loop.

Your goal→Model plans→Tool call→Your code runs the tool→Result back to model→Final answer
agent.ml
(* opam install cohttp-lwt-unix yojson *)
open Lwt.Syntax
module J = Yojson.Safe
module U = Yojson.Safe.Util

let url = Uri.of_string "http://localhost:11434/v1/chat/completions"
let tools = J.from_string {|[{"type":"function","function":{"name":"get_weather","description":"Weather for a city",
  "parameters":{"type":"object","properties":{"city":{"type":"string"}},"required":["city"]}}}]|}

let chat messages =
  let body = `Assoc [ ("model", `String "llama3.1"); ("messages", `List messages); ("tools", tools) ] in
  let* _, b = Cohttp_lwt_unix.Client.post url
      ~headers:(Cohttp.Header.init_with "Content-Type" "application/json")
      ~body:(Cohttp_lwt.Body.of_string (J.to_string body)) in
  let+ s = Cohttp_lwt.Body.to_string b in
  J.from_string s |> U.member "choices" |> U.index 0 |> U.member "message"

let rec loop messages n =
  if n = 0 then Lwt.return_unit else
  let* msg = chat messages in
  match U.member "tool_calls" msg with
  | `List (_ :: _ as calls) ->
    let replies = List.map (fun c ->
      let args = J.from_string (U.to_string (U.member "arguments" (U.member "function" c))) in
      let city = U.to_string (U.member "city" args) in
      `Assoc [ ("role", `String "tool"); ("tool_call_id", U.member "id" c);
               ("content", `String ("It is 24°C and sunny in " ^ city ^ ".")) ]) calls in
    loop (messages @ (msg :: replies)) (n - 1)
  | _ -> Lwt_io.printl (U.to_string (U.member "content" msg))

let () = Lwt_main.run (loop [ `Assoc [ ("role", `String "user");
  ("content", `String "Should I take an umbrella in Lahore today?") ] ] 5)

Build with Dune by listing cohttp-lwt-unix and yojson in the libraries field.

Run it locallyInstall Ollama, run ollama pull llama3.1, and keep it running. The code talks to its OpenAI-compatible API at http://localhost:11434/v1, so the same program also works with vLLM, llama.cpp server or a cloud API by changing the URL, model name and adding an API key.
Part IV · Your path

OCaml learning roadmap

Tick items off as you go. Your progress is saved in this browser.

Beginner

Intermediate

Advanced

Part IV · Your path

Projects to build

InterpreterA tiny calculator language with a parser.
To-do CLIA command-line app with Dune and Cmdliner.
LLM clientA typed chat client with tool calling.
Reference

OCaml cheat sheet

TaskSyntax
Valuelet x = 5
Functionlet add a b = a + b
Float maths2.0 *. x +. 1.0
List[1; 2; 3]
Matchmatch x with Some v -> v | None -> 0
Pipexs |> List.map f
Reference

Common mistakes

  • Ints and floats need different operators (+ vs +.).
  • List items are separated by semicolons; [1, 2] is a list containing one tuple.
  • Sequencing with ; inside if branches needs begin ... end or parentheses.
Reference

Where to go next

On TensorCore.si: Haskell volume, F# volume.

Keyboard: [ previous language · ] next language · / search the library