<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://batsov.com/feeds/OCaml.xml" rel="self" type="application/atom+xml" /><link href="https://batsov.com/" rel="alternate" type="text/html" hreflang="en" /><updated>2026-07-03T09:03:52+03:00</updated><id>https://batsov.com/feeds/OCaml.xml</id><title type="html">(think)</title><subtitle>Bozhidar Batsov&apos;s personal blog</subtitle><entry><title type="html">Neocaml 0.9: A Better REPL, Dune/Opam Completion, and More Robustness</title><link href="https://batsov.com/articles/2026/06/24/neocaml-0-9-repl-completion-and-robustness/" rel="alternate" type="text/html" title="Neocaml 0.9: A Better REPL, Dune/Opam Completion, and More Robustness" /><published>2026-06-24T10:00:00+03:00</published><updated>2026-06-24T10:00:00+03:00</updated><id>https://batsov.com/articles/2026/06/24/neocaml-0-9-repl-completion-and-robustness</id><content type="html" xml:base="https://batsov.com/articles/2026/06/24/neocaml-0-9-repl-completion-and-robustness/"><![CDATA[<p>It’s been a couple of months since the last <a href="https://github.com/bbatsov/neocaml">neocaml</a>
release, and the reason is simple — for a while there I was genuinely out of ideas.
Back when I shipped <a href="/articles/2026/03/25/neocaml-0-6-opam-dune-and-more/">0.6</a>
I declared (again!) that I was done with new features, and this time I almost meant
it. But ideas have a way of creeping back in, and 0.9 turned out to be a meaty
release. Here are the highlights.</p>

<h2 id="a-much-nicer-repl-experience">A much nicer REPL experience</h2>

<p>The biggest chunk of work went into the REPL (toplevel) integration. I’m well aware
that the OCaml toplevel isn’t terribly popular with seasoned OCaml developers — most
of them reach for a proper build and a debugger instead. But I think newcomers get a
lot of mileage out of a REPL, and (no surprise to anyone who’s followed my work) I’m
a Lisper at heart with a real soft spot for interactive development. Clojure and
Emacs Lisp spoiled me, and I want OCaml beginners to taste a bit of that too.</p>

<p>So, what’s new:</p>

<ul>
  <li><strong>A dedicated REPL per project.</strong> The REPL buffer is now named after its project
(e.g. <code class="language-plaintext highlighter-rouge">*OCaml: myproject*</code>), and the send commands route to the current buffer’s
project REPL. You can have several projects running side by side without them
stepping on each other.</li>
  <li>
    <p><strong>Choose your toplevel.</strong> The new <code class="language-plaintext highlighter-rouge">neocaml-repl-flavor</code> lets you pick between
<code class="language-plaintext highlighter-rouge">ocaml</code>, <code class="language-plaintext highlighter-rouge">utop</code>, and <code class="language-plaintext highlighter-rouge">dune-utop</code>. Set it globally, or per project via
<code class="language-plaintext highlighter-rouge">.dir-locals.el</code>:</p>

    <div class="language-emacs-lisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre><span class="p">((</span><span class="nv">neocaml-mode</span> <span class="o">.</span> <span class="p">((</span><span class="nv">neocaml-repl-flavor</span> <span class="o">.</span> <span class="nv">dune-utop</span><span class="p">))))</span>
</pre></td></tr></tbody></table></code></pre></div>    </div>

    <p>The active flavor shows up in the REPL’s mode line, so you always know what you’re
talking to.</p>
  </li>
  <li><strong>Send a phrase and step.</strong> <code class="language-plaintext highlighter-rouge">C-c C-n</code> (<code class="language-plaintext highlighter-rouge">neocaml-repl-send-phrase-and-step</code>) sends
the phrase at point to the REPL and moves on to the next one — perfect for walking
through a file top to bottom while you experiment.</li>
  <li><strong><code class="language-plaintext highlighter-rouge">#require</code> from Emacs.</strong> <code class="language-plaintext highlighter-rouge">neocaml-repl-require</code> loads a <code class="language-plaintext highlighter-rouge">findlib</code> package into the
running toplevel without you having to type the directive by hand.</li>
  <li><strong>Restart on demand.</strong> <code class="language-plaintext highlighter-rouge">neocaml-repl-restart</code> kills and restarts the toplevel when
things get into a weird state.</li>
</ul>

<h2 id="completion-for-opam-and-dune-no-lsp-required">Completion for Opam and Dune, no LSP required</h2>

<p>This one I’m particularly happy with. <code class="language-plaintext highlighter-rouge">.ml</code>/<code class="language-plaintext highlighter-rouge">.mli</code> files have <code class="language-plaintext highlighter-rouge">ocaml-lsp-server</code> to
lean on for completion, but the auxiliary file formats have no language server at all.
That’s exactly the kind of gap neocaml is meant to fill, so both <code class="language-plaintext highlighter-rouge">neocaml-dune-mode</code>
and <code class="language-plaintext highlighter-rouge">neocaml-opam-mode</code> now ship a <code class="language-plaintext highlighter-rouge">completion-at-point</code> backend.</p>

<p>In a <code class="language-plaintext highlighter-rouge">dune</code> file you get completion for stanza names, the field names valid for the
<em>enclosing</em> stanza, and library names inside <code class="language-plaintext highlighter-rouge">libraries</code>/<code class="language-plaintext highlighter-rouge">pps</code> fields:</p>

<pre><code class="language-dune">(library
 (name my_lib)
 (libraries str re|))   ; &lt;- completes both your own libraries and installed findlib ones
</code></pre>

<p>The library candidates combine your project’s own libraries with whatever’s installed
in the active Opam switch. And it’s switch-aware — if there’s a project-local switch
(an <code class="language-plaintext highlighter-rouge">_opam/</code> directory), neocaml detects it and queries it via <code class="language-plaintext highlighter-rouge">opam exec --</code>,
without any configuration on your part. The results are cached per project, so it
stays snappy.</p>

<p>In <code class="language-plaintext highlighter-rouge">opam</code> files you get completion for field and section names, and package names
inside <code class="language-plaintext highlighter-rouge">depends</code>/<code class="language-plaintext highlighter-rouge">depopts</code>/<code class="language-plaintext highlighter-rouge">conflicts</code> (sourced from <code class="language-plaintext highlighter-rouge">opam list</code>):</p>

<pre><code class="language-opam">depends: [
  "dune" {&gt;= "3.0"}
  "cmd|"   ; &lt;- completes to cmdliner and friends
]
</code></pre>

<p>Both backends can be toggled off via <code class="language-plaintext highlighter-rouge">neocaml-dune-complete-libraries</code> and
<code class="language-plaintext highlighter-rouge">neocaml-opam-complete-packages</code> if you’d rather not have them.</p>

<h2 id="robustness-improvements">Robustness improvements</h2>

<p>A good chunk of this release is the unglamorous but important work of making things
just behave correctly:</p>

<ul>
  <li><strong>Character literals and quoted strings at the syntactic layer.</strong> Tree-sitter
fontifies <code class="language-plaintext highlighter-rouge">'"'</code>, <code class="language-plaintext highlighter-rouge">'('</code>, and <code class="language-plaintext highlighter-rouge">{|raw "strings"|}</code> correctly, but the syntax table
underneath was getting confused — which broke <code class="language-plaintext highlighter-rouge">forward-sexp</code>, <code class="language-plaintext highlighter-rouge">delete-pair</code>, and
<code class="language-plaintext highlighter-rouge">electric-pair-mode</code> around those constructs. The fix was a good old
<code class="language-plaintext highlighter-rouge">syntax-propertize-function</code>. I wrote up the whole story over on Emacs Redux in
<a href="https://emacsredux.com/blog/2026/06/22/tree-sitter-modes-still-need-a-syntax-table/">Tree-sitter Modes Still Need a Syntax Table</a>,
if you’re into mode-writing internals.</li>
  <li><strong><code class="language-plaintext highlighter-rouge">project.el</code> integration.</strong> A directory with a <code class="language-plaintext highlighter-rouge">dune-project</code> file is now
recognized as a project root (even without version control), <code class="language-plaintext highlighter-rouge">_build/</code> and <code class="language-plaintext highlighter-rouge">_opam/</code>
are ignored, and <code class="language-plaintext highlighter-rouge">compile-command</code> defaults to <code class="language-plaintext highlighter-rouge">dune build</code> in dune projects.</li>
</ul>

<p>There’s more in there too — <code class="language-plaintext highlighter-rouge">ocamlformat</code> integration (<code class="language-plaintext highlighter-rouge">C-c C-f</code>), clickable URLs and
bug references in comments, a font-lock level selector, and richer menus across
the modes.</p>

<h2 id="ocaml-55-support-and-the-abi-14-balancing-act">OCaml 5.5 support, and the ABI 14 balancing act</h2>

<p><a href="https://discuss.ocaml.org/t/ocaml-5-5-0-released/18265">OCaml 5.5 was released</a> on
June 19th, so this felt like a good moment to ship 5.5 support in neocaml. The <code class="language-plaintext highlighter-rouge">ocaml</code>
and <code class="language-plaintext highlighter-rouge">ocaml-interface</code> grammars now track tree-sitter-ocaml v0.25.0, which brings the
5.5 grammar along with it.</p>

<p>There’s a wrinkle here that’s worth explaining, because it’s shaped the last few
releases. A tree-sitter grammar gets compiled into a parser that speaks a particular
<strong>ABI version</strong>, and Emacs can only load parsers up to the latest ABI supported by the
<code class="language-plaintext highlighter-rouge">libtree-sitter</code> it was <em>built against</em> — it’s not the Emacs version itself that sets
the ceiling. In practice a lot of Emacs 30 builds out there (notably Homebrew’s on
macOS) are linked against tree-sitter 0.24, which tops out at <strong>ABI 14</strong>; you need an
Emacs built against tree-sitter 0.25+ to load <strong>ABI 15</strong> grammars. The trouble is that
the tree-sitter 0.25 CLI now generates ABI 15 parsers by default, so any grammar
regenerated with current tooling produces something those builds simply can’t load —
you install it and it just errors out. Emacs 31 will ship with newer tree-sitter and
make ABI 15 the common case, but it’s not out yet. (This isn’t a neocaml problem as
such; it’s been biting tree-sitter modes across the ecosystem.)</p>

<p>After a few users ran into exactly this, I’ve made a deliberate decision: <strong>stick to
ABI 14 grammars until Emacs 31 is widely available.</strong> That effort started a couple of
releases back — in 0.8.1 I lowered the ABI requirement from 15 to 14 across the opam,
dune, and ocamllex modes, switched the menhir recipe to
<a href="https://github.com/tmcgilchrist/tree-sitter-menhir">tmcgilchrist/tree-sitter-menhir</a>,
and pinned ocamllex back to v0.24.0, all of which target ABI 14
(<a href="https://github.com/bbatsov/neocaml/issues/42">#42</a>). 0.9 extends that policy to the
core OCaml grammars.</p>

<p>The catch with v0.25.0 is precisely that it generates an ABI 15 parser. Happily, the
5.5 grammar didn’t actually need any ABI 15 features — the bump rode along with the CLI
upgrade — so an ABI 14 regeneration of the very same grammar is a drop-in. Big thanks
to <a href="https://github.com/314eter">314eter</a>, the <code class="language-plaintext highlighter-rouge">tree-sitter-ocaml</code> maintainer, for cutting
a <code class="language-plaintext highlighter-rouge">v0.25.0-abi14</code> tag for exactly this purpose
(<a href="https://github.com/tree-sitter/tree-sitter-ocaml/issues/141">#141</a>). The one snag was
that tagging normally triggers releases to NPM, crates.io, and PyPI, so I sent a small
PR to skip publishing for ABI-suffixed tags
(<a href="https://github.com/tree-sitter/tree-sitter-ocaml/pull/142">#142</a>), and the tag
followed. neocaml now pins both grammars to it.</p>

<h2 id="roadmap-and-docs">Roadmap and docs</h2>

<p>If you’re curious where neocaml is headed, I’ve started keeping a
<a href="https://github.com/bbatsov/neocaml/blob/main/ROADMAP.md">ROADMAP.md</a> with ideas and
guiding principles (short version: tree-sitter first, lean on the LSP stack for
<code class="language-plaintext highlighter-rouge">.ml</code>/<code class="language-plaintext highlighter-rouge">.mli</code>, and own the auxiliary modes that have no language server). The project
also has a proper documentation site now at
<a href="https://neocaml.org">neocaml.org</a>, so there’s a real
home for the details beyond the README.</p>

<h2 id="give-it-a-try">Give it a Try</h2>

<p>As always — update from <a href="https://melpa.org/#/neocaml">MELPA</a>, play with it, and let
me know how it goes. The full list of changes is in the
<a href="https://github.com/bbatsov/neocaml/releases/tag/v0.9.0">0.9.0 release notes</a>. Bug
reports, feature requests, and pull requests are all welcome on
<a href="https://github.com/bbatsov/neocaml">GitHub</a>.</p>

<p>That’s all from me, folks! Keep hacking!</p>]]></content><author><name>Bozhidar Batsov</name></author><category term="OCaml" /><category term="Emacs" /><summary type="html"><![CDATA[It’s been a couple of months since the last neocaml release, and the reason is simple — for a while there I was genuinely out of ideas. Back when I shipped 0.6 I declared (again!) that I was done with new features, and this time I almost meant it. But ideas have a way of creeping back in, and 0.9 turned out to be a meaty release. Here are the highlights.]]></summary></entry><entry><title type="html">Neocaml 0.6: Opam, Dune, and More</title><link href="https://batsov.com/articles/2026/03/25/neocaml-0-6-opam-dune-and-more/" rel="alternate" type="text/html" title="Neocaml 0.6: Opam, Dune, and More" /><published>2026-03-25T10:00:00+02:00</published><updated>2026-03-25T10:00:00+02:00</updated><id>https://batsov.com/articles/2026/03/25/neocaml-0-6-opam-dune-and-more</id><content type="html" xml:base="https://batsov.com/articles/2026/03/25/neocaml-0-6-opam-dune-and-more/"><![CDATA[<p>When I released <a href="/articles/2026/02/14/neocaml-0-1-ready-for-action/">neocaml 0.1</a>
last month, I thought I was more or less done with the (main) features for the
foreseeable future. The original scope was deliberately small — a couple of
Tree-sitter-powered OCaml major modes (for <code class="language-plaintext highlighter-rouge">.ml</code> and <code class="language-plaintext highlighter-rouge">.mli</code>), a REPL
integration, and not much else. I was quite happy with how things turned out and
figured the next steps would be mostly polish and bug fixes.</p>

<p>Versions 0.2-0.5 brought polish and bug fixes, but fundamentally the feature
set stayed the same. I was even more convinced a grand 1.0 release was just
around the corner.</p>

<p>I was wrong.</p>

<p>Of course, OCaml files don’t exist in isolation. They live alongside
<a href="https://opam.ocaml.org/">Opam</a> files that describe packages and
<a href="https://dune.readthedocs.io/">Dune</a> files that configure builds. And as I was
poking around the Tree-sitter ecosystem, I discovered that there were already
grammars for both
<a href="https://github.com/tmcgilchrist/tree-sitter-opam">Opam</a> and
<a href="https://github.com/tmcgilchrist/tree-sitter-dune">Dune</a> files. Given how
simple both formats are (Opam is mostly key-value pairs, Dune is
s-expressions), adding support for them turned out to be fairly
straightforward.</p>

<p>So here we are with neocaml 0.6, which is quite a bit bigger than I expected
originally.</p>

<p><strong>Note:</strong> One thing worth mentioning — all the new modes are completely
isolated from the core OCaml modes. They’re separate files with no hard
dependency on <code class="language-plaintext highlighter-rouge">neocaml-mode</code>, loaded only when you open the relevant file
types. I didn’t want to force them upon anyone — for me it’s convenient to get
Opam and Dune support out-of-the-box (given how ubiquitous they are in the OCaml
ecosystem), but I totally get it if someone doesn’t care about this.</p>

<p>Let me walk you through what’s new.</p>

<h2 id="neocaml-opam-mode">neocaml-opam-mode</h2>

<p>The new <code class="language-plaintext highlighter-rouge">neocaml-opam-mode</code> activates automatically for <code class="language-plaintext highlighter-rouge">.opam</code> and <code class="language-plaintext highlighter-rouge">opam</code>
files. It provides:</p>

<ul>
  <li>Tree-sitter-based font-lock (field names, strings, operators, version
constraints, filter expressions, etc.)</li>
  <li>Indentation (lists, sections, option braces)</li>
  <li>Imenu for navigating variables and sections</li>
  <li>A <strong>flymake backend</strong> that runs <code class="language-plaintext highlighter-rouge">opam lint</code> on the current buffer, giving you
inline diagnostics for missing fields, deprecated constructs, and syntax
errors</li>
</ul>

<p>The flymake backend registers automatically when <code class="language-plaintext highlighter-rouge">opam</code> is found in your PATH,
but you need to enable <code class="language-plaintext highlighter-rouge">flymake-mode</code> yourself:</p>

<div class="language-emacs-lisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre><span class="p">(</span><span class="nv">add-hook</span> <span class="ss">'neocaml-opam-mode-hook</span> <span class="nf">#'</span><span class="nv">flymake-mode</span><span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p><a href="https://github.com/flycheck/flycheck">Flycheck</a> users get <code class="language-plaintext highlighter-rouge">opam lint</code> support
out of the box via Flycheck’s built-in <code class="language-plaintext highlighter-rouge">opam</code> checker — no extra configuration
needed.</p>

<p>This bridges some of the gap with <a href="https://github.com/ocaml/tuareg">Tuareg</a>,
which also bundles an Opam major mode (<code class="language-plaintext highlighter-rouge">tuareg-opam-mode</code>). The Tree-sitter-based
approach gives us more accurate highlighting, and the flymake integration is a
nice bonus on top.</p>

<h2 id="neocaml-dune-mode">neocaml-dune-mode</h2>

<p><code class="language-plaintext highlighter-rouge">neocaml-dune-mode</code> handles <code class="language-plaintext highlighter-rouge">dune</code>, <code class="language-plaintext highlighter-rouge">dune-project</code>, and <code class="language-plaintext highlighter-rouge">dune-workspace</code> files
— all three use the same s-expression syntax and share a single Tree-sitter
grammar. You get:</p>

<ul>
  <li>Font-lock for stanza names, field names, action keywords, strings, module
names, library names, operators, and brackets</li>
  <li>Indentation with 1-space offset</li>
  <li>Imenu for stanza navigation</li>
  <li>Defun navigation and <code class="language-plaintext highlighter-rouge">which-func</code> support</li>
</ul>

<p>This removes the need to install the separate
<a href="https://melpa.org/#/dune">dune</a> package (the standalone <code class="language-plaintext highlighter-rouge">dune-mode</code> maintained
by the Dune developers) from MELPA. If you prefer to keep using it, that’s fine
too — neocaml’s README has
<a href="https://github.com/bbatsov/neocaml#using-the-legacy-dune-mode">instructions</a>
for overriding the <code class="language-plaintext highlighter-rouge">auto-mode-alist</code> entries.</p>

<h2 id="neocaml-dune-interaction-mode">neocaml-dune-interaction-mode</h2>

<p>Beyond editing Dune files, I wanted a simple way to run Dune commands from any
neocaml buffer. <code class="language-plaintext highlighter-rouge">neocaml-dune-interaction-mode</code> is a minor mode that provides
keybindings (under <code class="language-plaintext highlighter-rouge">C-c C-d</code>) and a “Dune” menu for common operations:</p>

<table>
  <thead>
    <tr>
      <th>Keybinding</th>
      <th>Command</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">C-c C-d b</code></td>
      <td>Build</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">C-c C-d t</code></td>
      <td>Test</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">C-c C-d c</code></td>
      <td>Clean</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">C-c C-d p</code></td>
      <td>Promote</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">C-c C-d f</code></td>
      <td>Format</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">C-c C-d u</code></td>
      <td>Launch utop with project libraries</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">C-c C-d r</code></td>
      <td>Run an executable</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">C-c C-d d</code></td>
      <td>Run any Dune command</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">C-c C-d .</code></td>
      <td>Find the nearest <code class="language-plaintext highlighter-rouge">dune</code> file</td>
    </tr>
  </tbody>
</table>

<p>All commands run via Emacs’s <code class="language-plaintext highlighter-rouge">compile</code>, so you get error navigation, clickable
source locations, and the full <code class="language-plaintext highlighter-rouge">compilation-mode</code> interface for free. With a
prefix argument (<code class="language-plaintext highlighter-rouge">C-u</code>), build, test, and fmt run in <strong>watch mode</strong>
(<code class="language-plaintext highlighter-rouge">--watch</code>), automatically rebuilding when files change.</p>

<p>The <code class="language-plaintext highlighter-rouge">utop</code> command is special — it launches through <code class="language-plaintext highlighter-rouge">neocaml-repl</code>, so you get
the full REPL integration (send region, send definition, etc.) with your
project’s libraries preloaded.</p>

<p>This mode is completely independent from <code class="language-plaintext highlighter-rouge">neocaml-dune-mode</code> — it doesn’t care
which major mode you’re using. You can enable it in OCaml buffers like this:</p>

<div class="language-emacs-lisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre><span class="p">(</span><span class="nv">add-hook</span> <span class="ss">'neocaml-base-mode-hook</span> <span class="nf">#'</span><span class="nv">neocaml-dune-interaction-mode</span><span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="rough-edges">Rough Edges</h2>

<p>Both the Opam and Dune Tree-sitter grammars are relatively young and will need
some more work for optimal results. I’ve been filing issues and contributing
patches upstream to improve them — for instance, the Dune grammar currently
<a href="https://github.com/tmcgilchrist/tree-sitter-dune/issues/9">flattens field-value
pairs</a> in a way
that makes indentation less precise than it could be, and neither grammar
supports <a href="https://github.com/tmcgilchrist/tree-sitter-dune/issues/10">variable
interpolation</a>
(<code class="language-plaintext highlighter-rouge">%{...}</code>) yet. These are very solvable problems and I expect the grammars to
improve over time.</p>

<h2 id="whats-next">What’s Next?</h2>

<p>At this point I think I’m (finally!) out of ideas for new functionality. This
time I mean it! Neocaml now covers pretty much everything I ever wanted,
especially when paired with the awesome
<a href="https://github.com/tarides/ocaml-eglot">ocaml-eglot</a>.</p>

<p>Down the road there might be support for OCamllex (<code class="language-plaintext highlighter-rouge">.mll</code>) or Menhir (<code class="language-plaintext highlighter-rouge">.mly</code>)
files, but only if adding them doesn’t bring significant complexity — both are
mixed languages with embedded OCaml code, which makes them fundamentally harder
to support well than the simple Opam and Dune formats.</p>

<p>I hope OCaml programmers will find the new functionality useful. If you’re using neocaml,
I’d love to hear how it’s working for you — bug reports, feature requests, and
general feedback are all welcome on
<a href="https://github.com/bbatsov/neocaml">GitHub</a>. You can find the full list of
changes in the
<a href="https://github.com/bbatsov/neocaml/blob/main/CHANGELOG.md">changelog</a>.</p>

<p>As usual — update from <a href="https://melpa.org/#/neocaml">MELPA</a>, kick the tires,
and let me know what you think.</p>

<p>That’s all I have for you today! Keep hacking!</p>]]></content><author><name>Bozhidar Batsov</name></author><category term="OCaml" /><category term="Emacs" /><summary type="html"><![CDATA[When I released neocaml 0.1 last month, I thought I was more or less done with the (main) features for the foreseeable future. The original scope was deliberately small — a couple of Tree-sitter-powered OCaml major modes (for .ml and .mli), a REPL integration, and not much else. I was quite happy with how things turned out and figured the next steps would be mostly polish and bug fixes.]]></summary></entry><entry><title type="html">Learning OCaml: String Interpolation</title><link href="https://batsov.com/articles/2026/03/03/learning-ocaml-string-interpolation/" rel="alternate" type="text/html" title="Learning OCaml: String Interpolation" /><published>2026-03-03T12:00:00+02:00</published><updated>2026-03-03T12:00:00+02:00</updated><id>https://batsov.com/articles/2026/03/03/learning-ocaml-string-interpolation</id><content type="html" xml:base="https://batsov.com/articles/2026/03/03/learning-ocaml-string-interpolation/"><![CDATA[<p>Most programming languages I’ve used have some form of string
interpolation. Ruby has <code class="language-plaintext highlighter-rouge">"Hello, #{name}!"</code>, Python has f-strings,
JavaScript has template literals, even Haskell has a few popular
interpolation libraries. It’s one of those small conveniences you don’t
think about until it’s gone.</p>

