Hello Here is the latest OCaml Weekly News, for the week of July 28 to August 04, 2026. Table of Contents ───────────────── SQLite rewritten in Pure OCaml with Claude Code for use in Mirage Unikernels New opam packages available : CamlSurf and Gles3 bcfg, a Boringly Simple ConFiGuration format Introcaml (alpha): Polymorphic Printing and Introspection for OCaml Learnings from benchmarking OxCaml-flavoured SWAR memchr OCaml Runtime Meeting: Mon, July 6 @ 10:00 UTC (10:00 London/Cambridge, 11:00 Paris, 7pm Sydney) Old CWN SQLite rewritten in Pure OCaml with Claude Code for use in Mirage Unikernels ════════════════════════════════════════════════════════════════════════════ Archive: Tej Pochiraju announced ─────────────────────── Hi all, For an internal project, I recently ported SQLite to pure OCaml for use within Mirage. Please find the [repo here]. Please note this is *AI generated* code - I designed and set up the test harness (Jepsen + SQLite parity) but didn't write any of the code. Beyond standard relational tables, there's fledgling support for column oriented tables to use in OLAP type queries. While already fairly capable, I expect this will see more commits as I make progress on the larger project. The larger goal is a full-fledged IoT based workflow framework I am building for use at my company, IoTReady.com. Our current framework is JS based and uses SQLite + Parquet as DBs. I wanted something similar but suitable for running within Mirage to meet some security and isolation density requirements. That framework is WIP and may not be released publicly. Hope this is useful to someone else too. Feel free to file feature requests and issues. Tej [repo here] New opam packages available : CamlSurf and Gles3 ════════════════════════════════════════════════ Archive: Christophe Raffalli announced ───────────────────────────── Dear Camlers, I am pleased to announce the latest version of *Gles3* and a new available software *CamlSurf* CamlSurf.1.0 ╌╌╌╌╌╌╌╌╌╌╌╌ CamlSurf is a software written using Gles3 that allows visualization of implicit surfaces and curves. You write a small script defining your surfaces and curves, then run one or more scripts with `CamlSurf_x11 script' or `CamlSurf_wayland script'. To install, `opam install CamlSurf_x11' or `opam install CamlSurf_wayland' should work (after `opam update'). See for the documentation and a nice video. Gles3.20260725 ╌╌╌╌╌╌╌╌╌╌╌╌╌╌ Gles3 is an interface to open GLES3.2 and EGL to do graphic programming. The main novelty compared to the previous version from 2016 are: • Support for two backends : x11 and wayland. windows (wgl) and macos (cocoa) backends are planned, volunteers are welcome. • Support for a lot of architectures : all the architectures provided by the opam-ci including freebsd and macos using quartzx as X server should work. One exception is win32, but actually it should build using x11 too. If I a volunteer can give it a try and tell me the msys2 or cygwin package list, I would do an update. Although it compiles on opam-ci, there might be bugs when running the examples. Bug report are welcome! • Multiple window : the new version can handle several windows. The simplest solution in that case is to run each event-loop in its own domain (see examples/windows). This is only because of that example that we require OCaml 5. Compilation from source should work on OCaml 4.14 too except for that example. • Basic support to load a font and convert a text to a texture. • Cleaned / extended examples, for instance a new example shows how to write a shader to get the 3D coordinates of a clicked pixel (allowing for precise object movement using the mouse) bcfg, a Boringly Simple ConFiGuration format ════════════════════════════════════════════ Archive: Calascibetta Romain announced ───────────────────────────── I am pleased to announce the first release of [`bcfg'], a tool and library for manipulating and generating configuration files in the [Simple ConFiGuration file format] (B stands for “boringly”). The `bcfg' project stems from Léo Andrès’s (@ancolie) [scfg] project (see ), but aims to go a step further: • it extends the syntax so that non-UTF-8 strings are accepted in a form similar to that specified in RFC822 (including _folding-whitespace_) • the dependency set has also been reduced to use only `ocamllex' instead of `sedlex', which is perfectly capable of handling UTF-8 strings • the distribution includes a `bcfgt' library based on @dbuenzli’s [jsont] design and a draft implementation of @swrup on `scfg' • the tool has been extended to include a linter (allowing configuration files to be reformatted) as well as a "`jq'-style" query tool to extract information from an expression and a configuration file • the ability to stream serialisation and deserialisation (as can be done with XML and the SAX model) The tools are also available on our apt.robur.coop repository via [our reproducible infrastructure]: ┌──── │ $ curl -fsSL https://apt.robur.coop/gpg.pub | gpg --dearmor > /usr/share/keyrings/apt.robur.coop.gpg │ $ echo "deb [signed-by=/usr/share/keyrings/apt.robur.coop.gpg] https://apt.robur.coop debian-13 main" \ │ | sudo tee /etc/apt/sources.list.d/robur.list │ $ sudo apt update │ $ sudo apt install bcfg └──── We recommend reading the [bcfg documentation] to learn more about this format, these libraries and these tools, in the hope that we might also resolve or further complicate the existential questions regarding the world’s best configuration format. [`bcfg'] [Simple ConFiGuration file format] [scfg] [jsont] [our reproducible infrastructure] [bcfg documentation] Introcaml (alpha): Polymorphic Printing and Introspection for OCaml ═══════════════════════════════════════════════════════════════════ Archive: Frédéric Bour announced ─────────────────────── We are excited to share an alpha preview of *Introcaml*, an extension of OCaml 5.5.0 that brings the power of toplevel-style introspection directly into user programs. What is Introcaml? ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ The OCaml toplevel has long been able to print arbitrary values without requiring explicit printer functions. However, this capability was internal to the toplevel. *Introcaml* brings this machinery into the standard library, enabling polymorphic printing and structural introspection of values of any type. By recovering structure from metadata embedded in compiled code, Introcaml allows you to inspect complex data structures, even those hidden behind abstraction barriers, without writing tedious `pp' functions. 🚀 How to Try It ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ Introcaml is available via opam. The recommended way to test the alpha version is to create a specific switch: ┌──── │ opam switch create 5.5.0+introcaml └──── ◊ Key Capabilities: • Polymorphic printing: Print any value regardless of its type, including abstract types. • Fully integrated with the toplevel, the debugger, the bytecode/native compilers, and their respective dynamic linkers. ◊ Code Examples ┌──── │ open Introspect.Print │ │ (* 1. Simple polymorphic printing *) │ type config = { host : string; port : int; debug : bool } │ print_any_endline { host = "localhost"; port = 8080; debug = true };; │ (* Output: {host = "localhost"; port = 8080; debug = true} *) │ │ (* 2. Breaking through abstraction *) │ module M = Map.Make(Int) │ print_any_endline (M.of_list [1, "one"; 2, "two"]);; │ (* Output: Node {l = Empty; v = 1; d = "one"; r = Node {l = Empty; v = 2; d = "two"; r = Empty; h = 1}; h = 2} *) │ │ (* 3. Quick 'n' Dirty printing with Introspect.P *) │ open Introspect.P │ let month = "August" │ let year = 2026 │ let () = println ["Welcome to "; month; " "; year; "!"] │ (* Output: Welcome to August 2026! *) └──── — 🛠 How it Works ╌╌╌╌╌╌╌╌╌╌╌╌╌╌ Introcaml implements a probabilistic metadata recovery scheme designed for high performance: 1. *Reserved bits:* It stores a "tag" in the reserved header bits of OCaml objects. 2. *Index:* A side-database (`Introspect.Index.t') maps these tags to a *descriptor* (`Introspect.Desc.t'), which describes the syntactic representation of the value. 3. *Zero overhead:* The compilation scheme is designed so that overhead is negligible in bytecode and virtually nonexistent in native mode. ◊ The [`Introspect'] API The new `Introspect' module provides several layers of access: *Low-level (for tool authors):* • `Desc': The representation of structural descriptors. • `Index': The mapping from object headers to descriptors. • `Dyn': A dynamic view of OCaml objects guided by descriptors, allowing for programmatic traversal (ideal for custom debug tools). *High-level (for general use):* • `Print': A `Format'-based polymorphic printing API. • `P': A convenience module for "quick and dirty" generic printing. — [`Introspect'] 🔍 Integration with existing printers ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ Previously, printing an opaque type in the toplevel or the debugger would simply result in `'. Now, these tools use type-directed printing by default but seamlessly switch to tag-based printing when encountering opaque constructions. ┌──── │ # let h = Hashtbl.create 3;; │ val h : ('_weak3, '_weak4) Hashtbl.t = │ │ {size = 0; data = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); │ seed = 0; initial_size = 16} └──── — ⚠️ Limitations & Current Status (Alpha) ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ As this is an alpha release, there are several known limitations. Some are temporary, while others are inherent to the design: • *Architecture & Compiler:* ‣ Does not support 32-bit architectures. (Tested on x86_64 and arm64). ‣ Not compatible with Flambda. ‣ `js_of_ocaml' is currently unsupported (though a fix is straightforward). • *Metadata Constraints:* ‣ *Constants:* Due to limited space for metadata, some constants cannot be printed (e.g., `print_any None' may print `0', but `println [None]' will likely succeed because the list wrapper provides metadata). ‣ *Poly-variants:* These are approximated (e.g., `print_any `A' may print `65 or `A'). ‣ *FFI:* Values originating from the FFI are not tagged and will be printed as raw tuples/values unless wrapped in a tagged structure. (Note: FFI compatibility is entirely preserved). • *Marshalling:* Tags are not preserved during marshalling by default. To preserve them, you must opt-in using the `Reserved_bits' flag: ┌──── │ let roundtrip flags x = Marshal.from_string (Marshal.to_string x flags) 0;; │ println [roundtrip [Reserved_bits] (ref 1)];; (* Output: {contents = 1} *) └──── • *Object Size:* By reserving 22 bits for metadata, the maximum length for arrays is \~4 billion elements and for strings is 32GB. — *Acknowledgements:* This work is funded by the [Ahrefs Grant Program for OCaml]. Kudos to Çağdaş Bozman et al. for the original work on `ocp-memprof', which provided the idea and infrastructure for repurposing header bits, and many thanks to the maintainers who have preserved this capability. 🤖 *Note:* No robots were harmed during the design and implementation of this feature, though their help was solicited for testing and proof-reading. [Ahrefs Grant Program for OCaml] Learnings from benchmarking OxCaml-flavoured SWAR memchr ════════════════════════════════════════════════════════ Archive: Keith discussed ─────────────── /Editor’s note: there were many long replies to this post, please follow the link above for the full discussion./ Hi everyone, just wanted to share a benchmarking project that I had created over the past few days for testing out OxCaml: I come from 10+ years of Rust, started hacking on it when it still had the Ruby-inspired closure syntax where parameters denoted by the two pipes are written inside of the block instead of outside of it (i.e. `{|arg| statements;}' instead of `|arg| { statements; }'). Certainly not the earliest batch, but early enough to know that before Rust was self-hosted, the compiler was written in OCaml, which is why I'm really interested in seeing how OCaml is "learning from its child". I had a brief stint in writing OCaml at a startup, but that was too short of a time to consider myself familiar with it. The README.md of the repo does a fairly good job in explaining the intricate details, and admittedly AI was used to write it, but what AI cannot tell you is the experience of writing it. Yes, I used AI, but no, I did not let it write everything, because one of the aims of this exercise is to help me learn both OCaml and OxCaml. Let's go through the good, the bad and the ugly parts of coding in OxCaml. Bear in mind that some of these points are due to me being an OCaml noob and am still learning things about the language. The Good ╌╌╌╌╌╌╌╌ *The conciseness and the compile-times*. I'm absolutely floored by just how elegant idiomatic OCaml source code looks. Everything just works without much ceremony: no braces nor brackets, no random `fn' keywords, and most importantly no cruft that gets in the way – you just `let there = light', and `there' was `light'. Not only that, the compile-verify-rewrite loop is quite fast, precisely because compile times are amazingly fast, so fast that even `utop' exists as a REPL, allowing me to test ideas out before committing to them. These aren't things that a Rustacean is used to: normally what happens is that whenever I encounter a new crate, I rely on docs.rs for documentation about the features coming from the crate, or spend time creating a new unit test and compiling it to see how it works. I can really now see why OCaml developers would want to continue writing OCaml despite it not being one of the most popular languages out there. *Dune*. This sort of ties back into the previous point on conciseness: build configuration is really just tuples/s-expressions. While I can still pretty much achieve the same effects in Rust with `Cargo.toml', everything being s-expressions in dune means that there really isn't much overhead. I now come to think of this style as *minimally yet sufficiently concise*, e.g. the brackets in an s-expression is the minimal structure around data, and semantics simply just fall out from what you put between the brackets. Integration with C is pretty seamless as well, along with Rust, though the latter still requires teaching dune to use cargo, which doesn't really strike me as surprising as C is much more well-established as the lingua franca in systems programming. *`[@@zero_alloc]'*. This is what OxCaml brings to the table and it delivers right away – mark your `val~s with it in your interface definitions, and it'll immediately try and catch any possible allocations that you make within your function. I'm used to zero-cost abstractions so being made aware that some language features aren't free in terms of allocations was quite new to me (I'm looking at you, ~option'). It makes sense upon closer inspection, but the initial surprise is still there – I guess I'm just really not used to thinking that nice language features have non-trivial costs associated with them. Anyway, the annotation certainly took a page from Rust, where the check happens *all at compilation time*, so code that compiles with that annotation is guaranteed not to allocate anything by the compiler. I love my parents. *Mutable local variables*. I know enough OCaml to know that the standard way is to use `'a ref', but seeing how `mutable' just works exactly the way I thought without much fanfare was quite nice. It also doesn't require using the `!' prefix when "dereferencing" the variable to read its value; the only downside is that it still does require `<-' for assignment. That's inevitable, since `=' has been overloaded with too much meaning; in fact, the odd one out here is logical equality, since let-bindings and record fields are arguably both in the same category of assignment operators. *Unboxed tuples and records*. They just work as expected, even in patterns. You really just need to prefix everything with `#', and it'll work, e.g. the common pattern that I often use in Rust when I need to return or bind two or more values at the same time is to use tuples, and in OxCaml it's pretty much the same: ┌──── │ let #(a, b) = if cond then #(#8L, foo) else #(#3L, bar) └──── *Automatic byte alignment*. OCaml strings and byte arrays are automatically aligned to a whole number of words, so I never need to worry about misalignment when reading the final partial word, nor requiring a prologue to first advance bytes so that it lands on a word boundary. The Bad ╌╌╌╌╌╌╌ *Functionality incompleteness*. This may just be a result of OxCaml still being a fairly new language extension, but some basic operations just aren't there when I reach out for them during development – logical infix operators, bit shifting, and even comparison infix operators. Some of them are not even in stdlib at all, but rather part of the compiler intrinsics – I had to dig `Bytes.unsafe_get_in64_ne_indexed_by_int64' out from the compiler source code in order to know that such functionalities even existed, and an unsafe load for unsigned `int8' currently doesn't exist even as an intrinsic anywhere. On the other hand, I get why the polymorphic infix operators don't work – `'a' has the `value' layout, and `int64#' has `bits64', and structural comparison between the two just wouldn't work. It would be so great if the polymorphic operators also ignores the layout that the arguments have as long as they match. *Performance impacts on certain functions*. Specifically, the ones that mix-and-match parameters with their boxed and unboxed counterparts. This may not actually affect anything, but I've been lied to by Claude so many times that I have to put this up, because intuitively speaking, unboxing and especially boxing has a non-negligible cost associated with them, and e.g. `Int64_u.shift_{left,right}' requires me to use an unboxed 2nd argument to indicate how many bits I want to shift by. I've been told that boxing for comparison using `Int64_u.compare' doesn't allocate anything despite having to compare the resulting boxed integer, and Claude says the disassembly from `memchr.a' proves it: ┌──── │ Int64_u.(compare ((to_int i) + 8) n < 0) │ --- │ 22a: lea 0x11(%rbx,%rbx,1),%r12 ; 2i + 17, i.e. tagged(i + 8) │ 22f: cmp %rdi,%r12 ; vs tagged n │ 232: jg 280 └──── I really can't know for sure whether this is every instruction emitted by the expression since `i' originally was an unboxed integer, and Claude may be missing the context where `i' is being retagged/reboxed again just to make this comparison. The unboxed variant looks like the following: ┌──── │ Int64_u.(compare (i + #32L) (of_int n) < 0) │ --- │ 101: lea 0x20(%rbx),%r12 ; i + 32 │ 109: cmp %rsi,%r12 ; vs n, pre-untagged at 0xd7 │ 10c: jg 224 └──── Taken at face value, both generate the same number of instructions and all but the parameters are identical, so if there's anything that's different, it has to be the hidden costs of the parameters being passed to these instructions. This bit matters, because it's in the hot loop execution path, and every instruction or nanosecond we save here can scale up to micro or even milliseconds saved. *Ergonomics*. This cuts both ways, I really like the localized imports that I showcased above with `Int64_u.(expression)' instead of tagging each function repeatedly, but doing it over and over again gets repetitive. I could indeed use a local import by doing `let open Int64_u in', however there are times where this doesn't work as expected, especially when I have to deal with boxed integers of varying sizes as well, like the aforementioned `int'. I don't think this is a problem that can be solved generally unless we solve the problem of polymorphic layouts, so I'd still list this as a pain point. The ugly ╌╌╌╌╌╌╌╌ *No continues/breaks/early returns*. This is starting to get really ugly – while I understand the argument from functional programmers that imperative code can usually be rewritten in a purer functional style without mutation, we're now in systems programming land, and every instruction emitted counts, and not being able to directly jump to where I want using continues, breaks or returns is a huge hole in my repertoire. Take for example, the code that I had to use to force an early exit in the loop: ┌──── │ while Int64_u.(i + #8L <= n64) do │ let r = swar_raw (Bytes.unsafe_get_int64_ne_indexed_by_int64 s i) in │ if not Int64_u.(r land mask = #0L) then ( │ hit <- i; │ i <- n64) │ else i <- Int64_u.(i + #8L) │ done └──── See what I did here? I essentially have to assign `n64' to `i' so that the loop condition fails just to "break" from the loop. And we're not done here either, because `i' denotes the offset at which to start reading bytes, so a later loop needs it to be preserved: ┌──── │ if Int64_u.(hit >= #0L) then i <- hit; └──── I got lucky here because I am able to recover `i' quite easily as I've assigned it to `hit', but one can easily imagine that you can't be as lucky with other loops. You may have to keep spare variables around just to store the state of the loop when you break it, just to preserve it and use it on subsequent code paths. Granted, this may not be as much of a problem as I make it to be, since it's not on the hot execution path but rather done at most once every loop, but `continue' is certainly not the case. I guess in a proper TCO'd recursive function, calling the function again with new parameters would be equivalent to a continue, but I haven't tested it out enough yet to really conclude that TCO is as fast as an imperative loop. *Attributes can be trivially defeated*. Not an OxCaml problem per se, but I accidentally defeated the usage of `[@inline always]' in the idiomatic ML flavour of memchr because the function I'm annotating captures a variable from the environment: ┌──── │ let[@inline always] swar_raw w = │ let w = Int64_u.(w lxor cs) in │ Int64_u.((w - ones) land lognot w) │ in └──── Both `cs' and `ones' aren't passed in as parameters, so now `swar_raw' becomes a real function that gets allocated *on the stack*, which means both `[@@zero_alloc]' and the benchmark's `mWd/Run' didn't catch it, as they measure *heap allocations*. Unfortunately, even stack allocations still cost performance, because calling functions are not free and requires setting up the call frame, loading the environment variables from memory, saving the context in the callee, and cleaning up before returning to the callee, none of which are trivial costs that can easily be eliminated. Admittedly, this was my personal skill issue for not being well-versed enough in OCaml, I just have a feeling that the compiler should have at least warned me that it isn't inlining the function as I told it to due to captured variables. Fixing this bug immediately brought the ML functional style memchr implementation down to the same speed as the imperative ML style memchr, and maybe even 1-2% faster based on benchmarks. Closing thoughts ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ Let's put into perspective about the numbers: OCaml's `Byte.index' is about 10x slower than the Rust/C equivalent, whereas OxCaml managed to get it down to about only 1.3x slower. This magnitude of difference is a huge win! With OxCaml as a language extension that may have its features upstreamed, OCaml can now have its system's slice of the cake and eat it too by being in the same order of magnitude as C/Rust. I'd say that OxCaml has completely justified its raison d'être, especially on the part where one can write idiomatic-ish OCaml syntax sprinkled with unboxed integers and still get performance close to bare metal. I think it's also good to address the friction points while developing in OxCaml – while writing this post, I've also tried to write a largely safe, idiomatic Rust version of memchr, and that alone is already about 10% faster than the OxCaml equivalent. What I wanted to bring up here isn't my varying skill levels in these two languages (although it may have indeed played a factor), but rather *how the programming language's philosophy guides the way you think*. If you've heard of linguistic relatively or the older Sapir-Whorf hypothesis in linguistics, this is exactly that, applied to programming languages – Rust is fundamentally about building safe, robust and efficient software, so the language features such as zero-cost abstractions, horrendously long compile times, bloated compiler error messages is designed exactly for you to write code that's going to be safe and fast by default. OCaml is not that, and it doesn't have to pretend to be something it isn't either, because it also has advantages over Rust that is hard to replicate there – fast compile times, layouts that are so uniform to the point where you really don't think about them, tail-call optimizations that brings loops/recursions close to bare-metal speed, and a richer type system that supports GADTs and monads, and let's not forget – without OCaml, there wouldn't be Rust. There's clearly still a lot that OCaml can offer to the world, and we (basically just me) still haven't seen all it has to offer yet. The pain points that I've listed are mostly because OxCaml is ostensibly written for systems programmers to write efficient code, and the syntax/tooling right now simply doesn't exactly support those goals just yet. Making it run faster is great; allowing anyone to easily write programs that are by default running efficiently is awesome. What's next ╌╌╌╌╌╌╌╌╌╌╌ OxCaml has SIMD support via `oxcaml_simd', so that's the natural next step to test and see how it fares when compared with the optimized C and Rust versions, the latter provided by BurntSushi's [`memchr' crate] (yes, it's his real username). After that, I'm looking to see if I can port some small and self-contained Rust crates over to OxCaml and continue experimenting on the performance characteristics. Bear in mind that `memchr' is a constrained example operating with only the input arguments; it'll be interesting to see how it works with systems that require storage and memory over time. [`memchr' crate] OCaml Runtime Meeting: Mon, July 6 @ 10:00 UTC (10:00 London/Cambridge, 11:00 Paris, 7pm Sydney) ════════════════════════════════════════════════════════════════════════════════════════════════ Archive: Continuing this thread, Tim McGilchrist announced ───────────────────────────────────────────────── Thank you for the correct @kit-ty-kate I'll post the notice a bit earlier for next month. The notes are available on The next edition will be Monday October 5th at the same time. Old CWN ═══════ If you happen to miss a CWN, you can [send me a message] and I'll mail it to you, or go take a look at [the archive] or the [RSS feed of the archives]. If you also wish to receive it every week by mail, you may subscribe to the [caml-list]. [Alan Schmitt] [send me a message] [the archive] [RSS feed of the archives] [caml-list] [Alan Schmitt]