Thicket data repository for the EEG
1{
2 "id": "https://www.tunbury.org/2025/07/01/ocaml-functors",
3 "title": "OCaml Functors",
4 "link": "https://www.tunbury.org/2025/07/01/ocaml-functors/",
5 "updated": "2025-07-01T00:00:00",
6 "published": "2025-07-01T00:00:00",
7 "summary": "In my OCaml project, I’d like to abstract away the details of running containers into specific modules based on the OS. Currently, I have working container setups for Windows and Linux, and I’ve haphazardly peppered if Sys.win32 then where I need differentiation, but this is OCaml, so let us use functors!",
8 "content": "<p>In my OCaml project, I’d like to abstract away the details of running containers into specific modules based on the OS. Currently, I have working container setups for Windows and Linux, and I’ve haphazardly peppered <code>if Sys.win32 then</code> where I need differentiation, but this is OCaml, so let us use <em>functors</em>!</p>\n\n<p>I started by fleshing out the bare bones in a new project. After <code>dune init project functor</code>, I created <code>bin/s.ml</code> containing the signature of the module <code>CONTAINER</code>.</p>\n\n<div><div><pre><code><span>module</span> <span>type</span> <span>CONTAINER</span> <span>=</span> <span>sig</span>\n <span>val</span> <span>run</span> <span>:</span> <span>string</span> <span>-></span> <span>unit</span>\n<span>end</span>\n</code></pre></div></div>\n\n<p>Then a trivial <code>bin/linux.ml</code>.</p>\n\n<div><div><pre><code><span>let</span> <span>run</span> <span>s</span> <span>=</span> <span>Printf</span><span>.</span><span>printf</span> <span>\"Linux container '%s'</span><span>\\n</span><span>\"</span> <span>s</span>\n</code></pre></div></div>\n\n<p>And <code>bin/windows.ml</code>.</p>\n\n<div><div><pre><code><span>let</span> <span>run</span> <span>s</span> <span>=</span> <span>Printf</span><span>.</span><span>printf</span> <span>\"Windows container '%s'</span><span>\\n</span><span>\"</span> <span>s</span>\n</code></pre></div></div>\n\n<p>Then in <code>bin/main.ml</code>, I can select the container system once and from then on use <code>Container.foo</code> to run the appropriate OS specific function.</p>\n\n<div><div><pre><code><span>let</span> <span>container</span> <span>=</span> <span>if</span> <span>Sys</span><span>.</span><span>win32</span> <span>then</span> <span>(</span><span>module</span> <span>Windows</span> <span>:</span> <span>S</span><span>.</span><span>CONTAINER</span><span>)</span> <span>else</span> <span>(</span><span>module</span> <span>Linux</span> <span>:</span> <span>S</span><span>.</span><span>CONTAINER</span><span>)</span>\n\n<span>module</span> <span>Container</span> <span>=</span> <span>(</span><span>val</span> <span>container</span><span>)</span>\n\n<span>let</span> <span>()</span> <span>=</span> <span>Container</span><span>.</span><span>run</span> <span>\"Hello, World!\"</span>\n</code></pre></div></div>\n\n<p>You can additionally create <code>windows.mli</code> and <code>linux.mli</code> containing simply <code>include S.CONTAINER</code>.</p>\n\n<p>Now, let’s imagine that we needed to have some specific configuration options depending upon whether we are running on Windows or Linux. For demonstration purposes, let’s use the user account. On Windows, this is a string, typically <code>ContainerAdministrator</code>, whereas on Linux, it’s an integer UID of value 0.</p>\n\n<p>We can update the module type in <code>bin/s.ml</code> to include the type <code>t</code>, and add an <code>init</code> function to return a <code>t</code> and add <code>t</code> as a parameter to <code>run</code>.</p>\n\n<div><div><pre><code><span>module</span> <span>type</span> <span>CONTAINER</span> <span>=</span> <span>sig</span>\n <span>type</span> <span>t</span>\n\n <span>val</span> <span>init</span> <span>:</span> <span>unit</span> <span>-></span> <span>t</span>\n <span>val</span> <span>run</span> <span>:</span> <span>t</span> <span>-></span> <span>string</span> <span>-></span> <span>unit</span>\n<span>end</span>\n</code></pre></div></div>\n\n<p>In <code>bin/linux.ml</code>, we can add the type and define <code>uid</code> as an integer, then add the <code>init</code> function to return the populated structure. <code>run</code> now accepts <code>t</code> as the first parameter.</p>\n\n<div><div><pre><code><span>type</span> <span>t</span> <span>=</span> <span>{</span>\n <span>uid</span> <span>:</span> <span>int</span><span>;</span>\n<span>}</span>\n\n<span>let</span> <span>init</span> <span>()</span> <span>=</span> <span>{</span> <span>uid</span> <span>=</span> <span>0</span> <span>}</span>\n\n<span>let</span> <span>run</span> <span>t</span> <span>s</span> <span>=</span> <span>Printf</span><span>.</span><span>printf</span> <span>\"Linux container user id %i says '%s'</span><span>\\n</span><span>\"</span> <span>t</span><span>.</span><span>uid</span> <span>s</span>\n</code></pre></div></div>\n\n<p>In a similar vein, <code>bin/windows.ml</code> is updated like this</p>\n\n<div><div><pre><code><span>type</span> <span>t</span> <span>=</span> <span>{</span>\n <span>username</span> <span>:</span> <span>string</span><span>;</span>\n<span>}</span>\n\n<span>let</span> <span>init</span> <span>()</span> <span>=</span> <span>{</span> <span>username</span> <span>=</span> <span>\"ContainerAdministrator\"</span> <span>}</span>\n\n<span>let</span> <span>run</span> <span>t</span> <span>s</span> <span>=</span> <span>Printf</span><span>.</span><span>printf</span> <span>\"Windows container user name %s says '%s'</span><span>\\n</span><span>\"</span> <span>t</span><span>.</span><span>username</span> <span>s</span>\n</code></pre></div></div>\n\n<p>And finally, in <code>bin/main.ml</code> we run <code>Container.init ()</code> and use the returned type as a parameter to <code>Container.run</code>.</p>\n\n<div><div><pre><code><span>let</span> <span>container</span> <span>=</span> <span>if</span> <span>Sys</span><span>.</span><span>win32</span> <span>then</span> <span>(</span><span>module</span> <span>Windows</span> <span>:</span> <span>S</span><span>.</span><span>CONTAINER</span><span>)</span> <span>else</span> <span>(</span><span>module</span> <span>Linux</span> <span>:</span> <span>S</span><span>.</span><span>CONTAINER</span><span>)</span>\n\n<span>module</span> <span>Container</span> <span>=</span> <span>(</span><span>val</span> <span>container</span><span>)</span>\n\n<span>let</span> <span>c</span> <span>=</span> <span>Container</span><span>.</span><span>init</span> <span>()</span>\n<span>let</span> <span>()</span> <span>=</span> <span>Container</span><span>.</span><span>run</span> <span>c</span> <span>\"Hello, World!\"</span>\n</code></pre></div></div>",
9 "content_type": "html",
10 "author": {
11 "name": "Mark Elvers",
12 "email": "mark.elvers@tunbury.org",
13 "uri": null
14 },
15 "categories": [
16 "ocaml",
17 "tunbury.org"
18 ],
19 "source": "https://www.tunbury.org/atom.xml"
20}