<p>OCaml doesn’t have built-in string interpolation. And here’s the funny
thing – I didn’t even notice when I was first learning the language. Looking
back at my <a href="/articles/2022/08/29/ocaml-at-first-glance/">first impressions</a>
article, I complained about the comment syntax, the semicolons in lists, the
lack of list comprehensions, and a dozen other things – but never once about
string interpolation. I was happily concatenating strings with <code class="language-plaintext highlighter-rouge">^</code> and using
<code class="language-plaintext highlighter-rouge">Printf.sprintf</code> without giving it a second thought.</p>

<p>I only started thinking about this while working on my
<a href="/articles/2026/03/03/ppx-for-mere-mortals/">PPX article</a> and going
through the catalog of popular PPX libraries. That’s when I stumbled upon
<code class="language-plaintext highlighter-rouge">ppx_string</code> and thought “wait, why <em>doesn’t</em> OCaml have interpolation?”</p>

<!--more-->

<h2 id="why-ocaml-doesnt-have-string-interpolation">Why OCaml Doesn’t Have String Interpolation</h2>

<p>The short answer: OCaml has no way to generically convert a value to a
string. There’s no universal <code class="language-plaintext highlighter-rouge">to_string</code> method, no <code class="language-plaintext highlighter-rouge">Show</code> typeclass, no
runtime reflection that would let the language figure out how to stringify
an arbitrary expression inside a string literal.</p>

<p>In Ruby, every object responds to <code class="language-plaintext highlighter-rouge">.to_s</code>. In Python, everything has
<code class="language-plaintext highlighter-rouge">__str__</code>. These languages can interpolate anything because there’s always
a fallback conversion available at runtime. OCaml’s type information is
erased at compile time, so the compiler would need to know <em>at compile
time</em> which conversion function to call for each interpolated expression –
and the language has no mechanism for that.<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup></p>

<p>OCaml does have <code class="language-plaintext highlighter-rouge">Printf.sprintf</code>, which is actually quite nice and
type-safe:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre><span class="k">let</span> <span class="n">greeting</span> <span class="n">name</span> <span class="n">age</span> <span class="o">=</span>
  <span class="nn">Printf</span><span class="p">.</span><span class="n">sprintf</span> <span class="s2">"Hello, %s! You are %d years old."</span> <span class="n">name</span> <span class="n">age</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>The format string <code class="language-plaintext highlighter-rouge">"%s ... %d"</code> is statically checked by the compiler –
if you pass an <code class="language-plaintext highlighter-rouge">int</code> where <code class="language-plaintext highlighter-rouge">%s</code> expects a string, you get a compile-time
error, not a runtime crash. That’s genuinely better than what most
dynamically typed languages offer. But it’s not interpolation – the
values aren’t inline in the string, and for complex expressions it gets
unwieldy fast.</p>

<p>There’s also plain string concatenation with <code class="language-plaintext highlighter-rouge">^</code>:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre><span class="k">let</span> <span class="n">greeting</span> <span class="n">name</span> <span class="n">age</span> <span class="o">=</span>
  <span class="s2">"Hello, "</span> <span class="o">^</span> <span class="n">name</span> <span class="o">^</span> <span class="s2">"! You are "</span> <span class="o">^</span> <span class="n">string_of_int</span> <span class="n">age</span> <span class="o">^</span> <span class="s2">" years old."</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>This works, but it’s ugly and error-prone for anything beyond trivial cases.</p>

<h2 id="ppx_string-to-the-rescue">ppx_string to the Rescue</h2>

<p><a href="https://github.com/janestreet/ppx_string">ppx_string</a> is a Jane Street PPX
that adds string interpolation to OCaml at compile time. The basic usage is
straightforward:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre><span class="k">let</span> <span class="n">greeting</span> <span class="n">name</span> <span class="o">=</span>
  <span class="p">[</span><span class="o">%</span><span class="kt">string</span> <span class="s2">"Hello, %{name}!"</span><span class="p">]</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>For non-string types, you specify the module whose <code class="language-plaintext highlighter-rouge">to_string</code> function
should be used:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre><span class="k">let</span> <span class="n">status</span> <span class="n">name</span> <span class="n">age</span> <span class="n">score</span> <span class="o">=</span>
  <span class="p">[</span><span class="o">%</span><span class="kt">string</span> <span class="s2">"Player %{name}, age %{age#Int}, score %{score#Float}"</span><span class="p">]</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">#Int</code> suffix tells the PPX to call <code class="language-plaintext highlighter-rouge">Int.to_string</code> on <code class="language-plaintext highlighter-rouge">age</code>, and
<code class="language-plaintext highlighter-rouge">#Float</code> calls <code class="language-plaintext highlighter-rouge">Float.to_string</code> on <code class="language-plaintext highlighter-rouge">score</code>. Note that <code class="language-plaintext highlighter-rouge">Int.to_string</code>,
<code class="language-plaintext highlighter-rouge">Float.to_string</code>, etc. are conventions from Jane Street’s <code class="language-plaintext highlighter-rouge">Base</code>/<code class="language-plaintext highlighter-rouge">Core</code>
libraries – OCaml’s <code class="language-plaintext highlighter-rouge">Stdlib</code> uses <code class="language-plaintext highlighter-rouge">string_of_int</code>, <code class="language-plaintext highlighter-rouge">string_of_float</code> and
so on, which won’t work with the <code class="language-plaintext highlighter-rouge">#Module</code> syntax. This is another reason
<code class="language-plaintext highlighter-rouge">ppx_string</code> really only makes sense within the Jane Street ecosystem.</p>

<p>Any module that exposes a <code class="language-plaintext highlighter-rouge">to_string</code> function works here – including your
own:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
</pre></td><td class="rouge-code"><pre><span class="k">module</span> <span class="nc">Player</span> <span class="o">=</span> <span class="k">struct</span>
  <span class="k">type</span> <span class="n">t</span> <span class="o">=</span> <span class="p">{</span> <span class="n">name</span> <span class="o">:</span> <span class="kt">string</span><span class="p">;</span> <span class="n">score</span> <span class="o">:</span> <span class="kt">int</span> <span class="p">}</span>
  <span class="k">let</span> <span class="n">to_string</span> <span class="n">p</span> <span class="o">=</span> <span class="nn">Printf</span><span class="p">.</span><span class="n">sprintf</span> <span class="s2">"%s (%d)"</span> <span class="n">p</span><span class="o">.</span><span class="n">name</span> <span class="n">p</span><span class="o">.</span><span class="n">score</span>
<span class="k">end</span>

<span class="k">let</span> <span class="n">announce</span> <span class="n">player</span> <span class="o">=</span>
  <span class="p">[</span><span class="o">%</span><span class="kt">string</span> <span class="s2">"Next up: %{player#Player}"</span><span class="p">]</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>You can also use arbitrary expressions inside the interpolation braces:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre><span class="k">let</span> <span class="n">summary</span> <span class="n">items</span> <span class="o">=</span>
  <span class="p">[</span><span class="o">%</span><span class="kt">string</span> <span class="s2">"Found %{List.length items |&gt; Int.to_string} items"</span><span class="p">]</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Though at that point you might be better off with a <code class="language-plaintext highlighter-rouge">let</code> binding or
<code class="language-plaintext highlighter-rouge">sprintf</code> for readability.</p>

<h3 id="tips-and-limitations">Tips and Limitations</h3>

<p>A few practical things worth knowing:</p>

<ul>
  <li>You need the <code class="language-plaintext highlighter-rouge">pps</code> stanza in your dune file:
    <pre><code class="language-dune">(library
 (name mylib)
 (preprocess (pps ppx_string)))
</code></pre>
  </li>
  <li>String values interpolate directly, everything else needs a conversion
suffix. Unlike Ruby where <code class="language-plaintext highlighter-rouge">.to_s</code> is called implicitly, <code class="language-plaintext highlighter-rouge">ppx_string</code>
requires you to be explicit about non-string types. This is annoying at
first, but it’s consistent with OCaml’s philosophy of being explicit about
types.</li>
  <li>It’s a Jane Street library. If you’re already in the Jane Street
ecosystem (<code class="language-plaintext highlighter-rouge">Core</code>, <code class="language-plaintext highlighter-rouge">Base</code>, etc.), adding <code class="language-plaintext highlighter-rouge">ppx_string</code> is trivial. If you’re
not, pulling in a Jane Street dependency just for string interpolation might
feel heavy. In that case, <code class="language-plaintext highlighter-rouge">Printf.sprintf</code> is honestly fine.</li>
  <li>It doesn’t work with the <code class="language-plaintext highlighter-rouge">Format</code> module. If you’re building strings for
pretty-printing, you’ll still want <code class="language-plaintext highlighter-rouge">Format.fprintf</code> or <code class="language-plaintext highlighter-rouge">Format.asprintf</code>.
<code class="language-plaintext highlighter-rouge">ppx_string</code> is for building plain strings, not format strings.</li>
  <li>Nested interpolation doesn’t work – you can’t nest <code class="language-plaintext highlighter-rouge">%{...}</code> inside
another <code class="language-plaintext highlighter-rouge">%{...}</code>. Keep it simple.</li>
</ul>

<h2 id="do-you-actually-need-string-interpolation">Do You Actually Need String Interpolation?</h2>

<p>Honestly? Probably not as much as you think. I’ve been writing OCaml for a
while now without it, and it rarely bothers me. Here’s why:</p>

<ul>
  <li><strong><code class="language-plaintext highlighter-rouge">Printf.sprintf</code> is good.</strong> It’s type-safe, it’s concise enough for most
cases, and it’s available everywhere without extra dependencies.</li>
  <li><strong>Most string building in OCaml happens through <code class="language-plaintext highlighter-rouge">Format</code>.</strong> If you’re
writing pretty-printers (which you will be, thanks to <code class="language-plaintext highlighter-rouge">[@@deriving show]</code>),
you’re using <code class="language-plaintext highlighter-rouge">Format.fprintf</code>, not string concatenation or interpolation.</li>
  <li><strong>OCaml code tends to be more compute-heavy than string-heavy.</strong> Compared
to, say, a Rails app or a shell script, the typical OCaml program just
doesn’t build that many ad-hoc strings.</li>
</ul>

<p>That said, when you <em>do</em> need to build a lot of human-readable strings –
error messages, log output, CLI formatting – interpolation is genuinely
nicer than <code class="language-plaintext highlighter-rouge">sprintf</code>. If you’re in the Jane Street ecosystem, there’s no
reason not to use <code class="language-plaintext highlighter-rouge">ppx_string</code>.</p>

<h2 id="closing-thoughts">Closing Thoughts</h2>

<p>The lack of string interpolation in OCaml is one of those things that sounds
worse than it actually is. In practice, <code class="language-plaintext highlighter-rouge">Printf.sprintf</code> and <code class="language-plaintext highlighter-rouge">Format</code> cover
the vast majority of use cases, and the code you write with them is arguably
clearer about types than magical interpolation would be.</p>

<p>It’s also a nice example of OCaml’s general philosophy: keep the language
core small, provide solid primitives (<code class="language-plaintext highlighter-rouge">Printf</code>, <code class="language-plaintext highlighter-rouge">Format</code>), and let the PPX
ecosystem fill in the syntactic sugar for those who want it. The same pattern
plays out with <code class="language-plaintext highlighter-rouge">[@@deriving show]</code> for printing, <code class="language-plaintext highlighter-rouge">ppx_let</code> for monadic
syntax, and many other conveniences.</p>

<p>Will OCaml ever get built-in string interpolation? Maybe. There have been
discussions on the forums over the years, and the language did absorb binding
operators (<code class="language-plaintext highlighter-rouge">let*</code>, <code class="language-plaintext highlighter-rouge">let+</code>) from the PPX world. But I wouldn’t hold my breath
– and honestly, I’m not sure I’d even notice if it landed.</p>

<p>That’s all I have for you today. Keep hacking!</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1">
      <p>This is the same fundamental problem that makes <a href="/articles/2026/03/01/printing-data-in-ocaml/">printing data structures</a> harder than in dynamically typed languages. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;&#xfe0e;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Bozhidar Batsov</name></author><category term="OCaml" /><category term="Learning OCaml" /><summary type="html"><![CDATA[Most programming languages I’ve used have some form of string interpolation. Ruby has "Hello, #{name}!", Python has f-strings, JavaScript has template literals, even Haskell has a few popular interpolation libraries. It’s one of those small conveniences you don’t think about until it’s gone.]]></summary></entry><entry><title type="html">Learning OCaml: PPX for Mere Mortals</title><link href="https://batsov.com/articles/2026/03/03/ppx-for-mere-mortals/" rel="alternate" type="text/html" title="Learning OCaml: PPX for Mere Mortals" /><published>2026-03-03T10:00:00+02:00</published><updated>2026-03-03T10:00:00+02:00</updated><id>https://batsov.com/articles/2026/03/03/ppx-for-mere-mortals</id><content type="html" xml:base="https://batsov.com/articles/2026/03/03/ppx-for-mere-mortals/"><![CDATA[<p>When I started learning OCaml I kept running into code like this:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="rouge-code"><pre><span class="k">type</span> <span class="n">person</span> <span class="o">=</span> <span class="p">{</span>
  <span class="n">name</span> <span class="o">:</span> <span class="kt">string</span><span class="p">;</span>
  <span class="n">age</span>  <span class="o">:</span> <span class="kt">int</span><span class="p">;</span>
<span class="p">}</span> <span class="p">[</span><span class="o">@@</span><span class="n">deriving</span> <span class="n">show</span><span class="o">,</span> <span class="n">eq</span><span class="p">]</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>My first reaction was “what the hell is <code class="language-plaintext highlighter-rouge">[@@deriving show, eq]</code>?” Coming from
languages like Ruby and Clojure, where metaprogramming is either built into the
runtime (reflection) or baked into the language itself (macros),
OCaml’s approach felt alien. There’s no runtime reflection, no macro system in the
Lisp sense – just this mysterious <code class="language-plaintext highlighter-rouge">@@</code> syntax that somehow generates code at
compile time.</p>

<p>That mystery is <strong>PPX</strong> (PreProcessor eXtensions), and once you understand it,
a huge chunk of the OCaml ecosystem suddenly makes a lot more sense. This article
is my attempt to demystify PPX for people like me – developers who want to
<em>use</em> PPX effectively without necessarily becoming PPX authors themselves.</p>

<!--more-->

<h2 id="what-is-ppx-and-why-should-you-care">What Is PPX and Why Should You Care?</h2>

<p>OCaml is a statically typed language with no runtime reflection. That means you
can’t do things like “iterate over all fields of a record at runtime” or
“automatically serialize any type to JSON.” The type information simply isn’t
available at runtime – it’s erased during compilation. One of my biggest
frustrations as a newcomer was not being able to just print arbitrary data for
debugging – there’s no generic <code class="language-plaintext highlighter-rouge">print</code> or <code class="language-plaintext highlighter-rouge">inspect</code> that works on any type.
That frustration was probably my first real interaction with PPX.</p>

<p>PPX solves this by generating code at <strong>compile time</strong>. When the OCaml compiler
parses your source code, it builds an Abstract Syntax Tree (AST) – a tree data
structure that represents the syntactic structure of your program. PPX rewriters
are programs that receive this AST, transform it, and return a modified AST back
to the compiler. The compiler then continues as if you had written the generated
code by hand.</p>

<p>In practical terms, this means that when you write:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre><span class="k">type</span> <span class="n">color</span> <span class="o">=</span> <span class="nc">Red</span> <span class="o">|</span> <span class="nc">Green</span> <span class="o">|</span> <span class="nc">Blue</span>
<span class="p">[</span><span class="o">@@</span><span class="n">deriving</span> <span class="n">show</span><span class="p">]</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>The PPX rewriter generates something like this behind the scenes:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
</pre></td><td class="rouge-code"><pre><span class="k">type</span> <span class="n">color</span> <span class="o">=</span> <span class="nc">Red</span> <span class="o">|</span> <span class="nc">Green</span> <span class="o">|</span> <span class="nc">Blue</span>

