# PPX Hacks for OCaml Context
I had this problem where I wanted to be able to create an extensible list of variants... but have the variants be closed after I was done with them. This is going to be useful for creating various contexts and middlewares in my OCaml SaaS framework I'm building.
That's a problem for another day to explain fully, but I just wanted to show that writing two PPX's to hack this together was actually quite easy! PPX doesn't have to be intimidating!
There's basically two different PPX that we have to construct. The first is for deriving our `context`, which looks like this:
```neovim
<pre>
<span class="-keyword">let</span> <span class="-variable"><span class="-function">args</span></span> <span class="Special"><span class="Delimiter">(</span><span class="Delimiter">)</span></span> <span class="Delimiter">=</span> <span class="-module">Deriving</span><span class="Delimiter">.</span><span class="-module">Args</span><span class="Delimiter">.</span><span class="Delimiter">(</span><span class="-variable">empty</span><span class="Delimiter">)</span>
<span class="-keyword">let</span> <span class="-variable"><span class="-function">generate_impl</span></span> <span class="Delimiter">~</span><span class="-variable">ctxt</span> <span class="Delimiter">(</span><span class="-variable"><span class="-character-special">_</span></span><span class="Delimiter">,</span> <span class="Delimiter">(</span><span class="-variable">type_declarations</span> <span class="Delimiter">:</span> <span class="-type">type_declaration</span> <span class="-type-builtin"><span class="-type">list</span></span><span class="Delimiter">)</span><span class="Delimiter">)</span> <span class="Delimiter">=</span>
<span class="-keyword">let</span> <span class="-variable">loc</span> <span class="Delimiter">=</span> <span class="-module">Expansion_context</span><span class="Delimiter">.</span><span class="-module">Deriver</span><span class="Delimiter">.</span><span class="-variable"><span class="-function">derived_item_loc</span></span> <span class="-variable">ctxt</span> <span class="-keyword">in</span>
<span class="-keyword">let</span> <span class="-variable">ty</span> <span class="Delimiter">=</span>
<span class="-keyword">match</span> <span class="-variable">type_declarations</span> <span class="-keyword">with</span>
<span class="Delimiter">|</span> <span class="Delimiter">[</span> <span class="-variable">ty</span> <span class="Delimiter">]</span> <span class="Delimiter">-></span> <span class="-variable">ty</span>
<span class="Delimiter">|</span> <span class="-variable"><span class="-character-special">_</span></span> <span class="Delimiter">-></span> <span class="-module">Util</span><span class="Delimiter">.</span><span class="-variable"><span class="-function">throw</span></span> <span class="Delimiter">~</span><span class="-label">loc</span> <span class="String">"ppx_table requires exactly one type declaration"</span>
<span class="-keyword">in</span>
<span class="-keyword">match</span> <span class="-variable">ty</span> <span class="-keyword">with</span>
<span class="Delimiter">|</span> <span class="Delimiter">{</span> <span class="-variable">ptype_manifest</span> <span class="Delimiter">=</span>
<span class="Special">Some</span> <span class="Delimiter">{</span> <span class="-variable">ptyp_desc</span> <span class="Delimiter">=</span> <span class="Special">Ptyp_variant</span> <span class="Delimiter">(</span><span class="Delimiter">[</span> <span class="Delimiter">{</span> <span class="-variable">prf_desc</span> <span class="Delimiter">=</span> <span class="Special">Rtag</span> <span class="Delimiter">(</span><span class="-variable">x</span><span class="Delimiter">,</span> <span class="-variable"><span class="-character-special">_</span></span><span class="Delimiter">,</span> <span class="Delimiter">[</span> <span class="-variable">t</span> <span class="Delimiter">]</span><span class="Delimiter">)</span><span class="Delimiter">;</span> _ <span class="Delimiter">}</span> <span class="Delimiter">]</span><span class="Delimiter">,</span> <span class="-variable"><span class="-character-special">_</span></span><span class="Delimiter">,</span> <span class="-variable"><span class="-character-special">_</span></span><span class="Delimiter">)</span> <span class="Delimiter">}</span>
<span class="Delimiter">;</span> _
<span class="Delimiter">}</span> <span class="Delimiter">-></span>
<span class="-keyword">let</span> <span class="-keyword">open</span> <span class="-module">Ast_builder</span><span class="Delimiter">.</span><span class="-module">Default</span> <span class="-keyword">in</span>
<span class="-keyword">let</span> <span class="-variable">construct</span> <span class="Delimiter">=</span> <span class="-variable"><span class="-function">pexp_variant</span></span> <span class="Delimiter">~</span><span class="-label">loc</span> <span class="-variable">x</span><span class="Delimiter">.</span><span class="-variable">txt</span> <span class="Delimiter">(</span><span class="Special">Some</span> <span class="Special">[%</span><span class="PreProc">expr</span> <span class="-variable">value</span><span class="Special"><span class="Delimiter">]</span></span><span class="Delimiter">)</span> <span class="-keyword">in</span>
<span class="-keyword">let</span> <span class="-variable">pat</span> <span class="Delimiter">=</span> <span class="-variable"><span class="-function">ppat_variant</span></span> <span class="Delimiter">~</span><span class="-label">loc</span> <span class="-variable">x</span><span class="Delimiter">.</span><span class="-variable">txt</span> <span class="Delimiter">(</span><span class="Special">Some</span> <span class="Delimiter">(</span><span class="-variable"><span class="-function">ppat_var</span></span> <span class="Delimiter">~</span><span class="-label">loc</span> <span class="Operator">@@</span> <span class="-module">Loc</span><span class="Delimiter">.</span><span class="-variable"><span class="-function">make</span></span> <span class="Delimiter">~</span><span class="-label">loc</span> <span class="String">"s"</span><span class="Delimiter">)</span><span class="Delimiter">)</span> <span class="-keyword">in</span>
<span class="Delimiter">[</span> <span class="Special">[%</span><span class="PreProc">stri</span> <span class="-keyword">let</span> <span class="-variable"><span class="-function">t</span></span> <span class="Delimiter">(</span><span class="-variable">value</span> <span class="Delimiter">:</span> <span class="Special">[%</span><span class="PreProc">t</span> <span class="-variable">t</span><span class="Special"><span class="Delimiter">]</span></span><span class="Delimiter">)</span> <span class="Delimiter">=</span> <span class="Special">[%</span><span class="PreProc">e</span> <span class="-variable">construct</span><span class="Special"><span class="Delimiter">]</span></span><span class="Special"><span class="Delimiter">]</span></span>
<span class="Delimiter">;</span> <span class="Special">[%</span><span class="PreProc">stri</span>
<span class="-keyword">let</span> <span class="-variable"><span class="-function">get</span></span> <span class="Delimiter">(</span><span class="-variable">l</span> <span class="Delimiter">:</span> <span class="Delimiter">[></span> <span class="-type">t</span> <span class="Delimiter">]</span> <span class="-type-builtin"><span class="-type">list</span></span><span class="Delimiter">)</span> <span class="Delimiter">:</span> <span class="Special">[%</span><span class="PreProc">t</span> <span class="-variable">t</span><span class="Special"><span class="Delimiter">]</span></span> <span class="Delimiter">=</span>
<span class="-module">Stdlib</span><span class="Delimiter">.</span><span class="-module">List</span><span class="Delimiter">.</span><span class="-variable"><span class="-function">find_map</span></span>
<span class="Delimiter">(</span><span class="-keyword">function</span>
<span class="Delimiter">|</span> <span class="Special">[%</span><span class="PreProc">p</span> <span class="-variable">pat</span><span class="Special"><span class="Delimiter">]</span></span> <span class="Delimiter">-></span> <span class="Special">Some</span> <span class="-variable">s</span>
<span class="Delimiter">|</span> <span class="-variable"><span class="-character-special">_</span></span> <span class="Delimiter">-></span> <span class="Special">None</span><span class="Delimiter">)</span>
<span class="-variable">l</span>
<span class="-_operator"><span class="Operator">|></span></span> <span class="-module">Stdlib</span><span class="Delimiter">.</span><span class="-module">Option</span><span class="Delimiter">.</span><span class="-variable"><span class="-function">get</span></span>
<span class="Delimiter">;;</span><span class="Special"><span class="Delimiter">]</span></span>
<span class="Delimiter">]</span>
<span class="Delimiter">|</span> <span class="-variable"><span class="-character-special">_</span></span> <span class="Delimiter">-></span> <span class="-module">Util</span><span class="Delimiter">.</span><span class="-variable"><span class="-function">throw</span></span> <span class="Delimiter">~</span><span class="-label">loc</span> <span class="String">"ppx_drive: context can only be applied to a single variant type"</span>
<span class="Delimiter">;;</span>
<span class="-keyword">let</span> <span class="-variable"><span class="-function">generator</span></span> <span class="Special"><span class="Delimiter">(</span><span class="Delimiter">)</span></span> <span class="Delimiter">=</span> <span class="-module">Deriving</span><span class="Delimiter">.</span><span class="-module">Generator</span><span class="Delimiter">.</span><span class="-module">V2</span><span class="Delimiter">.</span><span class="-variable"><span class="-function">make</span></span> <span class="Delimiter">(</span><span class="-variable"><span class="-function">args</span></span> <span class="Special"><span class="Delimiter">(</span><span class="Delimiter">)</span></span><span class="Delimiter">)</span> <span class="-variable">generate_impl</span>
<span class="-keyword">let</span> <span class="-variable">my_deriver</span> <span class="Delimiter">=</span> <span class="-module">Deriving</span><span class="Delimiter">.</span><span class="-variable"><span class="-function">add</span></span> <span class="String">"context"</span> <span class="Delimiter">~</span><span class="-label">str_type_decl</span><span class="Delimiter">:</span><span class="Delimiter">(</span><span class="-variable"><span class="-function">generator</span></span> <span class="Special"><span class="Delimiter">(</span><span class="Delimiter">)</span></span><span class="Delimiter">)</span></pre>
```
And the second PPX we have to create is for taking a list of contexts (all polymorphic variants) and created a list that is closed over their types.
```neovim
<pre>
<span class="-keyword">let</span> <span class="-variable">query_rule</span> <span class="Delimiter">=</span>
<span class="-keyword">let</span> <span class="-variable">context</span> <span class="Delimiter">=</span> <span class="-module">Extension</span><span class="Delimiter">.</span><span class="-module">Context</span><span class="Delimiter">.</span><span class="-variable">structure_item</span> <span class="-keyword">in</span>
<span class="-keyword">let</span> <span class="-variable"><span class="-function">extracter</span></span> <span class="Special"><span class="Delimiter">(</span><span class="Delimiter">)</span></span> <span class="Delimiter">=</span>
<span class="-keyword">let</span> <span class="-keyword">open</span> <span class="-module">Ast_pattern</span> <span class="-keyword">in</span>
<span class="-keyword">let</span> <span class="-variable">pat</span> <span class="Delimiter">=</span> <span class="-variable"><span class="-function">ppat_var</span></span> <span class="-variable">__</span> <span class="-keyword">in</span>
<span class="-keyword">let</span> <span class="-variable">binding</span> <span class="Delimiter">=</span> <span class="-variable"><span class="-function">value_binding</span></span> <span class="Delimiter">~</span><span class="-label">pat</span> <span class="Delimiter">~</span><span class="-label">expr</span><span class="Delimiter">:</span><span class="Delimiter">(</span><span class="-variable"><span class="-function">elist</span></span> <span class="-variable">__</span><span class="Delimiter">)</span> <span class="-keyword">in</span>
<span class="-variable"><span class="-function">pstr</span></span> <span class="Delimiter">(</span><span class="-variable"><span class="-function">pstr_value</span></span> <span class="-variable">nonrecursive</span> <span class="Delimiter">(</span><span class="-variable">binding</span> <span class="Operator">^::</span> <span class="-variable">nil</span><span class="Delimiter">)</span> <span class="Operator">^::</span> <span class="-variable">nil</span><span class="Delimiter">)</span>
<span class="-keyword">in</span>
<span class="-keyword">let</span> <span class="-variable">my_extender</span> <span class="Delimiter">=</span>
<span class="-module">Extension</span><span class="Delimiter">.</span><span class="-module">V3</span><span class="Delimiter">.</span><span class="-variable"><span class="-function">declare</span></span> <span class="String">"context"</span> <span class="-variable">context</span> <span class="Delimiter">(</span><span class="-variable"><span class="-function">extracter</span></span> <span class="Special"><span class="Delimiter">(</span><span class="Delimiter">)</span></span><span class="Delimiter">)</span>
<span class="Operator">@@</span> <span class="-keyword">fun</span> <span class="Delimiter">~</span><span class="-variable">ctxt</span> <span class="-variable">pat</span> <span class="-variable">query</span> <span class="Delimiter">-></span>
<span class="-keyword">let</span> <span class="-keyword">open</span> <span class="-module">Ast_builder</span><span class="Delimiter">.</span><span class="-module">Default</span> <span class="-keyword">in</span>
<span class="-keyword">let</span> <span class="-variable">loc</span> <span class="Delimiter">=</span> <span class="-module">Expansion_context</span><span class="Delimiter">.</span><span class="-module">Extension</span><span class="Delimiter">.</span><span class="-variable"><span class="-function">extension_point_loc</span></span> <span class="-variable">ctxt</span> <span class="-keyword">in</span>
<span class="-keyword">let</span> <span class="-variable">ty_ctx</span> <span class="Delimiter">=</span>
<span class="-module">List</span><span class="Delimiter">.</span><span class="-variable"><span class="-function">fold</span></span> <span class="-variable">query</span> <span class="Delimiter">~</span><span class="-label">init</span><span class="Delimiter">:</span><span class="Delimiter">[</span><span class="Delimiter">]</span> <span class="Delimiter">~</span><span class="-label">f</span><span class="Delimiter">:</span><span class="Delimiter">(</span><span class="-keyword">fun</span> <span class="-variable">acc</span> <span class="-variable">expr</span> <span class="Delimiter">-></span>
<span class="-keyword">match</span> <span class="-variable">expr</span> <span class="-keyword">with</span>
<span class="Delimiter">|</span> <span class="Delimiter">{</span> <span class="-variable">pexp_desc</span> <span class="Delimiter">=</span>
<span class="Special">Pexp_apply</span> <span class="Delimiter">(</span><span class="Delimiter">{</span> <span class="-variable">pexp_desc</span> <span class="Delimiter">=</span> <span class="Special">Pexp_ident</span> <span class="Delimiter">{</span> <span class="-variable">txt</span> <span class="Delimiter">=</span> <span class="Special">Ldot</span> <span class="Delimiter">(</span><span class="Special">Lident</span> <span class="-variable">m</span><span class="Delimiter">,</span> <span class="String">"t"</span><span class="Delimiter">)</span> <span class="-keyword">as</span> <span class="-variable">txt</span> <span class="Delimiter">}</span><span class="Delimiter">;</span> _ <span class="Delimiter">}</span><span class="Delimiter">,</span> <span class="-variable"><span class="-character-special">_</span></span><span class="Delimiter">)</span>
<span class="Delimiter">;</span> <span class="-variable">pexp_loc</span>
<span class="Delimiter">}</span> <span class="Delimiter">-></span>
<span class="-keyword">let</span> <span class="-variable">t</span> <span class="Delimiter">=</span> <span class="-variable"><span class="-function">ptyp_constr</span></span> <span class="Delimiter">~</span><span class="-label">loc</span> <span class="Delimiter">{</span> <span class="-variable">txt</span><span class="Delimiter">;</span> <span class="-variable">loc</span> <span class="Delimiter">}</span> <span class="Delimiter">[</span><span class="Delimiter">]</span> <span class="-keyword">in</span>
<span class="-keyword">let</span> <span class="-variable">f</span> <span class="Delimiter">=</span> <span class="Delimiter">{</span> <span class="-variable">prf_desc</span> <span class="Delimiter">=</span> <span class="Special">Rinherit</span> <span class="-variable">t</span><span class="Delimiter">;</span> <span class="-variable">prf_loc</span> <span class="Delimiter">=</span> <span class="-variable">pexp_loc</span><span class="Delimiter">;</span> <span class="-variable">prf_attributes</span> <span class="Delimiter">=</span> <span class="Delimiter">[</span><span class="Delimiter">]</span> <span class="Delimiter">}</span> <span class="-keyword">in</span>
<span class="-variable">f</span> <span class="Operator">::</span> <span class="-variable">acc</span>
<span class="Delimiter">|</span> <span class="-variable"><span class="-character-special">_</span></span> <span class="Delimiter">-></span> <span class="-module">Util</span><span class="Delimiter">.</span><span class="-variable"><span class="-function">throw</span></span> <span class="Delimiter">~</span><span class="-label">loc</span> <span class="String">"ppx_drive: expressions must be a Module.t"</span><span class="Delimiter">)</span>
<span class="-keyword">in</span>
<span class="-keyword">let</span> <span class="-variable">ty</span> <span class="Delimiter">=</span> <span class="-variable"><span class="-function">ptyp_variant</span></span> <span class="Delimiter">~</span><span class="-label">loc</span> <span class="-variable">ty_ctx</span> <span class="Special">Closed</span> <span class="Special">None</span> <span class="-keyword">in</span>
<span class="-keyword">let</span> <span class="-variable">query</span> <span class="Delimiter">=</span> <span class="-variable"><span class="-function">elist</span></span> <span class="Delimiter">~</span><span class="-label">loc</span> <span class="-variable">query</span> <span class="-keyword">in</span>
<span class="Special">[%</span><span class="PreProc">stri</span> <span class="-keyword">let</span> <span class="-variable">ctx</span> <span class="Delimiter">:</span> <span class="Special">[%</span><span class="PreProc">t</span> <span class="-variable">ty</span><span class="Special"><span class="Delimiter">]</span></span> <span class="-type-builtin"><span class="-type">list</span></span> <span class="Delimiter">=</span> <span class="Special">[%</span><span class="PreProc">e</span> <span class="-variable">query</span><span class="Special"><span class="Delimiter">]</span></span><span class="Special"><span class="Delimiter">]</span></span>
<span class="-keyword">in</span>
<span class="-module">Context_free</span><span class="Delimiter">.</span><span class="-module">Rule</span><span class="Delimiter">.</span><span class="-variable"><span class="-function">extension</span></span> <span class="-variable">my_extender</span>
<span class="Delimiter">;;</span>
<span class="-module">Driver</span><span class="Delimiter">.</span><span class="-variable"><span class="-function">register_transformation</span></span> <span class="Delimiter">~</span><span class="-label">rules</span><span class="Delimiter">:</span><span class="Delimiter">[</span> <span class="-variable">query_rule</span> <span class="Delimiter">]</span> <span class="String">"ppx_drive"</span></pre>
```
This let's us do some pretty easy & nifty tricks. I may make `deriving context` a little easier in the future - perhaps something like `let%context User = 'user string` or something, but it seems a little too magical... Anyway, this is what works now!
```neovim
<pre>
<span class="-keyword">module</span> <span class="-module">User</span> <span class="Delimiter">=</span> <span class="-keyword">struct</span>
<span class="-keyword">type</span> <span class="-type">t</span> <span class="Delimiter">=</span> <span class="Delimiter">[</span> <span class="Special">`user</span> <span class="-keyword">of</span> <span class="-type-builtin"><span class="-type">string</span></span> <span class="Delimiter">]</span> <span class="Special">[@@</span><span class="PreProc">deriving</span> <span class="-variable">context</span><span class="Special"><span class="Delimiter">]</span></span>
<span class="-keyword">end</span>
<span class="-keyword">module</span> <span class="-module">Log</span> <span class="Delimiter">=</span> <span class="-keyword">struct</span>
<span class="-keyword">type</span> <span class="-type">t</span> <span class="Delimiter">=</span> <span class="Delimiter">[</span> <span class="Special">`log</span> <span class="-keyword">of</span> <span class="-type-builtin"><span class="-type">string</span></span> <span class="Delimiter">-></span> <span class="-type-builtin"><span class="-type">unit</span></span> <span class="Delimiter">]</span> <span class="Special">[@@</span><span class="PreProc">deriving</span> <span class="-variable">context</span><span class="Special"><span class="Delimiter">]</span></span>
<span class="-keyword">end</span>
<span class="Comment"><span class="-spell">(* let ctx : [ User.t | Log.t ] list = [ User.t "teej_dv"; Log.t print_endline ] *)</span></span>
<span class="-keyword">let</span><span class="Special">%</span><span class="PreProc">context</span> <span class="-variable">ctx</span> <span class="Delimiter">=</span> <span class="Delimiter">[</span> <span class="-module">User</span><span class="Delimiter">.</span><span class="-variable"><span class="-function">t</span></span> <span class="String">"teej_dv"</span><span class="Delimiter">;</span> <span class="-module">Log</span><span class="Delimiter">.</span><span class="-variable"><span class="-function">t</span></span> <span class="-variable">print_endline</span> <span class="Delimiter">]</span>
<span class="-keyword">let</span> <span class="-variable">user</span> <span class="Delimiter">=</span> <span class="-module">User</span><span class="Delimiter">.</span><span class="-variable"><span class="-function">get</span></span> <span class="-variable">ctx</span>
```
This example works great! The `User.get` function retrieves the value from the context and is type safe! It doesn't return an option - but instead returns the actual value.
And what's even better is that if we do not include a `User.t` value in the list, it will not work, like below!
```neovim
<pre>
<span class="-keyword">let</span><span class="Special">%</span><span class="PreProc">context</span> <span class="-variable">ctx</span> <span class="Delimiter">=</span> <span class="Delimiter">[</span> <span class="-module">Log</span><span class="Delimiter">.</span><span class="-variable"><span class="-function">t</span></span> <span class="-variable">print_endline</span> <span class="Delimiter">]</span>
<span class="-keyword">let</span> <span class="-variable">user</span> <span class="Delimiter">=</span> <span class="-module">User</span><span class="Delimiter">.</span><span class="-variable"><span class="-function">get</span></span> <span class="-variable"><span class="DiagnosticUnderlineError">ctx</span></span>
<span class="DiagnosticError"> </span><span class="DiagnosticError">└──── </span><span class="DiagnosticError">This expression has type Log.t list but an expression was expected of type</span>
<span class="DiagnosticError"> </span><span class="DiagnosticError"> </span><span class="DiagnosticError"> [> User.t ] list</span>
<span class="DiagnosticError"> </span><span class="DiagnosticError"> </span><span class="DiagnosticError">Type Log.t = [ `log of string -> unit ] is not compatible with type</span>
<span class="DiagnosticError"> </span><span class="DiagnosticError"> </span><span class="DiagnosticError"> [> User.t ] = [> `user of string ]</span>
<span class="DiagnosticError"> </span><span class="DiagnosticError"> </span><span class="DiagnosticError">The first variant type does not allow tag(s) `user</span>
</pre>
```
It even has a pretty reasonable error, all things considered.