<span class="k">let</span> <span class="n">pp_color</span> <span class="n">fmt</span> <span class="o">=</span> <span class="k">function</span>    <span class="c">(* uses OCaml's Format module for pretty-printing *)</span>
  <span class="o">|</span> <span class="nc">Red</span> <span class="o">-&gt;</span> <span class="nn">Format</span><span class="p">.</span><span class="n">fprintf</span> <span class="n">fmt</span> <span class="s2">"Red"</span>
  <span class="o">|</span> <span class="nc">Green</span> <span class="o">-&gt;</span> <span class="nn">Format</span><span class="p">.</span><span class="n">fprintf</span> <span class="n">fmt</span> <span class="s2">"Green"</span>
  <span class="o">|</span> <span class="nc">Blue</span> <span class="o">-&gt;</span> <span class="nn">Format</span><span class="p">.</span><span class="n">fprintf</span> <span class="n">fmt</span> <span class="s2">"Blue"</span>

<span class="k">let</span> <span class="n">show_color</span> <span class="n">x</span> <span class="o">=</span> <span class="nn">Format</span><span class="p">.</span><span class="n">asprintf</span> <span class="s2">"%a"</span> <span class="n">pp_color</span> <span class="n">x</span>  <span class="c">(* returns the formatted string *)</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>You get a pretty-printer for free, derived from the type definition. No
boilerplate, no manual work, and it stays in sync with your type automatically.</p>

<p>If you’ve used Rust’s <code class="language-plaintext highlighter-rouge">#[derive(Debug, PartialEq)]</code> or Haskell’s <code class="language-plaintext highlighter-rouge">deriving
(Show, Eq)</code>, the idea is very similar. The syntax is different, but the
motivation is identical – generating repetitive code from type definitions.</p>

<h2 id="why-isnt-this-built-into-the-language">Why Isn’t This Built into the Language?</h2>

<p>If you’re coming from Rust, you might wonder why OCaml doesn’t just have a
built-in macro system like <code class="language-plaintext highlighter-rouge">proc_macro</code>. It’s a fair question, and the answer
says a lot about OCaml’s design philosophy.</p>

<p>OCaml has always favored a <strong>small, stable language core</strong>. The compiler is
famously lean and fast, and the language team is conservative about adding
complexity to the specification. A full macro system baked into the compiler
would be a significant undertaking – it would need to be designed, specified,
maintained, and kept compatible across versions, forever.</p>

<p>Instead, OCaml took a more minimal approach: the compiler provides just two
things – <strong>extension points</strong> and <strong>attributes</strong> – as syntactic hooks in the
AST. Everything else lives in the ecosystem. The actual PPX rewriters are
ordinary OCaml programs that happen to transform ASTs. The <a href="https://github.com/ocaml-ppx/ppxlib">ppxlib</a>
framework that ties it all together is a regular library, not part of the
compiler.</p>

<p>This has some real advantages:</p>

<ul>
  <li><strong>The ecosystem can evolve independently.</strong> ppxlib can ship new features,
fix bugs, and improve APIs without waiting for a compiler release. Compare
this to Rust, where changes to the proc macro system require the full RFC
process and a compiler update.</li>
  <li><strong>Tooling stays simple.</strong> Because <code class="language-plaintext highlighter-rouge">[@@deriving show]</code> and <code class="language-plaintext highlighter-rouge">[%blob "file"]</code>
are valid OCaml syntax, every tool – editors, formatters, documentation
generators – can parse PPX-annotated code without knowing anything about
the specific PPX. The code is always syntactically valid OCaml, even before
preprocessing.</li>
  <li><strong>The compiler stays lean.</strong> No macro expander, no hygiene system, no
special compilation phases – just a hook that says “here, transform this
AST before I type-check it.”</li>
</ul>

<p>The trade-offs are real, though. Rust’s proc macros are more tightly
integrated – you get better error messages pointing at macro-generated code,
better IDE support for macro expansions, and the macro system is a documented,
stable part of the language. With PPX, you’re sometimes left staring at
cryptic type errors in generated code and reaching for <code class="language-plaintext highlighter-rouge">dune describe pp</code> to
figure out what went wrong.</p>

<p>That said, OCaml’s approach feels very OCaml – pragmatic, minimal, and
trusting the ecosystem to build what’s needed on top of a simple foundation.
And in practice, it works remarkably well.</p>

<h2 id="a-bit-of-history">A Bit of History</h2>

<p>PPX wasn’t OCaml’s first metaprogramming system. Before PPX, there was
<strong>Camlp4</strong> (and its fork <strong>Camlp5</strong>) – a powerful but complex preprocessor that
maintained its own parser, separate from the compiler’s parser. Camlp4 could
extend OCaml’s syntax in arbitrary ways, which sounds great in theory but was a
maintenance nightmare in practice. Every OCaml release risked breaking Camlp4,
and code using Camlp4 extensions often couldn’t be processed by standard tools
like editors and documentation generators.</p>

<p>OCaml 4.02 (2014) introduced <strong>extension points</strong> and <strong>attributes</strong> directly
into the language grammar – syntactic hooks specifically designed for
preprocessor extensions. This was a much simpler and more maintainable approach:
PPX rewriters use the compiler’s own AST, the syntax is valid OCaml (so tools
can still parse your code), and the whole thing is conceptually just “AST in,
AST out.”</p>

<p>Camlp4 was officially retired in 2019. Today, the PPX ecosystem is built on
<a href="https://github.com/ocaml-ppx/ppxlib">ppxlib</a>, a unified framework that
provides a stable API across OCaml versions and handles all the plumbing for PPX
authors.</p>

<h2 id="understanding-the-syntax">Understanding the Syntax</h2>

<p>Before diving into specific libraries, let’s decode the bracket soup. PPX uses two
syntactic mechanisms built into OCaml:</p>

<h3 id="extension-nodes">Extension Nodes</h3>

<p>Extension nodes are placeholders that a PPX rewriter <strong>must</strong> replace with
generated code (compilation fails if no PPX handles them):</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
</pre></td><td class="rouge-code"><pre><span class="c">(* Expression-level extension *)</span>
<span class="k">let</span> <span class="n">pos</span> <span class="o">=</span> <span class="p">[</span><span class="o">%</span><span class="n">here</span><span class="p">]</span>            <span class="c">(* replaced with current source position *)</span>
<span class="k">let</span> <span class="n">data</span> <span class="o">=</span> <span class="p">[</span><span class="o">%</span><span class="n">blob</span> <span class="s2">"icon.png"</span><span class="p">]</span> <span class="c">(* replaced with file contents as a string *)</span>

<span class="c">(* The "let%" infix syntax is sugar for expression extensions *)</span>
<span class="k">let</span><span class="o">%</span><span class="n">bind</span> <span class="n">x</span> <span class="o">=</span> <span class="n">fetch</span> <span class="bp">()</span> <span class="k">in</span>     <span class="c">(* equivalent to [%bind let x = fetch () in ...] *)</span>
<span class="n">process</span> <span class="n">x</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="attributes">Attributes</h3>

<p>Attributes attach metadata to existing code. Unlike extension nodes, the
compiler silently ignores attributes that no PPX handles:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
</pre></td><td class="rouge-code"><pre><span class="c">(* [@attr] -- attaches to the nearest expression/pattern/type *)</span>
<span class="n">x</span> <span class="o">+</span> <span class="n">y</span> <span class="p">[</span><span class="o">@</span><span class="n">warning</span> <span class="s2">"-8"</span><span class="p">]</span>

<span class="c">(* [@@attr] -- attaches to the enclosing declaration *)</span>
<span class="k">type</span> <span class="n">t</span> <span class="o">=</span> <span class="p">{</span> <span class="n">name</span> <span class="o">:</span> <span class="kt">string</span><span class="p">;</span> <span class="n">age</span> <span class="o">:</span> <span class="kt">int</span> <span class="p">}</span>
<span class="p">[</span><span class="o">@@</span><span class="n">deriving</span> <span class="n">show</span><span class="p">]</span>

<span class="c">(* [@@@attr] -- floating attribute at module level *)</span>
<span class="p">[</span><span class="o">@@@</span><span class="n">warning</span> <span class="s2">"-32"</span><span class="p">]</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>The one you’ll see most often is <code class="language-plaintext highlighter-rouge">[@@deriving ...]</code> on type declarations. The
distinction between <code class="language-plaintext highlighter-rouge">@</code>, <code class="language-plaintext highlighter-rouge">@@</code>, and <code class="language-plaintext highlighter-rouge">@@@</code> is about scope – one <code class="language-plaintext highlighter-rouge">@</code> for the
innermost node, two for the enclosing declaration, three for the whole
module-level.</p>

<p><strong>Tip:</strong> Don’t worry about memorizing all of this upfront. In practice, you’ll
mostly use <code class="language-plaintext highlighter-rouge">[@@deriving ...]</code> and occasionally <code class="language-plaintext highlighter-rouge">[%extension ...]</code> or <code class="language-plaintext highlighter-rouge">let%...</code> –
and the specific PPX library’s documentation will tell you exactly which syntax
to use.</p>

<h2 id="using-ppx-with-dune">Using PPX with Dune</h2>

<p>To use a PPX library in your project, you add it to the <code class="language-plaintext highlighter-rouge">preprocess</code> stanza in
your <code class="language-plaintext highlighter-rouge">dune</code> file:</p>

<pre><code class="language-dune">(library
 (name mylib)
 (libraries yojson)
 (preprocess (pps ppx_deriving.show ppx_deriving.eq ppx_deriving_yojson)))
</code></pre>

<p>That’s it. List all the PPX rewriters you need after <code class="language-plaintext highlighter-rouge">pps</code>, and Dune takes
care of the rest (it even combines them into a single binary for performance). For
<code class="language-plaintext highlighter-rouge">ppx_deriving</code> plugins specifically, you use dotted names like <code class="language-plaintext highlighter-rouge">ppx_deriving.show</code>.</p>

<h2 id="the-ppx-libraries-youll-actually-use">The PPX Libraries You’ll Actually Use</h2>

<p>Let’s look at the PPX libraries that cover probably 90% of real-world use cases.</p>

<h3 id="ppx_deriving--the-swiss-army-knife">ppx_deriving – The Swiss Army Knife</h3>

<p><a href="https://github.com/ocaml-ppx/ppx_deriving">ppx_deriving</a> is the community’s
general-purpose deriving framework. It comes with several built-in plugins:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
</pre></td><td class="rouge-code"><pre><span class="k">type</span> <span class="n">point</span> <span class="o">=</span> <span class="p">{</span> <span class="n">x</span> <span class="o">:</span> <span class="kt">float</span><span class="p">;</span> <span class="n">y</span> <span class="o">:</span> <span class="kt">float</span> <span class="p">}</span>
<span class="p">[</span><span class="o">@@</span><span class="n">deriving</span> <span class="n">show</span><span class="o">,</span> <span class="n">eq</span><span class="o">,</span> <span class="n">ord</span><span class="p">]</span>

<span class="c">(* Generates:
   val pp_point : Format.formatter -&gt; point -&gt; unit
   val show_point : point -&gt; string
   val equal_point : point -&gt; point -&gt; bool
   val compare_point : point -&gt; point -&gt; int *)</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">show</code> is the one you’ll reach for first – it’s essentially the answer to
“how do I just print this thing?” that every OCaml newcomer asks sooner or
later. The most commonly used plugins:</p>

<table>
  <thead>
    <tr>
      <th>Plugin</th>
      <th>What it generates</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">show</code></td>
      <td><code class="language-plaintext highlighter-rouge">pp_t</code> and <code class="language-plaintext highlighter-rouge">show_t</code> – pretty-printing</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">eq</code></td>
      <td><code class="language-plaintext highlighter-rouge">equal_t</code> – structural equality</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">ord</code></td>
      <td><code class="language-plaintext highlighter-rouge">compare_t</code> – total ordering</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">make</code></td>
      <td>Smart constructors with optional/default args</td>
    </tr>
  </tbody>
</table>

<p>A neat convention: if your type is named <code class="language-plaintext highlighter-rouge">t</code> (as is idiomatic in OCaml), the
generated functions drop the type name suffix – you get <code class="language-plaintext highlighter-rouge">pp</code>, <code class="language-plaintext highlighter-rouge">show</code>, <code class="language-plaintext highlighter-rouge">equal</code>,
<code class="language-plaintext highlighter-rouge">compare</code> instead of <code class="language-plaintext highlighter-rouge">pp_t</code>, <code class="language-plaintext highlighter-rouge">show_t</code>, etc.</p>

<p>You can also customize behavior per field with attributes:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="rouge-code"><pre><span class="k">type</span> <span class="n">file</span> <span class="o">=</span> <span class="p">{</span>
  <span class="n">name</span> <span class="o">:</span> <span class="kt">string</span> <span class="p">[</span><span class="o">@</span><span class="n">equal</span> <span class="k">fun</span> <span class="n">a</span> <span class="n">b</span> <span class="o">-&gt;</span>
    <span class="nn">String</span><span class="p">.</span><span class="n">lowercase_ascii</span> <span class="n">a</span> <span class="o">=</span> <span class="nn">String</span><span class="p">.</span><span class="n">lowercase_ascii</span> <span class="n">b</span><span class="p">];</span>
  <span class="n">perm</span> <span class="o">:</span> <span class="kt">int</span>    <span class="p">[</span><span class="o">@</span><span class="n">compare</span> <span class="k">fun</span> <span class="n">a</span> <span class="n">b</span> <span class="o">-&gt;</span> <span class="n">compare</span> <span class="n">b</span> <span class="n">a</span><span class="p">]</span>  <span class="c">(* reverse order *)</span>
<span class="p">}</span> <span class="p">[</span><span class="o">@@</span><span class="n">deriving</span> <span class="n">eq</span><span class="o">,</span> <span class="n">ord</span><span class="p">]</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>And you can derive for anonymous types inline:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="rouge-code"><pre><span class="c">(* Sort a list of pairs by their second element *)</span>
<span class="k">let</span> <span class="n">sorted</span> <span class="o">=</span> <span class="nn">List</span><span class="p">.</span><span class="n">sort</span> <span class="p">[</span><span class="o">%</span><span class="n">derive</span><span class="o">.</span><span class="n">ord</span><span class="o">:</span> <span class="kt">int</span> <span class="o">*</span> <span class="kt">int</span><span class="p">]</span> <span class="n">pairs</span>

<span class="c">(* Print a list of pairs *)</span>
<span class="k">let</span> <span class="bp">()</span> <span class="o">=</span> <span class="n">print_endline</span> <span class="p">([</span><span class="o">%</span><span class="n">derive</span><span class="o">.</span><span class="n">show</span><span class="o">:</span> <span class="p">(</span><span class="kt">string</span> <span class="o">*</span> <span class="kt">int</span><span class="p">)</span> <span class="kt">list</span><span class="p">]</span> <span class="n">data</span><span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="ppx_deriving_yojson--json-serialization">ppx_deriving_yojson – JSON Serialization</h3>

<p><a href="https://github.com/ocaml-ppx/ppx_deriving_yojson">ppx_deriving_yojson</a>
generates JSON serialization and deserialization functions using the
<a href="https://github.com/ocaml-community/yojson">Yojson</a> library:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
</pre></td><td class="rouge-code"><pre><span class="k">type</span> <span class="n">person</span> <span class="o">=</span> <span class="p">{</span>
  <span class="n">name</span> <span class="o">:</span> <span class="kt">string</span><span class="p">;</span>
  <span class="n">age</span>  <span class="o">:</span> <span class="kt">int</span><span class="p">;</span>
  <span class="n">email</span> <span class="o">:</span> <span class="kt">string</span> <span class="n">option</span><span class="p">;</span>
<span class="p">}</span> <span class="p">[</span><span class="o">@@</span><span class="n">deriving</span> <span class="n">yojson</span><span class="p">]</span>

<span class="c">(* Generates:
   val person_to_yojson : person -&gt; Yojson.Safe.t
   val person_of_yojson : Yojson.Safe.t -&gt; (person, string) result *)</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="rouge-code"><pre><span class="k">let</span> <span class="n">alice</span> <span class="o">=</span> <span class="p">{</span> <span class="n">name</span> <span class="o">=</span> <span class="s2">"Alice"</span><span class="p">;</span> <span class="n">age</span> <span class="o">=</span> <span class="mi">30</span><span class="p">;</span> <span class="n">email</span> <span class="o">=</span> <span class="nc">Some</span> <span class="s2">"alice@example.com"</span> <span class="p">}</span>

<span class="k">let</span> <span class="n">json</span> <span class="o">=</span> <span class="n">person_to_yojson</span> <span class="n">alice</span> <span class="o">|&gt;</span> <span class="nn">Yojson</span><span class="p">.</span><span class="nn">Safe</span><span class="p">.</span><span class="n">to_string</span>
<span class="c">(* {"name":"Alice","age":30,"email":"alice@example.com"} *)</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>You can use <code class="language-plaintext highlighter-rouge">[@@deriving to_yojson]</code> or <code class="language-plaintext highlighter-rouge">[@@deriving of_yojson]</code> if you only
need one direction.</p>

<p>This is incredibly useful in practice – writing JSON
serializers by hand for complex types is tedious and error-prone.</p>

<h3 id="ppx_sexp_conv--s-expression-serialization">ppx_sexp_conv – S-expression Serialization</h3>

<p>If you’re using Jane Street’s <a href="https://github.com/janestreet/core">Core</a>
library, you’ll encounter S-expression serialization everywhere.
(<strong>Tip:</strong> Jane Street bundles most of their PPXs into a single
<a href="https://github.com/janestreet/ppx_jane">ppx_jane</a> package, so you can add
just <code class="language-plaintext highlighter-rouge">ppx_jane</code> to your <code class="language-plaintext highlighter-rouge">pps</code> instead of listing each one individually.)
<a href="https://github.com/janestreet/ppx_sexp_conv">ppx_sexp_conv</a> generates
converters between OCaml types and S-expressions:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
</pre></td><td class="rouge-code"><pre><span class="k">type</span> <span class="n">config</span> <span class="o">=</span> <span class="p">{</span>
  <span class="n">host</span> <span class="o">:</span> <span class="kt">string</span><span class="p">;</span>
  <span class="n">port</span> <span class="o">:</span> <span class="kt">int</span>    <span class="p">[</span><span class="o">@</span><span class="n">default</span> <span class="mi">8080</span><span class="p">];</span>
  <span class="n">debug</span> <span class="o">:</span> <span class="kt">bool</span>  <span class="p">[</span><span class="o">@</span><span class="n">sexp</span><span class="o">.</span><span class="kt">bool</span><span class="p">];</span>
<span class="p">}</span> <span class="p">[</span><span class="o">@@</span><span class="n">deriving</span> <span class="n">sexp</span><span class="p">]</span>

<span class="c">(* Generates:
   val sexp_of_config : config -&gt; Sexp.t
   val config_of_sexp : Sexp.t -&gt; config *)</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>The attributes here are quite handy – <code class="language-plaintext highlighter-rouge">[@default 8080]</code> provides a default
value during deserialization, and <code class="language-plaintext highlighter-rouge">[@sexp.bool]</code> means the field is represented
as a present/absent atom rather than <code class="language-plaintext highlighter-rouge">(debug true)</code>.</p>

<h3 id="ppx_fields_conv-and-ppx_variants_conv--accessor-generation">ppx_fields_conv and ppx_variants_conv – Accessor Generation</h3>

<p>Two more Jane Street PPXs that you’ll see a lot in Core-based codebases.
<a href="https://github.com/janestreet/ppx_fields_conv">ppx_fields_conv</a> generates
first-class accessors and iterators for record fields:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
</pre></td><td class="rouge-code"><pre><span class="k">type</span> <span class="n">person</span> <span class="o">=</span> <span class="p">{</span>
  <span class="n">name</span> <span class="o">:</span> <span class="kt">string</span><span class="p">;</span>
  <span class="n">age</span>  <span class="o">:</span> <span class="kt">int</span><span class="p">;</span>
<span class="p">}</span> <span class="p">[</span><span class="o">@@</span><span class="n">deriving</span> <span class="n">fields</span><span class="p">]</span>

<span class="c">(* Generates field accessors, a fold over all fields, iter, map, etc. *)</span>
<span class="k">let</span> <span class="bp">()</span> <span class="o">=</span> <span class="nn">Fields</span><span class="p">.</span><span class="n">iter</span>
  <span class="o">~</span><span class="n">name</span><span class="o">:</span><span class="p">(</span><span class="k">fun</span> <span class="n">name</span> <span class="o">-&gt;</span> <span class="nn">Printf</span><span class="p">.</span><span class="n">printf</span> <span class="s2">"name: %s</span><span class="se">\n</span><span class="s2">"</span> <span class="n">name</span><span class="p">)</span>
  <span class="o">~</span><span class="n">age</span><span class="o">:</span><span class="p">(</span><span class="k">fun</span> <span class="n">age</span> <span class="o">-&gt;</span> <span class="nn">Printf</span><span class="p">.</span><span class="n">printf</span> <span class="s2">"age: %d</span><span class="se">\n</span><span class="s2">"</span> <span class="n">age</span><span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p><a href="https://github.com/janestreet/ppx_variants_conv">ppx_variants_conv</a> does
something similar for variant types – generating constructors as functions,
fold/iter over all variants, and more.</p>

<h3 id="ppx_inline_test-and-ppx_expect--testing">ppx_inline_test and ppx_expect – Testing</h3>

<p>These Jane Street PPXs let you write tests directly in your source files:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
</pre></td><td class="rouge-code"><pre><span class="c">(* Simple boolean test *)</span>
<span class="k">let</span><span class="o">%</span><span class="n">test</span> <span class="s2">"addition works"</span> <span class="o">=</span> <span class="mi">1</span> <span class="o">+</span> <span class="mi">1</span> <span class="o">=</span> <span class="mi">2</span>

<span class="c">(* Test with side effects *)</span>
<span class="k">let</span><span class="o">%</span><span class="n">test_unit</span> <span class="s2">"list operations"</span> <span class="o">=</span>
  <span class="k">let</span> <span class="n">l</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">;</span> <span class="mi">2</span><span class="p">;</span> <span class="mi">3</span><span class="p">]</span> <span class="k">in</span>
  <span class="k">assert</span> <span class="p">(</span><span class="nn">List</span><span class="p">.</span><span class="n">length</span> <span class="n">l</span> <span class="o">=</span> <span class="mi">3</span><span class="p">);</span>
  <span class="k">assert</span> <span class="p">(</span><span class="nn">List</span><span class="p">.</span><span class="n">hd</span> <span class="n">l</span> <span class="o">=</span> <span class="mi">1</span><span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p><a href="https://github.com/janestreet/ppx_expect">ppx_expect</a> is particularly nice –
it captures printed output and compares it against expected output:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre><span class="k">let</span><span class="o">%</span><span class="n">expect_test</span> <span class="s2">"printing"</span> <span class="o">=</span>
  <span class="nn">List</span><span class="p">.</span><span class="n">iter</span> <span class="p">(</span><span class="k">fun</span> <span class="n">x</span> <span class="o">-&gt;</span> <span class="nn">Printf</span><span class="p">.</span><span class="n">printf</span> <span class="s2">"%d "</span> <span class="n">x</span><span class="p">)</span> <span class="p">[</span><span class="mi">1</span><span class="p">;</span> <span class="mi">2</span><span class="p">;</span> <span class="mi">3</span><span class="p">];</span>
  <span class="p">[</span><span class="o">%</span><span class="n">expect</span> <span class="p">{</span><span class="o">|</span> <span class="mi">1</span> <span class="mi">2</span> <span class="mi">3</span> <span class="o">|</span><span class="p">}]</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>If the output doesn’t match, the test fails and you can run <code class="language-plaintext highlighter-rouge">dune promote</code> to
automatically update the expected output in your source file. It’s a very
productive workflow for testing functions that produce output.</p>

<h3 id="ppx_let--monadic-syntax">ppx_let – Monadic Syntax</h3>

<p><a href="https://github.com/janestreet/ppx_let">ppx_let</a> provides syntactic sugar for
working with monads and other “container” types:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
</pre></td><td class="rouge-code"><pre><span class="c">(* Without ppx_let -- callback nesting hell *)</span>
<span class="k">let</span> <span class="n">result</span> <span class="o">=</span>
  <span class="n">bind</span> <span class="p">(</span><span class="n">fetch_user</span> <span class="n">id</span><span class="p">)</span> <span class="p">(</span><span class="k">fun</span> <span class="n">user</span> <span class="o">-&gt;</span>
    <span class="n">bind</span> <span class="p">(</span><span class="n">fetch_posts</span> <span class="n">user</span><span class="p">)</span> <span class="p">(</span><span class="k">fun</span> <span class="n">posts</span> <span class="o">-&gt;</span>
      <span class="n">return</span> <span class="p">(</span><span class="n">user</span><span class="o">,</span> <span class="n">posts</span><span class="p">)))</span>

<span class="c">(* With ppx_let -- reads like sequential code *)</span>
<span class="k">let</span> <span class="n">result</span> <span class="o">=</span>
  <span class="k">let</span><span class="o">%</span><span class="n">bind</span> <span class="n">user</span> <span class="o">=</span> <span class="n">fetch_user</span> <span class="n">id</span> <span class="k">in</span>
  <span class="k">let</span><span class="o">%</span><span class="n">bind</span> <span class="n">posts</span> <span class="o">=</span> <span class="n">fetch_posts</span> <span class="n">user</span> <span class="k">in</span>
  <span class="n">return</span> <span class="p">(</span><span class="n">user</span><span class="o">,</span> <span class="n">posts</span><span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>How does <code class="language-plaintext highlighter-rouge">ppx_let</code> know which <code class="language-plaintext highlighter-rouge">bind</code> to call? It looks for a <code class="language-plaintext highlighter-rouge">Let_syntax</code>
module in scope that provides the underlying <code class="language-plaintext highlighter-rouge">bind</code> and <code class="language-plaintext highlighter-rouge">map</code> functions. In
practice, you’ll typically open a module that defines <code class="language-plaintext highlighter-rouge">Let_syntax</code> before
using <code class="language-plaintext highlighter-rouge">let%bind</code>:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre><span class="k">open</span> <span class="nn">Lwt</span><span class="p">.</span><span class="nc">Syntax</span>  <span class="c">(* or Deferred.Let_syntax, etc. *)</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p><strong>Note:</strong> Since OCaml 4.08, the language has built-in
<a href="https://v2.ocaml.org/manual/bindingops.html">binding operators</a>
(<code class="language-plaintext highlighter-rouge">let*</code>, <code class="language-plaintext highlighter-rouge">and*</code>, <code class="language-plaintext highlighter-rouge">let+</code>, <code class="language-plaintext highlighter-rouge">and+</code>) that cover the basic use cases of <code class="language-plaintext highlighter-rouge">ppx_let</code>
without needing a preprocessor. If you’re not using Jane Street’s ecosystem,
binding operators are probably the simpler choice. <code class="language-plaintext highlighter-rouge">ppx_let</code> still offers extra
features like <code class="language-plaintext highlighter-rouge">match%bind</code>, <code class="language-plaintext highlighter-rouge">if%bind</code>, and optimized <code class="language-plaintext highlighter-rouge">let%mapn</code> though.</p>

<h3 id="ppx_blob--embedding-files">ppx_blob – Embedding Files</h3>

<p><a href="https://github.com/johnwhitington/ppx_blob">ppx_blob</a> is beautifully simple –
it embeds a file’s contents as a string at compile time:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre><span class="k">let</span> <span class="n">icon_data</span> <span class="o">=</span> <span class="p">[</span><span class="o">%</span><span class="n">blob</span> <span class="s2">"assets/icon.png"</span><span class="p">]</span>
<span class="k">let</span> <span class="n">shader_source</span> <span class="o">=</span> <span class="p">[</span><span class="o">%</span><span class="n">blob</span> <span class="s2">"shaders/vertex.glsl"</span><span class="p">]</span>
<span class="k">let</span> <span class="n">default_config</span> <span class="o">=</span> <span class="p">[</span><span class="o">%</span><span class="n">blob</span> <span class="s2">"config/defaults.json"</span><span class="p">]</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>No more worrying about file paths at runtime or packaging data files with your
binary. The file contents become part of your compiled program.</p>

<h3 id="ppx_string--string-interpolation">ppx_string – String Interpolation</h3>

<p>One thing that’s always bugged me about OCaml is the lack of string
interpolation. <a href="https://github.com/janestreet/ppx_string">ppx_string</a> fills that
gap:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre><span class="k">let</span> <span class="n">greeting</span> <span class="n">name</span> <span class="n">age</span> <span class="o">=</span>
  <span class="p">[</span><span class="o">%</span><span class="kt">string</span> <span class="s2">"Hello, %{name}! You are %{age#Int} years old."</span><span class="p">]</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">#Int</code> suffix tells the PPX to convert the value using <code class="language-plaintext highlighter-rouge">Int.to_string</code>. You
can use any module that provides a <code class="language-plaintext highlighter-rouge">to_string</code> function.</p>

<h2 id="writing-your-own-ppx-a-gentle-introduction">Writing Your Own PPX (A Gentle Introduction)</h2>

<p>Most OCaml developers will never <em>need</em> to write a PPX, but understanding the
basics helps demystify the whole system. Let’s build a very simple one.</p>

<p>Say we want an extension <code class="language-plaintext highlighter-rouge">[%upcase ...]</code> that converts a string literal to
uppercase at compile time. Here’s the complete implementation using
<a href="https://github.com/ocaml-ppx/ppxlib">ppxlib</a>:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
</pre></td><td class="rouge-code"><pre><span class="c">(* ppx_upcase.ml *)</span>
<span class="k">open</span> <span class="nc">Ppxlib</span>

<span class="k">let</span> <span class="n">extension</span> <span class="o">=</span>
  <span class="nn">Extension</span><span class="p">.</span><span class="nn">V3</span><span class="p">.</span><span class="n">declare</span>
    <span class="s2">"upcase"</span>
    <span class="nn">Extension</span><span class="p">.</span><span class="nn">Context</span><span class="p">.</span><span class="n">expression</span>
    <span class="nn">Ast_pattern</span><span class="p">.(</span><span class="n">single_expr_payload</span> <span class="p">(</span><span class="n">estring</span> <span class="n">__</span><span class="p">))</span>
    <span class="p">(</span><span class="k">fun</span> <span class="o">~</span><span class="n">ctxt</span> <span class="n">expr</span> <span class="o">-&gt;</span>
      <span class="k">let</span> <span class="n">loc</span> <span class="o">=</span> <span class="nn">Expansion_context</span><span class="p">.</span><span class="nn">Extension</span><span class="p">.</span><span class="n">extension_point_loc</span> <span class="n">ctxt</span> <span class="k">in</span>
      <span class="k">let</span> <span class="n">uppercased</span> <span class="o">=</span> <span class="nn">String</span><span class="p">.</span><span class="n">uppercase_ascii</span> <span class="n">expr</span> <span class="k">in</span>
      <span class="nn">Ast_builder</span><span class="p">.</span><span class="nn">Default</span><span class="p">.</span><span class="n">estring</span> <span class="o">~</span><span class="n">loc</span> <span class="n">uppercased</span><span class="p">)</span>

<span class="k">let</span> <span class="n">rule</span> <span class="o">=</span> <span class="nn">Context_free</span><span class="p">.</span><span class="nn">Rule</span><span class="p">.</span><span class="n">extension</span> <span class="n">extension</span>
<span class="k">let</span> <span class="bp">()</span> <span class="o">=</span> <span class="nn">Driver</span><span class="p">.</span><span class="n">register_transformation</span> <span class="o">~</span><span class="n">rules</span><span class="o">:</span><span class="p">[</span><span class="n">rule</span><span class="p">]</span> <span class="s2">"ppx_upcase"</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>The dune file:</p>

<pre><code class="language-dune">(library
 (name ppx_upcase)
 (kind ppx_rewriter)
 (libraries ppxlib))
</code></pre>

<p>And usage:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre><span class="k">let</span> <span class="n">greeting</span> <span class="o">=</span> <span class="p">[</span><span class="o">%</span><span class="n">upcase</span> <span class="s2">"hello world"</span><span class="p">]</span>
<span class="c">(* After preprocessing: let greeting = "HELLO WORLD" *)</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>The key pieces are:</p>

<ol>
  <li><strong><code class="language-plaintext highlighter-rouge">Extension.V3.declare</code></strong> – registers an extension with a name, the context where it
can appear (expressions, patterns, types, etc.), the expected payload pattern,
and an expansion function.</li>
  <li><strong><code class="language-plaintext highlighter-rouge">Ast_pattern</code></strong> – a pattern-matching DSL for destructuring AST nodes. Here
<code class="language-plaintext highlighter-rouge">estring __</code> matches a string literal and captures its value.</li>
  <li><strong><code class="language-plaintext highlighter-rouge">Ast_builder.Default</code></strong> – helpers for constructing AST nodes. <code class="language-plaintext highlighter-rouge">estring</code>
builds a string literal expression.</li>
  <li><strong><code class="language-plaintext highlighter-rouge">Driver.register_transformation</code></strong> – registers the rule with ppxlib’s
driver.</li>
</ol>

<p>For more complex PPXs (especially derivers), you’ll also want to use
<strong>Metaquot</strong> (<code class="language-plaintext highlighter-rouge">ppxlib.metaquot</code>), which lets you write AST-constructing code
using actual OCaml syntax instead of manual AST builder calls:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
</pre></td><td class="rouge-code"><pre><span class="c">(* Without metaquot -- verbose *)</span>
<span class="nn">Ast_builder</span><span class="p">.</span><span class="nn">Default</span><span class="p">.</span><span class="n">pexp_apply</span> <span class="o">~</span><span class="n">loc</span>
  <span class="p">(</span><span class="nn">Ast_builder</span><span class="p">.</span><span class="nn">Default</span><span class="p">.</span><span class="n">evar</span> <span class="o">~</span><span class="n">loc</span> <span class="s2">"string_of_int"</span><span class="p">)</span>
  <span class="p">[(</span><span class="nc">Nolabel</span><span class="o">,</span> <span class="n">expr</span><span class="p">)]</span>

<span class="c">(* With metaquot -- just write OCaml *)</span>
<span class="p">[</span><span class="o">%</span><span class="n">expr</span> <span class="n">string_of_int</span> <span class="p">[</span><span class="o">%</span><span class="n">e</span> <span class="n">expr</span><span class="p">]]</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>The ppxlib documentation has excellent
<a href="https://ocaml-ppx.github.io/ppxlib/ppxlib/quick_intro.html">tutorials</a> if you
want to go deeper.</p>

<h2 id="debugging-ppx-generated-code">Debugging PPX-generated Code</h2>

<p>One practical tip: when something goes wrong with PPX-generated code and you’re
staring at a confusing type error, you can inspect what the PPX actually
generated:</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
</pre></td><td class="rouge-code"><pre><span class="c"># Show the preprocessed output for a file (dune 3.x+)</span>
dune describe pp src/myfile.ml

<span class="c"># Alternatively, build the .pp.ml target and inspect it directly</span>
dune build src/.mylib.pp/myfile.pp.ml
<span class="nb">cat </span>_build/default/src/.mylib.pp/myfile.pp.ml

<span class="c"># Or use ocaml-print-intf to see the generated interface</span>
dune <span class="nb">exec</span> <span class="nt">--</span> ocaml-print-intf src/myfile.ml
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Seeing the expanded code often makes the error immediately obvious.</p>

<h2 id="the-ppx-ecosystem-today">The PPX Ecosystem Today</h2>

<p>Most of the introductory PPX content out there was written around 2018-2019,
so it’s worth noting how things have evolved since then.</p>

<p>The big story has been <strong>ppxlib’s consolidation of the ecosystem</strong>. Back in
2019, some PPX rewriters still used the older <code class="language-plaintext highlighter-rouge">ocaml-migrate-parsetree</code> (OMP)
library, creating fragmentation. By 2021, <a href="https://discuss.ocaml.org/t/an-update-on-the-state-of-the-ppx-ecosystem-and-ppxlib-s-transition/8200">nearly all PPXs had migrated to
ppxlib</a>,
effectively ending the split. Today ppxlib is <em>the</em> way to write PPX rewriters
– there’s no real alternative to consider.</p>

<p>The transition hasn’t always been smooth, though. In 2025, ppxlib 0.36.0
bumped its internal AST to match OCaml 5.2, which changed how functions are
represented in the parse tree. This <a href="https://discuss.ocaml.org/t/breaking-ppx-api-changes-and-the-package-ecosystem/16837">broke many downstream
PPXs</a>
and temporarily split the opam universe between packages that worked with the
new version and those that didn’t. The community worked through it with
proactive patching, but it highlighted an ongoing tension in the PPX world:
ppxlib shields you from most compiler changes, but major AST overhauls still
ripple through the ecosystem.</p>

<p>On the API side, ppxlib is gradually <a href="https://ocaml.org/changelog/2025-10-01-ppxlib-0362">deprecating its copy of
<code class="language-plaintext highlighter-rouge">Ast_helper</code></a> in favor of
<code class="language-plaintext highlighter-rouge">Ast_builder</code>, with plans to remove <code class="language-plaintext highlighter-rouge">Ast_helper</code> entirely in a future 1.0.0
release. If you’re writing a new PPX today, use <code class="language-plaintext highlighter-rouge">Ast_builder</code> exclusively.</p>

<p>Meanwhile, OCaml 4.08’s built-in binding operators (<code class="language-plaintext highlighter-rouge">let*</code>, <code class="language-plaintext highlighter-rouge">let+</code>, etc.) have
reduced the need for <code class="language-plaintext highlighter-rouge">ppx_let</code> in projects that don’t use Jane Street’s
ecosystem. It’s a nice example of the language absorbing a pattern that PPX
pioneered. Perhaps one day we’ll see more of this (e.g. native string interpolation).</p>

<h2 id="further-reading">Further Reading</h2>

<p>This article covers a lot of ground, but the PPX topic is pretty deep and complex,
so depending on how far you want to go you might want to read more on it.
Here are some of the best resources I’ve found on PPX:</p>

<ul>
  <li><a href="https://ocaml.org/docs/metaprogramming">Preprocessors and PPXs</a> – the
official OCaml documentation on metaprogramming. A solid reference, though
it assumes some comfort with the compiler internals.</li>
  <li><a href="https://tarides.com/blog/2019-05-09-an-introduction-to-ocaml-ppx-ecosystem/">An Introduction to OCaml PPX
Ecosystem</a>
– Nathan Rebours’ 2019 deep dive for Tarides. This is the most thorough
tutorial on <em>writing</em> PPX rewriters I’ve seen. Some API details have changed
since 2019 (notably the <code class="language-plaintext highlighter-rouge">Ast_helper</code> → <code class="language-plaintext highlighter-rouge">Ast_builder</code> shift), but the
concepts and approach are still excellent.</li>
  <li><a href="https://ocaml-ppx.github.io/ppxlib/ppxlib/quick_intro.html">ppxlib Quick
Introduction</a> –
ppxlib’s own getting-started guide. The best place to begin if you want to
write your own PPX.</li>
  <li><a href="http://ocamlverse.net/content/ppx.html">A Guide to PreProcessor
eXtensions</a> – OCamlverse’s
reference page with a comprehensive list of available PPX libraries.</li>
  <li><a href="https://whitequark.org/blog/2014/04/16/a-guide-to-extension-points-in-ocaml/">A Guide to Extension Points in
OCaml</a>
– Whitequark’s original 2014 guide that introduced many developers to PPX.
Historically interesting as a snapshot of the early PPX days.</li>
</ul>

<p>I was amused to see whitequark’s name pop up while I was doing research for
this article – we collaborated quite a bit back in the day on her Ruby
<a href="https://github.com/whitequark/parser">parser</a> project, which was instrumental
to <a href="https://github.com/rubocop/rubocop">RuboCop</a>. Seems you can find (former)
Rubyists in pretty much every language community.</p>

<h2 id="epilogue">Epilogue</h2>

<p>This article turned out to be a beast! I’ve wanted to write something on the subject
for quite a while now, but I’ve kept postponing it because I was too lazy to do
all the necessary research. I’ll feel quite relieved to put it behind me!</p>

<p>PPX might look intimidating at first – all those brackets and <code class="language-plaintext highlighter-rouge">@</code> symbols can
feel like line noise. But the core idea is simple: PPX generates boilerplate
code from your type definitions at compile time. You annotate your types with
what you want (<code class="language-plaintext highlighter-rouge">show</code>, <code class="language-plaintext highlighter-rouge">eq</code>, <code class="language-plaintext highlighter-rouge">yojson</code>, <code class="language-plaintext highlighter-rouge">sexp</code>, etc.), and the PPX rewriter
produces the code you’d otherwise have to write by hand.</p>

<p>For day-to-day OCaml programming, you really only need to know:</p>

<ol>
  <li><code class="language-plaintext highlighter-rouge">[@@deriving ...]</code> on type declarations to generate useful functions</li>
  <li>How to add PPX libraries to your dune file with <code class="language-plaintext highlighter-rouge">(pps ...)</code></li>
  <li>Which PPX libraries exist for common tasks (serialization, testing,
pretty-printing)</li>
</ol>

<p>The “writing your own PPX” part is there for when you need it, but honestly most
OCaml developers get by just fine using the existing ecosystem.</p>

<p>That’s all I have for you today. Keep hacking!</p>]]></content><author><name>Bozhidar Batsov</name></author><category term="OCaml" /><category term="Learning OCaml" /><category term="Meta-programming" /><summary type="html"><![CDATA[When I started learning OCaml I kept running into code like this:]]></summary></entry><entry><title type="html">Learning OCaml: Printing Data Structures</title><link href="https://batsov.com/articles/2026/03/01/printing-data-in-ocaml/" rel="alternate" type="text/html" title="Learning OCaml: Printing Data Structures" /><published>2026-03-01T18:00:00+02:00</published><updated>2026-03-01T18:00:00+02:00</updated><id>https://batsov.com/articles/2026/03/01/printing-data-in-ocaml</id><content type="html" xml:base="https://batsov.com/articles/2026/03/01/printing-data-in-ocaml/"><![CDATA[<p>If there’s one thing that frustrated me early on in my OCaml journey, it was
printing stuff. In Ruby I can <code class="language-plaintext highlighter-rouge">p</code> anything and get a useful representation.
In Clojure, <code class="language-plaintext highlighter-rouge">prn</code> just works on every data structure. In OCaml? There’s no
generic <code class="language-plaintext highlighter-rouge">print</code> that works on any type – the type information is erased at
runtime, so the language simply doesn’t know how to stringify an arbitrary
value.</p>

<p>This means you need to be explicit about how to print every type you define.
That sounds tedious (and it can be), but the community has developed tools
that make it mostly painless. Let me walk you through the common approaches.</p>

<!--more-->

<h2 id="printing-built-in-types">Printing Built-in Types</h2>

<p>For basic types, OCaml provides dedicated print functions in <code class="language-plaintext highlighter-rouge">Stdlib</code>:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
</pre></td><td class="rouge-code"><pre><span class="n">print_int</span> <span class="mi">42</span><span class="p">;;</span>
<span class="n">print_float</span> <span class="mi">3</span><span class="o">.</span><span class="mi">14</span><span class="p">;;</span>
<span class="n">print_string</span> <span class="s2">"hello"</span><span class="p">;;</span>
<span class="n">print_endline</span> <span class="s2">"hello with newline"</span><span class="p">;;</span>
<span class="n">print_char</span> <span class="k">'</span><span class="n">x'</span><span class="p">;;</span>
<span class="n">print_newline</span> <span class="bp">()</span><span class="p">;;</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>There are also simple conversion functions like <code class="language-plaintext highlighter-rouge">string_of_int</code> and
<code class="language-plaintext highlighter-rouge">string_of_float</code> when you just need a string:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre><span class="k">let</span> <span class="n">s</span> <span class="o">=</span> <span class="s2">"Age: "</span> <span class="o">^</span> <span class="n">string_of_int</span> <span class="mi">42</span><span class="p">;;</span>
<span class="c">(* "Age: 42" *)</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>And of course there’s <code class="language-plaintext highlighter-rouge">Printf.printf</code> for formatted output:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre><span class="nn">Printf</span><span class="p">.</span><span class="n">printf</span> <span class="s2">"Name: %s, Age: %d, Score: %.2f</span><span class="se">\n</span><span class="s2">"</span> <span class="s2">"Alice"</span> <span class="mi">30</span> <span class="mi">95</span><span class="o">.</span><span class="mi">5</span><span class="p">;;</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<blockquote class="prompt-tip">
  <p>A quick cheat sheet for the format specifiers you’ll use most often: <code class="language-plaintext highlighter-rouge">%s</code>
for strings, <code class="language-plaintext highlighter-rouge">%d</code> for ints, <code class="language-plaintext highlighter-rouge">%f</code> for floats (<code class="language-plaintext highlighter-rouge">%.2f</code> for 2 decimal places),
<code class="language-plaintext highlighter-rouge">%b</code> for bools, and <code class="language-plaintext highlighter-rouge">%S</code> for strings with quotes around them (handy for
debugging). You can use <code class="language-plaintext highlighter-rouge">Printf.sprintf</code> instead of <code class="language-plaintext highlighter-rouge">printf</code> to get a
string back rather than printing to stdout.</p>
</blockquote>

<p>This is all straightforward. The trouble starts when you want to print your
own types.</p>

<h2 id="the-running-example">The Running Example</h2>

<p>Let’s use something more fun than the usual <code class="language-plaintext highlighter-rouge">person</code> record. We’ll model
superheroes:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
</pre></td><td class="rouge-code"><pre><span class="k">type</span> <span class="n">power</span> <span class="o">=</span> <span class="nc">Flight</span> <span class="o">|</span> <span class="nc">SuperStrength</span> <span class="o">|</span> <span class="nc">Telepathy</span> <span class="o">|</span> <span class="nc">Speed</span> <span class="o">|</span> <span class="nc">Gadgets</span>

<span class="k">type</span> <span class="n">strength</span> <span class="o">=</span> <span class="nc">Human</span> <span class="o">|</span> <span class="nc">Enhanced</span> <span class="o">|</span> <span class="nc">Superhuman</span> <span class="o">|</span> <span class="nc">Cosmic</span>

<span class="k">type</span> <span class="n">superhero</span> <span class="o">=</span> <span class="p">{</span>
  <span class="n">name</span> <span class="o">:</span> <span class="kt">string</span><span class="p">;</span>
  <span class="n">alias</span> <span class="o">:</span> <span class="kt">string</span><span class="p">;</span>
  <span class="n">powers</span> <span class="o">:</span> <span class="n">power</span> <span class="kt">list</span><span class="p">;</span>
  <span class="n">strength</span> <span class="o">:</span> <span class="n">strength</span><span class="p">;</span>
  <span class="n">first_appearance</span> <span class="o">:</span> <span class="kt">int</span><span class="p">;</span>  <span class="c">(* year *)</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>And a few heroes to work with:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
</pre></td><td class="rouge-code"><pre><span class="k">let</span> <span class="n">batman</span> <span class="o">=</span> <span class="p">{</span>
  <span class="n">name</span> <span class="o">=</span> <span class="s2">"Bruce Wayne"</span><span class="p">;</span>
  <span class="n">alias</span> <span class="o">=</span> <span class="s2">"Batman"</span><span class="p">;</span>
  <span class="n">powers</span> <span class="o">=</span> <span class="p">[</span><span class="nc">Gadgets</span><span class="p">];</span>
  <span class="n">strength</span> <span class="o">=</span> <span class="nc">Human</span><span class="p">;</span>
  <span class="n">first_appearance</span> <span class="o">=</span> <span class="mi">1939</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">let</span> <span class="n">superman</span> <span class="o">=</span> <span class="p">{</span>
  <span class="n">name</span> <span class="o">=</span> <span class="s2">"Clark Kent"</span><span class="p">;</span>
  <span class="n">alias</span> <span class="o">=</span> <span class="s2">"Superman"</span><span class="p">;</span>
  <span class="n">powers</span> <span class="o">=</span> <span class="p">[</span><span class="nc">Flight</span><span class="p">;</span> <span class="nc">SuperStrength</span><span class="p">];</span>
  <span class="n">strength</span> <span class="o">=</span> <span class="nc">Cosmic</span><span class="p">;</span>
  <span class="n">first_appearance</span> <span class="o">=</span> <span class="mi">1938</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">let</span> <span class="n">wonder_woman</span> <span class="o">=</span> <span class="p">{</span>
  <span class="n">name</span> <span class="o">=</span> <span class="s2">"Diana Prince"</span><span class="p">;</span>
  <span class="n">alias</span> <span class="o">=</span> <span class="s2">"Wonder Woman"</span><span class="p">;</span>
  <span class="n">powers</span> <span class="o">=</span> <span class="p">[</span><span class="nc">Flight</span><span class="p">;</span> <span class="nc">SuperStrength</span><span class="p">;</span> <span class="nc">Telepathy</span><span class="p">];</span>
  <span class="n">strength</span> <span class="o">=</span> <span class="nc">Superhuman</span><span class="p">;</span>
  <span class="n">first_appearance</span> <span class="o">=</span> <span class="mi">1941</span><span class="p">;</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Now try <code class="language-plaintext highlighter-rouge">print_endline batman</code> and… you get a type error. OCaml has no
idea how to turn a <code class="language-plaintext highlighter-rouge">superhero</code> into a string. Let’s fix that.</p>

<h2 id="writing-manual-printers">Writing Manual Printers</h2>

<p>The most basic approach is to write dedicated print functions. For our
superhero types, we’d need to handle each layer:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
</pre></td><td class="rouge-code"><pre><span class="k">let</span> <span class="n">string_of_power</span> <span class="o">=</span> <span class="k">function</span>
  <span class="o">|</span> <span class="nc">Flight</span> <span class="o">-&gt;</span> <span class="s2">"Flight"</span>
  <span class="o">|</span> <span class="nc">SuperStrength</span> <span class="o">-&gt;</span> <span class="s2">"Super Strength"</span>
  <span class="o">|</span> <span class="nc">Telepathy</span> <span class="o">-&gt;</span> <span class="s2">"Telepathy"</span>
  <span class="o">|</span> <span class="nc">Speed</span> <span class="o">-&gt;</span> <span class="s2">"Speed"</span>
  <span class="o">|</span> <span class="nc">Gadgets</span> <span class="o">-&gt;</span> <span class="s2">"Gadgets"</span>

<span class="k">let</span> <span class="n">string_of_strength</span> <span class="o">=</span> <span class="k">function</span>
  <span class="o">|</span> <span class="nc">Human</span> <span class="o">-&gt;</span> <span class="s2">"Human"</span>
  <span class="o">|</span> <span class="nc">Enhanced</span> <span class="o">-&gt;</span> <span class="s2">"Enhanced"</span>
  <span class="o">|</span> <span class="nc">Superhuman</span> <span class="o">-&gt;</span> <span class="s2">"Superhuman"</span>
  <span class="o">|</span> <span class="nc">Cosmic</span> <span class="o">-&gt;</span> <span class="s2">"Cosmic"</span>

<span class="k">let</span> <span class="n">show_superhero</span> <span class="n">h</span> <span class="o">=</span>
  <span class="nn">Printf</span><span class="p">.</span><span class="n">sprintf</span> <span class="s2">"%s (%s) - %s, first appeared in %d, powers: %s"</span>
    <span class="n">h</span><span class="o">.</span><span class="n">alias</span> <span class="n">h</span><span class="o">.</span><span class="n">name</span>
    <span class="p">(</span><span class="n">string_of_strength</span> <span class="n">h</span><span class="o">.</span><span class="n">strength</span><span class="p">)</span>
    <span class="n">h</span><span class="o">.</span><span class="n">first_appearance</span>
    <span class="p">(</span><span class="n">h</span><span class="o">.</span><span class="n">powers</span> <span class="o">|&gt;</span> <span class="nn">List</span><span class="p">.</span><span class="n">map</span> <span class="n">string_of_power</span> <span class="o">|&gt;</span> <span class="nn">String</span><span class="p">.</span><span class="n">concat</span> <span class="s2">", "</span><span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="rouge-code"><pre><span class="n">print_endline</span> <span class="p">(</span><span class="n">show_superhero</span> <span class="n">batman</span><span class="p">);;</span>
<span class="c">(* Batman (Bruce Wayne) - Human, first appeared in 1939, powers: Gadgets *)</span>

<span class="n">print_endline</span> <span class="p">(</span><span class="n">show_superhero</span> <span class="n">wonder_woman</span><span class="p">);;</span>
<span class="c">(* Wonder Woman (Diana Prince) - Superhuman, first appeared in 1941, powers: Flight, Super Strength, Telepathy *)</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>This works, but it’s tedious. We had to write a conversion function for
every variant type and compose them manually. Every time you add a new
power or a new field to the record, you have to remember to update all
the printers. And for nested types the boilerplate multiplies fast.
Coming from languages where printing just works out of the box, this
feels like a lot of ceremony for something that should be trivial.</p>

<p>That said, manual printers give you full control over formatting, which is
exactly what you want for user-facing output (error messages, CLI output,
log lines).</p>

<h2 id="automatic-printing-with-ppx_deriving">Automatic Printing with ppx_deriving</h2>

<p>This is where <a href="https://github.com/ocaml-ppx/ppx_deriving">ppx_deriving</a>
saves the day. Its <code class="language-plaintext highlighter-rouge">show</code> plugin generates printer functions automatically
from your type definitions. Just add <code class="language-plaintext highlighter-rouge">[@@deriving show]</code>:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
</pre></td><td class="rouge-code"><pre><span class="k">type</span> <span class="n">power</span> <span class="o">=</span> <span class="nc">Flight</span> <span class="o">|</span> <span class="nc">SuperStrength</span> <span class="o">|</span> <span class="nc">Telepathy</span> <span class="o">|</span> <span class="nc">Speed</span> <span class="o">|</span> <span class="nc">Gadgets</span>
<span class="p">[</span><span class="o">@@</span><span class="n">deriving</span> <span class="n">show</span><span class="p">]</span>

<span class="k">type</span> <span class="n">strength</span> <span class="o">=</span> <span class="nc">Human</span> <span class="o">|</span> <span class="nc">Enhanced</span> <span class="o">|</span> <span class="nc">Superhuman</span> <span class="o">|</span> <span class="nc">Cosmic</span>
<span class="p">[</span><span class="o">@@</span><span class="n">deriving</span> <span class="n">show</span><span class="p">]</span>

<span class="k">type</span> <span class="n">superhero</span> <span class="o">=</span> <span class="p">{</span>
  <span class="n">name</span> <span class="o">:</span> <span class="kt">string</span><span class="p">;</span>
  <span class="n">alias</span> <span class="o">:</span> <span class="kt">string</span><span class="p">;</span>
  <span class="n">powers</span> <span class="o">:</span> <span class="n">power</span> <span class="kt">list</span><span class="p">;</span>
  <span class="n">strength</span> <span class="o">:</span> <span class="n">strength</span><span class="p">;</span>
  <span class="n">first_appearance</span> <span class="o">:</span> <span class="kt">int</span><span class="p">;</span>
<span class="p">}</span> <span class="p">[</span><span class="o">@@</span><span class="n">deriving</span> <span class="n">show</span><span class="p">]</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>That annotation generates two functions per type:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">pp_superhero : Format.formatter -&gt; superhero -&gt; unit</code> – a pretty-printer
for use with OCaml’s <code class="language-plaintext highlighter-rouge">Format</code> module</li>
  <li><code class="language-plaintext highlighter-rouge">show_superhero : superhero -&gt; string</code> – returns the string representation
directly</li>
</ul>

<p>Now printing is trivial:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="rouge-code"><pre><span class="n">print_endline</span> <span class="p">(</span><span class="n">show_superhero</span> <span class="n">batman</span><span class="p">);;</span>
<span class="c">(* { name = "Bruce Wayne"; alias = "Batman";
     powers = [Gadgets]; strength = Human;
     first_appearance = 1939 } *)</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>The generated printers are compositional – they know how to handle lists,
options, and other standard types automatically, as long as the element type
also derives <code class="language-plaintext highlighter-rouge">show</code>. So printing a list of heroes just works:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre><span class="k">let</span> <span class="n">justice_league</span> <span class="o">=</span> <span class="p">[</span><span class="n">batman</span><span class="p">;</span> <span class="n">superman</span><span class="p">;</span> <span class="n">wonder_woman</span><span class="p">]</span>

<span class="k">let</span> <span class="bp">()</span> <span class="o">=</span> <span class="n">print_endline</span> <span class="p">([</span><span class="o">%</span><span class="n">show</span><span class="o">:</span> <span class="n">superhero</span> <span class="kt">list</span><span class="p">]</span> <span class="n">justice_league</span><span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">[%show: superhero list]</code> syntax is really handy – it lets you derive a
printer for any type expression inline, without needing a separate type
declaration.</p>

<p>To use <code class="language-plaintext highlighter-rouge">ppx_deriving</code> in your project, add it to your <code class="language-plaintext highlighter-rouge">dune</code> file:</p>

<pre><code class="language-dune">(library
 (name mylib)
 (preprocess (pps ppx_deriving.show)))
</code></pre>

<p>I slap <code class="language-plaintext highlighter-rouge">[@@deriving show]</code> on pretty much every type I define these days.
The small compile-time cost is well worth the debugging convenience. If you
want to learn more about PPX in general, I wrote a <a href="/articles/2026/03/03/ppx-for-mere-mortals/">longer
article</a> about it.</p>

<h2 id="debugging-tips">Debugging Tips</h2>

<p>A few practical things I’ve picked up:</p>

<h3 id="debug-printing-mid-pipeline">Debug Printing Mid-pipeline</h3>

<p>You can sneak a print into a <code class="language-plaintext highlighter-rouge">|&gt;</code> chain without breaking the flow:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
</pre></td><td class="rouge-code"><pre><span class="k">let</span> <span class="n">debug</span> <span class="n">label</span> <span class="n">x</span> <span class="o">=</span>
  <span class="nn">Printf</span><span class="p">.</span><span class="n">printf</span> <span class="s2">"[DEBUG] %s: %s</span><span class="se">\n</span><span class="s2">"</span> <span class="n">label</span> <span class="p">(</span><span class="n">show_superhero</span> <span class="n">x</span><span class="p">);</span>
  <span class="n">x</span>

<span class="k">let</span> <span class="n">result</span> <span class="o">=</span>
  <span class="n">batman</span>
  <span class="o">|&gt;</span> <span class="n">debug</span> <span class="s2">"before upgrade"</span>
  <span class="o">|&gt;</span> <span class="n">upgrade_powers</span>
  <span class="o">|&gt;</span> <span class="n">debug</span> <span class="s2">"after upgrade"</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="inline-show--for-complex-types">Inline <code class="language-plaintext highlighter-rouge">[%show: ...]</code> for Complex Types</h3>

<p>When you need to print something and don’t want to define a type just for it:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre><span class="nn">Printf</span><span class="p">.</span><span class="n">printf</span> <span class="s2">"Heroes and powers: %s</span><span class="se">\n</span><span class="s2">"</span>
  <span class="p">([</span><span class="o">%</span><span class="n">show</span><span class="o">:</span> <span class="p">(</span><span class="kt">string</span> <span class="o">*</span> <span class="n">power</span> <span class="kt">list</span><span class="p">)</span> <span class="kt">list</span><span class="p">]</span>
    <span class="p">(</span><span class="nn">List</span><span class="p">.</span><span class="n">map</span> <span class="p">(</span><span class="k">fun</span> <span class="n">h</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="n">h</span><span class="o">.</span><span class="n">alias</span><span class="o">,</span> <span class="n">h</span><span class="o">.</span><span class="n">powers</span><span class="p">))</span> <span class="n">justice_league</span><span class="p">))</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h3 id="watch-out-for-missing-show-on-nested-types">Watch Out for Missing <code class="language-plaintext highlighter-rouge">show</code> on Nested Types</h3>

<p>If your record contains a type that doesn’t derive <code class="language-plaintext highlighter-rouge">show</code>, you’ll get a
compile error about a missing <code class="language-plaintext highlighter-rouge">pp_</code> function. The error message can be
cryptic – just look for the type that’s missing the derivation and add
<code class="language-plaintext highlighter-rouge">[@@deriving show]</code> to it.</p>

<h3 id="-with_path--false--for-cleaner-output"><code class="language-plaintext highlighter-rouge">{ with_path = false }</code> for Cleaner Output</h3>

<p>By default, derived printers qualify variant constructors with their module
path (e.g. <code class="language-plaintext highlighter-rouge">Superhero.Flight</code>). If you find this noisy:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="rouge-code"><pre><span class="k">type</span> <span class="n">power</span> <span class="o">=</span> <span class="nc">Flight</span> <span class="o">|</span> <span class="nc">SuperStrength</span> <span class="o">|</span> <span class="nc">Telepathy</span> <span class="o">|</span> <span class="nc">Speed</span> <span class="o">|</span> <span class="nc">Gadgets</span>
<span class="p">[</span><span class="o">@@</span><span class="n">deriving</span> <span class="n">show</span> <span class="p">{</span> <span class="n">with_path</span> <span class="o">=</span> <span class="bp">false</span> <span class="p">}]</span>

<span class="c">(* Now prints "Flight" instead of "Superhero.Flight" *)</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="printing-in-the-toplevel">Printing in the Toplevel</h2>

<p>If you’re exploring code in <code class="language-plaintext highlighter-rouge">utop</code> (or the plain <code class="language-plaintext highlighter-rouge">ocaml</code> toplevel), you’ll
notice that it already displays values of built-in types:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
</pre></td><td class="rouge-code"><pre><span class="o">#</span> <span class="mi">42</span><span class="p">;;</span>
<span class="o">-</span> <span class="o">:</span> <span class="kt">int</span> <span class="o">=</span> <span class="mi">42</span>
<span class="o">#</span> <span class="p">[</span><span class="mi">1</span><span class="p">;</span> <span class="mi">2</span><span class="p">;</span> <span class="mi">3</span><span class="p">];;</span>
<span class="o">-</span> <span class="o">:</span> <span class="kt">int</span> <span class="kt">list</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">;</span> <span class="mi">2</span><span class="p">;</span> <span class="mi">3</span><span class="p">]</span>
<span class="o">#</span> <span class="s2">"hello"</span><span class="p">;;</span>
<span class="o">-</span> <span class="o">:</span> <span class="kt">string</span> <span class="o">=</span> <span class="s2">"hello"</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>But for your own types, you just see the structure without much help:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre><span class="o">#</span> <span class="n">batman</span><span class="p">;;</span>
<span class="o">-</span> <span class="o">:</span> <span class="n">superhero</span> <span class="o">=</span> <span class="p">{</span><span class="n">name</span> <span class="o">=</span> <span class="s2">"Bruce Wayne"</span><span class="p">;</span> <span class="n">alias</span> <span class="o">=</span> <span class="s2">"Batman"</span><span class="p">;</span> <span class="o">...</span><span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Actually, the toplevel does a decent job with simple records and variants –
it knows the structure from the type definition. Where it falls short is
with abstract types or types from external modules where the representation
is hidden.</p>

<p>If you’ve derived <code class="language-plaintext highlighter-rouge">show</code> for your types, you can register the generated
pretty-printer with the toplevel using <code class="language-plaintext highlighter-rouge">#install_printer</code>:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre><span class="o">#</span> <span class="o">#</span><span class="n">install_printer</span> <span class="n">pp_superhero</span><span class="p">;;</span>
<span class="o">#</span> <span class="n">batman</span><span class="p">;;</span>
<span class="o">-</span> <span class="o">:</span> <span class="n">superhero</span> <span class="o">=</span> <span class="p">{</span> <span class="n">name</span> <span class="o">=</span> <span class="s2">"Bruce Wayne"</span><span class="p">;</span> <span class="n">alias</span> <span class="o">=</span> <span class="s2">"Batman"</span><span class="p">;</span> <span class="o">...</span> <span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>This tells the toplevel to use your <code class="language-plaintext highlighter-rouge">pp_superhero</code> function whenever it
needs to display a <code class="language-plaintext highlighter-rouge">superhero</code> value. This is especially useful for abstract
types or when you want a custom representation. The function you install
must have the signature <code class="language-plaintext highlighter-rouge">Format.formatter -&gt; 'a -&gt; unit</code> – which is exactly
what <code class="language-plaintext highlighter-rouge">[@@deriving show]</code> generates as <code class="language-plaintext highlighter-rouge">pp_*</code>.</p>

<h2 id="when-to-use-what">When to Use What</h2>

<p>For debugging and development, <code class="language-plaintext highlighter-rouge">[@@deriving show]</code> is the obvious winner.
It’s become such a standard part of my OCaml workflow that I barely think
about it anymore.</p>

<p>For user-facing output (error messages, CLI formatting, log lines), you’ll
still want manual formatting with <code class="language-plaintext highlighter-rouge">Printf.sprintf</code> or <code class="language-plaintext highlighter-rouge">Format.asprintf</code>.
The derived printers produce a generic representation that’s great for
debugging but not something you’d want to show end users.</p>

<p>And in the toplevel, make friends with <code class="language-plaintext highlighter-rouge">#install_printer</code> – it makes
interactive exploration much more pleasant.</p>

<p>That’s all I have for you today. Keep hacking!</p>]]></content><author><name>Bozhidar Batsov</name></author><category term="OCaml" /><category term="Learning OCaml" /><summary type="html"><![CDATA[If there’s one thing that frustrated me early on in my OCaml journey, it was printing stuff. In Ruby I can p anything and get a useful representation. In Clojure, prn just works on every data structure. In OCaml? There’s no generic print that works on any type – the type information is erased at runtime, so the language simply doesn’t know how to stringify an arbitrary value.]]></summary></entry><entry><title type="html">Learning OCaml: Working with Records</title><link href="https://batsov.com/articles/2026/03/01/working-with-ocaml-records/" rel="alternate" type="text/html" title="Learning OCaml: Working with Records" /><published>2026-03-01T11:30:00+02:00</published><updated>2026-03-01T11:30:00+02:00</updated><id>https://batsov.com/articles/2026/03/01/working-with-ocaml-records</id><content type="html" xml:base="https://batsov.com/articles/2026/03/01/working-with-ocaml-records/"><![CDATA[<p>Records are one of those things that look almost identical across ML-family
languages, so I didn’t expect many surprises when I started using them in
OCaml. For the most part I was right – but there were a few things worth
noting, especially if you’re coming from a language where records/structs
are mutable by default.</p>

<p>Let’s explore records using a fun data model – superheroes:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
</pre></td><td class="rouge-code"><pre><span class="k">type</span> <span class="n">power</span> <span class="o">=</span> <span class="nc">Flight</span> <span class="o">|</span> <span class="nc">SuperStrength</span> <span class="o">|</span> <span class="nc">Telepathy</span> <span class="o">|</span> <span class="nc">Speed</span> <span class="o">|</span> <span class="nc">Gadgets</span>

<span class="k">type</span> <span class="n">strength</span> <span class="o">=</span> <span class="nc">Human</span> <span class="o">|</span> <span class="nc">Enhanced</span> <span class="o">|</span> <span class="nc">Superhuman</span> <span class="o">|</span> <span class="nc">Cosmic</span>

<span class="k">type</span> <span class="n">superhero</span> <span class="o">=</span> <span class="p">{</span>
  <span class="n">name</span> <span class="o">:</span> <span class="kt">string</span><span class="p">;</span>
  <span class="n">alias</span> <span class="o">:</span> <span class="kt">string</span><span class="p">;</span>
  <span class="n">powers</span> <span class="o">:</span> <span class="n">power</span> <span class="kt">list</span><span class="p">;</span>
  <span class="n">strength</span> <span class="o">:</span> <span class="n">strength</span><span class="p">;</span>
  <span class="n">first_appearance</span> <span class="o">:</span> <span class="kt">int</span><span class="p">;</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<!--more-->

<h2 id="creating-records">Creating Records</h2>

<p>Creating a record is straightforward – list all fields with their values:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
</pre></td><td class="rouge-code"><pre><span class="k">let</span> <span class="n">batman</span> <span class="o">=</span> <span class="p">{</span>
  <span class="n">name</span> <span class="o">=</span> <span class="s2">"Bruce Wayne"</span><span class="p">;</span>
  <span class="n">alias</span> <span class="o">=</span> <span class="s2">"Batman"</span><span class="p">;</span>
  <span class="n">powers</span> <span class="o">=</span> <span class="p">[</span><span class="nc">Gadgets</span><span class="p">];</span>
  <span class="n">strength</span> <span class="o">=</span> <span class="nc">Human</span><span class="p">;</span>
  <span class="n">first_appearance</span> <span class="o">=</span> <span class="mi">1939</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">let</span> <span class="n">superman</span> <span class="o">=</span> <span class="p">{</span>
  <span class="n">name</span> <span class="o">=</span> <span class="s2">"Clark Kent"</span><span class="p">;</span>
  <span class="n">alias</span> <span class="o">=</span> <span class="s2">"Superman"</span><span class="p">;</span>
  <span class="n">powers</span> <span class="o">=</span> <span class="p">[</span><span class="nc">Flight</span><span class="p">;</span> <span class="nc">SuperStrength</span><span class="p">];</span>
  <span class="n">strength</span> <span class="o">=</span> <span class="nc">Cosmic</span><span class="p">;</span>
  <span class="n">first_appearance</span> <span class="o">=</span> <span class="mi">1938</span><span class="p">;</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Note that you don’t specify the type anywhere – the compiler infers it from
the field names. This works great when field names are unique, but can cause
trouble when two record types in scope share a field name:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
</pre></td><td class="rouge-code"><pre><span class="k">type</span> <span class="n">hero</span> <span class="o">=</span> <span class="p">{</span> <span class="n">name</span> <span class="o">:</span> <span class="kt">string</span><span class="p">;</span> <span class="n">power</span> <span class="o">:</span> <span class="kt">string</span> <span class="p">}</span>
<span class="k">type</span> <span class="n">villain</span> <span class="o">=</span> <span class="p">{</span> <span class="n">name</span> <span class="o">:</span> <span class="kt">string</span><span class="p">;</span> <span class="n">evil_plan</span> <span class="o">:</span> <span class="kt">string</span> <span class="p">}</span>

<span class="c">(* Which type is this? The compiler picks the most recently defined one -- villain *)</span>
<span class="k">let</span> <span class="n">mystery</span> <span class="o">=</span> <span class="p">{</span> <span class="n">name</span> <span class="o">=</span> <span class="s2">"Enigma"</span><span class="p">;</span> <span class="n">power</span> <span class="o">=</span> <span class="s2">"puzzles"</span> <span class="p">}</span>
<span class="c">(* Error: Unbound record field power *)</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>You can disambiguate by annotating the type:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre><span class="k">let</span> <span class="n">h</span> <span class="o">:</span> <span class="n">hero</span> <span class="o">=</span> <span class="p">{</span> <span class="n">name</span> <span class="o">=</span> <span class="s2">"Enigma"</span><span class="p">;</span> <span class="n">power</span> <span class="o">=</span> <span class="s2">"puzzles"</span> <span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Or by prefixing a field name with the module:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre><span class="k">let</span> <span class="n">h</span> <span class="o">=</span> <span class="p">{</span> <span class="nn">Hero</span><span class="p">.</span><span class="n">name</span> <span class="o">=</span> <span class="s2">"Enigma"</span><span class="p">;</span> <span class="n">power</span> <span class="o">=</span> <span class="s2">"puzzles"</span> <span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<blockquote class="prompt-tip">
  <p>In practice, this ambiguity rarely bites you because OCaml’s module system
naturally separates types into different scopes. The idiomatic approach is
to define one main record type per module (named <code class="language-plaintext highlighter-rouge">t</code> by convention), which
avoids collisions entirely.</p>
</blockquote>

<h2 id="accessing-fields">Accessing Fields</h2>

<p>Field access uses the usual dot notation:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre><span class="k">let</span> <span class="n">name</span> <span class="o">=</span> <span class="n">batman</span><span class="o">.</span><span class="n">name</span>               <span class="c">(* "Bruce Wayne" *)</span>
<span class="k">let</span> <span class="n">year</span> <span class="o">=</span> <span class="n">batman</span><span class="o">.</span><span class="n">first_appearance</span>   <span class="c">(* 1939 *)</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>You can use fields directly in expressions:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="rouge-code"><pre><span class="k">let</span> <span class="n">is_golden_age</span> <span class="n">hero</span> <span class="o">=</span> <span class="n">hero</span><span class="o">.</span><span class="n">first_appearance</span> <span class="o">&lt;</span> <span class="mi">1956</span>
<span class="k">let</span> <span class="bp">()</span> <span class="o">=</span> <span class="nn">Printf</span><span class="p">.</span><span class="n">printf</span> <span class="s2">"%s: golden age = %b</span><span class="se">\n</span><span class="s2">"</span>
  <span class="n">batman</span><span class="o">.</span><span class="n">alias</span> <span class="p">(</span><span class="n">is_golden_age</span> <span class="n">batman</span><span class="p">)</span>
<span class="c">(* Batman: golden age = true *)</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="functional-updates">Functional Updates</h2>

<p>Records are immutable by default in OCaml. You can’t modify a field in
place – instead, you create a new record with some fields changed using the
<code class="language-plaintext highlighter-rouge">with</code> keyword:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="rouge-code"><pre><span class="k">let</span> <span class="n">upgraded_batman</span> <span class="o">=</span> <span class="p">{</span> <span class="n">batman</span> <span class="k">with</span>
  <span class="n">powers</span> <span class="o">=</span> <span class="p">[</span><span class="nc">Gadgets</span><span class="p">;</span> <span class="nc">SuperStrength</span><span class="p">];</span>
  <span class="n">strength</span> <span class="o">=</span> <span class="nc">Enhanced</span><span class="p">;</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>This creates a new <code class="language-plaintext highlighter-rouge">superhero</code> record that’s a copy of <code class="language-plaintext highlighter-rouge">batman</code> with only
the specified fields changed. The original is untouched. If you’ve used
Haskell’s record update syntax or Erlang’s map update syntax, this will feel
familiar.</p>

<p>Functional updates are especially handy when your records have many fields
and you only want to change one or two:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="rouge-code"><pre><span class="k">let</span> <span class="n">justice_league</span> <span class="o">=</span> <span class="p">[</span><span class="n">batman</span><span class="p">;</span> <span class="n">superman</span><span class="p">]</span>

<span class="c">(* Promote everyone to Cosmic strength *)</span>
<span class="k">let</span> <span class="n">cosmic_league</span> <span class="o">=</span>
  <span class="nn">List</span><span class="p">.</span><span class="n">map</span> <span class="p">(</span><span class="k">fun</span> <span class="n">h</span> <span class="o">-&gt;</span> <span class="p">{</span> <span class="n">h</span> <span class="k">with</span> <span class="n">strength</span> <span class="o">=</span> <span class="nc">Cosmic</span> <span class="p">})</span> <span class="n">justice_league</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="mutable-fields">Mutable Fields</h2>

<p>While records are immutable by default, OCaml lets you mark individual
fields as <code class="language-plaintext highlighter-rouge">mutable</code>:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
</pre></td><td class="rouge-code"><pre><span class="k">type</span> <span class="n">battle_stats</span> <span class="o">=</span> <span class="p">{</span>
  <span class="n">hero</span> <span class="o">:</span> <span class="kt">string</span><span class="p">;</span>
  <span class="k">mutable</span> <span class="n">wins</span> <span class="o">:</span> <span class="kt">int</span><span class="p">;</span>
  <span class="k">mutable</span> <span class="n">losses</span> <span class="o">:</span> <span class="kt">int</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">let</span> <span class="n">stats</span> <span class="o">=</span> <span class="p">{</span> <span class="n">hero</span> <span class="o">=</span> <span class="s2">"Batman"</span><span class="p">;</span> <span class="n">wins</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">losses</span> <span class="o">=</span> <span class="mi">0</span> <span class="p">}</span>
<span class="k">let</span> <span class="bp">()</span> <span class="o">=</span> <span class="n">stats</span><span class="o">.</span><span class="n">wins</span> <span class="o">&lt;-</span> <span class="n">stats</span><span class="o">.</span><span class="n">wins</span> <span class="o">+</span> <span class="mi">1</span>   <span class="c">(* wins is now 1 *)</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">&lt;-</code> operator mutates the field in place. Note that only fields
explicitly marked <code class="language-plaintext highlighter-rouge">mutable</code> can be changed this way – trying to mutate an
immutable field is a compile error.</p>

<p>I’d use mutable fields sparingly – they’re there when you need them for
performance-critical code, but immutable records with functional updates are
generally the idiomatic approach in OCaml.</p>

<h2 id="pattern-matching-on-records">Pattern Matching on Records</h2>

<p>Like everything in OCaml, records work with pattern matching. You can
destructure them directly in function arguments:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
</pre></td><td class="rouge-code"><pre><span class="k">let</span> <span class="n">describe</span> <span class="p">{</span> <span class="n">alias</span><span class="p">;</span> <span class="n">strength</span><span class="p">;</span> <span class="n">first_appearance</span><span class="p">;</span> <span class="n">_</span> <span class="p">}</span> <span class="o">=</span>
  <span class="k">let</span> <span class="n">strength_str</span> <span class="o">=</span> <span class="k">match</span> <span class="n">strength</span> <span class="k">with</span>
    <span class="o">|</span> <span class="nc">Human</span> <span class="o">-&gt;</span> <span class="s2">"human-level"</span>
    <span class="o">|</span> <span class="nc">Enhanced</span> <span class="o">-&gt;</span> <span class="s2">"enhanced"</span>
    <span class="o">|</span> <span class="nc">Superhuman</span> <span class="o">-&gt;</span> <span class="s2">"superhuman"</span>
    <span class="o">|</span> <span class="nc">Cosmic</span> <span class="o">-&gt;</span> <span class="s2">"cosmic"</span>
  <span class="k">in</span>
  <span class="nn">Printf</span><span class="p">.</span><span class="n">printf</span> <span class="s2">"%s (%d) - %s strength</span><span class="se">\n</span><span class="s2">"</span>
    <span class="n">alias</span> <span class="n">first_appearance</span> <span class="n">strength_str</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">_</code> in the pattern tells the compiler you’re intentionally ignoring the
other fields. Without it, you’d get a warning about inexhaustive field
patterns.</p>

<p>You can also pattern match in <code class="language-plaintext highlighter-rouge">let</code> bindings:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre><span class="k">let</span> <span class="p">{</span> <span class="n">alias</span><span class="p">;</span> <span class="n">powers</span><span class="p">;</span> <span class="n">_</span> <span class="p">}</span> <span class="o">=</span> <span class="n">batman</span> <span class="k">in</span>
<span class="nn">Printf</span><span class="p">.</span><span class="n">printf</span> <span class="s2">"%s has %d powers</span><span class="se">\n</span><span class="s2">"</span> <span class="n">alias</span> <span class="p">(</span><span class="nn">List</span><span class="p">.</span><span class="n">length</span> <span class="n">powers</span><span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>And in <code class="language-plaintext highlighter-rouge">match</code> expressions for more complex logic:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
</pre></td><td class="rouge-code"><pre><span class="k">let</span> <span class="n">classify</span> <span class="o">=</span> <span class="k">function</span>
  <span class="o">|</span> <span class="p">{</span> <span class="n">strength</span> <span class="o">=</span> <span class="nc">Cosmic</span><span class="p">;</span> <span class="n">_</span> <span class="p">}</span> <span class="o">-&gt;</span> <span class="s2">"overpowered"</span>
  <span class="o">|</span> <span class="p">{</span> <span class="n">strength</span> <span class="o">=</span> <span class="nc">Human</span><span class="p">;</span> <span class="n">powers</span><span class="p">;</span> <span class="n">_</span> <span class="p">}</span> <span class="k">when</span> <span class="nn">List</span><span class="p">.</span><span class="n">length</span> <span class="n">powers</span> <span class="o">&gt;</span> <span class="mi">0</span> <span class="o">-&gt;</span>
    <span class="s2">"resourceful"</span>
  <span class="o">|</span> <span class="p">{</span> <span class="n">strength</span> <span class="o">=</span> <span class="nc">Human</span><span class="p">;</span> <span class="n">_</span> <span class="p">}</span> <span class="o">-&gt;</span> <span class="s2">"ordinary"</span>
  <span class="o">|</span> <span class="n">_</span> <span class="o">-&gt;</span> <span class="s2">"super"</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h2 id="field-punning">Field Punning</h2>

<p>You might have noticed the shorthand in the examples above –
<code class="language-plaintext highlighter-rouge">{ alias; strength; _ }</code> instead of <code class="language-plaintext highlighter-rouge">{ alias = alias; strength = strength; _ }</code>.
This is called “field punning” and it works both in patterns and when
constructing records:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
</pre></td><td class="rouge-code"><pre><span class="k">let</span> <span class="n">alias</span> <span class="o">=</span> <span class="s2">"Flash"</span>
<span class="k">let</span> <span class="n">name</span> <span class="o">=</span> <span class="s2">"Barry Allen"</span>
<span class="k">let</span> <span class="n">powers</span> <span class="o">=</span> <span class="p">[</span><span class="nc">Speed</span><span class="p">]</span>
<span class="k">let</span> <span class="n">strength</span> <span class="o">=</span> <span class="nc">Superhuman</span>
<span class="k">let</span> <span class="n">first_appearance</span> <span class="o">=</span> <span class="mi">1956</span>

<span class="c">(* these are equivalent *)</span>
<span class="k">let</span> <span class="n">flash_v1</span> <span class="o">=</span> <span class="p">{</span> <span class="n">name</span> <span class="o">=</span> <span class="n">name</span><span class="p">;</span> <span class="n">alias</span> <span class="o">=</span> <span class="n">alias</span><span class="p">;</span> <span class="n">powers</span> <span class="o">=</span> <span class="n">powers</span><span class="p">;</span>
                 <span class="n">strength</span> <span class="o">=</span> <span class="n">strength</span><span class="p">;</span> <span class="n">first_appearance</span> <span class="o">=</span> <span class="n">first_appearance</span> <span class="p">}</span>
<span class="k">let</span> <span class="n">flash_v2</span> <span class="o">=</span> <span class="p">{</span> <span class="n">name</span><span class="p">;</span> <span class="n">alias</span><span class="p">;</span> <span class="n">powers</span><span class="p">;</span> <span class="n">strength</span><span class="p">;</span> <span class="n">first_appearance</span> <span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>If you’re coming from JavaScript, this is the same idea as ES6’s shorthand
property names in object literals. It’s particularly nice when you’re
constructing a record from variables that already have the right names.</p>

<h2 id="records-and-modules">Records and Modules</h2>

<p>The idiomatic OCaml pattern is to define one main type per module, named <code class="language-plaintext highlighter-rouge">t</code>:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
</pre></td><td class="rouge-code"><pre><span class="c">(* superhero.ml *)</span>
<span class="k">type</span> <span class="n">power</span> <span class="o">=</span> <span class="nc">Flight</span> <span class="o">|</span> <span class="nc">SuperStrength</span> <span class="o">|</span> <span class="nc">Telepathy</span> <span class="o">|</span> <span class="nc">Speed</span> <span class="o">|</span> <span class="nc">Gadgets</span>
<span class="k">type</span> <span class="n">strength</span> <span class="o">=</span> <span class="nc">Human</span> <span class="o">|</span> <span class="nc">Enhanced</span> <span class="o">|</span> <span class="nc">Superhuman</span> <span class="o">|</span> <span class="nc">Cosmic</span>

<span class="k">type</span> <span class="n">t</span> <span class="o">=</span> <span class="p">{</span>
  <span class="n">name</span> <span class="o">:</span> <span class="kt">string</span><span class="p">;</span>
  <span class="n">alias</span> <span class="o">:</span> <span class="kt">string</span><span class="p">;</span>
  <span class="n">powers</span> <span class="o">:</span> <span class="n">power</span> <span class="kt">list</span><span class="p">;</span>
  <span class="n">strength</span> <span class="o">:</span> <span class="n">strength</span><span class="p">;</span>
  <span class="n">first_appearance</span> <span class="o">:</span> <span class="kt">int</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">let</span> <span class="n">create</span> <span class="o">~</span><span class="n">name</span> <span class="o">~</span><span class="n">alias</span> <span class="o">~</span><span class="n">powers</span> <span class="o">~</span><span class="n">strength</span> <span class="o">~</span><span class="n">first_appearance</span> <span class="o">=</span>
  <span class="p">{</span> <span class="n">name</span><span class="p">;</span> <span class="n">alias</span><span class="p">;</span> <span class="n">powers</span><span class="p">;</span> <span class="n">strength</span><span class="p">;</span> <span class="n">first_appearance</span> <span class="p">}</span>

<span class="k">let</span> <span class="n">is_golden_age</span> <span class="n">t</span> <span class="o">=</span> <span class="n">t</span><span class="o">.</span><span class="n">first_appearance</span> <span class="o">&lt;</span> <span class="mi">1956</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>This convention means you refer to the type as <code class="language-plaintext highlighter-rouge">Superhero.t</code> from outside
the module, and field access reads naturally as <code class="language-plaintext highlighter-rouge">hero.Superhero.alias</code> (or
just <code class="language-plaintext highlighter-rouge">hero.alias</code> if you’ve opened the module). It also avoids the field
name collision problem entirely, since each module has its own namespace.</p>

<h2 id="printing-records">Printing Records</h2>

<p>You can’t just <code class="language-plaintext highlighter-rouge">print</code> a record in OCaml – there’s no generic print that
works on any type. The easiest solution is <code class="language-plaintext highlighter-rouge">[@@deriving show]</code> from
<code class="language-plaintext highlighter-rouge">ppx_deriving</code>:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
</pre></td><td class="rouge-code"><pre><span class="k">type</span> <span class="n">superhero</span> <span class="o">=</span> <span class="p">{</span>
  <span class="n">name</span> <span class="o">:</span> <span class="kt">string</span><span class="p">;</span>
  <span class="n">alias</span> <span class="o">:</span> <span class="kt">string</span><span class="p">;</span>
  <span class="n">powers</span> <span class="o">:</span> <span class="n">power</span> <span class="kt">list</span><span class="p">;</span>
<span class="p">}</span> <span class="p">[</span><span class="o">@@</span><span class="n">deriving</span> <span class="n">show</span><span class="p">]</span>

<span class="k">let</span> <span class="bp">()</span> <span class="o">=</span> <span class="n">print_endline</span> <span class="p">(</span><span class="n">show_superhero</span> <span class="n">batman</span><span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>I wrote a <a href="/articles/2026/03/01/printing-data-in-ocaml/">dedicated article</a>
on printing data structures if you want the full story.</p>

<h2 id="records-vs-tuples">Records vs Tuples</h2>

<p>One question that comes up for beginners: when should you use a record
instead of a tuple?</p>

<p>Tuples are great for quick, throwaway groupings – returning two values from
a function, for example. But once you have more than two or three fields, or
when the meaning of each position isn’t obvious, records are almost always
better:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="rouge-code"><pre><span class="c">(* Tuple -- what does each position mean? *)</span>
<span class="k">let</span> <span class="n">hero</span> <span class="o">=</span> <span class="p">(</span><span class="s2">"Bruce Wayne"</span><span class="o">,</span> <span class="s2">"Batman"</span><span class="o">,</span> <span class="mi">1939</span><span class="p">)</span>

<span class="c">(* Record -- self-documenting *)</span>
<span class="k">let</span> <span class="n">hero</span> <span class="o">=</span> <span class="p">{</span> <span class="n">name</span> <span class="o">=</span> <span class="s2">"Bruce Wayne"</span><span class="p">;</span> <span class="n">alias</span> <span class="o">=</span> <span class="s2">"Batman"</span><span class="p">;</span> <span class="n">first_appearance</span> <span class="o">=</span> <span class="mi">1939</span> <span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Records also give you pattern matching with named fields, functional
updates with <code class="language-plaintext highlighter-rouge">with</code>, and better error messages from the compiler. If you
find yourself reaching for a tuple with more than 2-3 elements, it’s
probably time for a record.</p>

<h2 id="further-reading">Further Reading</h2>

<ul>
  <li><a href="https://dev.realworldocaml.org/records.html">Records</a> – the Real World
OCaml chapter on records. Goes deeper into functional updates, first-class
fields (via <code class="language-plaintext highlighter-rouge">ppx_fields_conv</code>), and the interaction between records and modules.</li>
  <li><a href="https://ocaml.org/docs/basic-data-types">Basic Data Types and Pattern Matching</a> –
the official ocaml.org guide covering records alongside other core data types.</li>
</ul>

<p>That’s all I have for you today. Keep hacking!</p>]]></content><author><name>Bozhidar Batsov</name></author><category term="OCaml" /><category term="Learning OCaml" /><summary type="html"><![CDATA[Records are one of those things that look almost identical across ML-family languages, so I didn’t expect many surprises when I started using them in OCaml. For the most part I was right – but there were a few things worth noting, especially if you’re coming from a language where records/structs are mutable by default.]]></summary></entry><entry><title type="html">Building Emacs Major Modes with Tree-sitter: Lessons Learned</title><link href="https://batsov.com/articles/2026/02/27/building-emacs-major-modes-with-treesitter-lessons-learned/" rel="alternate" type="text/html" title="Building Emacs Major Modes with Tree-sitter: Lessons Learned" /><published>2026-02-27T10:00:00+02:00</published><updated>2026-02-27T10:00:00+02:00</updated><id>https://batsov.com/articles/2026/02/27/building-emacs-major-modes-with-treesitter-lessons-learned</id><content type="html" xml:base="https://batsov.com/articles/2026/02/27/building-emacs-major-modes-with-treesitter-lessons-learned/"><![CDATA[<p>Over the past year I’ve been spending a lot of time building <a href="https://tree-sitter.github.io/tree-sitter/">Tree-sitter</a>-powered
major modes for Emacs – <a href="https://github.com/clojure-emacs/clojure-ts-mode">clojure-ts-mode</a>
(as co-maintainer), <a href="https://github.com/bbatsov/neocaml">neocaml</a> (from scratch),
and <a href="https://github.com/bbatsov/asciidoc-mode">asciidoc-mode</a> (also from scratch).
Between the three projects I’ve accumulated enough knowledge (and battle scars) to write about the
experience. This post distills the key lessons for anyone thinking about writing
a Tree-sitter-based major mode, or curious about what it’s actually like.</p>

<!--more-->

<h2 id="why-tree-sitter">Why Tree-sitter?</h2>

<p>Before Tree-sitter, Emacs font-locking was done with regular expressions and
indentation was handled by ad-hoc engines (SMIE, custom indent functions, or
pure regex heuristics). This works, but it has well-known problems:</p>

<ul>
  <li>
    <p><strong>Regex-based font-locking is fragile.</strong> Regexes can’t parse nested structures,
so they either under-match (missing valid code) or over-match (highlighting
inside strings and comments). Every edge case is another regex, and the patterns
become increasingly unreadable over time.</p>
  </li>
  <li>
    <p><strong>Indentation engines are complex.</strong> SMIE (the generic indentation engine for non-Tree-sitter modes) requires defining operator
precedence grammars for the language, which is hard to get right. Custom
indentation functions tend to grow into large, brittle state machines. Tuareg’s
indentation code, for example, is thousands of lines long.</p>
  </li>
</ul>

<p>Tree-sitter changes the game because you get a <strong>full, incremental, error-tolerant
syntax tree</strong> for free. Font-locking becomes “match this AST pattern, apply this
face”:</p>

<div class="language-emacs-lisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre><span class="c1">;; Highlight let-bound functions: match a let_binding with parameters</span>
<span class="p">(</span><span class="nv">let_binding</span> <span class="nv">pattern:</span> <span class="p">(</span><span class="nv">value_name</span><span class="p">)</span> <span class="nv">@font-lock-function-name-face</span>
             <span class="p">(</span><span class="nv">parameter</span><span class="p">)</span><span class="nb">+</span><span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>And indentation becomes “if the parent node is X, indent by Y”:</p>

<div class="language-emacs-lisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre><span class="c1">;; Children of a let_binding are indented by neocaml-indent-offset</span>
<span class="p">((</span><span class="nv">parent-is</span> <span class="s">"let_binding"</span><span class="p">)</span> <span class="nv">parent-bol</span> <span class="nv">neocaml-indent-offset</span><span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>The rules are declarative, composable, and much easier to reason about than
regex chains.</p>

<p>In practice, <code class="language-plaintext highlighter-rouge">neocaml</code>’s entire font-lock and indentation logic fits in about 350
lines of Elisp. The equivalent in tuareg is spread across thousands of lines.
That’s the real selling point: <strong>simpler, more maintainable code that handles more
edge cases correctly</strong>.</p>

<h2 id="challenges">Challenges</h2>

<p>That said, Tree-sitter in Emacs is not a silver bullet. Here’s what I ran into.</p>

<h3 id="every-grammar-is-different">Every grammar is different</h3>

<p>Tree-sitter grammars are written by different authors with different philosophies.
The <a href="https://github.com/tree-sitter/tree-sitter-ocaml">tree-sitter-ocaml</a>
grammar provides a rich, detailed AST with named fields. The
<a href="https://github.com/sogaiu/tree-sitter-clojure">tree-sitter-clojure</a> grammar,
by contrast, deliberately keeps things minimal – it only models syntax, not
semantics, because Clojure’s macro system makes static semantic analysis
unreliable.<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup> This means font-locking <code class="language-plaintext highlighter-rouge">def</code> forms in Clojure requires
predicate matching on symbol text, while in OCaml you can directly match
<code class="language-plaintext highlighter-rouge">let_binding</code> nodes with named fields.</p>

<p>To illustrate: here’s how you’d fontify a function definition in OCaml, where
the grammar gives you rich named fields:</p>

<div class="language-emacs-lisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre><span class="c1">;; OCaml: grammar provides named fields -- direct structural match</span>
<span class="p">(</span><span class="nv">let_binding</span> <span class="nv">pattern:</span> <span class="p">(</span><span class="nv">value_name</span><span class="p">)</span> <span class="nv">@font-lock-function-name-face</span>
             <span class="p">(</span><span class="nv">parameter</span><span class="p">)</span><span class="nb">+</span><span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>And here’s the equivalent in Clojure, where the grammar only gives you lists of
symbols and you need predicate matching:</p>

<div class="language-emacs-lisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="rouge-code"><pre><span class="c1">;; Clojure: grammar is syntax-only -- match by symbol text</span>
<span class="p">((</span><span class="nv">list_lit</span> <span class="ss">:anchor</span> <span class="p">(</span><span class="nv">sym_lit</span> <span class="nv">!namespace</span>
                            <span class="nv">name:</span> <span class="p">(</span><span class="nv">sym_name</span><span class="p">)</span> <span class="nv">@font-lock-keyword-face</span><span class="p">))</span>
 <span class="p">(</span><span class="ss">:match</span> <span class="o">,</span><span class="nv">clojure-ts--definition-keyword-regexp</span> <span class="nv">@font-lock-keyword-face</span><span class="p">))</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>You can’t learn “how to write Tree-sitter queries” generically – you need to
learn each grammar individually. The best tool for this is <code class="language-plaintext highlighter-rouge">treesit-explore-mode</code>
(to visualize the full parse tree) and <code class="language-plaintext highlighter-rouge">treesit-inspect-mode</code> (to see the node
at point). Use them constantly.</p>

<h3 id="grammar-quality-varies-wildly">Grammar quality varies wildly</h3>

<p>You’re dependent on someone else providing the grammar, and quality is all over
the map. The OCaml grammar is mature and well-maintained – it’s hosted under the
official <a href="https://github.com/tree-sitter">tree-sitter</a> GitHub org. The Clojure
grammar is small and stable by design. But not every language is so lucky.</p>

<p><a href="https://github.com/bbatsov/asciidoc-mode">asciidoc-mode</a> uses a
<a href="https://github.com/cathaysia/tree-sitter-asciidoc">third-party AsciiDoc grammar</a>
that employs a dual-parser architecture – one parser for block-level structure
(headings, lists, code blocks) and another for inline formatting (bold, italic,
links). This is the same approach used by Emacs’s built-in <code class="language-plaintext highlighter-rouge">markdown-ts-mode</code>,
and it makes sense for markup languages where block and inline syntax are largely
independent.</p>

<p>The problem is that the two parsers run independently on the same text, and they
can <strong>disagree</strong>. The inline parser misinterprets <code class="language-plaintext highlighter-rouge">*</code> and <code class="language-plaintext highlighter-rouge">**</code> list markers as
emphasis delimiters, creating spurious bold spans that swallow subsequent inline
content. The workaround is to use <code class="language-plaintext highlighter-rouge">:override t</code> on all block-level font-lock
rules so they win over the incorrect inline faces:</p>

<div class="language-emacs-lisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
</pre></td><td class="rouge-code"><pre><span class="c1">;; Block-level rules use :override t so block-level faces win over</span>
<span class="c1">;; spurious inline emphasis nodes (the inline parser misreads `*'</span>
<span class="c1">;; list markers as emphasis delimiters).</span>
<span class="ss">:language</span> <span class="ss">'asciidoc</span>
<span class="ss">:override</span> <span class="no">t</span>
<span class="ss">:feature</span> <span class="ss">'list</span>
<span class="o">'</span><span class="p">((</span><span class="nv">ordered_list_marker</span><span class="p">)</span> <span class="nv">@font-lock-constant-face</span>
  <span class="p">(</span><span class="nv">unordered_list_marker</span><span class="p">)</span> <span class="nv">@font-lock-constant-face</span><span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>This doesn’t fix inline elements consumed by the spurious emphasis – that
requires an upstream grammar fix. When you hit grammar-level issues like this,
you either fix them yourself (which means diving into the grammar’s JavaScript
source and C toolchain) or you live with workarounds. Either way, it’s a
reminder that your mode is only as good as the grammar underneath it.</p>

<p>Getting the font-locking right in <code class="language-plaintext highlighter-rouge">asciidoc-mode</code> was probably the most
challenging part of all three projects, precisely because of these grammar
quirks. I also ran into a subtle <code class="language-plaintext highlighter-rouge">treesit</code> behavior: the default font-lock mode
(<code class="language-plaintext highlighter-rouge">:override nil</code>) skips an entire captured range if <em>any</em> position within it
already has a face. So if you capture a parent node like <code class="language-plaintext highlighter-rouge">(inline_macro)</code> and a
child was already fontified, the whole thing gets skipped silently. The fix is
to capture specific child nodes instead:</p>

<div class="language-emacs-lisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
</pre></td><td class="rouge-code"><pre><span class="c1">;; BAD: entire node gets skipped if any child is already fontified</span>
<span class="c1">;; (inline_macro) @font-lock-function-call-face</span>

<span class="c1">;; GOOD: capture specific children</span>
<span class="p">(</span><span class="nv">inline_macro</span> <span class="p">(</span><span class="nv">macro_name</span><span class="p">)</span> <span class="nv">@font-lock-function-call-face</span><span class="p">)</span>
<span class="p">(</span><span class="nv">inline_macro</span> <span class="p">(</span><span class="nv">target</span><span class="p">)</span> <span class="nv">@font-lock-string-face</span><span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>These issues took a lot of trial and error to diagnose. The lesson: <strong>budget
extra time for font-locking when working with less mature grammars</strong>.</p>

<h3 id="grammar-versions-and-breaking-changes">Grammar versions and breaking changes</h3>

<p>Grammars evolve, and breaking changes happen. <code class="language-plaintext highlighter-rouge">clojure-ts-mode</code> switched from
the stable grammar to the <a href="https://github.com/sogaiu/tree-sitter-clojure/tree/unstable-20250526">experimental
branch</a>
because the stable version had metadata nodes as children of other nodes, which
caused <code class="language-plaintext highlighter-rouge">forward-sexp</code> and <code class="language-plaintext highlighter-rouge">kill-sexp</code> to behave incorrectly. The experimental
grammar makes metadata standalone nodes, fixing the navigation issues but
requiring all queries to be updated.</p>

<p><code class="language-plaintext highlighter-rouge">neocaml</code> pins to
<a href="https://github.com/tree-sitter/tree-sitter-ocaml/tree/v0.24.0">v0.24.0</a> of the
OCaml grammar. If you don’t pin versions, a grammar update can silently break
your font-locking or indentation.</p>

<p>The takeaway: <strong>always pin your grammar version</strong>, and include a mechanism to
detect outdated grammars. <code class="language-plaintext highlighter-rouge">clojure-ts-mode</code> tests a query that changed between
versions to detect incompatible grammars at startup.</p>

<h3 id="grammar-delivery">Grammar delivery</h3>

<p>Users shouldn’t have to manually clone repos and compile C code to use your
mode. Both <code class="language-plaintext highlighter-rouge">neocaml</code> and <code class="language-plaintext highlighter-rouge">clojure-ts-mode</code> include grammar recipes:</p>

<div class="language-emacs-lisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
</pre></td><td class="rouge-code"><pre><span class="p">(</span><span class="nv">defconst</span> <span class="nv">neocaml-grammar-recipes</span>
  <span class="o">'</span><span class="p">((</span><span class="nv">ocaml</span> <span class="s">"https://github.com/tree-sitter/tree-sitter-ocaml"</span>
           <span class="s">"v0.24.0"</span>
           <span class="s">"grammars/ocaml/src"</span><span class="p">)</span>
    <span class="p">(</span><span class="nv">ocaml-interface</span> <span class="s">"https://github.com/tree-sitter/tree-sitter-ocaml"</span>
                     <span class="s">"v0.24.0"</span>
                     <span class="s">"grammars/interface/src"</span><span class="p">)))</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>On first use, the mode checks <code class="language-plaintext highlighter-rouge">treesit-language-available-p</code> and offers to install
missing grammars via <code class="language-plaintext highlighter-rouge">treesit-install-language-grammar</code>. This works, but requires
a C compiler and Git on the user’s machine, which is not ideal.<sup id="fnref:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup></p>

<h3 id="the-emacs-tree-sitter-apis-are-a-moving-target">The Emacs Tree-sitter APIs are a moving target</h3>

<p>The Tree-sitter support in Emacs has been improving steadily, but each version
has its quirks:</p>

<p><strong>Emacs 29</strong> introduced Tree-sitter support but lacked several APIs. For instance,
<code class="language-plaintext highlighter-rouge">treesit-thing-settings</code> (used for structured navigation) doesn’t exist – you
need a fallback:</p>

<div class="language-emacs-lisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre><span class="c1">;; Fallback for Emacs 29 (no treesit-thing-settings)</span>
<span class="p">(</span><span class="nb">unless</span> <span class="p">(</span><span class="nb">boundp</span> <span class="ss">'treesit-thing-settings</span><span class="p">)</span>
  <span class="p">(</span><span class="nv">setq-local</span> <span class="nv">forward-sexp-function</span> <span class="nf">#'</span><span class="nv">neocaml-forward-sexp</span><span class="p">))</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p><strong>Emacs 30</strong> added <code class="language-plaintext highlighter-rouge">treesit-thing-settings</code>, sentence navigation, and better
indentation support. But it also had a bug in <code class="language-plaintext highlighter-rouge">treesit-range-settings</code> offsets
(<a href="https://debbugs.gnu.org/cgi/bugreport.cgi?bug=77848">#77848</a>) that broke
embedded parsers, and another in <code class="language-plaintext highlighter-rouge">treesit-transpose-sexps</code> that required
<code class="language-plaintext highlighter-rouge">clojure-ts-mode</code> to disable its Tree-sitter-aware version.</p>

<p><strong>Emacs 31</strong> has a bug in <code class="language-plaintext highlighter-rouge">treesit-forward-comment</code> where an off-by-one error
causes <code class="language-plaintext highlighter-rouge">uncomment-region</code> to leave <code class="language-plaintext highlighter-rouge">*)</code> behind on multi-line OCaml comments. I
had to skip the affected test with a version check:</p>

<div class="language-emacs-lisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre><span class="p">(</span><span class="nb">when</span> <span class="p">(</span><span class="nb">&gt;=</span> <span class="nv">emacs-major-version</span> <span class="mi">31</span><span class="p">)</span>
  <span class="p">(</span><span class="nb">signal</span> <span class="ss">'buttercup-pending</span>
          <span class="s">"Emacs 31 treesit-forward-comment bug (off-by-one)"</span><span class="p">))</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>The lesson: <strong>test your mode against multiple Emacs versions</strong>, and be prepared
to write version-specific workarounds. CI that runs against Emacs 29, 30, and
snapshot is essential.</p>

<h3 id="no-scm-file-support-yet">No .scm file support (yet)</h3>

<p>Most Tree-sitter grammars ship with <code class="language-plaintext highlighter-rouge">.scm</code> query files for syntax highlighting
(<code class="language-plaintext highlighter-rouge">highlights.scm</code>) and indentation (<code class="language-plaintext highlighter-rouge">indents.scm</code>). Editors like Neovim and
Helix use these directly. Emacs doesn’t – you have to manually translate the
<code class="language-plaintext highlighter-rouge">.scm</code> patterns into <code class="language-plaintext highlighter-rouge">treesit-font-lock-rules</code> and <code class="language-plaintext highlighter-rouge">treesit-simple-indent-rules</code>
calls in Elisp.</p>

<p>This is tedious and error-prone. For example, here’s a rule from the OCaml
grammar’s <code class="language-plaintext highlighter-rouge">highlights.scm</code>:</p>

<div class="language-scheme highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
</pre></td><td class="rouge-code"><pre><span class="c1">;; upstream .scm (used by Neovim, Helix, etc.)</span>
<span class="p">(</span><span class="nf">constructor_name</span><span class="p">)</span> <span class="nv">@type</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>And here’s the Elisp equivalent you’d write for Emacs:</p>

<div class="language-emacs-lisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="rouge-code"><pre><span class="c1">;; Emacs equivalent -- wrapped in treesit-font-lock-rules</span>
<span class="ss">:language</span> <span class="ss">'ocaml</span>
<span class="ss">:feature</span> <span class="ss">'type</span>
<span class="o">'</span><span class="p">((</span><span class="nv">constructor_name</span><span class="p">)</span> <span class="nv">@font-lock-type-face</span><span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>The query syntax is nearly identical, but you have to wrap everything in
<code class="language-plaintext highlighter-rouge">treesit-font-lock-rules</code> calls, map upstream capture names (<code class="language-plaintext highlighter-rouge">@type</code>) to Emacs
face names (<code class="language-plaintext highlighter-rouge">@font-lock-type-face</code>), assign features, and manage <code class="language-plaintext highlighter-rouge">:override</code>
behavior. You end up maintaining a parallel set of queries that can drift from
upstream. Emacs 31 will introduce
<a href="https://github.com/emacs-mirror/emacs/blob/master/lisp/treesit-x.el#L47"><code class="language-plaintext highlighter-rouge">define-treesit-generic-mode</code></a>
which will make it possible to use <code class="language-plaintext highlighter-rouge">.scm</code> files for font-locking, which should
help significantly. But for now, you’re hand-coding everything.</p>

<h2 id="tips-and-tricks">Tips and tricks</h2>

<h3 id="debugging-font-locking">Debugging font-locking</h3>

<p>When a face isn’t being applied where you expect:</p>

<ol>
  <li>Use <code class="language-plaintext highlighter-rouge">treesit-inspect-mode</code> to verify the node type at point matches your
query.</li>
  <li>Set <code class="language-plaintext highlighter-rouge">treesit--font-lock-verbose</code> to <code class="language-plaintext highlighter-rouge">t</code> to see which rules are firing.</li>
  <li>Check the font-lock feature level – your rule might be in level 4 while the
user has the default level 3. The features are assigned to levels via
<code class="language-plaintext highlighter-rouge">treesit-font-lock-feature-list</code>.</li>
  <li>Remember that <strong>rule order matters</strong>. Without <code class="language-plaintext highlighter-rouge">:override</code>, an earlier rule that
already fontified a region will prevent later rules from applying. This can be
intentional (e.g. builtin types at level 3 take precedence over generic types)
or a source of bugs.</li>
</ol>

<h3 id="use-the-font-lock-levels-wisely">Use the font-lock levels wisely</h3>

<p>Tree-sitter modes define four levels of font-locking via
<code class="language-plaintext highlighter-rouge">treesit-font-lock-feature-list</code>, and the default level in Emacs is 3. It’s
tempting to pile everything into levels 1–3 so users see maximum highlighting
out of the box, but resist the urge. When every token on the screen has a
different color, code starts looking like a Christmas tree and the important
things – keywords, definitions, types – stop standing out.</p>

<p>Less is more here. Here’s how <code class="language-plaintext highlighter-rouge">neocaml</code> distributes features across levels:</p>

<div class="language-emacs-lisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="rouge-code"><pre><span class="p">(</span><span class="nv">setq-local</span> <span class="nv">treesit-font-lock-feature-list</span>
            <span class="o">'</span><span class="p">((</span><span class="nv">comment</span> <span class="nv">definition</span><span class="p">)</span>
              <span class="p">(</span><span class="kt">keyword</span> <span class="nb">string</span> <span class="nc">number</span><span class="p">)</span>
              <span class="p">(</span><span class="nv">attribute</span> <span class="nv">builtin</span> <span class="nv">constant</span> <span class="k">type</span><span class="p">)</span>
              <span class="p">(</span><span class="nv">operator</span> <span class="nv">bracket</span> <span class="nv">delimiter</span> <span class="nv">variable</span> <span class="k">function</span><span class="p">)))</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>And <code class="language-plaintext highlighter-rouge">clojure-ts-mode</code> follows the same philosophy:</p>

<div class="language-emacs-lisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="rouge-code"><pre><span class="p">(</span><span class="nv">setq-local</span> <span class="nv">treesit-font-lock-feature-list</span>
            <span class="o">'</span><span class="p">((</span><span class="nv">comment</span> <span class="nv">definition</span><span class="p">)</span>
              <span class="p">(</span><span class="kt">keyword</span> <span class="nb">string</span> <span class="nb">char</span> <span class="nc">symbol</span> <span class="nv">builtin</span> <span class="k">type</span><span class="p">)</span>
              <span class="p">(</span><span class="nv">constant</span> <span class="nc">number</span> <span class="k">quote</span> <span class="nv">metadata</span> <span class="nv">doc</span> <span class="nv">regex</span><span class="p">)</span>
              <span class="p">(</span><span class="nv">bracket</span> <span class="nv">deref</span> <span class="k">function</span> <span class="nv">tagged-literals</span><span class="p">)))</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>The pattern is the same: essentials first, progressively more detail at higher
levels. This way the default experience (level 3) is clean and readable, and
users who want the full rainbow can bump <code class="language-plaintext highlighter-rouge">treesit-font-lock-level</code> to 4. Better
yet, they can use <code class="language-plaintext highlighter-rouge">treesit-font-lock-recompute-features</code> to cherry-pick
individual features regardless of level:</p>

<div class="language-emacs-lisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
</pre></td><td class="rouge-code"><pre><span class="c1">;; Enable 'function' (level 4) without enabling all of level 4</span>
<span class="p">(</span><span class="nv">treesit-font-lock-recompute-features</span> <span class="o">'</span><span class="p">(</span><span class="k">function</span><span class="p">)</span> <span class="no">nil</span><span class="p">)</span>

<span class="c1">;; Disable 'bracket' even if the user's level would include it</span>
<span class="p">(</span><span class="nv">treesit-font-lock-recompute-features</span> <span class="no">nil</span> <span class="o">'</span><span class="p">(</span><span class="nv">bracket</span><span class="p">))</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>This gives users fine-grained control without requiring mode authors to
anticipate every preference.</p>

<h3 id="debugging-indentation">Debugging indentation</h3>

<p>Indentation issues are harder to diagnose because they depend on tree structure,
rule ordering, and anchor resolution:</p>

<ol>
  <li>Set <code class="language-plaintext highlighter-rouge">treesit--indent-verbose</code> to <code class="language-plaintext highlighter-rouge">t</code> – this logs which rule matched for each
line, what anchor was computed, and the final column.</li>
  <li>Use <code class="language-plaintext highlighter-rouge">treesit-explore-mode</code> to understand the parent chain. The key question
is always: “what is the parent node, and which rule matches it?”</li>
  <li>
    <p>Remember that <strong>rule order matters</strong> for indentation too – the first matching
rule wins. A typical set of rules reads top to bottom from most specific to
most general:</p>

    <div class="language-emacs-lisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
</pre></td><td class="rouge-code"><pre><span class="c1">;; Closing delimiters align with the opening construct</span>
<span class="p">((</span><span class="nv">node-is</span> <span class="s">")"</span><span class="p">)</span> <span class="nv">parent-bol</span> <span class="mi">0</span><span class="p">)</span>
<span class="p">((</span><span class="nv">node-is</span> <span class="s">"end"</span><span class="p">)</span> <span class="nv">parent-bol</span> <span class="mi">0</span><span class="p">)</span>

<span class="c1">;; then/else clauses align with their enclosing if</span>
<span class="p">((</span><span class="nv">node-is</span> <span class="s">"then_clause"</span><span class="p">)</span> <span class="nv">parent-bol</span> <span class="mi">0</span><span class="p">)</span>
<span class="p">((</span><span class="nv">node-is</span> <span class="s">"else_clause"</span><span class="p">)</span> <span class="nv">parent-bol</span> <span class="mi">0</span><span class="p">)</span>

<span class="c1">;; Bodies inside then/else are indented</span>
<span class="p">((</span><span class="nv">parent-is</span> <span class="s">"then_clause"</span><span class="p">)</span> <span class="nv">parent-bol</span> <span class="nv">neocaml-indent-offset</span><span class="p">)</span>
<span class="p">((</span><span class="nv">parent-is</span> <span class="s">"else_clause"</span><span class="p">)</span> <span class="nv">parent-bol</span> <span class="nv">neocaml-indent-offset</span><span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></div>    </div>
  </li>
  <li>
    <p>Watch out for the <strong>empty-line problem</strong>: when the cursor is on a blank line,
Tree-sitter has no node at point. The indentation engine falls back to the root
<code class="language-plaintext highlighter-rouge">compilation_unit</code> node as the parent, which typically matches the top-level
rule and gives column 0. In neocaml I solved this with a <code class="language-plaintext highlighter-rouge">no-node</code> rule that
looks at the previous line’s last token to decide indentation:</p>

    <div class="language-emacs-lisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre><span class="p">(</span><span class="nv">no-node</span> <span class="nv">prev-line</span> <span class="nv">neocaml--empty-line-offset</span><span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></div>    </div>
  </li>
</ol>

<h3 id="build-a-comprehensive-test-suite">Build a comprehensive test suite</h3>

<p>This is the single most important piece of advice. Font-lock and indentation are
easy to break accidentally, and manual testing doesn’t scale. Both projects use
<a href="https://github.com/jorgenschaefer/emacs-buttercup">Buttercup</a> (a BDD testing
framework for Emacs) with custom test macros.</p>

<p><strong>Font-lock tests</strong> insert code into a buffer, run <code class="language-plaintext highlighter-rouge">font-lock-ensure</code>, and assert
that specific character ranges have the expected face:</p>

<div class="language-emacs-lisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre><span class="p">(</span><span class="nv">when-fontifying-it</span> <span class="s">"fontifies let-bound functions"</span>
  <span class="p">(</span><span class="s">"let greet name = ..."</span>
   <span class="p">(</span><span class="mi">5</span> <span class="mi">9</span> <span class="nv">font-lock-function-name-face</span><span class="p">)))</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p><strong>Indentation tests</strong> insert code, run <code class="language-plaintext highlighter-rouge">indent-region</code>, and assert the result
matches the expected indentation:</p>

<div class="language-emacs-lisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
</pre></td><td class="rouge-code"><pre><span class="p">(</span><span class="nv">when-indenting-it</span> <span class="s">"indents a match expression"</span>
  <span class="s">"match x with"</span>
  <span class="s">"| 0 -&gt; \"zero\""</span>
  <span class="s">"| n -&gt; string_of_int n"</span><span class="p">)</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p><strong>Integration tests</strong> load real source files and verify that both font-locking
and indentation survive <code class="language-plaintext highlighter-rouge">indent-region</code> on the full file. This catches
interactions between rules that unit tests miss.</p>

<p><code class="language-plaintext highlighter-rouge">neocaml</code> has 200+ automated tests and <code class="language-plaintext highlighter-rouge">clojure-ts-mode</code> has even more.
Investing in test infrastructure early pays off enormously – I can refactor
indentation rules with confidence because the suite catches regressions
immediately.</p>

<h4 id="a-personal-story-on-testing-roi">A personal story on testing ROI</h4>

<p>When I became the maintainer of
<a href="https://github.com/clojure-emacs/clojure-mode">clojure-mode</a> many years ago, I
really struggled with making changes. There were no font-lock or indentation
tests, so every change was a leap of faith – you’d fix one thing and break three
others without knowing until someone filed a bug report. I spent years working
on a testing approach I was happy with, alongside many great contributors, and
the return on investment was massive.</p>

<p>The same approach – almost the same test macros – carried over directly to
<code class="language-plaintext highlighter-rouge">clojure-ts-mode</code> when we built the Tree-sitter version. And later I reused the
pattern again in <code class="language-plaintext highlighter-rouge">neocaml</code> and <code class="language-plaintext highlighter-rouge">asciidoc-mode</code>. One investment in testing
infrastructure, four projects benefiting from it.</p>

<p>I know that automated tests, for whatever reason, never gained much traction in
the Emacs community. Many popular packages have no tests at all. I hope stories
like this convince you that investing in tests is really important and pays off
– not just for the project where you write them, but for every project you build
after.</p>

<h3 id="pre-compile-queries">Pre-compile queries</h3>

<p>This one is specific to <code class="language-plaintext highlighter-rouge">clojure-ts-mode</code> but applies broadly: compiling
Tree-sitter queries at runtime is expensive. If you’re building queries
dynamically (e.g. with <code class="language-plaintext highlighter-rouge">treesit-font-lock-rules</code> called at mode init time),
consider pre-compiling them as <code class="language-plaintext highlighter-rouge">defconst</code> values. This made a noticeable
difference in <code class="language-plaintext highlighter-rouge">clojure-ts-mode</code>’s startup time.</p>

<h2 id="a-note-on-naming">A note on naming</h2>

<p>The Emacs community has settled on a <code class="language-plaintext highlighter-rouge">-ts-mode</code> suffix convention for
Tree-sitter-based modes: <code class="language-plaintext highlighter-rouge">python-ts-mode</code>, <code class="language-plaintext highlighter-rouge">c-ts-mode</code>, <code class="language-plaintext highlighter-rouge">ruby-ts-mode</code>, and so
on. This makes sense when both a legacy mode and a Tree-sitter mode coexist in
Emacs core – users need to choose between them. But I think the convention is
being applied too broadly, and I’m afraid the resulting name fragmentation will
haunt the community for years.</p>

<p>For new packages that don’t have a legacy counterpart, the <code class="language-plaintext highlighter-rouge">-ts-mode</code> suffix is
unnecessary. I named my packages <code class="language-plaintext highlighter-rouge">neocaml</code> (not <code class="language-plaintext highlighter-rouge">ocaml-ts-mode</code>) and
<code class="language-plaintext highlighter-rouge">asciidoc-mode</code> (not <code class="language-plaintext highlighter-rouge">adoc-ts-mode</code>) because there was no prior <code class="language-plaintext highlighter-rouge">neocaml-mode</code>
or <code class="language-plaintext highlighter-rouge">asciidoc-mode</code> to disambiguate from. The <code class="language-plaintext highlighter-rouge">-ts-</code> infix is an implementation
detail that shouldn’t leak into the user-facing name. Will we rename everything
again when Tree-sitter becomes the default and the non-TS variants are removed?</p>

<p>Be bolder with naming. If you’re building something new, give it a name that
makes sense on its own merits, not one that encodes the parsing technology in the
package name.</p>

<h2 id="the-road-ahead">The road ahead</h2>

<p>I think the full transition to Tree-sitter in the Emacs community will take
3–5 years, optimistically. There are hundreds of major modes out there, many
maintained by a single person in their spare time. Converting a mode from regex
to Tree-sitter isn’t just a mechanical translation – you need to understand the
grammar, rewrite font-lock and indentation rules, handle version compatibility,
and build a new test suite. That’s a lot of work.</p>

<p>Interestingly, this might be one area where agentic coding tools can genuinely
help. The structure of Tree-sitter-based major modes is fairly uniform: grammar
recipes, font-lock rules, indentation rules, navigation settings, imenu. If you
give an AI agent a grammar and a reference to a high-quality mode like
<code class="language-plaintext highlighter-rouge">clojure-ts-mode</code>, it could probably scaffold a reasonable new mode fairly
quickly. The hard parts – debugging grammar quirks, handling edge cases, getting
indentation <em>just right</em> – would still need human attention, but the boilerplate
could be automated.</p>

<p>Still, knowing the Emacs community, I wouldn’t be surprised if a full migration
never actually completes. Many old-school modes work perfectly fine, their
maintainers have no interest in Tree-sitter, and “if it ain’t broke, don’t fix
it” is a powerful force. And that’s okay – diversity of approaches is part of
what makes Emacs Emacs.</p>

<h2 id="closing-thoughts">Closing thoughts</h2>

<p>Tree-sitter is genuinely great for building Emacs major modes. The code is
simpler, the results are more accurate, and incremental parsing means everything
stays fast even on large files. I wouldn’t go back to regex-based font-locking
willingly.</p>

<p>But it’s not magical. Grammars are inconsistent across languages, the Emacs APIs
are still maturing, you can’t reuse <code class="language-plaintext highlighter-rouge">.scm</code> files (yet), and you’ll hit
version-specific bugs that require tedious workarounds. The testing story is
better than with regex modes – tree structures are more predictable than regex
matches – but you still need a solid test suite to avoid regressions.</p>

<p>If you’re thinking about writing a Tree-sitter-based major mode, do it. The
ecosystem needs more of them, and the experience of working with syntax trees
instead of regexes is genuinely enjoyable. Just go in with realistic
expectations, pin your grammar versions, test against multiple Emacs releases,
and build your test suite early.</p>

<p>Anyways, I wish there was an article like this one when I was starting out
with <code class="language-plaintext highlighter-rouge">clojure-ts-mode</code> and <code class="language-plaintext highlighter-rouge">neocaml</code>, so there you have it. I hope that
the lessons I’ve learned along the way will help build better modes
with Tree-sitter down the road.</p>

<p>That’s all I have for you today. Keep hacking!</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1">
      <p>See the excellent <a href="https://github.com/sogaiu/tree-sitter-clojure/blob/master/doc/scope.md">scope discussion</a> in the tree-sitter-clojure repo for the rationale. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;&#xfe0e;</a></p>
    </li>
    <li id="fn:2">
      <p>There’s ongoing discussion in the Emacs community about distributing pre-compiled grammar binaries, but nothing concrete yet. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;&#xfe0e;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Bozhidar Batsov</name></author><category term="Emacs" /><category term="OCaml" /><category term="Clojure" /><category term="AsciiDoc" /><summary type="html"><![CDATA[Over the past year I’ve been spending a lot of time building Tree-sitter-powered major modes for Emacs – clojure-ts-mode (as co-maintainer), neocaml (from scratch), and asciidoc-mode (also from scratch). Between the three projects I’ve accumulated enough knowledge (and battle scars) to write about the experience. This post distills the key lessons for anyone thinking about writing a Tree-sitter-based major mode, or curious about what it’s actually like.]]></summary></entry><entry><title type="html">Setting up Emacs for OCaml Development: Neocaml Edition</title><link href="https://batsov.com/articles/2026/02/24/setting-up-emacs-for-ocaml-development-neocaml-edition/" rel="alternate" type="text/html" title="Setting up Emacs for OCaml Development: Neocaml Edition" /><published>2026-02-24T12:00:00+02:00</published><updated>2026-02-24T12:00:00+02:00</updated><id>https://batsov.com/articles/2026/02/24/setting-up-emacs-for-ocaml-development-neocaml-edition</id><content type="html" xml:base="https://batsov.com/articles/2026/02/24/setting-up-emacs-for-ocaml-development-neocaml-edition/"><![CDATA[<p>A few years ago I wrote about <a href="/articles/2022/08/23/setting-up-emacs-for-ocaml-development/">setting up Emacs for OCaml
development</a>.
Back then the recommended stack was <code class="language-plaintext highlighter-rouge">tuareg-mode</code> + <code class="language-plaintext highlighter-rouge">merlin-mode</code>, with Merlin
providing the bulk of the IDE experience. A lot has changed since then – the
OCaml tooling has evolved considerably, and I’ve been working on some new tools
myself. Time for an update.</p>

<h2 id="the-new-stack">The New Stack</h2>

<p>Here’s what I recommend today:</p>

<ul>
  <li><a href="https://github.com/bbatsov/neocaml">neocaml</a> instead of <code class="language-plaintext highlighter-rouge">tuareg-mode</code></li>
  <li><a href="https://github.com/tarides/ocaml-eglot">ocaml-eglot</a> instead of <code class="language-plaintext highlighter-rouge">merlin-mode</code></li>
  <li><a href="https://www.gnu.org/software/emacs/manual/html_mono/eglot.html">Eglot</a> (built into Emacs 29+) as the LSP client</li>
</ul>

<p>The key shift is from Merlin’s custom protocol to LSP.
<a href="https://github.com/ocaml/ocaml-lsp">ocaml-lsp-server</a> has matured
significantly since my original article – it’s no longer a thin wrapper around
Merlin. It now offers project-wide renaming, semantic highlighting, Dune RPC
integration, and OCaml-specific extensions like pattern match generation and
typed holes. <code class="language-plaintext highlighter-rouge">ocaml-eglot</code> is a lightweight Emacs package by
<a href="https://tarides.com/">Tarides</a> that bridges Eglot with these OCaml-specific
LSP extensions, giving you the full Merlin feature set through a standardized
protocol.</p>

<p>And <code class="language-plaintext highlighter-rouge">neocaml</code> is my own Tree-sitter-powered OCaml major mode – modern, lean,
and built for the LSP era. You can read more about it in the <a href="/articles/2026/02/14/neocaml-0-1-ready-for-action/">0.1 release
announcement</a>.</p>

<h2 id="the-essentials">The Essentials</h2>

<p>First, install the server-side tools:</p>

<div class="language-console highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre><span class="gp">$</span><span class="w"> </span>opam <span class="nb">install </span>ocaml-lsp-server
</pre></td></tr></tbody></table></code></pre></div></div>

<blockquote class="prompt-tip">
  <p>You no longer need to install <code class="language-plaintext highlighter-rouge">merlin</code> separately – <code class="language-plaintext highlighter-rouge">ocaml-lsp-server</code>
vendors it internally.</p>
</blockquote>

<p>Then set up Emacs:</p>

<div class="language-emacs-lisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
</pre></td><td class="rouge-code"><pre><span class="c1">;; Modern Tree-sitter-powered OCaml major mode</span>
<span class="p">(</span><span class="nb">use-package</span> <span class="nv">neocaml</span>
  <span class="ss">:ensure</span> <span class="no">t</span><span class="p">)</span>

<span class="c1">;; Major mode for editing Dune project files</span>
<span class="p">(</span><span class="nb">use-package</span> <span class="nv">dune</span>
  <span class="ss">:ensure</span> <span class="no">t</span><span class="p">)</span>

<span class="c1">;; OCaml-specific LSP extensions via Eglot</span>
<span class="p">(</span><span class="nb">use-package</span> <span class="nv">ocaml-eglot</span>
  <span class="ss">:ensure</span> <span class="no">t</span>
  <span class="ss">:hook</span> <span class="p">(</span><span class="nv">neocaml-mode</span> <span class="o">.</span> <span class="nv">ocaml-eglot-setup</span><span class="p">))</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>That’s it. Eglot ships with Emacs 29+, so there’s nothing extra to install for
the LSP client itself. When you open an OCaml file, Eglot will automatically
start <code class="language-plaintext highlighter-rouge">ocaml-lsp-server</code> and you’ll have completion, type information, code
navigation, diagnostics, and all the other goodies you’d expect.</p>

<p>Compare this to the old setup – no more <code class="language-plaintext highlighter-rouge">merlin-mode</code>, <code class="language-plaintext highlighter-rouge">merlin-eldoc</code>,
<code class="language-plaintext highlighter-rouge">flycheck-ocaml</code>, or manual Company configuration. LSP handles all of it
through a single, uniform interface.</p>

<h2 id="the-toplevel">The Toplevel</h2>

<p><code class="language-plaintext highlighter-rouge">neocaml</code> includes built-in REPL integration via <code class="language-plaintext highlighter-rouge">neocaml-repl-minor-mode</code>. The
basics work well:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">C-c C-z</code> – start or switch to the OCaml toplevel</li>
  <li><code class="language-plaintext highlighter-rouge">C-c C-c</code> – send the current definition</li>
  <li><code class="language-plaintext highlighter-rouge">C-c C-r</code> – send the selected region</li>
  <li><code class="language-plaintext highlighter-rouge">C-c C-b</code> – send the entire buffer</li>
</ul>

<p>If you want <code class="language-plaintext highlighter-rouge">utop</code> specifically, you’re still better off using
<a href="https://github.com/ocaml-community/utop">utop.el</a> alongside <code class="language-plaintext highlighter-rouge">neocaml</code>. Its
main advantage is that you get code completion inside the <code class="language-plaintext highlighter-rouge">utop</code> REPL within
Emacs – something <code class="language-plaintext highlighter-rouge">neocaml</code>’s built-in REPL integration doesn’t provide:</p>

<div class="language-emacs-lisp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
</pre></td><td class="rouge-code"><pre><span class="p">(</span><span class="nb">use-package</span> <span class="nv">utop</span>
  <span class="ss">:ensure</span> <span class="no">t</span>
  <span class="ss">:hook</span> <span class="p">(</span><span class="nv">neocaml-mode</span> <span class="o">.</span> <span class="nv">utop-minor-mode</span><span class="p">))</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>This will shadow <code class="language-plaintext highlighter-rouge">neocaml</code>’s REPL keybindings with <code class="language-plaintext highlighter-rouge">utop</code>’s, which is the
intended behavior.</p>

<p>That said, as I’ve grown more comfortable with OCaml I find myself using the
toplevel less and less. These days I rely more on a test-driven workflow –
write a test, run it, iterate. In particular I’m partial to the workflow
described in <a href="https://discuss.ocaml.org/t/whats-your-development-workflow/10358/7">this OCaml Discuss
thread</a> –
running <code class="language-plaintext highlighter-rouge">dune runtest</code> continuously and writing expect tests for quick feedback.
It’s a more structured approach that scales better than REPL-driven development,
especially as your projects grow.</p>

<h2 id="give-neocaml-a-try">Give neocaml a Try</h2>

<p>If you’re an OCaml programmer using Emacs, I’d love for you to take
<a href="https://github.com/bbatsov/neocaml">neocaml</a> for a spin. It’s available on
MELPA, so getting started is just an <code class="language-plaintext highlighter-rouge">M-x package-install</code> away. The project is
still young and I’m actively working on it – your feedback, bug reports, and
pull requests are invaluable. Let me know what works, what doesn’t, and what
you’d like to see next.</p>

<p>That’s all I have for you today. Keep hacking!</p>]]></content><author><name>Bozhidar Batsov</name></author><category term="Emacs" /><category term="OCaml" /><summary type="html"><![CDATA[A few years ago I wrote about setting up Emacs for OCaml development. Back then the recommended stack was tuareg-mode + merlin-mode, with Merlin providing the bulk of the IDE experience. A lot has changed since then – the OCaml tooling has evolved considerably, and I’ve been working on some new tools myself. Time for an update.]]></summary></entry><entry><title type="html">Neocaml 0.1: Ready for Action</title><link href="https://batsov.com/articles/2026/02/14/neocaml-0-1-ready-for-action/" rel="alternate" type="text/html" title="Neocaml 0.1: Ready for Action" /><published>2026-02-14T18:34:00+02:00</published><updated>2026-02-14T18:34:00+02:00</updated><id>https://batsov.com/articles/2026/02/14/neocaml-0-1-ready-for-action</id><content type="html" xml:base="https://batsov.com/articles/2026/02/14/neocaml-0-1-ready-for-action/"><![CDATA[<p><a href="https://github.com/bbatsov/neocaml">neocaml</a> 0.1 is finally out! Almost a year
after I <a href="/articles/2025/03/14/neocaml-a-new-emacs-package-for-ocaml-programming/">announced the
project</a>,
I’m happy to report that it has matured to the point where I feel comfortable
calling it ready for action. Even better - <code class="language-plaintext highlighter-rouge">neocaml</code> recently landed in
<a href="https://melpa.org/#/neocaml">MELPA</a>, which means installing it is now as easy
as:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre>M-x package-install &lt;RET&gt; neocaml &lt;RET&gt;
</pre></td></tr></tbody></table></code></pre></div></div>

<p>That’s quite the journey from “a fun experimental project” to a proper Emacs
package!</p>

<h2 id="why-neocaml">Why neocaml?</h2>

<p>You might be wondering what’s wrong with the existing options. The short answer -
nothing is <em>wrong</em> per se, but <code class="language-plaintext highlighter-rouge">neocaml</code> offers a different set of trade-offs:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">caml-mode</code> is ancient and barely maintained. It lacks many features that
modern Emacs users expect and it probably should have been deprecated a long time ago.</li>
  <li><code class="language-plaintext highlighter-rouge">tuareg-mode</code> is very powerful, but also very complex. It carries a lot of
legacy code and its regex-based font-locking and custom indentation engine show
their age. It’s a beast - in both the good and the bad sense of the word.</li>
  <li><code class="language-plaintext highlighter-rouge">neocaml</code> aims to be a modern, lean alternative that fully embraces
Tree-sitter. The codebase is small, well-documented, and easy to hack on. If
you’re running Emacs 29+ (and especially Emacs 30), Tree-sitter is the future
and <code class="language-plaintext highlighter-rouge">neocaml</code> is built entirely around it.</li>
</ul>

<p>Of course, <code class="language-plaintext highlighter-rouge">neocaml</code> is the youngest of the bunch and it doesn’t yet match
Tuareg’s feature completeness. But for many OCaml workflows it’s already more
than sufficient, especially when combined with LSP support.</p>

<p>I’ve started the project mostly because I thought that the existing
Emacs tooling for OCaml was somewhat behind the times - e.g. both
<code class="language-plaintext highlighter-rouge">caml-mode</code> and <code class="language-plaintext highlighter-rouge">tuareg-mode</code> have features that are no longer needed
in the era of <code class="language-plaintext highlighter-rouge">ocamllsp</code>.</p>

<p>Let me now walk you through the highlights of version 0.1.</p>

<h2 id="features">Features</h2>

<p>The current feature-set is relatively modest, but all the essential functionality
one would expect from an Emacs major mode is there.</p>

<h3 id="tree-sitter-powered-syntax-highlighting">Tree-sitter-powered Syntax Highlighting</h3>

<p><code class="language-plaintext highlighter-rouge">neocaml</code> leverages Tree-sitter for syntax highlighting, which is both more
accurate and more performant than the traditional regex-based approaches used by
<code class="language-plaintext highlighter-rouge">caml-mode</code> and <code class="language-plaintext highlighter-rouge">tuareg-mode</code>. The font-locking supports 4 customizable
intensity levels (controlled via <code class="language-plaintext highlighter-rouge">treesit-font-lock-level</code>, default 3), so you
can pick the amount of color that suits your taste.</p>

<p>Both <code class="language-plaintext highlighter-rouge">.ml</code> (source) and <code class="language-plaintext highlighter-rouge">.mli</code> (interface) files get their own major modes with
dedicated highlighting rules.</p>

<h3 id="tree-sitter-powered-indentation">Tree-sitter-powered Indentation</h3>

<p>Indentation has always been tricky for OCaml modes, and I won’t pretend it’s
perfect yet, but <code class="language-plaintext highlighter-rouge">neocaml</code>’s Tree-sitter-based indentation engine is already quite
usable. It also supports cycle-indent functionality, so hitting <code class="language-plaintext highlighter-rouge">TAB</code> repeatedly
will cycle through plausible indentation levels - a nice quality-of-life feature
when the indentation rules can’t fully determine the “right” indent.</p>

<p>If you prefer, you can still delegate indentation to external tools like
<code class="language-plaintext highlighter-rouge">ocp-indent</code> or even Tuareg’s indentation functions. Still, I think most people
will be quite satisfied with the built-in indentation logic.</p>

<h3 id="code-navigation-and-imenu">Code Navigation and Imenu</h3>

<p><code class="language-plaintext highlighter-rouge">neocaml</code> provides proper structural navigation commands (<code class="language-plaintext highlighter-rouge">beginning-of-defun</code>,
<code class="language-plaintext highlighter-rouge">end-of-defun</code>, <code class="language-plaintext highlighter-rouge">forward-sexp</code>) powered by Tree-sitter, plus <code class="language-plaintext highlighter-rouge">imenu</code> integration –
navigating definitions in a buffer has never been easier.</p>

<p>The older modes provide very similar functionality as well, of course,
but the use of Tree-sitter in <code class="language-plaintext highlighter-rouge">neocaml</code> makes such commands more reliable and
robust.</p>

<h3 id="repl-integration">REPL Integration</h3>

<p>No OCaml mode would be complete without REPL (toplevel) integration. <code class="language-plaintext highlighter-rouge">neocaml-repl-minor-mode</code>
provides all the essentials:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">C-c C-z</code> - Start or switch to the OCaml REPL</li>
  <li><code class="language-plaintext highlighter-rouge">C-c C-c</code> - Send the current definition</li>
  <li><code class="language-plaintext highlighter-rouge">C-c C-r</code> - Send the selected region</li>
  <li><code class="language-plaintext highlighter-rouge">C-c C-b</code> - Send the entire buffer</li>
  <li><code class="language-plaintext highlighter-rouge">C-c C-p</code> - Send a phrase (code until <code class="language-plaintext highlighter-rouge">;;</code>)</li>
</ul>

<p>The default REPL is <code class="language-plaintext highlighter-rouge">ocaml</code>, but you can easily switch to <code class="language-plaintext highlighter-rouge">utop</code> via
<code class="language-plaintext highlighter-rouge">neocaml-repl-program-name</code>.</p>

<p>I’m still on the fence on whether I want to invest time into making the REPL-integration
more powerful or keep it as simple as possible. Right now it’s definitely not a big
priority for me, but I want to match what the other older OCaml modes offered in that regard.</p>

<h3 id="lsp-support">LSP Support</h3>

<p><code class="language-plaintext highlighter-rouge">neocaml</code> works great with <a href="https://www.gnu.org/software/emacs/manual/html_mono/eglot.html">Eglot</a> and
<code class="language-plaintext highlighter-rouge">ocamllsp</code>, automatically setting the appropriate language IDs for both <code class="language-plaintext highlighter-rouge">.ml</code> and
<code class="language-plaintext highlighter-rouge">.mli</code> files. Pair <code class="language-plaintext highlighter-rouge">neocaml</code> with
<a href="https://github.com/tarides/ocaml-eglot">ocaml-eglot</a> and you get a pretty
solid OCaml development experience.</p>

<p>The creation of LSP really simplified the lives of major mode authors like me, as now
many of the features that were historically major mode specific are provided by
LSP clients out-of-the-box.</p>

<p>That’s also another reason why you probably want a leaner major mode like <code class="language-plaintext highlighter-rouge">neocaml-mode</code>.</p>

<h3 id="other-goodies">Other Goodies</h3>

<p>But, wait, there’s more!</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">C-c C-a</code> to quickly switch between <code class="language-plaintext highlighter-rouge">.ml</code> and <code class="language-plaintext highlighter-rouge">.mli</code> files</li>
  <li>Prettify-symbols support for common OCaml operators</li>
  <li>Automatic installation of the required Tree-sitter grammars via <code class="language-plaintext highlighter-rouge">M-x neocaml-install-grammars</code></li>
  <li>Compatibility with <a href="https://github.com/ocaml/merlin">Merlin</a> for those who prefer it over LSP</li>
</ul>

<h2 id="the-road-ahead">The Road Ahead</h2>

<p>There’s still plenty of work to do:</p>

<ul>
  <li>Support for additional OCaml file types (e.g. <code class="language-plaintext highlighter-rouge">.mld</code>)</li>
  <li>Improvements to structured navigation using newer Emacs Tree-sitter APIs</li>
  <li>Improvements to the test suite</li>
  <li>Addressing feedback from real-world OCaml users</li>
  <li>Actually writing some fun OCaml code with <code class="language-plaintext highlighter-rouge">neocaml</code></li>
</ul>

<p>If you’re following me, you probably know that I’m passionate about both Emacs
and OCaml. I hope that <code class="language-plaintext highlighter-rouge">neocaml</code> will be my way to contribute to the awesome
OCaml community.</p>

<p>I’m not sure how quickly things will move, but I’m committed to making <code class="language-plaintext highlighter-rouge">neocaml</code>
the best OCaml editing experience on Emacs. Time will tell how far I’ll get!</p>

<h2 id="give-it-a-try">Give it a Try</h2>

<p>If you’re an OCaml programmer using Emacs, I’d love for you to take <code class="language-plaintext highlighter-rouge">neocaml</code> for
a spin. Install it from MELPA, kick the tires, and let me know what you think.
Bug reports, feature requests, and pull requests are all most welcome on
<a href="https://github.com/bbatsov/neocaml">GitHub</a>!</p>

<p>That’s all from me, folks! Keep hacking!</p>]]></content><author><name>Bozhidar Batsov</name></author><category term="OCaml" /><category term="Emacs" /><summary type="html"><![CDATA[neocaml 0.1 is finally out! Almost a year after I announced the project, I’m happy to report that it has matured to the point where I feel comfortable calling it ready for action. Even better - neocaml recently landed in MELPA, which means installing it is now as easy as:]]></summary></entry><entry><title type="html">Learning OCaml: Having Fun with the Fun Module</title><link href="https://batsov.com/articles/2025/07/19/learning-ocaml-having-fun-with-the-fun-module/" rel="alternate" type="text/html" title="Learning OCaml: Having Fun with the Fun Module" /><published>2025-07-19T14:20:00+03:00</published><updated>2025-07-19T14:20:00+03:00</updated><id>https://batsov.com/articles/2025/07/19/learning-ocaml-having-fun-with-the-fun-module</id><content type="html" xml:base="https://batsov.com/articles/2025/07/19/learning-ocaml-having-fun-with-the-fun-module/"><![CDATA[<p>When I started to play with OCaml I was kind of surprised that there was no
<code class="language-plaintext highlighter-rouge">id</code> (identity) function that was available out-of-box (in <code class="language-plaintext highlighter-rouge">Stdlib</code> module,
that’s auto-opened). A quick search led me to the
<a href="https://ocaml.org/manual/5.3/api/Fun.html">Fun</a> module, which is part of the
standard library and is nested under
<code class="language-plaintext highlighter-rouge">Stdlib</code>. It was introduced in OCaml 4.08, alongside other
modules such as <code class="language-plaintext highlighter-rouge">Int</code>, <code class="language-plaintext highlighter-rouge">Result</code> and <code class="language-plaintext highlighter-rouge">Option</code>.<sup id="fnref:1"><a href="#fn:1" class="footnote" rel="footnote" role="doc-noteref">1</a></sup></p>

<p>The <code class="language-plaintext highlighter-rouge">Fun</code> module provides a few basic combinators for working with functions.
Let’s go over them briefly:</p>

<ul>
  <li>
    <p><strong><code class="language-plaintext highlighter-rouge">Fun.id</code></strong></p>

    <div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre><span class="nn">Fun</span><span class="p">.</span><span class="n">id</span> <span class="o">:</span> <span class="k">'</span><span class="n">a</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">a</span>
</pre></td></tr></tbody></table></code></pre></div>    </div>

    <p>The identity function: returns its single argument unchanged.</p>
  </li>
  <li>
    <p><strong><code class="language-plaintext highlighter-rouge">Fun.const</code></strong></p>

    <div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre><span class="nn">Fun</span><span class="p">.</span><span class="n">const</span> <span class="o">:</span> <span class="k">'</span><span class="n">a</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">b</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">a</span>
</pre></td></tr></tbody></table></code></pre></div>    </div>

    <p>Returns a function that always returns the first argument, ignoring its second argument.</p>
  </li>
  <li>
    <p><strong><code class="language-plaintext highlighter-rouge">Fun.compose</code></strong></p>

    <div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre><span class="nn">Fun</span><span class="p">.</span><span class="n">compose</span> <span class="o">:</span> <span class="p">(</span><span class="k">'</span><span class="n">b</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">c</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">b</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">a</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">c</span>
</pre></td></tr></tbody></table></code></pre></div>    </div>

    <p>Composes two functions, applying the second function to the result of the
first. Haskell and F# have special syntax for function composition, but that’s
not the case in OCaml. (although you can easily map this to some operator if
you wish to do so) Also, <code class="language-plaintext highlighter-rouge">compose</code> was introduced a bit later than the other
functions in the module - namely in OCaml 5.2.</p>
  </li>
  <li>
    <p><strong><code class="language-plaintext highlighter-rouge">Fun.flip</code></strong></p>

    <div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre><span class="nn">Fun</span><span class="p">.</span><span class="n">flip</span> <span class="o">:</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">b</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">c</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">b</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">a</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">c</span>
</pre></td></tr></tbody></table></code></pre></div>    </div>

    <p>Reverses the order of arguments to a two-argument function.</p>
  </li>
  <li>
    <p><strong><code class="language-plaintext highlighter-rouge">Fun.negate</code></strong></p>

    <div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre><span class="nn">Fun</span><span class="p">.</span><span class="n">negate</span> <span class="o">:</span> <span class="p">(</span><span class="k">'</span><span class="n">a</span> <span class="o">-&gt;</span> <span class="kt">bool</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="k">'</span><span class="n">a</span> <span class="o">-&gt;</span> <span class="kt">bool</span>
</pre></td></tr></tbody></table></code></pre></div>    </div>

    <p>Negates a boolean-returning function, returning the opposite boolean value. Useful when you want to provide
a pair of inverse predicates (e.g. <code class="language-plaintext highlighter-rouge">is_positive</code> and <code class="language-plaintext highlighter-rouge">is_negative</code>)</p>
  </li>
</ul>

<p>I believe that those functions are pretty self-explanatory, but still below we’ll go over
a few examples of using them:</p>

<div class="language-ocaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
</pre></td><td class="rouge-code"><pre><span class="c">(* Fun.id *)</span>
<span class="nn">Fun</span><span class="p">.</span><span class="n">id</span> <span class="mi">42</span>
<span class="c">(* 42 *)</span>

<span class="c">(* Fun.const *)</span>
<span class="k">let</span> <span class="n">always_hello</span> <span class="o">=</span> <span class="nn">Fun</span><span class="p">.</span><span class="n">const</span> <span class="s2">"hello"</span>
<span class="n">always_hello</span> <span class="mi">12345</span>
<span class="c">(* "hello" *)</span>

<span class="c">(* Fun.flip *)</span>
<span class="k">let</span> <span class="n">subtract</span> <span class="n">a</span> <span class="n">b</span> <span class="o">=</span> <span class="n">a</span> <span class="o">-</span> <span class="n">b</span>
<span class="k">let</span> <span class="n">flipped_subtract</span> <span class="o">=</span> <span class="nn">Fun</span><span class="p">.</span><span class="n">flip</span> <span class="n">subtract</span>
<span class="n">flipped_subtract</span> <span class="mi">2</span> <span class="mi">10</span>
<span class="c">(* 8 *)</span>

<span class="c">(* Fun.negate *)</span>
<span class="k">let</span> <span class="n">is_odd</span> <span class="o">=</span> <span class="n">negate</span> <span class="n">is_even</span>
<span class="n">is_odd</span> <span class="mi">5</span>
<span class="c">(* true *)</span>

<span class="c">(* Fun.compose *)</span>
<span class="k">let</span> <span class="n">comp_f</span> <span class="o">=</span> <span class="nn">Fun</span><span class="p">.</span><span class="n">compose</span> <span class="p">(</span><span class="k">fun</span> <span class="n">x</span> <span class="o">-&gt;</span> <span class="n">x</span> <span class="o">*</span> <span class="n">x</span><span class="p">)</span> <span class="p">(</span><span class="k">fun</span> <span class="n">x</span> <span class="o">-&gt;</span> <span class="n">x</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span>
<span class="n">comp_f</span> <span class="mi">5</span>
<span class="c">(* 36 *)</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>Admittedly the examples are not great, but I hope they managed to convey how to use
the various combinators.</p>

<p>Those are definitely not the type of functions that you would use every day, but they can
be useful in certain situations. Obviously I needed <code class="language-plaintext highlighter-rouge">id</code> at some point to discover the
<code class="language-plaintext highlighter-rouge">Fun</code> module in the first place, and all of the functions there can be considered
“classic” combinators in functional programming.</p>

<p>In practice most often I need <code class="language-plaintext highlighter-rouge">id</code> and <code class="language-plaintext highlighter-rouge">negate</code>, and infrequently <code class="language-plaintext highlighter-rouge">comp</code> and <code class="language-plaintext highlighter-rouge">const</code>.
Right now I’m struggling to come up with good use-cases for <code class="language-plaintext highlighter-rouge">flip</code>, but
I’m sure those exist. Perhaps you’ll share some examples in the comments?</p>

<p>How often do you use the various combinators? Which ones do you find most useful?</p>

<p>I find myself wondering if such fundamental functions shouldn’t have been part of
<code class="language-plaintext highlighter-rouge">Stdlib</code> module directly, but overall I really like the modular standard library approach
that OCaml’s team has been working towards in the past several years.<sup id="fnref:2"><a href="#fn:2" class="footnote" rel="footnote" role="doc-noteref">2</a></sup> The important
thing in the end of the day is to know that these functions exist and you can
make use of them. Writing this short article will definitely help me to remember
this.</p>

<p>That’s all I have for you today. Keep hacking!</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1">
      <p>It was part of some broader efforts to slim down <code class="language-plaintext highlighter-rouge">Stdlib</code> and move in the direction of a more modular standard library. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;&#xfe0e;</a></p>
    </li>
    <li id="fn:2">
      <p>And obviously you can open the <code class="language-plaintext highlighter-rouge">Fun</code> module if you wish to at whatever level you desire. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;&#xfe0e;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Bozhidar Batsov</name></author><category term="OCaml" /><category term="Learning OCaml" /><summary type="html"><![CDATA[When I started to play with OCaml I was kind of surprised that there was no id (identity) function that was available out-of-box (in Stdlib module, that’s auto-opened). A quick search led me to the Fun module, which is part of the standard library and is nested under Stdlib. It was introduced in OCaml 4.08, alongside other modules such as Int, Result and Option.1 It was part of some broader efforts to slim down Stdlib and move in the direction of a more modular standard library. &#8617;&#xfe0e;]]></summary></entry></feed>