<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Hercules Lemke Merscher</title>
    <description>The latest articles on Forem by Hercules Lemke Merscher (@bitmaybewise).</description>
    <link>https://forem.com/bitmaybewise</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F187316%2Faed95d8a-d5c5-4d3e-8dab-513ed83b24c3.jpeg</url>
      <title>Forem: Hercules Lemke Merscher</title>
      <link>https://forem.com/bitmaybewise</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/bitmaybewise"/>
    <language>en</language>
    <item>
      <title>Tsonnet #35 — Call me maybe, but make it typed, part 1</title>
      <dc:creator>Hercules Lemke Merscher</dc:creator>
      <pubDate>Wed, 15 Apr 2026 07:00:00 +0000</pubDate>
      <link>https://forem.com/bitmaybewise/tsonnet-35-call-me-maybe-but-make-it-typed-part-1-58i2</link>
      <guid>https://forem.com/bitmaybewise/tsonnet-35-call-me-maybe-but-make-it-typed-part-1-58i2</guid>
      <description>&lt;p&gt;Welcome to the &lt;a href="https://gitlab.com/bitmaybewise/tsonnet" rel="noopener noreferrer"&gt;Tsonnet&lt;/a&gt; series!&lt;/p&gt;

&lt;p&gt;If you're not following along, check out how it all started in &lt;a href="https://dev.to/bitmaybewise/tsonnet-0-from-zero-to-interpreter-54na"&gt;the first post of the series&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In the previous post, we added warnings for unused variables and untouched bindings:&lt;/p&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/bitmaybewise/tsonnet-34-dabbling-with-untouched-bindings-i5l" class="crayons-story__hidden-navigation-link"&gt;Tsonnet #34 - Dabbling with untouched bindings&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/bitmaybewise" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F187316%2Faed95d8a-d5c5-4d3e-8dab-513ed83b24c3.jpeg" alt="bitmaybewise profile" class="crayons-avatar__image" width="279" height="279"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/bitmaybewise" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Hercules Lemke Merscher
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Hercules Lemke Merscher
                
              
              &lt;div id="story-author-preview-content-3355366" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/bitmaybewise" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F187316%2Faed95d8a-d5c5-4d3e-8dab-513ed83b24c3.jpeg" class="crayons-avatar__image" alt="" width="279" height="279"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Hercules Lemke Merscher&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/bitmaybewise/tsonnet-34-dabbling-with-untouched-bindings-i5l" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Mar 15&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/bitmaybewise/tsonnet-34-dabbling-with-untouched-bindings-i5l" id="article-link-3355366"&gt;
          Tsonnet #34 - Dabbling with untouched bindings
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/tsonnet"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;tsonnet&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/jsonnet"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;jsonnet&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/compiler"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;compiler&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
            &lt;a href="https://dev.to/bitmaybewise/tsonnet-34-dabbling-with-untouched-bindings-i5l#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            14 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


&lt;p&gt;Now let's get to the most exciting feature in any programming language: &lt;strong&gt;functions&lt;/strong&gt;!&lt;/p&gt;

&lt;h2&gt;
  
  
  What are we even working with here?
&lt;/h2&gt;

&lt;p&gt;A simple inline function with a positional parameter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;samples/functions/positional_params.jsonnet&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;my_function(x)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="err"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;my_function(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And a function with a multiline body:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;samples/functions/multiline.jsonnet&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;simple_function(x,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;y)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;y;&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;multiline_function(x)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;temp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="err"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;temp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;temp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;;&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;multiline_function(&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;simple_function(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Parsing (a.k.a. making sense of text)
&lt;/h2&gt;

&lt;p&gt;We need two new AST variants -- one for function definition, one for function call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/lib/ast.ml b/lib/ast.ml
index ace6241..106f37c 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/lib/ast.ml
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/lib/ast.ml
&lt;/span&gt;&lt;span class="p"&gt;@@ -90,6 +90,8 @@&lt;/span&gt; type expr =
   | Local of position * (string * expr) list
   | Seq of expr list
   | IndexedExpr of position * string * expr
&lt;span class="gi"&gt;+  | FunctionDef of position * (string * string list * expr)
+  | FunctionCall of position * string * expr list
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt; and object_entry =
   | ObjectField of string * expr
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the parser rules to match:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/lib/parser.mly b/lib/parser.mly
index 7adb1ee..102d202 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/lib/parser.mly
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/lib/parser.mly
&lt;/span&gt;&lt;span class="p"&gt;@@ -59,6 +59,7 @@&lt;/span&gt; assignable_expr:
   | op = unary_op; e = assignable_expr { UnaryOp (with_pos $startpos $endpos, op, e) }
   | e = indexed_expr { e }
   | e = obj_field_access { e }
&lt;span class="gi"&gt;+  | e = funcall { e }
&lt;/span&gt;   ;
&lt;span class="err"&gt;
&lt;/span&gt; indexed_expr:
&lt;span class="p"&gt;@@ -173,7 +174,28 @@&lt;/span&gt; var:
   varname = ID; ASSIGN; e = assignable_expr { (varname, e) };
&lt;span class="err"&gt;
&lt;/span&gt; vars:
&lt;span class="gd"&gt;-  LOCAL; vars = separated_nonempty_list(COMMA, var) { Local (with_pos $startpos $endpos, vars) };
&lt;/span&gt;&lt;span class="gi"&gt;+  | LOCAL; vars = separated_nonempty_list(COMMA, var) { Local (with_pos $startpos $endpos, vars) }
+  | LOCAL; def = fundef { FunctionDef (with_pos $startpos $endpos, def) }
+  ;
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt; single_var:
&lt;span class="gd"&gt;-  LOCAL; var_expr = var { Local (with_pos $startpos $endpos, [var_expr]) };
&lt;/span&gt;&lt;span class="gi"&gt;+  | LOCAL; var_expr = var { Local (with_pos $startpos $endpos, [var_expr]) }
+  ;
+
+fundef:
+  | fname = ID;
+    LEFT_PAREN; params = separated_nonempty_list(COMMA, ID); RIGHT_PAREN;
+    ASSIGN;
+    body = fundef_body { (fname, params, body) }
+  ;
+
+fundef_body:
+  | e = assignable_expr { e }
+  | local_bindings = vars; SEMICOLON; body = fundef_body { Seq [local_bindings; body] }
+  ;
+
+funcall:
+  | fname = ID;
+    LEFT_PAREN; params = separated_nonempty_list(COMMA, assignable_expr); RIGHT_PAREN
+    { FunctionCall (with_pos $startpos $endpos, fname, params) }
+  ;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;fundef_body&lt;/code&gt; deserves a note: it lets us nest &lt;code&gt;local&lt;/code&gt; bindings inside the function body, which is what makes &lt;code&gt;multiline_function&lt;/code&gt; work. A function body is either a plain expression or a local binding followed by a semicolon and the rest of the body, recursively.&lt;/p&gt;

&lt;h2&gt;
  
  
  Interpreting: where things actually happen
&lt;/h2&gt;

&lt;p&gt;While working on the interpreter, I noticed that &lt;code&gt;evaluating_fields&lt;/code&gt; was a misleading name — &lt;code&gt;interpret_ident&lt;/code&gt; handles not just object fields, but regular local bindings too. Renamed it to &lt;code&gt;evaluating_bindings&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/lib/interpreter.ml b/lib/interpreter.ml
index c8755bc..d512c0f 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/lib/interpreter.ml
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/lib/interpreter.ml
&lt;/span&gt;&lt;span class="p"&gt;@@ -2,7 +2,14 @@&lt;/span&gt; open Ast
 open Result
 open Syntax_sugar
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;-let evaluating_fields = ref ObjectFields.empty
&lt;/span&gt;&lt;span class="gi"&gt;+let evaluating_bindings = ref ObjectFields.empty
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also needed a helper to run a function in a fresh evaluation environment, then restore the previous state afterwards:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ocaml"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;with_fresh_evaluating_bindings&lt;/span&gt; &lt;span class="n"&gt;fn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;saved_evaluating_bindings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;evaluating_bindings&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
  &lt;span class="n"&gt;evaluating_bindings&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nn"&gt;ObjectFields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fn&lt;/span&gt; &lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
  &lt;span class="n"&gt;evaluating_bindings&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;saved_evaluating_bindings&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;result&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This matters for function calls: we don't want the caller's evaluation state leaking into the function body.&lt;/p&gt;

&lt;p&gt;The new pattern-matching cases route to their respective handlers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt; (** [interpret expr] interprets and reduce the intermediate AST [expr] into a result AST. *)
 let rec interpret env expr =
&lt;span class="p"&gt;@@ -19,6 +26,8 @@&lt;/span&gt; let rec interpret env expr =
   | Local (_, vars) -&amp;gt; interpret_local env vars
   | Seq exprs -&amp;gt; interpret_seq env exprs
   | IndexedExpr (pos, varname, index_expr) -&amp;gt; interpret_indexed_expr env (pos, varname, index_expr)
&lt;span class="gi"&gt;+  | FunctionDef (pos, def) -&amp;gt; interpret_function_def env (pos, def)
+  | FunctionCall (pos, fname, params) -&amp;gt; interpret_function_call env (pos, fname, params)
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt; and interpret_indexed_expr env (pos, varname, index_expr) =
   let* (env', index_expr') = interpret env index_expr in
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Function definition just registers the function in the environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ocaml"&gt;&lt;code&gt;&lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;interpret_function_def&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fname&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;env'&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_local&lt;/span&gt; &lt;span class="n"&gt;fname&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;FunctionDef&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fname&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
  &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env'&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Function call is where things get interesting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ocaml"&gt;&lt;code&gt;&lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;interpret_function_call&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fname&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;call_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="nn"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find_opt&lt;/span&gt; &lt;span class="n"&gt;fname&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Some&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;FunctionDef&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;def_params&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nn"&gt;List&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compare_lengths&lt;/span&gt; &lt;span class="n"&gt;call_params&lt;/span&gt; &lt;span class="n"&gt;def_params&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="nn"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error_at&lt;/span&gt; &lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="s2"&gt;"wrong number of param(s)"&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;bindings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="nn"&gt;List&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mapi&lt;/span&gt;
          &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;param_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;List&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nth&lt;/span&gt; &lt;span class="n"&gt;def_params&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;param_name&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="n"&gt;call_params&lt;/span&gt;
      &lt;span class="k"&gt;in&lt;/span&gt;
      &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;env'&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;List&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fold_left&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_local&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;env&lt;/span&gt;
        &lt;span class="n"&gt;bindings&lt;/span&gt;
      &lt;span class="k"&gt;in&lt;/span&gt;
      &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;with_fresh_evaluating_bindings&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;interpret&lt;/span&gt; &lt;span class="n"&gt;env'&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;in&lt;/span&gt;
      &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="nn"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error_at&lt;/span&gt; &lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;var_not_found&lt;/span&gt; &lt;span class="n"&gt;fname&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step by step:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Check that the number of arguments matches the definition. I could skip this in the interpreter and rely on the type checker alone, but I'm keeping it in both for now -- type checking can be bypassed during development for faster iteration.&lt;/li&gt;
&lt;li&gt;Pair up each call argument with its corresponding parameter name.&lt;/li&gt;
&lt;li&gt;Add those bindings to the environment.&lt;/li&gt;
&lt;li&gt;Interpret the function body in that environment, being careful to return the &lt;em&gt;caller's&lt;/em&gt; environment, not the one modified inside the body.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Making sure this isn’t completely illegal
&lt;/h2&gt;

&lt;p&gt;We don't have type annotations yet, so the type checker needs to infer parameter types and the return type. Here's the motivating example -- the first call is fine, the second should error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;samples/semantics/invalid_function_call_type.jsonnet&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;my_function(x)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="err"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;my_function(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;my_function(&lt;/span&gt;&lt;span class="s2"&gt;"oops"&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We need three new type variants:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/lib/type.ml b/lib/type.ml
index cad9ad1..bdbf357 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/lib/type.ml
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/lib/type.ml
&lt;/span&gt;&lt;span class="p"&gt;@@ -16,6 +16,9 @@&lt;/span&gt; type tsonnet_type =
   | TruntimeObject of Env.env_id * t_object_entry list
   | TobjectPtr of Env.env_id * t_object_scope
   | Lazy of expr
&lt;span class="gi"&gt;+  | Tunresolved
+  | TfunctionDef of (string * tsonnet_type) list (* params: name * type *) * expr (* body *) * tsonnet_type (* return *)
+  | TfunctionCall of tsonnet_type list * tsonnet_type
&lt;/span&gt; and t_object_entry =
   | TobjectField of string * tsonnet_type
   | TobjectExpr of tsonnet_type
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="p"&gt;@@ -54,6 +57,17 @@&lt;/span&gt; let rec to_string = function
       | TobjectTopLevel -&amp;gt; "$"
     in Printf.sprintf "%s (%d)" s id
   | Lazy ty -&amp;gt; string_of_type ty
&lt;span class="gi"&gt;+  | TfunctionDef (params, _, return) -&amp;gt;
+    Printf.sprintf "function(%s) -&amp;gt; %s"
+      (List.map (fun (name, ty) -&amp;gt; name ^ ": " ^ to_string ty) params
+      |&amp;gt; String.concat ", "
+      )
+      (to_string return)
+  | TfunctionCall (params_type, return) -&amp;gt;
+    Printf.sprintf "function(%s) -&amp;gt; %s"
+      (List.map to_string params_type |&amp;gt; String.concat ", ")
+      (to_string return)
+  | Tunresolved -&amp;gt; "&amp;lt;unresolved&amp;gt;"
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt; let rec collect_free_idents = function
   | Unit | Null _ | Number _ | String _ | Bool _ -&amp;gt; []
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Tunresolved&lt;/code&gt; is the key one. At declaration time, we don't know what types the parameters will have -- that only becomes clear at the call site. So we use &lt;code&gt;Tunresolved&lt;/code&gt; as a placeholder and fill it in later.&lt;/p&gt;

&lt;p&gt;Two new error messages to go with it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/lib/error.ml b/lib/error.ml
index e287af9..e08c670 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/lib/error.ml
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/lib/error.ml
&lt;/span&gt;&lt;span class="p"&gt;@@ -28,6 +28,10 @@&lt;/span&gt; module Msg = struct
   let type_non_indexable_type ty = ty ^ " is a non-indexable type"
   let type_non_indexable_field field = field ^ " is a non-indexable value"
   let type_invalid_lookup_key expr = "Invalid object lookup key: " ^ expr
&lt;span class="gi"&gt;+  let type_wrong_number_of_params expected got =
+    Printf.sprintf "Expected %d argument(s), got %d" expected got
+  let type_mismatch ~expected ~got =
+    Printf.sprintf "Expected type %s, got %s" expected got
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;   (* Interpreter messages *)
   let interp_division_by_zero = "Division by zero"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The new cases in &lt;code&gt;translate&lt;/code&gt; forward to specialised functions, same pattern as always:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;@@ -162,6 +176,8 @@&lt;/span&gt; let rec translate venv expr =
   | BinOp (pos, op, e1, e2) -&amp;gt; translate_bin_op venv pos op e1 e2
   | UnaryOp (pos, op, expr) -&amp;gt; translate_unary_op venv (pos, op, expr)
   | IndexedExpr (pos, varname, index_expr) -&amp;gt; translate_indexed_expr venv (pos, varname, index_expr)
&lt;span class="gi"&gt;+  | FunctionDef (pos, def) -&amp;gt; translate_function_def venv (pos, def)
+  | FunctionCall (pos, fname, params) -&amp;gt; translate_function_call venv (pos, fname, params)
&lt;/span&gt;   | expr' -&amp;gt;
     error (Error.Msg.type_invalid_expr (string_of_type expr'))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At declaration time, all types are &lt;code&gt;Tunresolved&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ocaml"&gt;&lt;code&gt;&lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;translate_function_def&lt;/span&gt; &lt;span class="n"&gt;venv&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fun_name&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="c"&gt;(* As of now, we don't know the input types at declaration *)&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;params_typed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;List&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Tunresolved&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
  &lt;span class="c"&gt;(* We also don't know the result type *)&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;fun_def&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TfunctionDef&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params_typed&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Tunresolved&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
  &lt;span class="c"&gt;(* So, function declaration will have an unresolved type definition,
     that only later it will be translated: before function call translation!
     After first function call, concrete types are set and subsequent calls will
     type check against the initial type assignment(s). *)&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;venv'&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_local&lt;/span&gt; &lt;span class="n"&gt;fun_name&lt;/span&gt; &lt;span class="n"&gt;fun_def&lt;/span&gt; &lt;span class="n"&gt;venv&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
  &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;venv'&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fun_def&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At call time, &lt;code&gt;Tunresolved&lt;/code&gt; gets replaced with concrete types -- and subsequent calls are checked against those:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ocaml"&gt;&lt;code&gt;&lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;translate_function_call&lt;/span&gt; &lt;span class="n"&gt;venv&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fname&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;call_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="c"&gt;(* 1. retrieve TfunctionDef from venv *)&lt;/span&gt;
  &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="nn"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find_opt&lt;/span&gt; &lt;span class="n"&gt;fname&lt;/span&gt; &lt;span class="n"&gt;venv&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Some&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TfunctionDef&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;def_params&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body_expr&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;return_type&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;(* check arity *)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nn"&gt;List&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compare_lengths&lt;/span&gt; &lt;span class="n"&gt;call_params&lt;/span&gt; &lt;span class="n"&gt;def_params&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;then&lt;/span&gt;
      &lt;span class="nn"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error_at&lt;/span&gt; &lt;span class="n"&gt;pos&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;type_wrong_number_of_params&lt;/span&gt;
          &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;List&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="n"&gt;def_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;List&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="n"&gt;call_params&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="c"&gt;(* 2. type check each positional parameter passed in the function call *)&lt;/span&gt;
      &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;venv'&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resolved_params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="nn"&gt;List&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fold_left2&lt;/span&gt;
          &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="n"&gt;acc&lt;/span&gt; &lt;span class="n"&gt;call_param&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;param_name&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;def_param_type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;venv'&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;acc&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;venv''&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;call_param_type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;translate&lt;/span&gt; &lt;span class="n"&gt;venv'&lt;/span&gt; &lt;span class="n"&gt;call_param&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
            &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;def_param_type&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;
            &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Tunresolved&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
              &lt;span class="c"&gt;(* 2a. unresolved: accept and record the concrete type *)&lt;/span&gt;
              &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;venv''&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params'&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;param_name&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;call_param_type&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;
            &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
              &lt;span class="c"&gt;(* 2b. resolved: type check against the concrete type *)&lt;/span&gt;
              &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;call_param_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt;
              &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;venv''&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params'&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;param_name&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;
              &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nn"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error_at&lt;/span&gt; &lt;span class="n"&gt;pos&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;type_mismatch&lt;/span&gt;
                  &lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to_string&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                  &lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="n"&gt;got&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to_string&lt;/span&gt; &lt;span class="n"&gt;call_param_type&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
          &lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;venv&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;[]&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
          &lt;span class="n"&gt;call_params&lt;/span&gt;
          &lt;span class="n"&gt;def_params&lt;/span&gt;
      &lt;span class="k"&gt;in&lt;/span&gt;
      &lt;span class="c"&gt;(* 3. type check return *)&lt;/span&gt;
      &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;body_venv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;List&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fold_left&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ty&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_local&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="n"&gt;ty&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;venv'&lt;/span&gt;
        &lt;span class="n"&gt;resolved_params&lt;/span&gt;
      &lt;span class="k"&gt;in&lt;/span&gt;
      &lt;span class="c"&gt;(* translate the body with resolved param types in scope *)&lt;/span&gt;
      &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body_type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;translate&lt;/span&gt; &lt;span class="n"&gt;body_venv&lt;/span&gt; &lt;span class="n"&gt;body_expr&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
      &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;resolved_return&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;return_type&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;
        &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Tunresolved&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
          &lt;span class="c"&gt;(* 3a. first call: infer return type from body *)&lt;/span&gt;
          &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="n"&gt;body_type&lt;/span&gt;
        &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
          &lt;span class="c"&gt;(* 3b. subsequent calls: check body type matches *)&lt;/span&gt;
          &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;body_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt;
          &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt;
          &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nn"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error_at&lt;/span&gt; &lt;span class="n"&gt;pos&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;type_mismatch&lt;/span&gt;
              &lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to_string&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
              &lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="n"&gt;got&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to_string&lt;/span&gt; &lt;span class="n"&gt;body_type&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="k"&gt;in&lt;/span&gt;
      &lt;span class="c"&gt;(* 4. update env with the now-resolved function type *)&lt;/span&gt;
      &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;resolved_fun&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TfunctionDef&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resolved_params&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body_expr&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resolved_return&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
      &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;venv_with_resolved_fun&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_local&lt;/span&gt; &lt;span class="n"&gt;fname&lt;/span&gt; &lt;span class="n"&gt;resolved_fun&lt;/span&gt; &lt;span class="n"&gt;venv'&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
      &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;venv_with_resolved_fun&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resolved_return&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="nn"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error_at&lt;/span&gt; &lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;var_not_found&lt;/span&gt; &lt;span class="n"&gt;fname&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One limitation worth mentioning: the first call wins. If the first call passes a value with the wrong type for the intended use, the type checker won't catch it -- that's what type annotations are for. They're coming once Tsonnet reaches a reasonable level of Jsonnet compliance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Proof that it (mostly) works
&lt;/h2&gt;

&lt;p&gt;Ta-da!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;dune &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; tsonnet samples/functions/positional_params.jsonnet
6

&lt;span class="nv"&gt;$ &lt;/span&gt;dune &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; tsonnet samples/functions/multiline.jsonnet
&lt;span class="o"&gt;[&lt;/span&gt; 6, 7 &lt;span class="o"&gt;]&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;dune &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; tsonnet samples/semantics/invalid_function_call_type.jsonnet
ERROR: samples/semantics/invalid_function_call_type.jsonnet:2:18 Expected &lt;span class="nb"&gt;type &lt;/span&gt;Number, got String

2: my_function&lt;span class="o"&gt;(&lt;/span&gt;3&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; my_function&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"oops"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The error could be more precise -- ideally it would point at the offending argument rather than the entire expression. That's a detail I'll get to later; not something that adds much right now.&lt;/p&gt;

&lt;p&gt;The cram tests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/test/cram/functions.t b/test/cram/functions.t
&lt;/span&gt;&lt;span class="p"&gt;new file mode 100644
&lt;/span&gt;&lt;span class="gh"&gt;index 0000000..0ad7e43
&lt;/span&gt;&lt;span class="gd"&gt;--- /dev/null
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/test/cram/functions.t
&lt;/span&gt;&lt;span class="p"&gt;@@ -0,0 +1,5 @@&lt;/span&gt;
&lt;span class="gi"&gt;+  $ tsonnet ../../samples/functions/positional_params.jsonnet
+  6
+
+  $ tsonnet ../../samples/functions/multiline.jsonnet
+  [ 6, 7 ]
&lt;/span&gt;&lt;span class="gh"&gt;diff --git a/test/cram/semantics.t b/test/cram/semantics.t
index de60a1b..c9a790e 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/test/cram/semantics.t
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/test/cram/semantics.t
&lt;/span&gt;&lt;span class="p"&gt;@@ -289,3 +322,16 @@&lt;/span&gt;
                  ^^
   ---
   1
&lt;span class="gi"&gt;+
+  $ tsonnet ../../samples/semantics/invalid_function_call_type.jsonnet
+  ERROR: ../../samples/semantics/invalid_function_call_type.jsonnet:2:18 Expected type Number, got String
+  
+  2: my_function(3) &amp;amp;&amp;amp; my_function("oops")
+     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+  [1]
+
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Basic functions are in: positional parameters, multiline bodies, arity checks, and type inference that resolves on the first call. Not bad for a first round.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://gitlab.com/-/snippets/5977975" rel="noopener noreferrer"&gt;Here&lt;/a&gt; is the entire diff.&lt;/p&gt;

&lt;p&gt;Next up, we make function calls a little more forgiving — default arguments are coming.&lt;/p&gt;




&lt;p&gt;Thanks for reading Bit Maybe Wise! First caller wins the type. &lt;a href="https://bitmaybewise.com/" rel="noopener noreferrer"&gt;Subscribe&lt;/a&gt; to lock in yours.&lt;/p&gt;

&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@anniespratt" rel="noopener noreferrer"&gt;Annie Spratt&lt;/a&gt; on &lt;a href="https://unsplash.com" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>tsonnet</category>
      <category>jsonnet</category>
      <category>compiler</category>
    </item>
    <item>
      <title>ABEND dump #26</title>
      <dc:creator>Hercules Lemke Merscher</dc:creator>
      <pubDate>Wed, 08 Apr 2026 21:27:00 +0000</pubDate>
      <link>https://forem.com/bitmaybewise/abend-dump-26-47eh</link>
      <guid>https://forem.com/bitmaybewise/abend-dump-26-47eh</guid>
      <description>&lt;p&gt;Welcome to the ABEND dump #26!&lt;/p&gt;

&lt;p&gt;Don't know what is an “ABEND dump”?! &lt;a href="https://bitmaybewise.substack.com/i/68092102/what-is-abend-dump" rel="noopener noreferrer"&gt;I'm glad you asked&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can check the previous ABEND dump here:&lt;/p&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/bitmaybewise/abend-dump-25-5640" class="crayons-story__hidden-navigation-link"&gt;ABEND dump #25&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/bitmaybewise" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F187316%2Faed95d8a-d5c5-4d3e-8dab-513ed83b24c3.jpeg" alt="bitmaybewise profile" class="crayons-avatar__image" width="279" height="279"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/bitmaybewise" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Hercules Lemke Merscher
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Hercules Lemke Merscher
                
              
              &lt;div id="story-author-preview-content-3296778" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/bitmaybewise" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F187316%2Faed95d8a-d5c5-4d3e-8dab-513ed83b24c3.jpeg" class="crayons-avatar__image" alt="" width="279" height="279"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Hercules Lemke Merscher&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/bitmaybewise/abend-dump-25-5640" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Feb 28&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/bitmaybewise/abend-dump-25-5640" id="article-link-3296778"&gt;
          ABEND dump #25
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/abenddump"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;abenddump&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
            &lt;a href="https://dev.to/bitmaybewise/abend-dump-25-5640#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            2 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;





&lt;h2&gt;
  
  
  &lt;a href="https://www.theaustralian.com.au/business/technology/tech-boss-uses-ai-and-chatgpt-to-create-cancer-vaccine-for-his-dying-dog/news-story/292a21bcbe93efa17810bfcfcdfadbf7" rel="noopener noreferrer"&gt;Tech boss uses AI and ChatGPT to create cancer vaccine for his dying dog&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;This is WILD! This is what I want AI for, a truly relevant use case. I feel like there's so much potential for applications in medicine. What a time to be alive! &lt;/p&gt;

&lt;p&gt;I know, I know. There's plenty of reasons to be wary about AI, but let's be positive for a moment, this is truly good and magical!&lt;/p&gt;

&lt;p&gt;And speaking of which...&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://osteosarc.com/" rel="noopener noreferrer"&gt;Explore Sid's Data&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://sytse.com/cancer" rel="noopener noreferrer"&gt;Sid Sijbrandij&lt;/a&gt;, former GitLab CEO, is fighting an osteosarcoma (a rare bone cancer). He's open-sourcing his experimental treatment data to help advancing cancer research. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sid rocks!&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://www.youtube.com/watch?v=aoag03mSuXQ" rel="noopener noreferrer"&gt;The internet was weeks away from disaster and no one knew&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/aoag03mSuXQ"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://www.technologyreview.com/2026/03/10/1134099/how-pokemon-go-is-helping-robots-deliver-pizza-on-time/" rel="noopener noreferrer"&gt;How Pokémon Go is giving delivery robots an inch-perfect view of the world&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;"When the product is free, you are the product" -- in the AI era, this sentence never made more sense than now.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://blundercheck.timberschroff.com/p/systems-thinking-is-brain-rot-for" rel="noopener noreferrer"&gt;Systems Thinking is Brain Rot for Analysts&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;This got me thinking.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://www.goodreads.com/book/show/59616977-building-a-second-brain" rel="noopener noreferrer"&gt;Building a Second Brain&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;I forgot to recommend this book in the previous issue. &lt;/p&gt;

&lt;p&gt;Last year I got hooked into the &lt;strong&gt;Zettelkasten&lt;/strong&gt; system to organize my thoughts in a digital world, but it was simply too complex to grasp, and what really made it work for me was following the &lt;a href="https://fortelabs.com/blog/para/" rel="noopener noreferrer"&gt;PARA Method&lt;/a&gt; presented by &lt;a href="https://www.goodreads.com/author/show/17177938.Tiago_Forte" rel="noopener noreferrer"&gt;Tiago Forte&lt;/a&gt; in &lt;strong&gt;Building a Second Brain&lt;/strong&gt; book.&lt;/p&gt;

&lt;p&gt;I may write more about it here, eventually.&lt;/p&gt;

&lt;p&gt;There's a version of this book written in &lt;a href="https://www.goodreads.com/book/show/182090676-criando-um-segundo-c-rebro" rel="noopener noreferrer"&gt;portuguese&lt;/a&gt; too, which was the one I read.&lt;/p&gt;




&lt;p&gt;Thanks for reading the ABEND dump! AI curing cancer, robots delivering pizza, open-source treatment data -- what a time to be alive. &lt;a href="https://bitmaybewise.substack.com" rel="noopener noreferrer"&gt;Subscribe&lt;/a&gt; and we'll keep marveling at it together.&lt;/p&gt;

</description>
      <category>abenddump</category>
    </item>
    <item>
      <title>Tsonnet #34 - Dabbling with untouched bindings</title>
      <dc:creator>Hercules Lemke Merscher</dc:creator>
      <pubDate>Sun, 15 Mar 2026 16:26:39 +0000</pubDate>
      <link>https://forem.com/bitmaybewise/tsonnet-34-dabbling-with-untouched-bindings-i5l</link>
      <guid>https://forem.com/bitmaybewise/tsonnet-34-dabbling-with-untouched-bindings-i5l</guid>
      <description>&lt;p&gt;Welcome to the &lt;a href="https://gitlab.com/bitmaybewise/tsonnet" rel="noopener noreferrer"&gt;Tsonnet&lt;/a&gt; series!&lt;/p&gt;

&lt;p&gt;If you're not following along, check out how it all started in &lt;a href="https://dev.to/bitmaybewise/tsonnet-0-from-zero-to-interpreter-54na"&gt;the first post of the series&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In the previous post, we wrapped up the arithmetic tutorial:&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/bitmaybewise" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F187316%2Faed95d8a-d5c5-4d3e-8dab-513ed83b24c3.jpeg" alt="bitmaybewise"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/bitmaybewise/tsonnet-33-the-arithmetic-tutorial-must-go-on-1694" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Tsonnet #33 - The arithmetic tutorial must go on&lt;/h2&gt;
      &lt;h3&gt;Hercules Lemke Merscher ・ Mar 13&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#tsonnet&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#jsonnet&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#compiler&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;p&gt;This time, before proceeding to cover the next tutorial, I'm going to revisit one laziness aspect: the evaluation (or no-evaluation) of untouched bindings. &lt;/p&gt;

&lt;h2&gt;
  
  
  Warnings, not errors
&lt;/h2&gt;

&lt;p&gt;Up until now, Tsonnet would hard-error on cyclic references regardless of whether the offending variable was ever touched. That's a bit heavy-handed. Jsonnet is lazily evaluated -- if a variable has a cycle but is never used, the program should still run. We should warn, not panic. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;samples/variables/untouched_variable.jsonnet&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="err"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;b&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Jsonnet behaves like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;jsonnet samples/variables/untouched_variable.jsonnet
42
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Jsonnet does nothing to alert the programmer that something is unused. Maybe that belongs to a linter, but the compiler not even warning is a bad experience, IMHO.&lt;/p&gt;

&lt;p&gt;The same logic applies to unused variables: they're suspicious, but they shouldn't crash anything.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;samples/objects/untouched_field.jsonnet&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;a:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;b:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;result.b&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;jsonnet samples/objects/untouched_field.jsonnet
42
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's work through each of these in order.&lt;/p&gt;

&lt;h2&gt;
  
  
  Warn on cyclic refs for unused variables
&lt;/h2&gt;

&lt;p&gt;The first thing we need is a way to tell which variables are actually reachable from the body of a &lt;code&gt;local&lt;/code&gt; expression. For that, I added &lt;code&gt;collect_free_idents&lt;/code&gt; and &lt;code&gt;reachable_bindings&lt;/code&gt; to the type checker:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ocaml"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;rec&lt;/span&gt; &lt;span class="n"&gt;collect_free_idents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Unit&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Null&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Bool&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;[]&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Ident&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Array&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exprs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;List&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;concat_map&lt;/span&gt; &lt;span class="n"&gt;collect_free_idents&lt;/span&gt; &lt;span class="n"&gt;exprs&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;BinOp&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;collect_free_idents&lt;/span&gt; &lt;span class="n"&gt;e1&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;collect_free_idents&lt;/span&gt; &lt;span class="n"&gt;e2&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;UnaryOp&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;collect_free_idents&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Seq&lt;/span&gt; &lt;span class="n"&gt;exprs&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;List&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;concat_map&lt;/span&gt; &lt;span class="n"&gt;collect_free_idents&lt;/span&gt; &lt;span class="n"&gt;exprs&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;ParsedObject&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="nn"&gt;List&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;concat_map&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt;
      &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;ObjectField&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;collect_free_idents&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
      &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;ObjectExpr&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;collect_free_idents&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;entries&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;ObjectFieldAccess&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exprs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;List&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;concat_map&lt;/span&gt; &lt;span class="n"&gt;collect_free_idents&lt;/span&gt; &lt;span class="n"&gt;exprs&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;IndexedExpr&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt; &lt;span class="n"&gt;collect_free_idents&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Local&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vars&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;List&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;concat_map&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;collect_free_idents&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;vars&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;[]&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;reachable_bindings&lt;/span&gt; &lt;span class="n"&gt;bindings&lt;/span&gt; &lt;span class="n"&gt;initial_idents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;rec&lt;/span&gt; &lt;span class="n"&gt;go&lt;/span&gt; &lt;span class="n"&gt;visited&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt;
    &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;visited&lt;/span&gt;
    &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt; &lt;span class="n"&gt;rest&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nn"&gt;List&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="n"&gt;visited&lt;/span&gt;
      &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="n"&gt;go&lt;/span&gt; &lt;span class="n"&gt;visited&lt;/span&gt; &lt;span class="n"&gt;rest&lt;/span&gt;
      &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;new_idents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
          &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="nn"&gt;List&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assoc_opt&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="n"&gt;bindings&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;
          &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Some&lt;/span&gt; &lt;span class="n"&gt;expr&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;collect_free_idents&lt;/span&gt; &lt;span class="n"&gt;expr&lt;/span&gt;
          &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;None&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;[]&lt;/span&gt;
        &lt;span class="k"&gt;in&lt;/span&gt;
        &lt;span class="n"&gt;go&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt; &lt;span class="n"&gt;visited&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_idents&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;in&lt;/span&gt;
  &lt;span class="n"&gt;go&lt;/span&gt; &lt;span class="bp"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;initial_idents&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;collect_free_idents&lt;/code&gt; walks an expression and collects every identifier referenced. &lt;code&gt;reachable_bindings&lt;/code&gt; does a simple graph traversal starting from the identifiers used in the body, following variable references transitively. If a binding is never reachable from the body, it's unused.&lt;/p&gt;

&lt;p&gt;I'm delegating part of what used to be &lt;code&gt;Local&lt;/code&gt;-processing into the &lt;code&gt;translate_seq&lt;/code&gt; function, where the logic actually runs, to keep things tidy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/lib/type.ml b/lib/type.ml
index fbe68b1..2f4bf41 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/lib/type.ml
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/lib/type.ml
&lt;/span&gt;&lt;span class="p"&gt;@@ -154,23 +187,15 @@&lt;/span&gt; let rec translate venv expr =
     )
   | ParsedObject (pos, entries) -&amp;gt; translate_object venv pos entries
   | ObjectFieldAccess (pos, scope, chain) -&amp;gt; translate_object_field_access venv pos scope chain
&lt;span class="gd"&gt;-  | Local (pos, vars) -&amp;gt;
&lt;/span&gt;&lt;span class="gi"&gt;+  | Local (_pos, vars) -&amp;gt;
&lt;/span&gt;     let venv' = List.fold_left
       (* Adds an expr to the env to be evaluated at a later point in time (when required) *)
       (fun venv (varname, var_expr) -&amp;gt; Env.add_local varname (Lazy var_expr) venv)
       venv
       vars
&lt;span class="gd"&gt;-    in
-    let* _ = List.fold_left
-      (fun ok' (varname, _) -&amp;gt; ok' &amp;gt;&amp;gt;= fun _ -&amp;gt; check_cyclic_refs venv' varname [] pos)
-      (ok ())
-      vars
&lt;/span&gt;     in ok (venv', Tunit)
   | Seq exprs -&amp;gt;
&lt;span class="gd"&gt;-    List.fold_left
-      (fun acc expr -&amp;gt; acc &amp;gt;&amp;gt;= fun (venv, _) -&amp;gt; translate venv expr)
-      (ok (venv, Tunit))
-      exprs
&lt;/span&gt;&lt;span class="gi"&gt;+    translate_seq venv exprs
&lt;/span&gt;   | BinOp (pos, op, e1, e2) -&amp;gt;
     translate_bin_op venv pos op e1 e2
   | UnaryOp (pos, op, expr) -&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ocaml"&gt;&lt;code&gt;&lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;translate_seq&lt;/span&gt; &lt;span class="n"&gt;venv&lt;/span&gt; &lt;span class="n"&gt;exprs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;rec&lt;/span&gt; &lt;span class="n"&gt;collect_locals&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt;
    &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Local&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vars&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt; &lt;span class="n"&gt;rest&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;all_vars&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;collect_locals&lt;/span&gt; &lt;span class="n"&gt;rest&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;List&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="n"&gt;vars&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;all_vars&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;rest&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;in&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;rec&lt;/span&gt; &lt;span class="n"&gt;go&lt;/span&gt; &lt;span class="n"&gt;venv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt;
    &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="bp"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;venv&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Tunit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;translate&lt;/span&gt; &lt;span class="n"&gt;venv&lt;/span&gt; &lt;span class="n"&gt;expr&lt;/span&gt;
    &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Local&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;exprs&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;all_pos_vars&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;collect_locals&lt;/span&gt; &lt;span class="n"&gt;exprs&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
      &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;all_vars&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;List&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt; &lt;span class="n"&gt;snd&lt;/span&gt; &lt;span class="n"&gt;all_pos_vars&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
      &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;venv'&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;List&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fold_left&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="n"&gt;venv&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;varname&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;var_expr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
          &lt;span class="nn"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_local&lt;/span&gt; &lt;span class="n"&gt;varname&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Lazy&lt;/span&gt; &lt;span class="n"&gt;var_expr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;venv&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;venv&lt;/span&gt;
        &lt;span class="n"&gt;all_vars&lt;/span&gt;
      &lt;span class="k"&gt;in&lt;/span&gt;
      &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;body_idents&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;List&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;concat_map&lt;/span&gt; &lt;span class="n"&gt;collect_free_idents&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
      &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;reachable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reachable_bindings&lt;/span&gt; &lt;span class="n"&gt;all_vars&lt;/span&gt; &lt;span class="n"&gt;body_idents&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
      &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;List&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fold_left&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="n"&gt;acc&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;varname&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;acc&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;=&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
          &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;check_cyclic_refs&lt;/span&gt; &lt;span class="n"&gt;venv'&lt;/span&gt; &lt;span class="n"&gt;varname&lt;/span&gt; &lt;span class="bp"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;
          &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Ok&lt;/span&gt; &lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="bp"&gt;()&lt;/span&gt;
          &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nn"&gt;List&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt; &lt;span class="n"&gt;varname&lt;/span&gt; &lt;span class="n"&gt;reachable&lt;/span&gt;
            &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;warn&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;type_cyclic_reference&lt;/span&gt; &lt;span class="n"&gt;varname&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="bp"&gt;()&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="bp"&gt;()&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;all_pos_vars&lt;/span&gt;
      &lt;span class="k"&gt;in&lt;/span&gt;
      &lt;span class="n"&gt;go&lt;/span&gt; &lt;span class="n"&gt;venv'&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;
    &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;expr&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt; &lt;span class="n"&gt;rest&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;venv'&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;translate&lt;/span&gt; &lt;span class="n"&gt;venv&lt;/span&gt; &lt;span class="n"&gt;expr&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
      &lt;span class="n"&gt;go&lt;/span&gt; &lt;span class="n"&gt;venv'&lt;/span&gt; &lt;span class="n"&gt;rest&lt;/span&gt;
  &lt;span class="k"&gt;in&lt;/span&gt;
  &lt;span class="n"&gt;go&lt;/span&gt; &lt;span class="n"&gt;venv&lt;/span&gt; &lt;span class="n"&gt;exprs&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key part: if a cyclic reference involves a variable that's reachable from the body, we still error. If it's unreachable, we just warn and move on. Here's what it looks like in practice:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;dune &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; tsonnet samples/variables/untouched_invalid_variable.jsonnet
&lt;span class="go"&gt;Warning: .../untouched_invalid_variable.jsonnet:1:31 Cyclic reference found for c

&lt;/span&gt;&lt;span class="gp"&gt;1: local a = 1, b = a, c = d, d = c;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt;   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Warning: .../untouched_invalid_variable.jsonnet:1:24 Cyclic reference found for d

&lt;/span&gt;&lt;span class="gp"&gt;1: local a = 1, b = a, c = d, d = c;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="go"&gt;   ^^^^^^^^^^^^^^^^^^^^^^^^^^
1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;c&lt;/code&gt; and &lt;code&gt;d&lt;/code&gt; have a cycle between them, but &lt;code&gt;b&lt;/code&gt; is what the program actually evaluates. So we warn, and produce the result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Warn on unused variables
&lt;/h2&gt;

&lt;p&gt;With reachability analysis in place, unused variable warnings follow naturally. A variable is unused if it's not in the &lt;code&gt;reachable&lt;/code&gt; set. Adding that to &lt;code&gt;translate_seq&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/lib/type.ml b/lib/type.ml
index 2f4bf41..0e7a680 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/lib/type.ml
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/lib/type.ml
&lt;/span&gt;&lt;span class="p"&gt;@@ -247,6 +247,11 @@&lt;/span&gt; and translate_seq venv exprs =
       (* Determine which vars are reachable from the body *)
       let body_idents = List.concat_map collect_free_idents body in
       let reachable = reachable_bindings all_vars body_idents in
&lt;span class="gi"&gt;+      (* Warn on unused variables *)
+      List.iter (fun (pos, (varname, _)) -&amp;gt;
+        if not (List.mem varname reachable)
+        then Error.warn (Error.Msg.type_unused_variable varname) pos
+      ) all_pos_vars;
&lt;/span&gt;       (* Check cycles: error for reachable, warn for unreachable *)
       let* () = List.fold_left
         (fun acc (pos, (varname, _)) -&amp;gt; acc &amp;gt;&amp;gt;= fun () -&amp;gt;
&lt;span class="p"&gt;@@ -255,7 +260,7 @@&lt;/span&gt; and translate_seq venv exprs =
           | Error msg -&amp;gt;
             if List.mem varname reachable
             then error msg
&lt;span class="gd"&gt;-            else (prerr_endline ("Warning: " ^ msg); ok ())
&lt;/span&gt;&lt;span class="gi"&gt;+            else (Error.warn (Error.Msg.type_cyclic_reference varname) pos; ok ())
&lt;/span&gt;         )
         (ok ())
         all_pos_vars
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also refactored the warning infrastructure a bit. Instead of calling &lt;code&gt;prerr_endline&lt;/code&gt; inline, there's now a proper &lt;code&gt;Error.warn&lt;/code&gt; function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/lib/error.ml b/lib/error.ml
index ac8ae48..508ae0c 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/lib/error.ml
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/lib/error.ml
&lt;/span&gt;&lt;span class="p"&gt;@@ -21,6 +21,7 @@&lt;/span&gt; module Msg = struct
&lt;span class="err"&gt;
&lt;/span&gt;   (* Type checker messages *)
   let type_cyclic_reference varname = "Cyclic reference found for " ^ varname
&lt;span class="gi"&gt;+  let type_unused_variable varname = "Unused variable " ^ varname
&lt;/span&gt;   let type_non_indexable_value ty = ty ^ " is a non indexable value"
   let type_expected_integer_index ty = "Expected Integer index, got " ^ ty
   let type_invalid_expr expr = "Invalid type " ^ expr
&lt;span class="p"&gt;@@ -104,3 +105,8 @@&lt;/span&gt; let trace (err: string) (pos: position) : (string, string) result =
     (fun content -&amp;gt; ok (Printf.sprintf "%s\n%s" (trace_file_position err pos) content))
&lt;span class="err"&gt;
&lt;/span&gt; let error_at pos = fun msg -&amp;gt; trace msg pos &amp;gt;&amp;gt;= error
&lt;span class="gi"&gt;+
+let warn msg pos =
+  match trace msg pos with
+  | Ok formatted -&amp;gt; prerr_endline ("Warning: " ^ formatted)
+  | Error _ -&amp;gt; prerr_endline ("Warning: " ^ msg)
&lt;/span&gt;&lt;span class="gh"&gt;diff --git a/lib/error.mli b/lib/error.mli
index 0bdd708..c09b930 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/lib/error.mli
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/lib/error.mli
&lt;/span&gt;&lt;span class="p"&gt;@@ -18,6 +18,7 @@&lt;/span&gt; module Msg : sig
&lt;span class="err"&gt;
&lt;/span&gt;   (* Type checker messages *)
   val type_cyclic_reference : string -&amp;gt; string
&lt;span class="gi"&gt;+  val type_unused_variable : string -&amp;gt; string
&lt;/span&gt;   val type_non_indexable_value : string -&amp;gt; string
   val type_expected_integer_index : string -&amp;gt; string
   val type_invalid_expr : string -&amp;gt; string
&lt;span class="p"&gt;@@ -37,3 +38,4 @@&lt;/span&gt; end
&lt;span class="err"&gt;
&lt;/span&gt; val trace : string -&amp;gt; Ast.position -&amp;gt; (string, string) result
 val error_at : Ast.position -&amp;gt; string -&amp;gt; ('a, string) result
&lt;span class="gi"&gt;+val warn : string -&amp;gt; Ast.position -&amp;gt; unit
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Much cleaner. Let's see it in action:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;dune &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; tsonnet samples/variables/untouched_variable.jsonnet
Warning: .../untouched_variable.jsonnet:1:0 Unused variable a

1: &lt;span class="nb"&gt;local &lt;/span&gt;a &lt;span class="o"&gt;=&lt;/span&gt; 1, b &lt;span class="o"&gt;=&lt;/span&gt; 42&lt;span class="p"&gt;;&lt;/span&gt;
   ^^^^^^^^^^^^^^^^^^^^
42
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;a&lt;/code&gt; is defined but never used. The program still returns &lt;code&gt;42&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Warn on untouched object fields
&lt;/h2&gt;

&lt;p&gt;Variables were the easy part. Objects are more involved because we're dealing with two separate phases -- the type checker and the interpreter -- and both need to handle cyclic field references gracefully.&lt;/p&gt;

&lt;p&gt;The approach is the same: if a cyclic object field is never accessed during evaluation, warn instead of error. The tricky bit is detecting "accessed during evaluation" correctly.&lt;/p&gt;

&lt;p&gt;Both modules now track which fields are actively being evaluated using a mutable &lt;code&gt;ObjectFields&lt;/code&gt; set:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ocaml"&gt;&lt;code&gt;&lt;span class="c"&gt;(* In interpreter.ml *)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;evaluating_fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ref&lt;/span&gt; &lt;span class="nn"&gt;ObjectFields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;empty&lt;/span&gt;

&lt;span class="c"&gt;(* In type.ml *)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;translating_fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ref&lt;/span&gt; &lt;span class="nn"&gt;ObjectFields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;empty&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When we start evaluating a field, we add it to the set. When we're done (or on error), we remove it. If we try to evaluate a field already in the set -- cycle detected:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/lib/interpreter.ml b/lib/interpreter.ml
index fe248b9..391c1e2 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/lib/interpreter.ml
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/lib/interpreter.ml
&lt;/span&gt;&lt;span class="p"&gt;@@ -21,9 +23,17 @@&lt;/span&gt; let rec interpret env expr =
   | ObjectPtr _ as obj_ptr -&amp;gt; ok (env, obj_ptr)
   | ObjectFieldAccess (pos, scope, chain) -&amp;gt; interpret_object_field_access env (pos, scope, chain)
   | Ident (pos, varname) -&amp;gt;
&lt;span class="gd"&gt;-    Env.find_var varname env
-      ~succ:(fun env' expr -&amp;gt; interpret env' expr)
-      ~err:(Error.error_at pos)
&lt;/span&gt;&lt;span class="gi"&gt;+    if ObjectFields.mem varname !evaluating_fields then
+      Error.error_at pos (Error.Msg.type_cyclic_reference varname)
+    else begin
+      evaluating_fields := ObjectFields.add varname !evaluating_fields;
+      let result = Env.find_var varname env
+        ~succ:(fun env' expr -&amp;gt; interpret env' expr)
+        ~err:(Error.error_at pos)
+      in
+      evaluating_fields := ObjectFields.remove varname !evaluating_fields;
+      result
+    end
&lt;/span&gt;   | BinOp (pos, op, e1, e2) -&amp;gt; interpret_bin_op env (pos, op, e1, e2)
   | UnaryOp (pos, op, expr) -&amp;gt;
     let* (env', expr') = interpret env expr in
&lt;span class="p"&gt;@@ -201,9 +211,18 @@&lt;/span&gt; and interpret_object_field_access env (pos, scope, chain_exprs) =
       match field_expr with
       | String (pos, field) | Ident (pos, field) -&amp;gt;
         let* (obj_id, field_env) = get_obj_id in
&lt;span class="gd"&gt;-        Env.get_obj_field field obj_id field_env
-          ~succ:(interpret)
-          ~err:(Error.error_at pos)
&lt;/span&gt;&lt;span class="gi"&gt;+        let key = Env.uniq_field_ident obj_id field in
+        if ObjectFields.mem key !evaluating_fields then
+          Error.error_at pos (Error.Msg.type_cyclic_reference key)
+        else begin
+          evaluating_fields := ObjectFields.add key !evaluating_fields;
+          let result = Env.get_obj_field field obj_id field_env
+            ~succ:(interpret)
+            ~err:(Error.error_at pos)
+          in
+          evaluating_fields := ObjectFields.remove key !evaluating_fields;
+          result
+        end
&lt;/span&gt;       | Number _ as index_expr -&amp;gt;
         (* Handle array/string indexing: prev_expr[number] *)
         Result.fold
&lt;span class="p"&gt;@@ -223,19 +242,27 @@&lt;/span&gt; and interpret_runtime_object env (pos, obj_env, fields) =
 and interpret_runtime_object_fields obj_env fields =
   match Env.Map.find_opt "self" obj_env with
   | Some (ObjectPtr (obj_id, _)) -&amp;gt;
&lt;span class="gd"&gt;-    let* field_list =
&lt;/span&gt;&lt;span class="gi"&gt;+    let field_list =
&lt;/span&gt;       ObjectFields.fold
         (fun field acc -&amp;gt;
&lt;span class="gd"&gt;-          let* evaluated_fields = acc in
&lt;/span&gt;           let key = Env.uniq_field_ident obj_id field in
&lt;span class="gd"&gt;-          match Env.Map.find_opt key obj_env with
-          | Some expr -&amp;gt;
-            let* (_, evaluated) = interpret obj_env expr in
-            ok ((field, evaluated) :: evaluated_fields)
-          | None -&amp;gt; acc
&lt;/span&gt;&lt;span class="gi"&gt;+          if ObjectFields.mem key !evaluating_fields then
+            acc (* Skip: cyclic reference detected *)
+          else
+            match Env.Map.find_opt key obj_env with
+            | Some expr -&amp;gt;
+              evaluating_fields := ObjectFields.add key !evaluating_fields;
+              let result =
+                match interpret obj_env expr with
+                | Ok (_, evaluated) -&amp;gt; (field, evaluated) :: acc
+                | Error _ -&amp;gt; acc
+              in
+              evaluating_fields := ObjectFields.remove key !evaluating_fields;
+              result
+            | None -&amp;gt; acc
&lt;/span&gt;         )
         fields
&lt;span class="gd"&gt;-        (ok [])
&lt;/span&gt;&lt;span class="gi"&gt;+        []
&lt;/span&gt;     in ok (List.rev field_list)
   | _ -&amp;gt; ok []
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For object fields that are never accessed, the type checker now warns instead of erroring. &lt;code&gt;translate_object&lt;/code&gt; was changed to iterate over entries and emit warnings rather than propagate errors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/lib/type.ml b/lib/type.ml
index 0e7a680..8b41545 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/lib/type.ml
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/lib/type.ml
&lt;/span&gt;&lt;span class="p"&gt;@@ -152,13 +154,21 @@&lt;/span&gt; let rec translate venv expr =
   | Number _ -&amp;gt; ok (venv, Tnumber)
   | String _ -&amp;gt; ok (venv, Tstring)
   | Ident (pos, varname) -&amp;gt;
&lt;span class="gd"&gt;-    Env.find_var varname venv
-      ~succ:(fun venv ty -&amp;gt;
-        match ty with
-        | Lazy expr -&amp;gt; translate venv expr
-        | _ -&amp;gt; ok (venv, ty)
-      )
-      ~err:(Error.error_at pos)
&lt;/span&gt;&lt;span class="gi"&gt;+    if ObjectFields.mem varname !translating_fields then
+      Error.error_at pos (Error.Msg.type_cyclic_reference varname)
+    else begin
+      translating_fields := ObjectFields.add varname !translating_fields;
+      let result = Env.find_var varname venv
+        ~succ:(fun venv ty -&amp;gt;
+          match ty with
+          | Lazy expr -&amp;gt; translate venv expr
+          | _ -&amp;gt; ok (venv, ty)
+        )
+        ~err:(Error.error_at pos)
+      in
+      translating_fields := ObjectFields.remove varname !translating_fields;
+      result
+    end
&lt;/span&gt;   | Array (_pos, elems) -&amp;gt;
     (* As of now, we compare each element and if all have the same type,
       it is an array of this type, otherwise it will be an array of any.
&lt;span class="p"&gt;@@ -298,32 +308,36 @@&lt;/span&gt; and translate_object venv pos entries =
     entries
   in
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;-  (* Check for cyclical references among object fields *)
-  let* () = List.fold_left
-      (fun ok' entry -&amp;gt; ok' &amp;gt;&amp;gt;= fun _ -&amp;gt;
-        match entry with
-        | ObjectField (attr, _) -&amp;gt;
-          check_cyclic_refs venv (Env.uniq_field_ident obj_id attr) [] pos
-        | _ -&amp;gt; ok'
-      )
-      (ok ())
-      entries
-  in
&lt;/span&gt;&lt;span class="gi"&gt;+  (* Check for cyclical references among object fields
+    (warn, don't error when the reference is not part of
+    the evaluation tree)
+  *)
+  List.iter
+    (fun entry -&amp;gt;
+      match entry with
+      | ObjectField (attr, _) -&amp;gt;
+        (match check_cyclic_refs venv (Env.uniq_field_ident obj_id attr) [] pos with
+        | Ok () -&amp;gt; ()
+        | Error _ -&amp;gt; Error.warn (Error.Msg.type_cyclic_reference (Env.uniq_field_ident obj_id attr)) pos)
+      | _ -&amp;gt; ()
+    )
+    entries;
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;-  (* Then translate object fields *)
-  let* entry_types = List.fold_left
-    (fun result entry -&amp;gt;
-      let* entries' = result in
&lt;/span&gt;&lt;span class="gi"&gt;+  (* Translate object fields lazily: warn on errors, skip invalid fields *)
+  let entry_types = List.fold_left
+    (fun entries' entry -&amp;gt;
&lt;/span&gt;       match entry with
       | ObjectField (attr, _) -&amp;gt;
&lt;span class="gd"&gt;-        let* (_, entry_ty) = Env.get_obj_field attr obj_id venv
&lt;/span&gt;&lt;span class="gi"&gt;+        (match Env.get_obj_field attr obj_id venv
&lt;/span&gt;           ~succ:translate_lazy
           ~err:(Error.error_at pos)
&lt;span class="gd"&gt;-        in ok (entries' @ [TobjectField (attr, entry_ty)])
&lt;/span&gt;&lt;span class="gi"&gt;+        with
+        | Ok (_, entry_ty) -&amp;gt; entries' @ [TobjectField (attr, entry_ty)]
+        | Error _ -&amp;gt; entries')
&lt;/span&gt;       | _ -&amp;gt;
&lt;span class="gd"&gt;-        result
&lt;/span&gt;&lt;span class="gi"&gt;+        entries'
&lt;/span&gt;     )
&lt;span class="gd"&gt;-    (ok [])
&lt;/span&gt;&lt;span class="gi"&gt;+    []
&lt;/span&gt;     entries
   in
   (* Remove self and $ from the environment to prevent leaking *)
&lt;span class="p"&gt;@@ -372,9 +386,18 @@&lt;/span&gt; and translate_object_field_access venv pos scope chain_exprs =
       match field_expr with
       | String (_, field) | Ident (_, field) -&amp;gt;
         let* obj_id = get_obj_id in
&lt;span class="gd"&gt;-        Env.get_obj_field field obj_id venv
-          ~succ:translate_lazy
-          ~err:(Error.error_at pos)
&lt;/span&gt;&lt;span class="gi"&gt;+        let key = Env.uniq_field_ident obj_id field in
+        if ObjectFields.mem key !translating_fields then
+          Error.error_at pos (Error.Msg.type_cyclic_reference key)
+        else begin
+          translating_fields := ObjectFields.add key !translating_fields;
+          let result = Env.get_obj_field field obj_id venv
+            ~succ:translate_lazy
+            ~err:(Error.error_at pos)
+          in
+          translating_fields := ObjectFields.remove key !translating_fields;
+          result
+        end
&lt;/span&gt;       | Number (pos, _) -&amp;gt;
         (* Handle numeric indexing of strings and arrays *)
         (match prev_ty with
&lt;span class="p"&gt;@@ -429,4 +452,5 @@&lt;/span&gt; let check (config : Config.t) expr  =
   else
     let* _ = translate Env.empty expr in
     Env.Id.reset ();
&lt;span class="gi"&gt;+    translating_fields := ObjectFields.empty;
&lt;/span&gt;     ok expr
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's a sample that demonstrates the distinction. When we only access &lt;code&gt;result.b&lt;/code&gt;, the cyclic &lt;code&gt;c&lt;/code&gt;/&lt;code&gt;d&lt;/code&gt; pair just produces warnings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;samples/objects/untouched_invalid_field.jsonnet&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;a:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;b:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;self.a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;c:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;self.d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;d:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;self.c&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;result.b&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;dune &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; tsonnet samples/objects/untouched_invalid_field.jsonnet
Warning: .../untouched_invalid_field.jsonnet:1:0 Unused variable result

...

Warning: .../untouched_invalid_field.jsonnet:1:15 Cyclic reference found &lt;span class="k"&gt;for &lt;/span&gt;1-&amp;gt;c

...

Warning: .../untouched_invalid_field.jsonnet:1:15 Cyclic reference found &lt;span class="k"&gt;for &lt;/span&gt;1-&amp;gt;d

...
1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Meanwhile, if a cyclic field is actually accessed at runtime, it still errors hard:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;samples/semantics/invalid_object_with_cyclic_field.jsonnet&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;a:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;b:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;self.c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;c:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;self.b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;dune &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; tsonnet samples/semantics/invalid_object_with_cyclic_field.jsonnet
Warning: .../invalid_object_with_cyclic_field.jsonnet:1:0 Cyclic reference found &lt;span class="k"&gt;for &lt;/span&gt;1-&amp;gt;b
...
.../invalid_object_with_cyclic_field.jsonnet:3:12 Cyclic reference found &lt;span class="k"&gt;for &lt;/span&gt;1-&amp;gt;c
...
&lt;span class="o"&gt;[&lt;/span&gt;1]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The type checker warns (because it doesn't know at type-check time which fields will be accessed), but the interpreter finds the cycle at runtime and errors properly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Warn on cyclic refs for untouched object fields
&lt;/h2&gt;

&lt;p&gt;The previous changes got the type checker side right, but the interpreter was still getting it wrong. When rendering an object, &lt;code&gt;interpret_runtime_object_fields&lt;/code&gt; was folding into a plain list and silently skipping any field that errored -- including cyclic ones. So if you did access a cyclic field at runtime, you'd get an empty result instead of an error. Not great.&lt;/p&gt;

&lt;p&gt;The fix is straightforward: go back to a monadic fold and let errors propagate normally. The cycle detection in &lt;code&gt;interpret_object_field_access&lt;/code&gt; already handles the "is this field in a cycle?" question via &lt;code&gt;evaluating_fields&lt;/code&gt; -- &lt;code&gt;interpret_runtime_object_fields&lt;/code&gt; doesn't need to second-guess it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/lib/interpreter.ml b/lib/interpreter.ml
index 391c1e2..60f8a72 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/lib/interpreter.ml
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/lib/interpreter.ml
&lt;/span&gt;&lt;span class="p"&gt;@@ -242,27 +242,19 @@&lt;/span&gt; and interpret_runtime_object env (pos, obj_env, fields) =
 and interpret_runtime_object_fields obj_env fields =
   match Env.Map.find_opt "self" obj_env with
   | Some (ObjectPtr (obj_id, _)) -&amp;gt;
&lt;span class="gd"&gt;-    let field_list =
&lt;/span&gt;&lt;span class="gi"&gt;+    let* field_list =
&lt;/span&gt;       ObjectFields.fold
         (fun field acc -&amp;gt;
&lt;span class="gi"&gt;+          let* evaluated_fields = acc in
&lt;/span&gt;           let key = Env.uniq_field_ident obj_id field in
&lt;span class="gd"&gt;-          if ObjectFields.mem key !evaluating_fields then
-            acc (* Skip: cyclic reference detected *)
-          else
-            match Env.Map.find_opt key obj_env with
-            | Some expr -&amp;gt;
-              evaluating_fields := ObjectFields.add key !evaluating_fields;
-              let result =
-                match interpret obj_env expr with
-                | Ok (_, evaluated) -&amp;gt; (field, evaluated) :: acc
-                | Error _ -&amp;gt; acc
-              in
-              evaluating_fields := ObjectFields.remove key !evaluating_fields;
-              result
-            | None -&amp;gt; acc
&lt;/span&gt;&lt;span class="gi"&gt;+          match Env.Map.find_opt key obj_env with
+          | Some expr -&amp;gt;
+            let* (_, evaluated) = interpret obj_env expr in
+            ok ((field, evaluated) :: evaluated_fields)
+          | None -&amp;gt; acc
&lt;/span&gt;         )
         fields
&lt;span class="gd"&gt;-        []
&lt;/span&gt;&lt;span class="gi"&gt;+        (ok [])
&lt;/span&gt;     in ok (List.rev field_list)
   | _ -&amp;gt; ok []
&lt;span class="err"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's also a small fix in the type checker's &lt;code&gt;translate_object_field_access&lt;/code&gt;. When chaining into a &lt;code&gt;TruntimeObject&lt;/code&gt;, the field lookup was using the outer &lt;code&gt;venv&lt;/code&gt; -- meaning &lt;code&gt;self&lt;/code&gt; and &lt;code&gt;$&lt;/code&gt; weren't in scope. The fix builds a proper &lt;code&gt;field_venv&lt;/code&gt; before looking up the field, the same way the interpreter does it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/lib/type.ml b/lib/type.ml
index 8b41545..200f533 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/lib/type.ml
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/lib/type.ml
&lt;/span&gt;&lt;span class="p"&gt;@@ -376,22 +376,31 @@&lt;/span&gt; and translate_object_field_access venv pos scope chain_exprs =
     (fun acc field_expr -&amp;gt;
       let* (venv, prev_ty) = acc in
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;-      let get_obj_id =
&lt;/span&gt;&lt;span class="gi"&gt;+      let get_obj_id_and_env =
&lt;/span&gt;         match prev_ty with
&lt;span class="gd"&gt;-        | TobjectPtr (obj_id, _) -&amp;gt; ok obj_id
-        | TruntimeObject (obj_id, _) -&amp;gt; ok obj_id
&lt;/span&gt;&lt;span class="gi"&gt;+        | TobjectPtr (obj_id, _) -&amp;gt; ok (obj_id, venv)
+        | TruntimeObject (obj_id, _) -&amp;gt;
+          (* TODO: we haven't included the environment in TruntimeObject yet.
+             It must be done such as Ast.RuntimeObject *)
+          let field_venv =
+            Env.add_local "self" (TobjectPtr (obj_id, TobjectSelf)) venv
+          in
+          let field_venv =
+            Env.add_local_when_not_present "$" (TobjectPtr (obj_id, TobjectTopLevel)) field_venv |&amp;gt; fst
+          in
+          ok (obj_id, field_venv)
&lt;/span&gt;         | _ -&amp;gt; Error.error_at pos Error.Msg.must_be_object
       in
&lt;span class="err"&gt;
&lt;/span&gt;       match field_expr with
       | String (_, field) | Ident (_, field) -&amp;gt;
&lt;span class="gd"&gt;-        let* obj_id = get_obj_id in
&lt;/span&gt;&lt;span class="gi"&gt;+        let* (obj_id, field_venv) = get_obj_id_and_env in
&lt;/span&gt;         let key = Env.uniq_field_ident obj_id field in
         if ObjectFields.mem key !translating_fields then
           Error.error_at pos (Error.Msg.type_cyclic_reference key)
         else begin
           translating_fields := ObjectFields.add key !translating_fields;
&lt;span class="gd"&gt;-          let result = Env.get_obj_field field obj_id venv
&lt;/span&gt;&lt;span class="gi"&gt;+          let result = Env.get_obj_field field obj_id field_venv
&lt;/span&gt;             ~succ:translate_lazy
             ~err:(Error.error_at pos)
           in
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I sprinkled a &lt;code&gt;TODO&lt;/code&gt; here because I don't want to do this refactoring now. XD&lt;/p&gt;

&lt;p&gt;Everything is captured in the cram tests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/samples/semantics/valid_object_with_cyclic_field.jsonnet b/samples/semantics/invalid_object_with_cyclic_field.jsonnet
&lt;/span&gt;&lt;span class="p"&gt;similarity index 100%
rename from samples/semantics/valid_object_with_cyclic_field.jsonnet
rename to samples/semantics/invalid_object_with_cyclic_field.jsonnet
&lt;/span&gt;&lt;span class="gh"&gt;diff --git a/test/cram/objects.t b/test/cram/objects.t
index b200032..e7ae092 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/test/cram/objects.t
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/test/cram/objects.t
&lt;/span&gt;&lt;span class="p"&gt;@@ -24,7 +24,7 @@&lt;/span&gt;
&lt;span class="err"&gt;
&lt;/span&gt;   $ tsonnet ../../samples/objects/untouched_field.jsonnet
   Warning: ../../samples/objects/untouched_field.jsonnet:1:0 Unused variable result
&lt;span class="gd"&gt;-  
&lt;/span&gt;&lt;span class="gi"&gt;+
&lt;/span&gt;   1: local result = {
      ^^^^^^^^^^^^^^^^
   2:     a: 1,
&lt;span class="p"&gt;@@ -32,3 +32,39 @@&lt;/span&gt;
   3:     b: 42,
      ^^^^^^^^^^
   42
&lt;span class="gi"&gt;+
+
+  $ tsonnet ../../samples/objects/untouched_invalid_field.jsonnet
+  Warning: ../../samples/objects/untouched_invalid_field.jsonnet:1:0 Unused variable result
+
+  1: local result = {
+     ^^^^^^^^^^^^^^^^
+  2:     a: 1,
+     ^^^^^^^^^
+  3:     b: self.a,
+     ^^^^^^^^^^^^^^
+  4:     c: self.d,
+     ^^^^^^^^^^^^^^
+  5:     d: self.c
+     ^^^^^^^^^^^^^
+  Warning: ../../samples/objects/untouched_invalid_field.jsonnet:1:15 Cyclic reference found for 1-&amp;gt;c
+
+  1: local result = {
+     ^^^^^^^^^^^^^^^^
+  2:     a: 1,
+
+  3:     b: self.a,
+
+  4:     c: self.d,
+
+  Warning: ../../samples/objects/untouched_invalid_field.jsonnet:1:15 Cyclic reference found for 1-&amp;gt;d
+
+  1: local result = {
+     ^^^^^^^^^^^^^^^^
+  2:     a: 1,
+
+  3:     b: self.a,
+
+  4:     c: self.d,
+
+  1
&lt;/span&gt;&lt;span class="gh"&gt;diff --git a/test/cram/semantics.t b/test/cram/semantics.t
index 4513915..c98851d 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/test/cram/semantics.t
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/test/cram/semantics.t
&lt;/span&gt;&lt;span class="p"&gt;@@ -52,7 +52,11 @@&lt;/span&gt;
      ^^^^^^^^^^^^^^^^
   4:     local c = b,
      ^^^^^^^^^^^^^^^^
&lt;span class="gd"&gt;-  {}
&lt;/span&gt;&lt;span class="gi"&gt;+  ../../samples/semantics/invalid_binding_cycle_object_locals.jsonnet:4:14 Cyclic reference found for b
+  
+  4:     local c = b,
+     ^^^^^^^^^^^^^^^^
+  [1]
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;   $ tsonnet ../../samples/semantics/invalid_binding_cycle_binop.jsonnet
   ../../samples/semantics/invalid_binding_cycle_binop.jsonnet:2:10 Cyclic reference found for a
&lt;span class="p"&gt;@@ -92,7 +96,11 @@&lt;/span&gt;
      ^^^^^^^^^^^^^^
   3:     b: self.a,
      ^^^^^^^^^^^^^^
&lt;span class="gd"&gt;-  {}
&lt;/span&gt;&lt;span class="gi"&gt;+  ../../samples/semantics/invalid_binding_cycle_object_fields.jsonnet:2:12 Cyclic reference found for 1-&amp;gt;b
+  
+  2:     a: self.b,
+     ^^^^^^^^^^^^^^
+  [1]
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;   $ tsonnet ../../samples/semantics/invalid_binding_cycle_object_nested_field.jsonnet
   Warning: ../../samples/semantics/invalid_binding_cycle_object_nested_field.jsonnet:1:0 Cyclic reference found for 1-&amp;gt;a
&lt;span class="p"&gt;@@ -135,7 +143,11 @@&lt;/span&gt;
               ^^^^^^^^^
   4:     },
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;-  { "a": {} }
&lt;/span&gt;&lt;span class="gi"&gt;+  ../../samples/semantics/invalid_binding_cycle_object_nested_field.jsonnet:3:17 Cyclic reference found for 1-&amp;gt;b
+  
+  3:         value: $.b
+     ^^^^^^^^^^^^^^^^^^
+  [1]
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;   $ tsonnet ../../samples/semantics/invalid_binding_cycle_outer_object_fields.jsonnet
   Warning: ../../samples/semantics/invalid_binding_cycle_outer_object_fields.jsonnet:1:0 Cyclic reference found for 1-&amp;gt;a
&lt;span class="p"&gt;@@ -154,7 +166,11 @@&lt;/span&gt;
      ^^^^^^^^^^^
   3:     b: $.a,
      ^^^^^^^^^^^
&lt;span class="gd"&gt;-  {}
&lt;/span&gt;&lt;span class="gi"&gt;+  ../../samples/semantics/invalid_binding_cycle_outer_object_fields.jsonnet:2:9 Cyclic reference found for 1-&amp;gt;b
+  
+  2:     a: $.b,
+     ^^^^^^^^^^^
+  [1]
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;   $ tsonnet ../../samples/semantics/invalid_binding_cycle_object_field_and_local.jsonnet
   Warning: ../../samples/semantics/invalid_binding_cycle_object_field_and_local.jsonnet:1:0 Cyclic reference found for 1-&amp;gt;b
&lt;span class="p"&gt;@@ -165,7 +181,11 @@&lt;/span&gt;
      ^^^^^^^^^^^^^^^^^^^^^
   3:     b: a,
      ^^^^^^^^^
&lt;span class="gd"&gt;-  {}
&lt;/span&gt;&lt;span class="gi"&gt;+  ../../samples/semantics/invalid_binding_cycle_object_field_and_local.jsonnet:3:7 Cyclic reference found for a
+  
+  3:     b: a,
+     ^^^^^^^^^
+  [1]
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;   $ tsonnet ../../samples/semantics/invalid_binding_cycle_indexed_field.jsonnet
   Warning: ../../samples/semantics/invalid_binding_cycle_indexed_field.jsonnet:1:0 Cyclic reference found for 1-&amp;gt;arr
&lt;span class="p"&gt;@@ -184,10 +204,14 @@&lt;/span&gt;
      ^^^^^^^^^^^^^^^^^^^^^^
   3:     first: self.arr[0]
      ^^^^^^^^^^^^^^^^^^^^^^
&lt;span class="gd"&gt;-  {}
&lt;/span&gt;&lt;span class="gi"&gt;+  ../../samples/semantics/invalid_binding_cycle_indexed_field.jsonnet:2:15 Cyclic reference found for 1-&amp;gt;first
+  
+  2:     arr: [self.first],
+     ^^^^^^^^^^^^^^^^^^^^^^
+  [1]
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;-  $ tsonnet ../../samples/semantics/valid_object_with_cyclic_field.jsonnet
-  Warning: ../../samples/semantics/valid_object_with_cyclic_field.jsonnet:1:0 Cyclic reference found for 1-&amp;gt;b
&lt;/span&gt;&lt;span class="gi"&gt;+  $ tsonnet ../../samples/semantics/invalid_object_with_cyclic_field.jsonnet
+  Warning: ../../samples/semantics/invalid_object_with_cyclic_field.jsonnet:1:0 Cyclic reference found for 1-&amp;gt;b
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;   1: {
      ^
&lt;span class="p"&gt;@@ -197,7 +221,7 @@&lt;/span&gt;
      ^^^^^^^^^^^^^^
   4:     c: self.b,
      ^^^^^^^^^^^^^^
&lt;span class="gd"&gt;-  Warning: ../../samples/semantics/valid_object_with_cyclic_field.jsonnet:1:0 Cyclic reference found for 1-&amp;gt;c
&lt;/span&gt;&lt;span class="gi"&gt;+  Warning: ../../samples/semantics/invalid_object_with_cyclic_field.jsonnet:1:0 Cyclic reference found for 1-&amp;gt;c
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;   1: {
      ^
&lt;span class="p"&gt;@@ -207,7 +231,11 @@&lt;/span&gt;
      ^^^^^^^^^^^^^^
   4:     c: self.b,
      ^^^^^^^^^^^^^^
&lt;span class="gd"&gt;-  { "a": 1 }
&lt;/span&gt;&lt;span class="gi"&gt;+  ../../samples/semantics/invalid_object_with_cyclic_field.jsonnet:3:12 Cyclic reference found for 1-&amp;gt;c
+  
+  3:     b: self.c,
+     ^^^^^^^^^^^^^^
+  [1]
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;   $ tsonnet ../../samples/semantics/valid_object_access_non_cyclic_field.jsonnet
   Warning: ../../samples/semantics/valid_object_access_non_cyclic_field.jsonnet:1:0 Unused variable obj
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One thing I noticed while working through the test cases: the sample that used to be called &lt;code&gt;valid_object_with_cyclic_field.jsonnet&lt;/code&gt; is not actually valid -- it errors when the cyclic fields are accessed. Renamed it to &lt;code&gt;invalid_object_with_cyclic_field.jsonnet&lt;/code&gt;. These things happen when you're naming files before you've implemented the feature that would tell you whether they're valid or not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The reachability analysis for variables and the &lt;code&gt;evaluating_fields&lt;/code&gt; tracking for objects both push in the same direction -- lean on lazy evaluation instead of fighting it. This is the nature of Jsonnet, and Tsonnet should embrace it.&lt;/p&gt;

&lt;p&gt;You can check the entire diff &lt;a href="https://gitlab.com/-/snippets/5969491" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I think, after that, we can start playing with much more fun things: functions!&lt;/p&gt;




&lt;p&gt;Thanks for reading Bit Maybe Wise! If you too believe a cycle you never touch shouldn't ruin your day, &lt;a href="https://bitmaybewise.substack.com/" rel="noopener noreferrer"&gt;subscribe&lt;/a&gt; and let's keep being reasonably lenient together.&lt;/p&gt;

&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@sebastian_unrau" rel="noopener noreferrer"&gt;Sebastian Unrau&lt;/a&gt; on &lt;a href="https://unsplash.com" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>tsonnet</category>
      <category>jsonnet</category>
      <category>compiler</category>
    </item>
    <item>
      <title>Tsonnet #33 - The arithmetic tutorial must go on</title>
      <dc:creator>Hercules Lemke Merscher</dc:creator>
      <pubDate>Fri, 13 Mar 2026 10:04:57 +0000</pubDate>
      <link>https://forem.com/bitmaybewise/tsonnet-33-the-arithmetic-tutorial-must-go-on-1694</link>
      <guid>https://forem.com/bitmaybewise/tsonnet-33-the-arithmetic-tutorial-must-go-on-1694</guid>
      <description>&lt;p&gt;Welcome to the &lt;a href="https://gitlab.com/bitmaybewise/tsonnet" rel="noopener noreferrer"&gt;Tsonnet&lt;/a&gt; series!&lt;/p&gt;

&lt;p&gt;If you're not following along, check out how it all started in &lt;a href="https://dev.to/bitmaybewise/tsonnet-0-from-zero-to-interpreter-54na"&gt;the first post of the series&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In the previous post, we added a full set of binary operators:&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/bitmaybewise/tsonnet-32-done-but-getting-there-4h7m" class="crayons-story__hidden-navigation-link"&gt;Tsonnet #32 - != done, but getting there&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/bitmaybewise" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F187316%2Faed95d8a-d5c5-4d3e-8dab-513ed83b24c3.jpeg" alt="bitmaybewise profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/bitmaybewise" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Hercules Lemke Merscher
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Hercules Lemke Merscher
                
              
              &lt;div id="story-author-preview-content-3311307" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/bitmaybewise" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F187316%2Faed95d8a-d5c5-4d3e-8dab-513ed83b24c3.jpeg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Hercules Lemke Merscher&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/bitmaybewise/tsonnet-32-done-but-getting-there-4h7m" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Mar 5&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/bitmaybewise/tsonnet-32-done-but-getting-there-4h7m" id="article-link-3311307"&gt;
          Tsonnet #32 - != done, but getting there
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/tsonnet"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;tsonnet&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/jsonnet"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;jsonnet&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/compiler"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;compiler&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/bitmaybewise/tsonnet-32-done-but-getting-there-4h7m" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;1&lt;span class="hidden s:inline"&gt; reaction&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/bitmaybewise/tsonnet-32-done-but-getting-there-4h7m#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            11 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;




&lt;p&gt;We were &lt;em&gt;almost&lt;/em&gt; done with the &lt;a href="https://jsonnet.org/learning/tutorial.html#arithmetic" rel="noopener noreferrer"&gt;arithmetic tutorial&lt;/a&gt;. Two pieces were still missing: object merging, and division by zero error handling. Let's wrap those up.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftv9bdgo4vk5usyxzrm8e.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftv9bdgo4vk5usyxzrm8e.jpeg" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Merging objects
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;+&lt;/code&gt; operator is overloaded to act as an operator to merge two objects.&lt;/p&gt;

&lt;p&gt;This sample file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsonnet"&gt;&lt;code&gt;&lt;span class="c1"&gt;// samples/objects/merge.jsonnet&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Results in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"a"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"b"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"c"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The right-hand side overrides the left-hand side fields.&lt;/p&gt;

&lt;p&gt;The cram test looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/test/cram/objects.t b/test/cram/objects.t
index 9c12260..bfb25bd 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/test/cram/objects.t
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/test/cram/objects.t
&lt;/span&gt;&lt;span class="p"&gt;@@ -18,3 +18,6 @@&lt;/span&gt;
&lt;span class="err"&gt;
&lt;/span&gt;   $ tsonnet ../../samples/objects/toplevel_field_lookup_chain.jsonnet
   { "answer": { "value": 42 }, "answer_to_the_ultimate_question": 42 }
&lt;span class="gi"&gt;+
+  $ tsonnet ../../samples/objects/merge.jsonnet
+  { "a": 1, "b": 3, "c": 4 }
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'm adding a helper function in the &lt;code&gt;Ast&lt;/code&gt; module to merge two lists of fields:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ocaml"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nc"&gt;Object&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;merge_fields&lt;/span&gt; &lt;span class="n"&gt;fields1&lt;/span&gt; &lt;span class="n"&gt;fields2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="c"&gt;(* fields2 comes after and has preference over fields1 *)&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;updated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;List&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="nn"&gt;List&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assoc_opt&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="n"&gt;fields2&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;
        &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Some&lt;/span&gt; &lt;span class="n"&gt;v'&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;None&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;fields1&lt;/span&gt;
    &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="c"&gt;(* (k,v) in fields2 not present in fields1 *)&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;new_fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;List&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;not&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;List&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mem_assoc&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="n"&gt;fields1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="n"&gt;fields2&lt;/span&gt;
    &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="c"&gt;(* then we merge *)&lt;/span&gt;
    &lt;span class="n"&gt;updated&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt; &lt;span class="n"&gt;new_fields&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The type checker only needs to include the overloaded operation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/lib/type.ml b/lib/type.ml
index d553f5c..fbe68b1 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/lib/type.ml
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/lib/type.ml
&lt;/span&gt;&lt;span class="p"&gt;@@ -321,6 +321,8 @@&lt;/span&gt; and translate_bin_op venv pos op e1 e2 =
   | Add, _, Tstring | Add, Tstring, _ -&amp;gt; ok (venv'', Tstring)
   | Add, Tnumber, Tnumber -&amp;gt; ok (venv'', Tnumber)
   | Add, (Tarray _), (Tarray _) -&amp;gt; ok (venv'', Tarray Tany)
&lt;span class="gi"&gt;+  | Add, (Tobject _ | TruntimeObject _ | TobjectPtr _), (Tobject _ | TruntimeObject _ | TobjectPtr _) -&amp;gt;
+    ok (venv'', Tany)
&lt;/span&gt;   | Subtract, Tnumber, Tnumber -&amp;gt; ok (venv'', Tnumber)
   | Multiply, Tnumber, Tnumber -&amp;gt; ok (venv'', Tnumber)
   | Divide, Tnumber, Tnumber -&amp;gt; ok (venv'', Tnumber)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The interpreter implements the new &lt;code&gt;interpret_object_merge_op&lt;/code&gt; function to deal with that, and we pattern-match in the &lt;code&gt;interpret_bin_op&lt;/code&gt; function to add it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/lib/interpreter.ml b/lib/interpreter.ml
index ddc8041..24f0595 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/lib/interpreter.ml
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/lib/interpreter.ml
&lt;/span&gt;&lt;span class="p"&gt;@@ -66,6 +66,19 @@&lt;/span&gt; and interpret_array_concat_op env e1 e2 =
   | _ -&amp;gt;
     error Error.Msg.interp_invalid_concat
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gi"&gt;+and interpret_object_merge_op env pos e1 e2 =
+  let eval_object = function
+    | EvaluatedObject _ as obj -&amp;gt; ok (env, obj)
+    | RuntimeObject _ as obj -&amp;gt; interpret env obj
+    | _ -&amp;gt; error Error.Msg.invalid_binary_op
+  in
+  let* (_, e1') = eval_object e1 in
+  let* (_, e2') = eval_object e2 in
+  match e1', e2' with
+  | EvaluatedObject (_, fields1), EvaluatedObject (_, fields2) -&amp;gt;
+    ok (env, EvaluatedObject (pos, Object.merge_fields fields1 fields2))
+  | _ -&amp;gt; error Error.Msg.invalid_binary_op
+
&lt;/span&gt; and interpret_array env (pos, exprs) =
   let* (env', evaluated_exprs) = List.fold_left
     (fun result expr -&amp;gt;
&lt;span class="p"&gt;@@ -234,6 +247,8 @@&lt;/span&gt; and interpret_bin_op env (pos, op, e1, e2) =
    | Add, (Array _ as v1), (Array _ as v2)  -&amp;gt;
      interpret_array_concat_op env2 v1 v2
&lt;span class="gi"&gt;+   | Add, (EvaluatedObject _ | RuntimeObject _ as v1), (EvaluatedObject _ | RuntimeObject _ as v2) -&amp;gt;
+     interpret_object_merge_op env2 pos v1 v2
&lt;/span&gt;    | In, (String _ | Ident _ as field), (EvaluatedObject _ | RuntimeObject (_, _, _) as obj) -&amp;gt;
      interpret_in_op env2 pos field obj
    | _, v1, v2 -&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Done!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dune &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; tsonnet samples/objects/merge.jsonnet
&lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"a"&lt;/span&gt;: 1, &lt;span class="s2"&gt;"b"&lt;/span&gt;: 3, &lt;span class="s2"&gt;"c"&lt;/span&gt;: 4 &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Division by zero
&lt;/h2&gt;

&lt;p&gt;Next we should deal with the division by zero error.&lt;/p&gt;

&lt;p&gt;Here's how Jsonnet handles it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;jsonnet samples/errors/divide_by_zero.jsonnet
RUNTIME ERROR: Division by zero.
    samples/errors/divide_by_zero.jsonnet:1:1-6 &lt;span class="err"&gt;$&lt;/span&gt;
    During evaluation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And here are the sample files and how I want it to look in Tsonnet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/samples/errors/divide_by_zero.jsonnet b/samples/errors/divide_by_zero.jsonnet
&lt;/span&gt;&lt;span class="p"&gt;new file mode 100644
&lt;/span&gt;&lt;span class="gh"&gt;index 0000000..59a5d52
&lt;/span&gt;&lt;span class="gd"&gt;--- /dev/null
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/samples/errors/divide_by_zero.jsonnet
&lt;/span&gt;&lt;span class="p"&gt;@@ -0,0 +1 @@&lt;/span&gt;
&lt;span class="gi"&gt;+5 / 0
&lt;/span&gt;&lt;span class="gh"&gt;diff --git a/samples/errors/divide_by_zero_float.jsonnet b/samples/errors/divide_by_zero_float.jsonnet
&lt;/span&gt;&lt;span class="p"&gt;new file mode 100644
&lt;/span&gt;&lt;span class="gh"&gt;index 0000000..20f3764
&lt;/span&gt;&lt;span class="gd"&gt;--- /dev/null
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/samples/errors/divide_by_zero_float.jsonnet
&lt;/span&gt;&lt;span class="p"&gt;@@ -0,0 +1 @@&lt;/span&gt;
&lt;span class="gi"&gt;+5 / 0.0
&lt;/span&gt;&lt;span class="gh"&gt;diff --git a/samples/errors/modulo_by_zero.jsonnet b/samples/errors/modulo_by_zero.jsonnet
&lt;/span&gt;&lt;span class="p"&gt;new file mode 100644
&lt;/span&gt;&lt;span class="gh"&gt;index 0000000..be4046b
&lt;/span&gt;&lt;span class="gd"&gt;--- /dev/null
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/samples/errors/modulo_by_zero.jsonnet
&lt;/span&gt;&lt;span class="p"&gt;@@ -0,0 +1 @@&lt;/span&gt;
&lt;span class="gi"&gt;+5 % 0
&lt;/span&gt;&lt;span class="gh"&gt;diff --git a/samples/errors/modulo_by_zero_float.jsonnet b/samples/errors/modulo_by_zero_float.jsonnet
&lt;/span&gt;&lt;span class="p"&gt;new file mode 100644
&lt;/span&gt;&lt;span class="gh"&gt;index 0000000..8f666b8
&lt;/span&gt;&lt;span class="gd"&gt;--- /dev/null
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/samples/errors/modulo_by_zero_float.jsonnet
&lt;/span&gt;&lt;span class="p"&gt;@@ -0,0 +1 @@&lt;/span&gt;
&lt;span class="gi"&gt;+5 % 0.0
&lt;/span&gt;&lt;span class="gh"&gt;diff --git a/test/cram/errors.t b/test/cram/errors.t
index 7c117b8..dfcac7e 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/test/cram/errors.t
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/test/cram/errors.t
&lt;/span&gt;&lt;span class="p"&gt;@@ -93,9 +105,43 @@&lt;/span&gt;
   $ tsonnet ../../samples/errors/object_outer_most_ref_out_of_scope.jsonnet
   ../../samples/errors/object_outer_most_ref_out_of_scope.jsonnet:2:13 No top-level object found
&lt;span class="err"&gt;
&lt;/span&gt;   2: local _two = $.one + 1;
      ^^^^^^^^^^^^^^^^^^^^^^^
   [1]
&lt;span class="gi"&gt;+
+
+  $ tsonnet ../../samples/errors/divide_by_zero.jsonnet
+  ../../samples/errors/divide_by_zero.jsonnet:1:0 Division by zero
+  
+  1: 5 / 0
+     ^^^^^
+  [1]
+
+
+  $ tsonnet ../../samples/errors/divide_by_zero_float.jsonnet
+  ../../samples/errors/divide_by_zero_float.jsonnet:1:0 Division by zero
+  
+  1: 5 / 0.0
+     ^^^^^^^
+  [1]
+
+
+  $ tsonnet ../../samples/errors/modulo_by_zero.jsonnet
+  ../../samples/errors/modulo_by_zero.jsonnet:1:0 Division by zero
+  
+  1: 5 % 0
+     ^^^^^
+  [1]
+
+
+  $ tsonnet ../../samples/errors/modulo_by_zero_float.jsonnet
+  ../../samples/errors/modulo_by_zero_float.jsonnet:1:0 Division by zero
+  
+  1: 5 % 0.0
+     ^^^^^^^
+  [1]
+
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not a huge difference from the reference implementation, but I like seeing the faulty operation highlighted in the source — much easier to spot and fix.&lt;/p&gt;

&lt;p&gt;Let's add the error message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/lib/error.ml b/lib/error.ml
index 8c6b53f..ac8ae48 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/lib/error.ml
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/lib/error.ml
&lt;/span&gt;&lt;span class="p"&gt;@@ -29,6 +29,7 @@&lt;/span&gt; module Msg = struct
   let type_invalid_lookup_key expr = "Invalid object lookup key: " ^ expr
&lt;span class="err"&gt;
&lt;/span&gt;   (* Interpreter messages *)
&lt;span class="gi"&gt;+  let interp_division_by_zero = "Division by zero"
&lt;/span&gt;   let interp_invalid_concat = "Invalid concatenation operation"
   let interp_invalid_lookup = "Invalid object lookup"
   let interp_cannot_interpret expr = Printf.sprintf "Expression %s cannot be interpreted" expr
&lt;span class="gh"&gt;diff --git a/lib/error.mli b/lib/error.mli
index 58d7e8e..0bdd708 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/lib/error.mli
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/lib/error.mli
&lt;/span&gt;&lt;span class="p"&gt;@@ -26,6 +26,7 @@&lt;/span&gt; module Msg : sig
   val type_invalid_lookup_key : string -&amp;gt; string
&lt;span class="err"&gt;
&lt;/span&gt;   (* Interpreter messages *)
&lt;span class="gi"&gt;+  val interp_division_by_zero : string
&lt;/span&gt;   val interp_invalid_concat : string
   val interp_invalid_lookup : string
   val interp_cannot_interpret : string -&amp;gt; string
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;interpret_arith_op&lt;/code&gt; function only needs to pattern-match on &lt;code&gt;Int&lt;/code&gt; and &lt;code&gt;Float&lt;/code&gt; zero before the division — pretty simple. Have I mentioned how much I enjoy pattern matching?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/lib/interpreter.ml b/lib/interpreter.ml
index 24f0595..fe248b9 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/lib/interpreter.ml
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/lib/interpreter.ml
&lt;/span&gt;&lt;span class="p"&gt;@@ -280,6 +280,8 @@&lt;/span&gt; and interpret_arith_op env (pos, bin_op, n1, n2) =
     ok (env, Number (pos, Float ((float_of_int a) *. b)))
   | Multiply, Number (_, Float a), Number (_, Float b) -&amp;gt;
     ok (env, Number (pos, Float (a *. b)))
&lt;span class="gi"&gt;+  | Divide, _, Number (_, n) when n = Int 0 || n = Float 0.0 -&amp;gt;
+    Error.error_at pos Error.Msg.interp_division_by_zero
&lt;/span&gt;   | Divide, Number (_, Int a), Number (_, Int b) -&amp;gt;
     ok (env, Number (pos, Float ((float_of_int a) /. (float_of_int b))))
   | Divide, Number (_, Float a), Number (_, Int b) -&amp;gt;
&lt;span class="p"&gt;@@ -288,6 +290,8 @@&lt;/span&gt; and interpret_arith_op env (pos, bin_op, n1, n2) =
     ok (env, Number (pos, Float ((float_of_int a) /. b)))
   | Divide, Number (_, Float a), Number (_, Float b) -&amp;gt;
     ok (env, Number (pos, Float (a /. b)))
&lt;span class="gi"&gt;+  | Modulo, _, Number (_, n) when n = Int 0 || n = Float 0.0 -&amp;gt;
+    Error.error_at pos Error.Msg.interp_division_by_zero
&lt;/span&gt;   | Modulo, Number (_, Int a), Number (_, Int b) -&amp;gt;
     ok (env, Number (pos, Int (a mod b)))
   | Modulo, Number (_, Float a), Number (_, Int b) -&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The guard clauses (&lt;code&gt;when n = Int 0 || n = Float 0.0&lt;/code&gt;) catch both integer and float zero before the division cases get a chance to run. &lt;/p&gt;

&lt;h2&gt;
  
  
  Completing the tutorial
&lt;/h2&gt;

&lt;p&gt;With those two additions in place, &lt;code&gt;samples/tutorials/arith.jsonnet&lt;/code&gt; is now fully supported -- minus the string formatting bits I'm deliberately setting aside for later. Here's the file with those commented out:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;concat_array:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;concat_string:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;equality&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;equality&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;x:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
             &lt;/span&gt;&lt;span class="err"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;x:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;ex&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Bitwise&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;operations&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;first&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;cast&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;int.&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;ex&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;self.ex&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Modulo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;operator.&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;ex&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;self.ex&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Boolean&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;logic&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;ex&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Mixing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;objects&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;together&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;obj:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;a:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;b:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;b:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;c:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;field&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;an&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;object&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;obj_member:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'foo'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;foo:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;String&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;formatting&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;str&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'The&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;self.ex&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="err"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;self.ex&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'.'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;str&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'The&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;self.ex&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;%g.'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;self.ex&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;str&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'ex&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;=%&lt;/span&gt;&lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="err"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ex&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="err"&gt;=%&lt;/span&gt;&lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="err"&gt;f'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;self.ex&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;self.ex&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;By&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;passing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;we&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;allow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ex&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ex&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;to&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;be&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;extracted&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;internally.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;str&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'ex&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;=%(ex&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="err"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ex&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="err"&gt;=%(ex&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="err"&gt;f'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;textual&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;templating&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;entire&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;files:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;str&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;|||&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="err"&gt;ex&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;=%(ex&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="err"&gt;f&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="err"&gt;ex&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="err"&gt;=%(ex&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="err"&gt;f&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;|||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dune &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; tsonnet samples/tutorials/arith.jsonnet
&lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"concat_array"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt; 1, 2, 3, 4 &lt;span class="o"&gt;]&lt;/span&gt;,
  &lt;span class="s2"&gt;"concat_string"&lt;/span&gt;: &lt;span class="s2"&gt;"1234"&lt;/span&gt;,
  &lt;span class="s2"&gt;"equality1"&lt;/span&gt;: &lt;span class="nb"&gt;false&lt;/span&gt;,
  &lt;span class="s2"&gt;"equality2"&lt;/span&gt;: &lt;span class="nb"&gt;true&lt;/span&gt;,
  &lt;span class="s2"&gt;"ex1"&lt;/span&gt;: 1.6666666666666665,
  &lt;span class="s2"&gt;"ex2"&lt;/span&gt;: 3,
  &lt;span class="s2"&gt;"ex3"&lt;/span&gt;: 1.6666666666666665,
  &lt;span class="s2"&gt;"ex4"&lt;/span&gt;: &lt;span class="nb"&gt;true&lt;/span&gt;,
  &lt;span class="s2"&gt;"obj"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"a"&lt;/span&gt;: 1, &lt;span class="s2"&gt;"b"&lt;/span&gt;: 3, &lt;span class="s2"&gt;"c"&lt;/span&gt;: 4 &lt;span class="o"&gt;}&lt;/span&gt;,
  &lt;span class="s2"&gt;"obj_member"&lt;/span&gt;: &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;String formatting is next on the list -- but there are more interesting features to tackle first, so I'm parking it for now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Two small additions -- object merging and division by zero -- and the arithmetic tutorial is effectively done. The pattern-matching guard clauses made the zero-division check almost embarrassingly straightforward, and the &lt;code&gt;merge_fields&lt;/code&gt; helper slotted in without touching anything else. &lt;/p&gt;

&lt;p&gt;You can check the entire diff &lt;a href="https://gitlab.com/-/snippets/5969112" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;If you, too, believe dividing by zero should be an error and not a NaN, &lt;a href="https://bitmaybewise.substack.com/" rel="noopener noreferrer"&gt;subscribe&lt;/a&gt; and let's keep each other honest.&lt;/p&gt;

&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@lg17" rel="noopener noreferrer"&gt;Lance Grandahl&lt;/a&gt; on &lt;a href="https://unsplash.com" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>tsonnet</category>
      <category>jsonnet</category>
      <category>compiler</category>
    </item>
    <item>
      <title>Tsonnet #32 - != done, but getting there</title>
      <dc:creator>Hercules Lemke Merscher</dc:creator>
      <pubDate>Thu, 05 Mar 2026 08:56:22 +0000</pubDate>
      <link>https://forem.com/bitmaybewise/tsonnet-32-done-but-getting-there-4h7m</link>
      <guid>https://forem.com/bitmaybewise/tsonnet-32-done-but-getting-there-4h7m</guid>
      <description>&lt;p&gt;Welcome to the &lt;a href="https://gitlab.com/bitmaybewise/tsonnet" rel="noopener noreferrer"&gt;Tsonnet&lt;/a&gt; series!&lt;/p&gt;

&lt;p&gt;If you're not following along, check out how it all started in &lt;a href="https://dev.to/bitmaybewise/tsonnet-0-from-zero-to-interpreter-54na"&gt;the first post of the series&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In the previous post, the equality operation was simplified:&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/bitmaybewise/tsonnet-31-taking-back-control-of-equality-5gn6" class="crayons-story__hidden-navigation-link"&gt;Tsonnet #31 - Taking back control of equality&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/bitmaybewise" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F187316%2Faed95d8a-d5c5-4d3e-8dab-513ed83b24c3.jpeg" alt="bitmaybewise profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/bitmaybewise" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Hercules Lemke Merscher
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Hercules Lemke Merscher
                
              
              &lt;div id="story-author-preview-content-3303243" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/bitmaybewise" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F187316%2Faed95d8a-d5c5-4d3e-8dab-513ed83b24c3.jpeg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Hercules Lemke Merscher&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/bitmaybewise/tsonnet-31-taking-back-control-of-equality-5gn6" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Mar 2&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/bitmaybewise/tsonnet-31-taking-back-control-of-equality-5gn6" id="article-link-3303243"&gt;
          Tsonnet #31 - Taking back control of equality
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/tsonnet"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;tsonnet&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/jsonnet"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;jsonnet&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/compiler"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;compiler&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/bitmaybewise/tsonnet-31-taking-back-control-of-equality-5gn6" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;1&lt;span class="hidden s:inline"&gt; reaction&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/bitmaybewise/tsonnet-31-taking-back-control-of-equality-5gn6#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            9 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;




&lt;p&gt;Now, in order to complete the &lt;a href="https://jsonnet.org/learning/tutorial.html#arithmetic" rel="noopener noreferrer"&gt;arithmetic tutorial&lt;/a&gt;, there's a myriad of operations still left to be implemented. They are quite simple to add actually, so let's do this ASAP!&lt;/p&gt;

&lt;h2&gt;
  
  
  Operations everywhere!
&lt;/h2&gt;

&lt;p&gt;Here are all the operations we are going to add to the lexer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/lib/lexer.mll b/lib/lexer.mll
index 6f51b6f..a4c3745 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/lib/lexer.mll
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/lib/lexer.mll
&lt;/span&gt;&lt;span class="p"&gt;@@ -46,6 +46,13 @@&lt;/span&gt; rule read =
   | "@\"" { read_single_line_verbatim_double_quoted_string (Buffer.create 16) lexbuf }
   | "@'" { read_single_line_verbatim_single_quoted_string (Buffer.create 16) lexbuf }
   | "==" { EQUALITY }
&lt;span class="gi"&gt;+  | "!=" { INEQUALITY }
+  | "&amp;gt;=" { GREATER_EQUAL }
+  | "&amp;lt;=" { LESS_EQUAL }
+  | "&amp;lt;&amp;lt;" { SHIFT_LEFT }
+  | "&amp;gt;&amp;gt;" { SHIFT_RIGHT }
+  | '&amp;gt;' { GREATER }
+  | '&amp;lt;' { LESS }
&lt;/span&gt;   | '[' { LEFT_SQR_BRACKET }
   | ']' { RIGHT_SQR_BRACKET }
   | '{' { LEFT_CURLY_BRACKET }
&lt;span class="p"&gt;@@ -58,14 +65,21 @@&lt;/span&gt; rule read =
   | '-' { MINUS }
   | '*' { MULTIPLY }
   | '/' { DIVIDE }
&lt;span class="gi"&gt;+  | '%' { MODULO }
&lt;/span&gt;   | '!' { NOT }
   | '~' { BITWISE_NOT }
&lt;span class="gi"&gt;+  | "||" { LOGICAL_OR }
+  | '|' { BITWISE_OR }
+  | "&amp;amp;&amp;amp;" { LOGICAL_AND }
+  | "&amp;amp;" { BITWISE_AND }
+  | '^' { BITWISE_XOR }
&lt;/span&gt;   | '=' { ASSIGN }
   | ';' { SEMICOLON }
   | '.' { DOT }
   | "local" { LOCAL }
   | "self" { SELF }
   | "$" { TOP_LEVEL_OBJ }
&lt;span class="gi"&gt;+  | "in" { IN }
&lt;/span&gt;   | id { ID (Lexing.lexeme lexbuf) }
   | _ { raise (SyntaxError ("Unexpected char: " ^ Lexing.lexeme lexbuf)) }
   | eof { EOF }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The parser changes are quite straightforward too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/lib/parser.mly b/lib/parser.mly
index 456e33e..7adb1ee 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/lib/parser.mly
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/lib/parser.mly
&lt;/span&gt;&lt;span class="p"&gt;@@ -20,17 +20,19 @@&lt;/span&gt;
 %token COLON
 %token DOT
 %token SELF TOP_LEVEL_OBJ
&lt;span class="gd"&gt;-%token PLUS MINUS MULTIPLY DIVIDE
&lt;/span&gt;&lt;span class="gi"&gt;+%token PLUS MINUS MULTIPLY DIVIDE MODULO
&lt;/span&gt; %left PLUS MINUS
&lt;span class="gd"&gt;-%left MULTIPLY DIVIDE
&lt;/span&gt;&lt;span class="gi"&gt;+%left MULTIPLY DIVIDE MODULO
&lt;/span&gt; %token &amp;lt;string&amp;gt; ID
&lt;span class="gd"&gt;-%token NOT BITWISE_NOT
-%left NOT BITWISE_NOT
&lt;/span&gt;&lt;span class="gi"&gt;+%token NOT BITWISE_NOT BITWISE_OR BITWISE_AND BITWISE_XOR LOGICAL_AND LOGICAL_OR
+%left  NOT BITWISE_NOT BITWISE_OR BITWISE_AND BITWISE_XOR LOGICAL_AND LOGICAL_OR
&lt;/span&gt; %token SEMICOLON
 %token LOCAL
 %token ASSIGN
&lt;span class="gd"&gt;-%token EQUALITY
-%left EQUALITY
&lt;/span&gt;&lt;span class="gi"&gt;+%token EQUALITY INEQUALITY GREATER GREATER_EQUAL LESS LESS_EQUAL IN
+%left EQUALITY INEQUALITY GREATER GREATER_EQUAL LESS LESS_EQUAL IN
+%token SHIFT_LEFT SHIFT_RIGHT
+%left SHIFT_LEFT SHIFT_RIGHT
&lt;/span&gt; %token EOF
&lt;span class="err"&gt;
&lt;/span&gt; %start &amp;lt;Ast.expr&amp;gt; prog
&lt;span class="p"&gt;@@ -139,12 +141,26 @@&lt;/span&gt; obj_field_access:
   ;
&lt;span class="err"&gt;
&lt;/span&gt; %inline bin_op:
&lt;span class="gd"&gt;-  | PLUS { Add }
-  | MINUS { Subtract }
-  | MULTIPLY { Multiply }
-  | DIVIDE { Divide }
-  | EQUALITY { Equality }
-  ;
&lt;/span&gt;&lt;span class="gi"&gt;+     | PLUS { Add }
+     | MINUS { Subtract }
+     | MULTIPLY { Multiply }
+     | DIVIDE { Divide }
+     | MODULO { Modulo }
+     | EQUALITY { Equality }
+     | INEQUALITY { Inequality }
+     | GREATER { GreaterThan }
+     | GREATER_EQUAL { GreaterThanOrEqual }
+     | LESS { LessThan }
+     | LESS_EQUAL { LessThanOrEqual }
+     | BITWISE_OR { BitwiseOr }
+     | BITWISE_AND { BitwiseAnd }
+     | BITWISE_XOR { BitwiseXor }
+     | LOGICAL_AND { LogicalAnd }
+     | LOGICAL_OR { LogicalOr }
+     | IN { In }
+     | SHIFT_LEFT { ShiftLeft }
+     | SHIFT_RIGHT { ShiftRight }
+     ;
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt; %inline unary_op:
   | PLUS { Plus }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I only had to introduce the new symbols we'd use for each operation.&lt;/p&gt;

&lt;p&gt;The new AST variants:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/lib/ast.ml b/lib/ast.ml
index 03f413e..5111806 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/lib/ast.ml
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/lib/ast.ml
&lt;/span&gt;&lt;span class="p"&gt;@@ -5,7 +5,21 @@&lt;/span&gt; type bin_op =
   | Subtract
   | Multiply
   | Divide
&lt;span class="gi"&gt;+  | Modulo
&lt;/span&gt;   | Equality
&lt;span class="gi"&gt;+  | Inequality
+  | GreaterThan
+  | GreaterThanOrEqual
+  | LessThan
+  | LessThanOrEqual
+  | BitwiseOr
+  | BitwiseAnd
+  | BitwiseXor
+  | LogicalAnd
+  | LogicalOr
+  | In
+  | ShiftLeft
+  | ShiftRight
&lt;/span&gt;   [@@deriving qcheck, show]
&lt;span class="err"&gt;
&lt;/span&gt; type unary_op =
&lt;span class="p"&gt;@@ -160,7 +174,21 @@&lt;/span&gt; let rec string_of_type = function
     | Subtract -&amp;gt; "-"
     | Multiply -&amp;gt; "*"
     | Divide -&amp;gt; "/"
&lt;span class="gi"&gt;+    | Modulo -&amp;gt; "%"
&lt;/span&gt;     | Equality -&amp;gt; "=="
&lt;span class="gi"&gt;+    | Inequality -&amp;gt; "!="
+    | GreaterThan -&amp;gt; "&amp;gt;"
+    | GreaterThanOrEqual -&amp;gt; "&amp;gt;="
+    | LessThan -&amp;gt; "&amp;lt;"
+    | LessThanOrEqual -&amp;gt; "&amp;lt;="
+    | BitwiseOr -&amp;gt; "|"
+    | BitwiseAnd -&amp;gt; "&amp;amp;"
+    | BitwiseXor -&amp;gt; "^"
+    | LogicalAnd -&amp;gt; "&amp;amp;&amp;amp;"
+    | LogicalOr -&amp;gt; "||"
+    | In -&amp;gt; "in"
+    | ShiftLeft -&amp;gt; "&amp;lt;&amp;lt;"
+    | ShiftRight -&amp;gt; "&amp;gt;&amp;gt;"
&lt;/span&gt;     in prefix ^ " " ^ bin_op
   | UnaryOp (_, unary_op, _) -&amp;gt;
     let prefix = "Unary Operation" in
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The type checker changes are also straightforward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/lib/type.ml b/lib/type.ml
index 5de3c4c..d553f5c 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/lib/type.ml
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/lib/type.ml
&lt;/span&gt;&lt;span class="p"&gt;@@ -320,10 +320,29 @@&lt;/span&gt; and translate_bin_op venv pos op e1 e2 =
   match op, e1', e2' with
   | Add, _, Tstring | Add, Tstring, _ -&amp;gt; ok (venv'', Tstring)
   | Add, Tnumber, Tnumber -&amp;gt; ok (venv'', Tnumber)
&lt;span class="gi"&gt;+  | Add, (Tarray _), (Tarray _) -&amp;gt; ok (venv'', Tarray Tany)
&lt;/span&gt;   | Subtract, Tnumber, Tnumber -&amp;gt; ok (venv'', Tnumber)
   | Multiply, Tnumber, Tnumber -&amp;gt; ok (venv'', Tnumber)
   | Divide, Tnumber, Tnumber -&amp;gt; ok (venv'', Tnumber)
&lt;span class="gi"&gt;+  | Modulo, Tnumber, Tnumber -&amp;gt; ok (venv'', Tnumber)
+  | BitwiseOr, Tnumber, Tnumber -&amp;gt; ok (venv'', Tnumber)
+  | BitwiseAnd, Tnumber, Tnumber -&amp;gt; ok (venv'', Tnumber)
+  | BitwiseXor, Tnumber, Tnumber -&amp;gt; ok (venv'', Tnumber)
+  | ShiftLeft, Tnumber, Tnumber -&amp;gt; ok (venv'', Tnumber)
+  | ShiftRight, Tnumber, Tnumber -&amp;gt; ok (venv'', Tnumber)
+  | LogicalAnd, Tbool, Tbool -&amp;gt; ok (venv'', Tbool)
+  | LogicalOr, Tbool, Tbool -&amp;gt; ok (venv'', Tbool)
&lt;/span&gt;   | Equality, _, _ -&amp;gt; ok (venv'', Tbool)
&lt;span class="gi"&gt;+  | Inequality, _, _ -&amp;gt; ok (venv'', Tbool)
+  | GreaterThan, Tnumber, Tnumber -&amp;gt; ok (venv'', Tbool)
+  | GreaterThanOrEqual, Tnumber, Tnumber -&amp;gt; ok (venv'', Tbool)
+  | LessThan, Tnumber, Tnumber -&amp;gt; ok (venv'', Tbool)
+  | LessThanOrEqual, Tnumber, Tnumber -&amp;gt; ok (venv'', Tbool)
+  | GreaterThan, Tstring, Tstring -&amp;gt; ok (venv'', Tbool)
+  | GreaterThanOrEqual, Tstring, Tstring -&amp;gt; ok (venv'', Tbool)
+  | LessThan, Tstring, Tstring -&amp;gt; ok (venv'', Tbool)
+  | LessThanOrEqual, Tstring, Tstring -&amp;gt; ok (venv'', Tbool)
+  | In, Tstring, (Tobject _ | Tany | TruntimeObject _ | TobjectPtr _) -&amp;gt; ok (venv'', Tbool)
&lt;/span&gt;   | _ -&amp;gt; Error.trace Error.Msg.invalid_binary_op pos &amp;gt;&amp;gt;= error
&lt;span class="err"&gt;
&lt;/span&gt; let check (config : Config.t) expr  =
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What changes on evaluation?
&lt;/h2&gt;

&lt;p&gt;The array concatenation was missing, so I added that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/lib/interpreter.ml b/lib/interpreter.ml
index 0b522f2..689e786 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/lib/interpreter.ml
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/lib/interpreter.ml
&lt;/span&gt;&lt;span class="p"&gt;@@ -44,7 +44,7 @@&lt;/span&gt; let rec interpret env expr =
       )
       ~err:(Error.error_at pos)
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;-and interpret_concat_op env e1 e2 =
&lt;/span&gt;&lt;span class="gi"&gt;+and interpret_string_concat_op env e1 e2 =
&lt;/span&gt;     match e1, e2 with
     | String (_, s1), String (_, s2) -&amp;gt;
       ok (env, String (dummy_pos, s1^s2))
&lt;span class="p"&gt;@@ -59,6 +59,13 @@&lt;/span&gt; and interpret_concat_op env e1 e2 =
     | _ -&amp;gt;
       error Error.Msg.interp_invalid_concat
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gi"&gt;+and interpret_array_concat_op env e1 e2 =
+    match e1, e2 with
+    | Array (pos, exprs1), Array (_, exprs2) -&amp;gt;
+      ok (env, Array (pos, List.append exprs1 exprs2))
+    | _ -&amp;gt;
+      error Error.Msg.interp_invalid_concat
+
&lt;/span&gt; and interpret_array env (pos, exprs) =
   let* (env', evaluated_exprs) = List.fold_left
     (fun result expr -&amp;gt;
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="p"&gt;@@ -220,16 +227,20 @@&lt;/span&gt; and interpret_runtime_object_fields obj_env fields =
   | _ -&amp;gt; ok []
&lt;span class="err"&gt;
&lt;/span&gt; and interpret_bin_op env (pos, op, e1, e2) =
&lt;span class="gd"&gt;-  let* (env1, e1') = interpret env e1 in
-  let* (env2, e2') = interpret env1 e2 in
-  match op, e1', e2' with
-  | Add, (String _ as v1), (_ as v2) | Add, (_ as v1), (String _ as v2) -&amp;gt;
-    interpret_concat_op env2 v1 v2
-  | _, v1, v2 -&amp;gt;
-    interpret_arith_op env2 (pos, op, v1, v2)
&lt;/span&gt;&lt;span class="gi"&gt;+   let* (env1, e1') = interpret env e1 in
+   let* (env2, e2') = interpret env1 e2 in
+   match op, e1', e2' with
+   | Add, (String _ as v1), (_ as v2) | Add, (_ as v1), (String _ as v2) -&amp;gt;
+     interpret_string_concat_op env2 v1 v2
+   | Add, (Array _ as v1), (Array _ as v2)  -&amp;gt;
+     interpret_array_concat_op env2 v1 v2
+   | In, (String _ | Ident _ as field), (EvaluatedObject _ | RuntimeObject (_, _, _) as obj) -&amp;gt;
+     interpret_in_op env2 pos field obj
+   | _, v1, v2 -&amp;gt;
+     interpret_arith_op env2 (pos, op, v1, v2)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The previous &lt;code&gt;interpret_concat_op&lt;/code&gt; was renamed to be more specific: &lt;code&gt;interpret_string_concat_op&lt;/code&gt;. A new function was added to handle the concatenation of arrays: &lt;code&gt;interpret_array_concat_op&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We still don't have one for objects, but we'll get there eventually.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;interpret_arith_op&lt;/code&gt; grew into a large matching function. It's a lot of lines, but it's easy to follow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ocaml"&gt;&lt;code&gt;&lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;interpret_arith_op&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bin_op&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
   &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;bin_op&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n2&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Add&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Add&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+.&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;float_of_int&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Add&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;float_of_int&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+.&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Add&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+.&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Subtract&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Subtract&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;-.&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;float_of_int&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Subtract&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;float_of_int&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-.&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Subtract&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;-.&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Multiply&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Multiply&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;*.&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;float_of_int&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Multiply&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;float_of_int&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*.&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Multiply&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;*.&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Divide&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;float_of_int&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/.&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;float_of_int&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Divide&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;/.&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;float_of_int&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Divide&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;float_of_int&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/.&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Divide&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;/.&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Modulo&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="ow"&gt;mod&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Modulo&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Float&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rem&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;float_of_int&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;))))&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Modulo&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Float&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rem&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;float_of_int&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Modulo&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Float&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rem&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;BitwiseOr&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="ow"&gt;lor&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;BitwiseOr&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;int_of_float&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="ow"&gt;lor&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;BitwiseOr&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="ow"&gt;lor&lt;/span&gt; &lt;span class="n"&gt;int_of_float&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;BitwiseOr&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;int_of_float&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="ow"&gt;lor&lt;/span&gt; &lt;span class="n"&gt;int_of_float&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;BitwiseAnd&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="ow"&gt;land&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;BitwiseAnd&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;int_of_float&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="ow"&gt;land&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;BitwiseAnd&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="ow"&gt;land&lt;/span&gt; &lt;span class="n"&gt;int_of_float&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;BitwiseAnd&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;int_of_float&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="ow"&gt;land&lt;/span&gt; &lt;span class="n"&gt;int_of_float&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;BitwiseXor&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="ow"&gt;lxor&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;BitwiseXor&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;int_of_float&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="ow"&gt;lxor&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;BitwiseXor&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="ow"&gt;lxor&lt;/span&gt; &lt;span class="n"&gt;int_of_float&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;BitwiseXor&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;int_of_float&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="ow"&gt;lxor&lt;/span&gt; &lt;span class="n"&gt;int_of_float&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;ShiftLeft&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="ow"&gt;lsl&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;ShiftLeft&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;int_of_float&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="ow"&gt;lsl&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;ShiftLeft&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="ow"&gt;lsl&lt;/span&gt; &lt;span class="n"&gt;int_of_float&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;ShiftLeft&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;int_of_float&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="ow"&gt;lsl&lt;/span&gt; &lt;span class="n"&gt;int_of_float&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;ShiftRight&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;lsr&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;ShiftRight&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;int_of_float&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;lsr&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;ShiftRight&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;lsr&lt;/span&gt; &lt;span class="n"&gt;int_of_float&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;ShiftRight&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;int_of_float&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;lsr&lt;/span&gt; &lt;span class="n"&gt;int_of_float&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;LogicalAnd&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Bool&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Bool&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Bool&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;LogicalOr&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Bool&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Bool&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Bool&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Equality&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Array&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;items1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Array&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;items2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;(* Early exit: skip evaluation if lengths differ for efficiency *)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nn"&gt;List&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="n"&gt;items1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;List&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="n"&gt;items2&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
      &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Bool&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;
      &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;evaluated1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;interpret_array&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;items1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
      &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;evaluated2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;interpret_array&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;items2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
      &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Bool&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;evaluated1&lt;/span&gt; &lt;span class="o"&gt;=~&lt;/span&gt; &lt;span class="n"&gt;evaluated2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Equality&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v2&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;eval_expr1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;interpret&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="n"&gt;v1&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;eval_expr2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;interpret&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="n"&gt;v2&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Bool&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;eval_expr1&lt;/span&gt; &lt;span class="o"&gt;=~&lt;/span&gt; &lt;span class="n"&gt;eval_expr2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Inequality&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v2&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="c"&gt;(* Inequality is, simply put, negation of equality *)&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;interpret_arith_op&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Equality&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;expr&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;
    &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Bool&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Bool&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;not&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;trace&lt;/span&gt; &lt;span class="nn"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;invalid_binary_op&lt;/span&gt; &lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;GreaterThan&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v2&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
   &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Bool&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;Compare&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gt&lt;/span&gt; &lt;span class="n"&gt;v1&lt;/span&gt; &lt;span class="n"&gt;v2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;GreaterThanOrEqual&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v2&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
   &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Bool&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;Compare&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gte&lt;/span&gt; &lt;span class="n"&gt;v1&lt;/span&gt; &lt;span class="n"&gt;v2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;LessThan&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v2&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
   &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Bool&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;Compare&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lt&lt;/span&gt; &lt;span class="n"&gt;v1&lt;/span&gt; &lt;span class="n"&gt;v2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;LessThanOrEqual&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v2&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
   &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Bool&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;Compare&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lte&lt;/span&gt; &lt;span class="n"&gt;v1&lt;/span&gt; &lt;span class="n"&gt;v2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="nn"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;trace&lt;/span&gt; &lt;span class="nn"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;invalid_binary_op&lt;/span&gt; &lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Most operations map directly to their OCaml equivalents. Equality we took care in the previous post. The exception is the comparison operators -- I had to abstract those under the &lt;code&gt;Ast.Compare&lt;/code&gt; module:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ocaml"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nc"&gt;Compare&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;_cmp_num&lt;/span&gt; &lt;span class="n"&gt;n1&lt;/span&gt; &lt;span class="n"&gt;n2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;to_float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;float_of_int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Float&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="nn"&gt;Float&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compare&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to_float&lt;/span&gt; &lt;span class="n"&gt;n1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to_float&lt;/span&gt; &lt;span class="n"&gt;n2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;_cmp_str&lt;/span&gt; &lt;span class="n"&gt;s1&lt;/span&gt; &lt;span class="n"&gt;s2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;String&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compare&lt;/span&gt; &lt;span class="n"&gt;s1&lt;/span&gt; &lt;span class="n"&gt;s2&lt;/span&gt;

  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;_compare&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt; &lt;span class="n"&gt;v1&lt;/span&gt; &lt;span class="n"&gt;v2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v2&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;
    &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_cmp_num&lt;/span&gt; &lt;span class="n"&gt;n1&lt;/span&gt; &lt;span class="n"&gt;n2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_cmp_str&lt;/span&gt; &lt;span class="n"&gt;s1&lt;/span&gt; &lt;span class="n"&gt;s2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class="c"&gt;(* unreachable: the type checker rejects comparisons on
         non-numeric/non-string types before evaluation *)&lt;/span&gt;
      &lt;span class="bp"&gt;false&lt;/span&gt;

  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;gt&lt;/span&gt; &lt;span class="n"&gt;v1&lt;/span&gt; &lt;span class="n"&gt;v2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_compare&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;v1&lt;/span&gt; &lt;span class="n"&gt;v2&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;gte&lt;/span&gt; &lt;span class="n"&gt;v1&lt;/span&gt; &lt;span class="n"&gt;v2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_compare&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;v1&lt;/span&gt; &lt;span class="n"&gt;v2&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;lt&lt;/span&gt; &lt;span class="n"&gt;v1&lt;/span&gt; &lt;span class="n"&gt;v2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_compare&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;v1&lt;/span&gt; &lt;span class="n"&gt;v2&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;lte&lt;/span&gt; &lt;span class="n"&gt;v1&lt;/span&gt; &lt;span class="n"&gt;v2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_compare&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;v1&lt;/span&gt; &lt;span class="n"&gt;v2&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Number comparisons work as you'd expect. Strings are compared by unicode codepoint order, which OCaml's &lt;a href="https://ocaml.org/manual/5.4/api/String.html#VALcompare" rel="noopener noreferrer"&gt;String.compare&lt;/a&gt; handles out of the box.&lt;/p&gt;

&lt;p&gt;There's also a new &lt;code&gt;interpret_in_op&lt;/code&gt; that checks if a field is present in an object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;samples/operations/in.jsonnet&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;has_field:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'foo'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;foo:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;has_no_field:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'xyz'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;foo:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="err"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;field_name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'bar'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;has_field_bar:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;field_name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;bar:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;has_no_field_bar:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;field_name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;baz:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ocaml"&gt;&lt;code&gt;&lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;interpret_in_op&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;field_str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;EvaluatedObject&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Ident&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;field_str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;EvaluatedObject&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;field_exists&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;List&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exists&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;field_str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Bool&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;field_exists&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;field_str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;RuntimeObject&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Ident&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;field_str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;RuntimeObject&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;field_exists&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;ObjectFields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exists&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;field_str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Bool&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;field_exists&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="nn"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;trace&lt;/span&gt; &lt;span class="nn"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nn"&gt;Msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;invalid_binary_op&lt;/span&gt; &lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We must pattern match on both &lt;code&gt;String&lt;/code&gt; and &lt;code&gt;Ident&lt;/code&gt; -- these are the two variants we use to define field names in objects, e.g. &lt;code&gt;{ a: 42 }&lt;/code&gt; or &lt;code&gt;{ "a": 42 }&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verifying
&lt;/h2&gt;

&lt;p&gt;New sample files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//samples/arrays/concat.jsonnet&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;samples/operations/modulo.jsonnet&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;samples/operations/shift_left.jsonnet&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;samples/operations/shift_right.jsonnet&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;samples/operations/bitwise.jsonnet&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;or:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.6&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;xor:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;^&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;and:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;samples/operations/gt_gte_lt_lte.jsonnet&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;gt_&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;gt_&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="err"&gt;gte_&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;gte_&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="err"&gt;lt_&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;lt_&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="err"&gt;lte_&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;lte_&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;samples/operations/in.jsonnet&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;has_field:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'foo'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;foo:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;has_no_field:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'xyz'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;foo:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="err"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;field_name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'bar'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;has_field_bar:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;field_name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;bar:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;has_no_field_bar:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;field_name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;baz:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;samples/operations/inequality.jsonnet&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;eq_number_number:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;dif_number_number:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;dif_number_str:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="err"&gt;eq_str_str:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"42"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"42"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;dif_str_str:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"42"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="err"&gt;eq_array_array:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;dif_array_array:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="err"&gt;eq_obj_obj:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;x:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;y:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;z:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;z:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;x:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;y:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;dif_obj_obj:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;a:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;b:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;b:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;c:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="err"&gt;eq_complex_obj_complex_obj:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;x:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;x:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;dif_complex_obj_complex_obj:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;a:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;b:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;b:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;a:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;samples/operations/logical.jsonnet&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;and_&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;and_&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;or_&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;or_&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;samples/strings/comparison.jsonnet&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Two&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;strings&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;can&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;be&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;compared&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`&amp;lt;`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;(unicode&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;codepoint&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;order).&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;gt_&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"é"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"e"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;gte_&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"é"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"e"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;lt_&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"e"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"é"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;lte_&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"e"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"é"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="err"&gt;gt_&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"é"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"e"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;gte_&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"é"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"e"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;lt_&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"e"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"é"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;lte_&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"e"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"é"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="err"&gt;ordered:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;std.sort(&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"b"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"é"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"A"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;samples/operations/in.jsonnet&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;has_field:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'foo'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;foo:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;has_no_field:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'xyz'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;foo:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="err"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;field_name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'bar'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;has_field_bar:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;field_name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;bar:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;has_no_field_bar:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;field_name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;baz:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Of course, I match each one with a cram test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/test/cram/arrays.t b/test/cram/arrays.t
&lt;/span&gt;&lt;span class="p"&gt;new file mode 100644
&lt;/span&gt;&lt;span class="gh"&gt;index 0000000..b840584
&lt;/span&gt;&lt;span class="gd"&gt;--- /dev/null
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/test/cram/arrays.t
&lt;/span&gt;&lt;span class="p"&gt;@@ -0,0 +1,2 @@&lt;/span&gt;
&lt;span class="gi"&gt;+  $ tsonnet ../../samples/arrays/concat.jsonnet
+  [ 1, 2, 3, 4 ]
&lt;/span&gt;&lt;span class="gh"&gt;diff --git a/test/cram/operations.t b/test/cram/operations.t
index bb1c93a..503084f 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/test/cram/operations.t
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/test/cram/operations.t
&lt;/span&gt;&lt;span class="p"&gt;@@ -1,22 +1,25 @@&lt;/span&gt;
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;-  $ tsonnet ../../samples/equality.jsonnet
&lt;/span&gt;&lt;span class="gi"&gt;+  $ tsonnet ../../samples/operations/modulo.jsonnet
+  1
+
+  $ tsonnet ../../samples/operations/equality.jsonnet
&lt;/span&gt;   {
     "dif_array_array": false,
     "dif_complex_obj_complex_obj": false,
&lt;span class="p"&gt;@@ -30,3 +33,23 @@&lt;/span&gt;
     "eq_obj_obj": true,
     "eq_str_str": true
   }
&lt;span class="gi"&gt;+
+  $ tsonnet ../../samples/operations/bitwise.jsonnet
+  { "and": 1, "or": 3, "xor": 2 }
+
+  $ tsonnet ../../samples/operations/logical.jsonnet
+  { "and_false": false, "and_true": true, "or_false": false, "or_true": true }
+
+  $ tsonnet ../../samples/operations/in.jsonnet
+  {
+    "has_field": true,
+    "has_field_bar": true,
+    "has_no_field": false,
+    "has_no_field_bar": false
+  }
+
+  $ tsonnet ../../samples/operations/shift_left.jsonnet
+  2
+
+  $ tsonnet ../../samples/operations/shift_right.jsonnet
+  2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;What looked like a mountain of work turned out to be quite mechanical -- once the scaffolding is in place, each new operator slotted in with minimal friction.&lt;/p&gt;

&lt;p&gt;You can check the entire diff &lt;a href="https://gitlab.com/-/snippets/5964588" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Thanks for reading Bit Maybe Wise! If &lt;code&gt;'foo' in { foo: 1 }&lt;/code&gt; returns &lt;code&gt;true&lt;/code&gt; for you too, &lt;a href="https://bitmaybewise.substack.com/" rel="noopener noreferrer"&gt;subscribe&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@joa70" rel="noopener noreferrer"&gt;Joachim Schnürle&lt;/a&gt; on &lt;a href="https://unsplash.com" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>tsonnet</category>
      <category>jsonnet</category>
      <category>compiler</category>
    </item>
    <item>
      <title>Tsonnet #31 - Taking back control of equality</title>
      <dc:creator>Hercules Lemke Merscher</dc:creator>
      <pubDate>Mon, 02 Mar 2026 20:40:42 +0000</pubDate>
      <link>https://forem.com/bitmaybewise/tsonnet-31-taking-back-control-of-equality-5gn6</link>
      <guid>https://forem.com/bitmaybewise/tsonnet-31-taking-back-control-of-equality-5gn6</guid>
      <description>&lt;p&gt;Welcome to the &lt;a href="https://gitlab.com/bitmaybewise/tsonnet" rel="noopener noreferrer"&gt;Tsonnet&lt;/a&gt; series!&lt;/p&gt;

&lt;p&gt;If you're not following along, check out how it all started in &lt;a href="https://dev.to/bitmaybewise/tsonnet-0-from-zero-to-interpreter-54na"&gt;the first post of the series&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In the previous post, I implemented the equality operator:&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/bitmaybewise/tsonnet-30-dabbling-with-equality-17fp" class="crayons-story__hidden-navigation-link"&gt;Tsonnet #30 - Dabbling with equality&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/bitmaybewise" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F187316%2Faed95d8a-d5c5-4d3e-8dab-513ed83b24c3.jpeg" alt="bitmaybewise profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/bitmaybewise" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Hercules Lemke Merscher
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Hercules Lemke Merscher
                
              
              &lt;div id="story-author-preview-content-3208845" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/bitmaybewise" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F187316%2Faed95d8a-d5c5-4d3e-8dab-513ed83b24c3.jpeg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Hercules Lemke Merscher&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/bitmaybewise/tsonnet-30-dabbling-with-equality-17fp" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jan 30 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/bitmaybewise/tsonnet-30-dabbling-with-equality-17fp" id="article-link-3208845"&gt;
          Tsonnet #30 - Dabbling with equality
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/tsonnet"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;tsonnet&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/jsonnet"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;jsonnet&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/compiler"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;compiler&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
            &lt;a href="https://dev.to/bitmaybewise/tsonnet-30-dabbling-with-equality-17fp#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            10 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;




&lt;p&gt;In a messy way, I should say. So, let's take back control.&lt;/p&gt;

&lt;h2&gt;
  
  
  Encoding the compiler phases as phantom types
&lt;/h2&gt;

&lt;p&gt;In the previous post I mentioned this, and it was my initial idea. The premise has its merit. We encode each compiler phase in the type system, as each phase will only deal with AST variants that matter in that stage. I still believe this should be the end state of the compiler, but I played with it, and decided not to pursue this path. At least, not yet.&lt;/p&gt;

&lt;p&gt;I made a quick and dirty attempt, you can see the diff &lt;a href="https://gitlab.com/-/snippets/5933225" rel="noopener noreferrer"&gt;here&lt;/a&gt;, but in the end, I was not satisfied with how it turned out. &lt;/p&gt;

&lt;p&gt;Why? Because it introduced too much noise, and made the code brittle, IMO. I don't want to enforce too much strictness at this point while the compiler is still changing. Though I still believe this is going to be valuable eventually.&lt;/p&gt;

&lt;p&gt;For now, I chose a different and simpler path.&lt;/p&gt;

&lt;h2&gt;
  
  
  A new variant
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;EvaluatedObject&lt;/code&gt; comes into play:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/lib/ast.ml b/lib/ast.ml
index ab91d6f..03f413e 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/lib/ast.ml
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/lib/ast.ml
&lt;/span&gt;&lt;span class="p"&gt;@@ -55,11 +55,16 @@&lt;/span&gt; module ObjectFields = struct
 end
&lt;span class="err"&gt;
&lt;/span&gt; type expr =
&lt;span class="gi"&gt;+  (* terminal variants can appear in any stage, representable in the final JSON *)
&lt;/span&gt;   | Unit
   | Null of position
   | Number of position * number
   | Bool of position * bool
   | String of position * string
&lt;span class="gi"&gt;+  | EvaluatedObject of position * (string * expr) list
+
+  (* interpreted variants were parsed, interpreted and transformed,
+    but are not terminal, and are waiting to be transformed into terminal variants *)
&lt;/span&gt;   | Ident of position * string
   | Array of position * expr list
   | ParsedObject of position * object_entry list
&lt;span class="p"&gt;@@ -71,6 +76,7 @@&lt;/span&gt; type expr =
   | Local of position * (string * expr) list
   | Seq of expr list
   | IndexedExpr of position * string * expr
&lt;span class="gi"&gt;+
&lt;/span&gt; and object_entry =
   | ObjectField of string * expr
   | ObjectExpr of expr
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we'll draw a line between the type variants that are terminal (can be representable as JSON), and those that are merely there for intermediate steps (used by the interpreter).&lt;/p&gt;

&lt;p&gt;This gives us the power to simplify our workflow, starting with the &lt;code&gt;semantic_equal&lt;/code&gt; function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;@@ -100,11 +106,12 @@&lt;/span&gt; let rec semantic_equal evaluated_expr1 evaluated_expr2 =
   | Array (_, items1), Array (_, items2) -&amp;gt;
     List.length items1 = List.length items2
     &amp;amp;&amp;amp; List.for_all2 semantic_equal items1 items2
&lt;span class="gd"&gt;-  | ParsedObject (_, entries1), ParsedObject (_, entries2) -&amp;gt;
-    List.length entries1 = List.length entries2
-    &amp;amp;&amp;amp; List.for_all2 object_entry_semantic_equal entries1 entries2
-  | RuntimeObject (_, env1, fields1), RuntimeObject (_, env2, fields2) -&amp;gt;
-    runtime_object_semantic_equal (env1, fields1) (env2, fields2)
&lt;/span&gt;&lt;span class="gi"&gt;+  | EvaluatedObject (_, fields1), EvaluatedObject (_, fields2) -&amp;gt;
+    List.length fields1 = List.length fields2
+    &amp;amp;&amp;amp; List.for_all2
+      (fun (k1, v1) (k2, v2) -&amp;gt; k1 = k2 &amp;amp;&amp;amp; semantic_equal v1 v2)
+      fields1
+      fields2
&lt;/span&gt;   | ObjectPtr (id1, scope1), ObjectPtr (id2, scope2) -&amp;gt;
     id1 = id2 &amp;amp;&amp;amp; scope1 = scope2
   | _, _ -&amp;gt;
&lt;span class="p"&gt;@@ -112,36 +119,6 @@&lt;/span&gt; let rec semantic_equal evaluated_expr1 evaluated_expr2 =
       just representable values such as number, boolean, string, object, array *)
     false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Intermediate object variants are not relevant in this function anymore.&lt;/p&gt;

&lt;p&gt;We can remove some ugly code now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;-and object_entry_semantic_equal entry1 entry2 =
-  match (entry1, entry2) with
-  | ObjectField (name1, e1), ObjectField (name2, e2) -&amp;gt;
-    name1 = name2 &amp;amp;&amp;amp; semantic_equal e1 e2
-  | ObjectExpr e1, ObjectExpr e2 -&amp;gt;
-    semantic_equal e1 e2
-  | _, _ -&amp;gt; false
-
-and runtime_object_semantic_equal (env1, fields1) (env2, fields2) =
-  (* RuntimeObjects contain lazy (unevaluated) fields.
-    Full semantic comparison of field values requires
-    evaluation, which must be done in the interpreter. *)
-  let get_obj_id env =
-    match Env.Map.find_opt "self" env with
-    | Some (ObjectPtr (obj_id, _)) -&amp;gt; Some obj_id
-    | _ -&amp;gt; None
-  in
-  (match (get_obj_id env1, get_obj_id env2) with
-  | Some obj_id1, Some obj_id2 -&amp;gt;
-    ObjectFields.equal fields1 fields2
-    &amp;amp;&amp;amp; ObjectFields.for_all (fun field -&amp;gt;
-      let key1 = Env.uniq_field_ident obj_id1 field in
-      let key2 = Env.uniq_field_ident obj_id2 field in
-      match (Env.Map.find_opt key1 env1, Env.Map.find_opt key2 env2) with
-      | Some v1, Some v2 -&amp;gt; semantic_equal v1 v2
-      | _, _ -&amp;gt; false
-    ) fields1
-  | _, _ -&amp;gt; false
-  )
-
&lt;/span&gt; let ( =~ ) = semantic_equal
&lt;span class="err"&gt;
&lt;/span&gt; let debug (config : Config.t) (ast : expr) : (expr, string) result =
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By accepting only &lt;code&gt;EvaluatedObject&lt;/code&gt; in the &lt;code&gt;semantic_equal&lt;/code&gt; function, we invert the control of who evaluates the AST, leaving the responsibility for the interpreter. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnzdt6zny74fynx1j4ris.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnzdt6zny74fynx1j4ris.gif" alt="this is the way"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The interpreter owns the evaluation
&lt;/h2&gt;

&lt;p&gt;Before the evaluation changes, let's make sure parsing equality has the right associativity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/lib/parser.mly b/lib/parser.mly
index 158e4ab..456e33e 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/lib/parser.mly
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/lib/parser.mly
&lt;/span&gt;&lt;span class="p"&gt;@@ -30,6 +30,7 @@&lt;/span&gt;
 %token LOCAL
 %token ASSIGN
 %token EQUALITY
&lt;span class="gi"&gt;+%left EQUALITY
&lt;/span&gt; %token EOF
&lt;span class="err"&gt;
&lt;/span&gt; %start &amp;lt;Ast.expr&amp;gt; prog
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;EvaluatedObject&lt;/code&gt; will be returned as-is by &lt;code&gt;interpret&lt;/code&gt; function, and the pattern match that used to return &lt;code&gt;RuntimeObject&lt;/code&gt; as-is will call &lt;code&gt;interpret_runtime_object&lt;/code&gt; from now on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/lib/interpreter.ml b/lib/interpreter.ml
index de0dd80..0b522f2 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/lib/interpreter.ml
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/lib/interpreter.ml
&lt;/span&gt;&lt;span class="p"&gt;@@ -14,10 +14,10 @@&lt;/span&gt; let interpret_unary_op (op: unary_op) (evaluated_expr: expr) =
 (** [interpret expr] interprets and reduce the intermediate AST [expr] into a result AST. *)
 let rec interpret env expr =
   match expr with
&lt;span class="gd"&gt;-  | Null _ | Bool _ | String _ | Number _ -&amp;gt; ok (env, expr)
&lt;/span&gt;&lt;span class="gi"&gt;+  | Null _ | Bool _ | String _ | Number _ | EvaluatedObject _ -&amp;gt; ok (env, expr)
&lt;/span&gt;   | Array (pos, exprs) -&amp;gt; interpret_array env (pos, exprs)
   | ParsedObject (pos, entries) -&amp;gt; interpret_object env (pos, entries)
&lt;span class="gd"&gt;-  | RuntimeObject _ as runtime_obj -&amp;gt; ok (env, runtime_obj)
&lt;/span&gt;&lt;span class="gi"&gt;+  | RuntimeObject (pos, obj_env, fields) -&amp;gt; interpret_runtime_object env (pos, obj_env, fields)
&lt;/span&gt;   | ObjectPtr _ as obj_ptr -&amp;gt; ok (env, obj_ptr)
   | ObjectFieldAccess (pos, scope, chain) -&amp;gt; interpret_object_field_access env (pos, scope, chain)
   | Ident (pos, varname) -&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The recursive nature of &lt;code&gt;interpret&lt;/code&gt; is extremely flexible and elegant to reduce AST variants. Don't you think?&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Json&lt;/code&gt; module can be simplified a lot with this change too. Here's a sneak peek of its usage from now on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;@@ -49,10 +49,12 @@&lt;/span&gt; and interpret_concat_op env e1 e2 =
     | String (_, s1), String (_, s2) -&amp;gt;
       ok (env, String (dummy_pos, s1^s2))
     | String (_, s1), val2 -&amp;gt;
&lt;span class="gd"&gt;-      let* s2 = Json.expr_to_string ~eval:interpret (env, val2) in
&lt;/span&gt;&lt;span class="gi"&gt;+      let* (_, val2) = interpret env val2 in
+      let* s2 = Json.expr_to_string val2 in
&lt;/span&gt;       ok (env, String (dummy_pos, s1^s2))
     | val1, String (_, s2) -&amp;gt;
&lt;span class="gd"&gt;-      let* s1 = Json.expr_to_string ~eval:interpret (env, val1) in
&lt;/span&gt;&lt;span class="gi"&gt;+      let* (_, val1) = interpret env val1 in
+      let* s1 = Json.expr_to_string val1 in
&lt;/span&gt;       ok (env, String (dummy_pos, s1^s2))
     | _ -&amp;gt;
       error Error.Msg.interp_invalid_concat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We'll get back to it. Let's get back to &lt;code&gt;interpret_runtime_object&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;@@ -97,25 +99,6 @@&lt;/span&gt; and interpret_seq env exprs =
     interpret env expr &amp;gt;&amp;gt;= fun (env', _) -&amp;gt;
     interpret env' (Seq exprs')
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;-and fully_evaluate_obj_fields obj_env fields =
-  let get_obj_id env =
-    match Env.Map.find_opt "self" env with
-    | Some (ObjectPtr (obj_id, _)) -&amp;gt; Some obj_id
-    | _ -&amp;gt; None
-  in
-  match get_obj_id obj_env with
-  | None -&amp;gt; ok Env.Map.empty
-  | Some obj_id -&amp;gt;
-    ObjectFields.fold (fun field acc -&amp;gt;
-      let* evaluated_fields = acc in
-      let key = Env.uniq_field_ident obj_id field in
-      match Env.Map.find_opt key obj_env with
-      | Some expr -&amp;gt;
-        let* (_, evaluated) = interpret obj_env expr in
-        ok (Env.Map.add field evaluated evaluated_fields)
-      | None -&amp;gt; acc
-    ) fields (ok Env.Map.empty)
-
&lt;/span&gt; and interpret_object env (pos, entries) =
   let* obj_id = Env.Id.generate () in
   let obj_env = Env.add_local "self" (ObjectPtr (obj_id, Self)) env in
&lt;span class="p"&gt;@@ -213,6 +196,29 @@&lt;/span&gt; and interpret_object_field_access env (pos, scope, chain_exprs) =
     (ok (env', obj))
     chain_exprs
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gi"&gt;+and interpret_runtime_object env (pos, obj_env, fields) =
+  let* evaluated_fields = interpret_runtime_object_fields obj_env fields in
+  ok (env, EvaluatedObject (pos, evaluated_fields))
+
+and interpret_runtime_object_fields obj_env fields =
+  match Env.Map.find_opt "self" obj_env with
+  | Some (ObjectPtr (obj_id, _)) -&amp;gt;
+    let* field_list =
+      ObjectFields.fold
+        (fun field acc -&amp;gt;
+          let* evaluated_fields = acc in
+          let key = Env.uniq_field_ident obj_id field in
+          match Env.Map.find_opt key obj_env with
+          | Some expr -&amp;gt;
+            let* (_, evaluated) = interpret obj_env expr in
+            ok ((field, evaluated) :: evaluated_fields)
+          | None -&amp;gt; acc
+        )
+        fields
+        (ok [])
+    in ok (List.rev field_list)
+  | _ -&amp;gt; ok []
+
&lt;/span&gt; and interpret_bin_op env (pos, op, e1, e2) =
   let* (env1, e1') = interpret env e1 in
   let* (env2, e2') = interpret env1 e2 in
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the equality operation becomes much more manageable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;@@ -256,37 +262,54 @@&lt;/span&gt; and interpret_arith_op env (pos, bin_op, n1, n2) =
     ok (env, Number (pos, Float ((float_of_int a) /. b)))
   | Divide, Number (_, Float a), Number (_, Float b) -&amp;gt;
     ok (env, Number (pos, Float (a /. b)))
&lt;span class="gd"&gt;-  | Equality, RuntimeObject (_, env1, fields1), RuntimeObject (_, env2, fields2) -&amp;gt;
-    if not (ObjectFields.equal fields1 fields2) then
-      ok (env, Bool (pos, false))
-    else
-      let* evaluated1 = fully_evaluate_obj_fields env1 fields1 in
-      let* evaluated2 = fully_evaluate_obj_fields env2 fields2 in
-      let* are_equal = ObjectFields.fold (fun field acc -&amp;gt;
-        let* all_equal = acc in
-        if not all_equal then ok false
-        else
-          match (Env.Map.find_opt field evaluated1, Env.Map.find_opt field evaluated2) with
-          | Some v1, Some v2 -&amp;gt; ok (v1 =~ v2)
-          | _, _ -&amp;gt; ok false
-      ) fields1 (ok true) in
-      ok (env, Bool (pos, are_equal))
&lt;/span&gt;   | Equality, Array (_, items1), Array (_, items2) -&amp;gt;
&lt;span class="gi"&gt;+    (* Early exit: skip evaluation if lengths differ for efficiency *)
&lt;/span&gt;     if List.length items1 &amp;lt;&amp;gt; List.length items2 then
       ok (env, Bool (pos, false))
     else
&lt;span class="gd"&gt;-      let* are_equal = List.fold_left2 (fun acc item1 item2 -&amp;gt;
-        let* all_equal = acc in
-        if not all_equal then ok false
-        else
-          match interpret_bin_op env (pos, Equality, item1, item2) with
-          | Ok (_, Bool (_, eq)) -&amp;gt; ok eq
-          | _ -&amp;gt; ok false
-      ) (ok true) items1 items2 in
-      ok (env, Bool (pos, are_equal))
&lt;/span&gt;&lt;span class="gi"&gt;+      let* (_, evaluated1) = interpret_array env (pos, items1) in
+      let* (_, evaluated2) = interpret_array env (pos, items2) in
+      ok (env, Bool (pos, evaluated1 =~ evaluated2))
&lt;/span&gt;   | Equality, v1, v2 -&amp;gt;
&lt;span class="gd"&gt;-    ok (env, Bool (pos, v1 =~ v2))
&lt;/span&gt;&lt;span class="gi"&gt;+    let* (_, eval_expr1) = interpret env v1 in
+    let* (_, eval_expr2) = interpret env v2 in
+    ok (env, Bool (pos, eval_expr1 =~ eval_expr2))
&lt;/span&gt;   | _ -&amp;gt;
     Error.trace Error.Msg.invalid_binary_op pos &amp;gt;&amp;gt;= error
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before handing over the interpretation phase to the serialization phase, now that we delineated the terminal variants, let's deep evaluate the AST expression:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ocaml"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;rec&lt;/span&gt; &lt;span class="n"&gt;deep_eval&lt;/span&gt; &lt;span class="n"&gt;expr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;expr&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Null&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Bool&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="n"&gt;expr&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;Array&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;evaluated_items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;List&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fold_left&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="n"&gt;acc&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="kt"&gt;list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;acc&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;evaluated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;deep_eval&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
        &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;evaluated&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt; &lt;span class="kt"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="bp"&gt;[]&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;items&lt;/span&gt;
    &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Array&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;List&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rev&lt;/span&gt; &lt;span class="n"&gt;evaluated_items&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;EvaluatedObject&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;evaluated_fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;List&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fold_left&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="n"&gt;acc&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="kt"&gt;list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;acc&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;evaluated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;deep_eval&lt;/span&gt; &lt;span class="n"&gt;expr&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
        &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;evaluated&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;::&lt;/span&gt; &lt;span class="kt"&gt;list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="bp"&gt;[]&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;fields&lt;/span&gt;
    &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;EvaluatedObject&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;List&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rev&lt;/span&gt; &lt;span class="n"&gt;evaluated_fields&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;RuntimeObject&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;evaluated&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;interpret&lt;/span&gt; &lt;span class="nn"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;empty&lt;/span&gt; &lt;span class="n"&gt;expr&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="n"&gt;deep_eval&lt;/span&gt; &lt;span class="n"&gt;evaluated&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;expr&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;evaluated&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;interpret&lt;/span&gt; &lt;span class="nn"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;empty&lt;/span&gt; &lt;span class="n"&gt;expr&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="n"&gt;deep_eval&lt;/span&gt; &lt;span class="n"&gt;evaluated&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;deep_eval&lt;/code&gt; function guarantees that we have only terminal values before serializing.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;eval&lt;/code&gt; function now works with a 2 step interpretation. First the &lt;code&gt;interpret&lt;/code&gt; that will evaluate the AST expressions lazily, and the &lt;code&gt;deep_eval&lt;/code&gt; that will walk down the AST transforming the variants into terminal variants:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ocaml"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;eval&lt;/span&gt; &lt;span class="n"&gt;expr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;interpret&lt;/span&gt; &lt;span class="nn"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;empty&lt;/span&gt; &lt;span class="n"&gt;expr&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;expr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;deep_eval&lt;/span&gt; &lt;span class="n"&gt;expr&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
  &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="n"&gt;expr&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do you know what we can do now? Simplify the JSON rendering!&lt;/p&gt;

&lt;h2&gt;
  
  
  JSON is finally free from the evaluation bad smell
&lt;/h2&gt;

&lt;p&gt;Look how simple the &lt;code&gt;Json&lt;/code&gt; module became -- simple like in the very early stages of Tsonnet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/lib/json.ml b/lib/json.ml
index c8e2159..a40cea6 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/lib/json.ml
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/lib/json.ml
&lt;/span&gt;&lt;span class="p"&gt;@@ -2,15 +2,7 @@&lt;/span&gt; open Ast
 open Result
 open Syntax_sugar
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;-(* The interpreter type allows us to break the circular dependency
-   between Json and Interpreter modules.
-   Ideally, the Json module does not need to know anything about the
-   previous step.
-   TODO: Json module fuctions should not receive unevaluated values.
-   Guarantee Interpreter generates a new and evaluated AST. *)
-type interpreter = expr Env.Map.t -&amp;gt; expr -&amp;gt; ((expr Env.Map.t * expr), string) result
-
-let rec value_to_yojson ~(eval: interpreter) (env : expr Env.Map.t) (expr : Ast.expr) : (Yojson.t, string) result =
&lt;/span&gt;&lt;span class="gi"&gt;+let rec value_to_yojson (expr : Ast.expr) : (Yojson.t, string) result =
&lt;/span&gt;   match expr with
   | Number (_, n) -&amp;gt;
     ok (match n with
&lt;span class="p"&gt;@@ -20,31 +12,29 @@&lt;/span&gt; let rec value_to_yojson ~(eval: interpreter) (env : expr Env.Map.t) (expr : Ast.
   | Bool (_, b) -&amp;gt; ok (`Bool b)
   | String (_, s) -&amp;gt; ok (`String s)
   | Array (_, values) -&amp;gt;
&lt;span class="gd"&gt;-    let expr_to_list expr' = to_list (value_to_yojson ~eval env expr') in
-    let results = values |&amp;gt; List.map expr_to_list |&amp;gt; List.concat in
-    ok (`List results)
-  | RuntimeObject (pos, obj_env, fieldset) -&amp;gt; obj_to_yojson ~eval env (pos, obj_env, fieldset)
-  | expr -&amp;gt; error (Error.Msg.value_not_represetable_as_json (string_of_type expr))
-
-and obj_to_yojson ~(eval: interpreter) _env (pos, obj_env, fieldset) =
-  let* fields =
-    ObjectFields.fold
-      (fun field acc -&amp;gt;
-        let* obj_id = match Env.find_opt "self" obj_env with
-        | Some (Ast.ObjectPtr (obj_id, _)) -&amp;gt; ok obj_id
-        | _ -&amp;gt; error Error.Msg.must_be_object
-        in
-        let* (env', expr) =
-          Env.get_obj_field field obj_id obj_env ~succ:eval ~err:(Error.error_at pos)
-        in
-        let* yo_value = value_to_yojson ~eval env' expr in
-        let* fields = acc in
-        ok ((field, yo_value) :: fields)
&lt;/span&gt;&lt;span class="gi"&gt;+    let* results = List.fold_left
+      (fun acc v -&amp;gt;
+        let* list = acc in
+        let* json = value_to_yojson v in
+        ok (json :: list)
&lt;/span&gt;       )
&lt;span class="gd"&gt;-      fieldset
&lt;/span&gt;       (ok [])
&lt;span class="gd"&gt;-  in ok (`Assoc (List.rev fields))
&lt;/span&gt;&lt;span class="gi"&gt;+      values
+    in
+    ok (`List (List.rev results))
+  | EvaluatedObject (_, fields) -&amp;gt;
+    let* json_fields = List.fold_left
+      (fun acc (name, expr) -&amp;gt;
+        let* list = acc in
+        let* json = value_to_yojson expr in
+        ok ((name, json) :: list)
+      )
+      (ok [])
+      fields
+    in
+    ok (`Assoc (List.rev json_fields))
+  | expr -&amp;gt; error (Error.Msg.value_not_represetable_as_json (string_of_type expr))
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;-let expr_to_string ~(eval: interpreter) (env, expr) =
-  let yojson = value_to_yojson ~eval env expr
&lt;/span&gt;&lt;span class="gi"&gt;+let expr_to_string expr =
+  let yojson = value_to_yojson expr
&lt;/span&gt;   in Result.map Yojson.pretty_to_string yojson
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It doesn't need to care about how &lt;code&gt;expr&lt;/code&gt; is interpreted anymore -- single responsibility:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/lib/tsonnet.ml b/lib/tsonnet.ml
index 6913295..d142c9e 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/lib/tsonnet.ml
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/lib/tsonnet.ml
&lt;/span&gt;&lt;span class="p"&gt;@@ -23,4 +23,4 @@&lt;/span&gt; let run (config : Config.t) (filename: string) : (string, string) result =
     &amp;gt;&amp;gt;= Ast.debug config
     &amp;gt;&amp;gt;= Type.check config
     &amp;gt;&amp;gt;= Interpreter.eval
&lt;span class="gd"&gt;-    &amp;gt;&amp;gt;= Json.expr_to_string ~eval:Interpreter.interpret
&lt;/span&gt;&lt;span class="gi"&gt;+    &amp;gt;&amp;gt;= Json.expr_to_string
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5foc98yvb1ywoxm5nac5.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5foc98yvb1ywoxm5nac5.gif" alt="feels good"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;EvaluatedObject&lt;/code&gt; variant did a lot of heavy lifting here. By drawing a clear line between terminal and intermediate AST variants, we were able to push the responsibility for evaluation squarely back onto the interpreter, simplify &lt;code&gt;semantic_equal&lt;/code&gt; into something I'm no longer embarrassed to look at, and let the &lt;code&gt;Json&lt;/code&gt; module be blissfully ignorant of how things got evaluated. That TODO comment in the previous post aged well — sometimes the cleanest solutions come from just letting a problem sleep on it.&lt;/p&gt;

&lt;p&gt;The entire diff can be seen &lt;a href="https://gitlab.com/-/snippets/5962499" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Thanks for reading Bit Maybe Wise! &lt;a href="https://bitmaybewise.substack.com/" rel="noopener noreferrer"&gt;Subscribe&lt;/a&gt; to follow along as I convince OCaml's type system to do my job for me.&lt;/p&gt;

&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@jeshoots" rel="noopener noreferrer"&gt;JESHOOTS.COM&lt;/a&gt; on &lt;a href="https://unsplash.com" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>tsonnet</category>
      <category>jsonnet</category>
      <category>compiler</category>
    </item>
    <item>
      <title>ABEND dump #25</title>
      <dc:creator>Hercules Lemke Merscher</dc:creator>
      <pubDate>Sat, 28 Feb 2026 18:14:27 +0000</pubDate>
      <link>https://forem.com/bitmaybewise/abend-dump-25-5640</link>
      <guid>https://forem.com/bitmaybewise/abend-dump-25-5640</guid>
      <description>&lt;p&gt;Welcome to the ABEND dump #25!&lt;/p&gt;

&lt;p&gt;Don't know what is an “ABEND dump”?! &lt;a href="https://bitmaybewise.substack.com/i/68092102/what-is-abend-dump" rel="noopener noreferrer"&gt;I'm glad you asked&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can check the previous ABEND dump here:&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/bitmaybewise/abend-dump-24-45oc" class="crayons-story__hidden-navigation-link"&gt;ABEND dump #24&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/bitmaybewise" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F187316%2Faed95d8a-d5c5-4d3e-8dab-513ed83b24c3.jpeg" alt="bitmaybewise profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/bitmaybewise" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Hercules Lemke Merscher
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Hercules Lemke Merscher
                
              
              &lt;div id="story-author-preview-content-3133983" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/bitmaybewise" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F187316%2Faed95d8a-d5c5-4d3e-8dab-513ed83b24c3.jpeg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Hercules Lemke Merscher&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/bitmaybewise/abend-dump-24-45oc" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Dec 31 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/bitmaybewise/abend-dump-24-45oc" id="article-link-3133983"&gt;
          ABEND dump #24
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/abenddump"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;abenddump&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/bitmaybewise/abend-dump-24-45oc" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;1&lt;span class="hidden s:inline"&gt; reaction&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/bitmaybewise/abend-dump-24-45oc#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            3 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;




&lt;p&gt;Everybody is talking about AI lately, so inevitably I'd share the drama in the ABEND dump eventually, and here we are.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;a href="https://x.com/exec_sum/status/2024472886052835740?s=20" rel="noopener noreferrer"&gt;Sam Altman (OpenAI) and Dario Amodei (Anthropic) refuse to hold hands&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;WTF with these guys?! Are they in the first grade yet? ¬¬&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F93qg0yhxvenenmgcg55i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F93qg0yhxvenenmgcg55i.png" alt="OpenAI vs Anthropic"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Which lead us to...&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://x.com/claudeai/status/2019071113741906403?s=20" rel="noopener noreferrer"&gt;Claude and Ads&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgamlw2mwso1levhnbjgz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgamlw2mwso1levhnbjgz.png" alt="Claude Ads"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I feel like I'm living in a Black Mirror episode now.&lt;/p&gt;

&lt;p&gt;You don't know what I'm talking about? Go watch this:&lt;/p&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/buVDDbjFCBc"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

&lt;p&gt;At least, it is doing work for us, and reducing our burden. Or, isn't it?&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://hbr.org/2026/02/ai-doesnt-reduce-work-it-intensifies-it" rel="noopener noreferrer"&gt;AI doesn't reduce work, it intensifies it&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;While some programmers were freaking out, fearing being replace by AI. Well, now there's some proof that it is not robing us the work, but in fact creating more work for us. Not exactly what we were expecting, right?&lt;/p&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/FahA3C8Xpqo"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://registerspill.thorstenball.com/p/joy-and-curiosity-75" rel="noopener noreferrer"&gt;Joy &amp;amp; Curiosity #75&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;And Thorsten's Substack is my favorite source of AI related stuff these days. Like this quote from &lt;a href="https://www.modular.com/blog/the-claude-c-compiler-what-it-reveals-about-the-future-of-software" rel="noopener noreferrer"&gt;Chris Lattner&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Lower barriers to implementation do not reduce the importance of engineers; instead, they elevate the importance of vision, judgment, and taste. When creation becomes easier, deciding &lt;em&gt;what is worth creating&lt;/em&gt; becomes the harder problem. AI accelerates execution, but meaning, direction, and responsibility remain fundamentally human.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Long live to tasteful software!&lt;/p&gt;

&lt;p&gt;Speaking of which...&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://www.peonping.com/" rel="noopener noreferrer"&gt;Stop babysitting your terminal&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdiobcm6s1sr1t1lfvswy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdiobcm6s1sr1t1lfvswy.png" alt="peon ping"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I can't believe I used &lt;a href="https://opencode.ai/" rel="noopener noreferrer"&gt;opencode&lt;/a&gt; all this time without this! &lt;/p&gt;

&lt;p&gt;I never played WoW, so I tweaked peonping to have Bender from Futurama telling me things have completed. It's so useful, and so much fun XD&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy5q3akuzvefk1r4b0mg2.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy5q3akuzvefk1r4b0mg2.gif" alt="bender neat"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Thanks for reading the ABEND dump! Where the content is curated by a human, for humans, with the occasional Bender GIF. That's a feature, not a bug.&lt;/p&gt;

</description>
      <category>abenddump</category>
    </item>
    <item>
      <title>ABEND dump #24</title>
      <dc:creator>Hercules Lemke Merscher</dc:creator>
      <pubDate>Wed, 31 Dec 2025 08:00:00 +0000</pubDate>
      <link>https://forem.com/bitmaybewise/abend-dump-24-45oc</link>
      <guid>https://forem.com/bitmaybewise/abend-dump-24-45oc</guid>
      <description>&lt;p&gt;Welcome to &lt;strong&gt;ABEND dump #24&lt;/strong&gt;!&lt;/p&gt;

&lt;p&gt;If you wanna know what is the "ABEND dump", &lt;a href="https://bitmaybewise.substack.com/i/68092102/what-is-abend-dump" rel="noopener noreferrer"&gt;I've got you covered&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;You can check the previous ABEND dump here: &lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/bitmaybewise/abend-dump-23-3o9j" class="crayons-story__hidden-navigation-link"&gt;ABEND dump #23&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/bitmaybewise" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F187316%2Faed95d8a-d5c5-4d3e-8dab-513ed83b24c3.jpeg" alt="bitmaybewise profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/bitmaybewise" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Hercules Lemke Merscher
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Hercules Lemke Merscher
                
              
              &lt;div id="story-author-preview-content-3010798" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/bitmaybewise" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F187316%2Faed95d8a-d5c5-4d3e-8dab-513ed83b24c3.jpeg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Hercules Lemke Merscher&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/bitmaybewise/abend-dump-23-3o9j" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Nov 10 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/bitmaybewise/abend-dump-23-3o9j" id="article-link-3010798"&gt;
          ABEND dump #23
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/abenddump"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;abenddump&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
            &lt;a href="https://dev.to/bitmaybewise/abend-dump-23-3o9j#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            3 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;




&lt;p&gt;Yeah, I know, I missed the opportunity to release this on Christmas eve. I've been unplugged from the usual tech content treadmill -- less blog reading, less social media doom-scrolling. Turns out playing "Clair Obscur: Expedition 33" and hanging out with family is way more interesting than doomscrolling through yet another AI hot take. Who knew? &lt;/p&gt;

&lt;p&gt;This one's lighter than usual, but sometimes that's exactly what we need.&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;a href="https://youtu.be/3Y1G9najGiI?si=JRXuJnAfO3gG6NlO" rel="noopener noreferrer"&gt;AWS re:Invent 2025 - Keynote with Dr. Werner Vogels&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;The first 5 minutes of this keynote captures something I've been thinking about the AI trend: we're treating it like it's unprecedented, but software development has always evolved. Remember when everyone said the web would replace desktop apps? Or mobile would kill the web? Or cloud would eliminate sysadmins? &lt;/p&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/3Y1G9najGiI"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

&lt;p&gt;AI-assisted development might seem revolutionary because of the pace, but ultimately it's another tool in the toolbox. People will adapt, integrate it into their workflows, and move on to worrying about the next thing. The hype cycle is exhausting, but the underlying truth remains: good engineering principles outlast trendy tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://nithinbekal.com/posts/ruby-4-0/" rel="noopener noreferrer"&gt;What's new in Ruby 4.0&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Ruby turned 30 this Christmas, and as tradition demands, dropped a new version. Ruby 4.0 brings some interesting changes, though honestly, I haven't dug deep enough to have strong opinions yet.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Ruby::Box&lt;/code&gt; stands out as potentially game-changing -- it's essentially a way to box values for safe mutation tracking, which could help with some gnarly concurrency patterns. I'm curious to see how the community adopts it. Will it become ubiquitous like blocks and procs, or will it be one of those features that everyone acknowledges is cool but rarely uses?&lt;/p&gt;

&lt;p&gt;The official announcement is &lt;a href="https://www.ruby-lang.org/en/news/2025/11/17/ruby-4-0-0-preview2-released/" rel="noopener noreferrer"&gt;here&lt;/a&gt; if you want the full details.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://www.youtube.com/watch?v=j-BwR-Cw0Gk&amp;amp;list=PL2HVqYf7If8cY4wLk7JUQ2f0JXY_xMQm2" rel="noopener noreferrer"&gt;Advent of Compiler Optimisations&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Matt Goldbolt's &lt;a href="https://xania.org/202511/advent-of-compiler-optimisation" rel="noopener noreferrer"&gt;Advent of Compiler Optimisations&lt;/a&gt; is a phenomenal series breaking down compiler quirks and optimizations. Fair warning: I'm still working through it -- my assembly knowledge isn't where I'd like it to be, so some episodes require multiple rewatches.&lt;/p&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/j-BwR-Cw0Gk"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

&lt;p&gt;But that's exactly why it's valuable. Seeing how seemingly innocent code changes can trigger wildly different compiler behaviors is eye-opening. It's the kind of series that makes you question every performance assumption you've ever made.&lt;/p&gt;

&lt;p&gt;If you're into compilers, low-level programming, or just enjoy having your mind bent by optimization strategies, this is worth your time.&lt;/p&gt;

&lt;p&gt;And, speaking of compilers... &lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://bitmaybewise.substack.com/p/tsonnet-series-table-of-contents" rel="noopener noreferrer"&gt;Tsonnet series - Table of contents&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;I put together an index of all my Tsonnet posts so far. If you've been following along, now you have one place to reference everything. If you haven't, you can skim through and see if anything catches your interest.&lt;/p&gt;

&lt;p&gt;Building a compiler from scratch has been educational (and occasionally maddening) side projects I've taken on. The table of contents makes it easier to follow the journey from "what is Tsonnet?" to "oh god, self-referential objects are a nightmare."&lt;/p&gt;




&lt;p&gt;Thanks for reading the ABEND dump! May your holidays be free of production incidents and full of whatever brings you joy -- whether that's diving into compiler optimizations or finally beating that video game. &lt;/p&gt;

&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@thanhpure" rel="noopener noreferrer"&gt;Thanh Tran&lt;/a&gt; on &lt;a href="https://unsplash.com" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>abenddump</category>
    </item>
    <item>
      <title>Tsonnet #29 - Making inner references work</title>
      <dc:creator>Hercules Lemke Merscher</dc:creator>
      <pubDate>Sat, 13 Dec 2025 10:15:21 +0000</pubDate>
      <link>https://forem.com/bitmaybewise/tsonnet-29-making-inner-references-work-502o</link>
      <guid>https://forem.com/bitmaybewise/tsonnet-29-making-inner-references-work-502o</guid>
      <description>&lt;p&gt;Welcome to the &lt;a href="https://gitlab.com/bitmaybewise/tsonnet" rel="noopener noreferrer"&gt;Tsonnet&lt;/a&gt; series!&lt;/p&gt;

&lt;p&gt;If you're not following along, check out how it all started in &lt;a href="https://dev.to/bitmaybewise/tsonnet-0-from-zero-to-interpreter-54na"&gt;the first post of the series&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In the previous post, I added &lt;code&gt;ppx_deriving.show&lt;/code&gt; to help debug the AST and centralized configuration:&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/bitmaybewise/tsonnet-28-debugging-gets-pretty-printed-cbf" class="crayons-story__hidden-navigation-link"&gt;Tsonnet #28 - Debugging gets pretty (printed)&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/bitmaybewise" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F187316%2Faed95d8a-d5c5-4d3e-8dab-513ed83b24c3.jpeg" alt="bitmaybewise profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/bitmaybewise" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Hercules Lemke Merscher
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Hercules Lemke Merscher
                
              
              &lt;div id="story-author-preview-content-3099561" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/bitmaybewise" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F187316%2Faed95d8a-d5c5-4d3e-8dab-513ed83b24c3.jpeg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Hercules Lemke Merscher&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/bitmaybewise/tsonnet-28-debugging-gets-pretty-printed-cbf" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Dec 11 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/bitmaybewise/tsonnet-28-debugging-gets-pretty-printed-cbf" id="article-link-3099561"&gt;
          Tsonnet #28 - Debugging gets pretty (printed)
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/jsonnet"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;jsonnet&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/tsonnet"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;tsonnet&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/compiler"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;compiler&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/bitmaybewise/tsonnet-28-debugging-gets-pretty-printed-cbf" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;1&lt;span class="hidden s:inline"&gt; reaction&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/bitmaybewise/tsonnet-28-debugging-gets-pretty-printed-cbf#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            6 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;




&lt;p&gt;Time to tackle another piece of the Jsonnet tutorial: inner references. This is the second part of working with object references, and it gets... interesting.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we're building
&lt;/h2&gt;

&lt;p&gt;Here's the sample file we need to handle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;samples/tutorials/inner-reference.jsonnet&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;Martini:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;drink&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;ingredients:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;kind:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Farmer's Gin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;qty:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;kind:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'Dry&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;White&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Vermouth'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="err"&gt;qty:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;drink.ingredients&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;.qty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;garnish:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'Olive'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;served:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'Straight&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Up'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tricky bit? That &lt;code&gt;local drink = self&lt;/code&gt; inside the Martini object. The variable &lt;code&gt;drink&lt;/code&gt; holds a reference to the object itself, and then we use it to access &lt;code&gt;drink.ingredients[0].qty&lt;/code&gt;. This is self-reference through an intermediate variable.&lt;/p&gt;

&lt;p&gt;The expected output (as a cram test):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/test/cram/tutorials.t b/test/cram/tutorials.t
index b9c1f6f..9fe544c 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/test/cram/tutorials.t
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/test/cram/tutorials.t
&lt;/span&gt;&lt;span class="p"&gt;@@ -79,3 +79,15 @@&lt;/span&gt;
       "served": "Tall"
     }
   }
&lt;span class="gi"&gt;+
+  $ tsonnet ../../samples/tutorials/inner-reference.jsonnet
+  {
+    "Martini": {
+      "garnish": "Olive",
+      "ingredients": [
+        { "kind": "Farmer's Gin", "qty": 1 },
+        { "kind": "Dry White Vermouth", "qty": 1 }
+      ],
+      "served": "Straight Up"
+    }
+  }
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Extending the AST with variable references
&lt;/h2&gt;

&lt;p&gt;We already have &lt;code&gt;Self&lt;/code&gt; and &lt;code&gt;TopLevel&lt;/code&gt; for object scopes. Now we need a way to reference objects through variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;@@ -76,6 +76,7 @@&lt;/span&gt; and object_entry =
 and object_scope =
   | Self
   | TopLevel
&lt;span class="gi"&gt;+  | ObjVarRef of string
&lt;/span&gt; [@@deriving show]
&lt;span class="err"&gt;
&lt;/span&gt; let dummy_expr = Unit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Objects also need their own environment. Before, we were storing just an &lt;code&gt;env_id&lt;/code&gt; in &lt;code&gt;RuntimeObject&lt;/code&gt;, but now we need the full environment to properly resolve these variable references:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;@@ -62,7 +62,7 @@&lt;/span&gt; type expr =
   | Ident of position * string
   | Array of position * expr list
   | ParsedObject of position * object_entry list
&lt;span class="gd"&gt;-  | RuntimeObject of position * (Env.env_id [@opaque]) * ObjectFields.t
&lt;/span&gt;&lt;span class="gi"&gt;+  | RuntimeObject of position * (expr Env.Map.t [@opaque]) * ObjectFields.t
&lt;/span&gt;   | ObjectPtr of (Env.env_id [@opaque]) * object_scope
   | ObjectFieldAccess of position * object_scope * expr list
   | BinOp of position * bin_op * expr * expr
&lt;span class="p"&gt;@@ -109,9 +111,8 @@&lt;/span&gt; let rec string_of_type = function
   | ParsedObject (_, fields) -&amp;gt;
     Printf.sprintf "PlainObject{%s}"
       (String.concat ", " (List.map string_of_object_entry fields))
&lt;span class="gd"&gt;-  | RuntimeObject (_, (Env.EnvId id), fields) -&amp;gt;
-    Printf.sprintf "obj&amp;lt;%d&amp;gt;{%s}" id
-      (String.concat ", " (ObjectFields.to_list fields))
&lt;/span&gt;&lt;span class="gi"&gt;+  | RuntimeObject (_, _env, fields) -&amp;gt;
+    Printf.sprintf "obj{%s}" (String.concat ", " (ObjectFields.to_list fields))
&lt;/span&gt;   | BinOp (_, bin_op, _, _) -&amp;gt;
     let prefix = "Binary Operation" in
     let bin_op = match bin_op with
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Wrestling with the parser
&lt;/h2&gt;

&lt;p&gt;The parser changes are where things get hairy. We need to handle three cases:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;self.field&lt;/code&gt; or &lt;code&gt;$.field&lt;/code&gt; (with optional chains)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;drink.field&lt;/code&gt; (variable reference requiring at least one field access)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;drink[expr]&lt;/code&gt; (variable reference with bracket notation)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The second case is crucial for interpreting the inner reference sample file.&lt;/p&gt;

&lt;p&gt;Here's the updated grammar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/lib/parser.mly b/lib/parser.mly
index 9d4ca52..6a5b704 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/lib/parser.mly
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/lib/parser.mly
&lt;/span&gt;&lt;span class="p"&gt;@@ -103,38 +103,38 @@&lt;/span&gt; obj_field_list:
   ;
&lt;span class="err"&gt;
&lt;/span&gt; obj_field_expr:
&lt;span class="gd"&gt;-  | DOT; e = indexed_expr { e }
&lt;/span&gt;&lt;span class="gi"&gt;+  | DOT; LEFT_SQR_BRACKET; e = assignable_expr; RIGHT_SQR_BRACKET { e }
&lt;/span&gt;   | DOT; id = identifier { id }
   ;
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gi"&gt;+obj_field_chain_item:
+  | e = obj_field_expr { e }
+  | LEFT_SQR_BRACKET; e = assignable_expr; RIGHT_SQR_BRACKET { e }
+  ;
+
&lt;/span&gt; obj_field_chain:
   | { [] }
&lt;span class="gd"&gt;-  | id = obj_field_expr; ids = obj_field_chain { id :: ids }
&lt;/span&gt;&lt;span class="gi"&gt;+  | id = obj_field_chain_item; ids = obj_field_chain { id :: ids }
+  ;
+
+obj_field_chain_nonempty:
+  | id = obj_field_chain_item { [id] }
+  | id = obj_field_chain_item; ids = obj_field_chain_nonempty { id :: ids }
&lt;/span&gt;   ;
&lt;span class="err"&gt;
&lt;/span&gt; obj_scope:
   | SELF { Self }
   | TOP_LEVEL_OBJ { TopLevel }
&lt;span class="gi"&gt;+  | id = ID { ObjVarRef id }
&lt;/span&gt;   ;
&lt;span class="err"&gt;
&lt;/span&gt; obj_field_access:
&lt;span class="gd"&gt;-  | scope = obj_scope; chain = obj_field_chain { ObjectFieldAccess (with_pos $startpos $endpos, scope, chain) }
-  (* The first bracketed expr when accessing an object field
-     must be explicitly declared here, instead of being part
-     of `object_field_expr`.
-
-     Adding the bracketed expr there will make the grammar unclear
-     since Menhir will need to decide between parsing one of the options:
-     1) .identifier
-     2) .identifier[expr]
-
-     By tying to the scope, such as $[expr], the grammar is now clear
-     and Menhir doesn't need to decide on its own.
-  *)
-  | scope = obj_scope;
-    LEFT_SQR_BRACKET; e = assignable_expr; RIGHT_SQR_BRACKET;
-    chain = obj_field_chain
-    { ObjectFieldAccess (with_pos $startpos $endpos, scope, e :: chain) }
&lt;/span&gt;&lt;span class="gi"&gt;+  (* For self and $, allow empty chain *)
+  | SELF; chain = obj_field_chain { ObjectFieldAccess (with_pos $startpos $endpos, Self, chain) }
+  | TOP_LEVEL_OBJ; chain = obj_field_chain { ObjectFieldAccess (with_pos $startpos $endpos, TopLevel, chain) }
+  (* For ID-based scope, only match if there's a dot (obj_field_expr) or multiple bracket accesses *)
+  | id = ID; field = obj_field_expr; chain = obj_field_chain { ObjectFieldAccess (with_pos $startpos $endpos, ObjVarRef id, field :: chain) }
+  | id = ID; LEFT_SQR_BRACKET; e = assignable_expr; RIGHT_SQR_BRACKET; rest = obj_field_chain_nonempty { ObjectFieldAccess (with_pos $startpos $endpos, ObjVarRef id, e :: rest) }
&lt;/span&gt;   ;
&lt;span class="err"&gt;
&lt;/span&gt; %inline number:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The comments in the original code explain it well, but the key insight is: we need separate rules for &lt;code&gt;self&lt;/code&gt;/&lt;code&gt;$&lt;/code&gt; (which can stand alone or have chains) versus variable references (which must have at least one field access to be meaningful).&lt;/p&gt;

&lt;p&gt;If you're fuzzy on shift/reduce conflicts, I wrote about debugging them &lt;a href="https://dev.to/bitmaybewise/debugging-shift-reduce-conflicts-lessons-learned-building-tsonnets-parser-53j3"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scope validation
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;Scope&lt;/code&gt; module needs a small update to handle &lt;code&gt;ObjVarRef&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/lib/scope.ml b/lib/scope.ml
index 8846f39..ecbf17c 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/lib/scope.ml
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/lib/scope.ml
&lt;/span&gt;&lt;span class="p"&gt;@@ -103,14 +103,19 @@&lt;/span&gt; and validate_object_field_access pos scope context =
     local x = self.field;
     local x = $.field;
     outside of objects *)
&lt;span class="gd"&gt;-  if not context.in_object
-  then
-    let with_error_msg = match scope with
-                        | Self -&amp;gt; Error.Msg.self_out_of_scope
-                        | TopLevel -&amp;gt; Error.Msg.no_toplevel_object
-    in
-    Error.trace with_error_msg pos &amp;gt;&amp;gt;= error
-  else ok ()
&lt;/span&gt;&lt;span class="gi"&gt;+  match scope with
+  | Self | TopLevel -&amp;gt;
+    if not context.in_object then
+      let with_error_msg = match scope with
+        | Self -&amp;gt; Error.Msg.self_out_of_scope
+        | TopLevel -&amp;gt; Error.Msg.no_toplevel_object
+        | ObjVarRef _ -&amp;gt; "" (* unreachable *)
+      in
+      Error.trace with_error_msg pos &amp;gt;&amp;gt;= error
+    else ok ()
+  | ObjVarRef _ -&amp;gt;
+    (* Variable references are allowed anywhere *)
+    ok ()
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt; and validate_locals vars context =
   (* This is crucial - it catches: local x = self.field; outside objects *)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Variable references don't have the same scoping restrictions as &lt;code&gt;self&lt;/code&gt; and &lt;code&gt;$&lt;/code&gt; -- they're just regular identifiers that happen to point to objects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before we proceed to the relevant part
&lt;/h2&gt;

&lt;p&gt;The interpreter went through a more than trivial refactoring.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;interpret_concat_op&lt;/code&gt; moved below to be part of the recursive definition of &lt;code&gt;interpret&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/lib/interpreter.ml b/lib/interpreter.ml
index 77b3ac2..ca33fd6 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/lib/interpreter.ml
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/lib/interpreter.ml
&lt;/span&gt;&lt;span class="p"&gt;@@ -21,17 +21,6 @@&lt;/span&gt; let interpret_arith_op (op: bin_op) (n1: number) (n2: number) =
   | Divide, (Int a), (Float b) -&amp;gt; Float ((float_of_int a) /. b)
   | Divide, (Float a), (Float b) -&amp;gt; Float (a /. b)
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;-let interpret_concat_op env (e1 : expr) (e2 : expr) : (expr, string) result =
-  match e1, e2 with
-  | String (_, s1), String (_, s2) -&amp;gt;
-    ok (String (dummy_pos, s1^s2))
-  | String (_, s1), val2 -&amp;gt;
-    let* s2 = Json.expr_to_string (env, val2) in ok (String (dummy_pos, s1^s2))
-  | val1, String (_, s2) -&amp;gt;
-    let* s1 = Json.expr_to_string (env, val1) in ok (String (dummy_pos, s1^s2))
-  | _ -&amp;gt;
-    error Error.Msg.interp_invalid_concat
-
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same has been done with &lt;code&gt;interpret_local&lt;/code&gt; and &lt;code&gt;interpret_seq&lt;/code&gt; -- it's cleaner to read now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt; let interpret_unary_op (op: unary_op) (evaluated_expr: expr) =
   match op, evaluated_expr with
   | Plus, number -&amp;gt; ok number
&lt;span class="p"&gt;@@ -48,6 +37,7 @@&lt;/span&gt; let rec interpret env expr =
   | Array (pos, exprs) -&amp;gt; interpret_array env (pos, exprs)
   | ParsedObject (pos, entries) -&amp;gt; interpret_object env (pos, entries)
   | RuntimeObject _ as runtime_obj -&amp;gt; ok (env, runtime_obj)
&lt;span class="gi"&gt;+  | ObjectPtr _ as obj_ptr -&amp;gt; ok (env, obj_ptr)
&lt;/span&gt;   | ObjectFieldAccess (pos, scope, chain) -&amp;gt; interpret_object_field_access env (pos, scope, chain)
   | Ident (pos, varname) -&amp;gt;
     Env.find_var varname env
&lt;span class="p"&gt;@@ -70,16 +60,9 @@&lt;/span&gt; let rec interpret env expr =
     Result.fold (interpret_unary_op op expr')
       ~ok:(fun expr' -&amp;gt; ok (env', expr'))
       ~error:(Error.error_at pos)
&lt;span class="gd"&gt;-  | Local (_, vars) -&amp;gt;
-    let acc_fun env (varname, expr) = Env.add_local varname expr env in
-    let env' = List.fold_left acc_fun env vars
-    in ok (env', Unit)
&lt;/span&gt;&lt;span class="gi"&gt;+  | Local (_, vars) -&amp;gt; interpret_local env vars
&lt;/span&gt;   | Unit -&amp;gt; ok (env, Unit)
&lt;span class="gd"&gt;-  | Seq exprs -&amp;gt;
-    (match exprs with
-    | [] -&amp;gt; ok (env, Unit)
-    | [expr] -&amp;gt; interpret env expr
-    | (expr :: exprs) -&amp;gt; interpret env expr &amp;gt;&amp;gt;= fun (env', _) -&amp;gt; interpret env' (Seq exprs))
&lt;/span&gt;&lt;span class="gi"&gt;+  | Seq exprs -&amp;gt; interpret_seq env exprs
&lt;/span&gt;   | IndexedExpr (pos, varname, index_expr) -&amp;gt;
     let* (env', index_expr') = interpret env index_expr in
     Env.find_var varname env'
&lt;span class="p"&gt;@@ -90,8 +73,17 @@&lt;/span&gt; let rec interpret env expr =
           ~error:(Error.error_at pos)
       )
       ~err:(Error.error_at pos)
&lt;span class="gd"&gt;-    | expr -&amp;gt;
-      error (Error.Msg.interp_cannot_interpret (string_of_type expr))
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The only difference in &lt;code&gt;interpret_concat_op&lt;/code&gt; is the new &lt;code&gt;eval&lt;/code&gt; parameter passed to &lt;code&gt;Json.expr_to_string&lt;/code&gt; -- we'll come to it in a bit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gi"&gt;+
+and interpret_concat_op env (e1 : expr) (e2 : expr) : (expr, string) result =
+    match e1, e2 with
+    | String (_, s1), String (_, s2) -&amp;gt;
+      ok (String (dummy_pos, s1^s2))
+    | String (_, s1), val2 -&amp;gt;
+      let* s2 = Json.expr_to_string ~eval:interpret (env, val2) in ok (String (dummy_pos, s1^s2))
+    | val1, String (_, s2) -&amp;gt;
+      let* s1 = Json.expr_to_string ~eval:interpret (env, val1) in ok (String (dummy_pos, s1^s2))
+    | _ -&amp;gt;
+      error Error.Msg.interp_invalid_concat
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt; and interpret_array env (pos, exprs) =
   let* (env', evaluated_exprs) = List.fold_left
&lt;span class="p"&gt;@@ -104,14 +96,44 @@&lt;/span&gt; and interpret_array env (pos, exprs) =
     exprs
   in ok (env', Array (pos, evaluated_exprs))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And &lt;code&gt;interpret_seq&lt;/code&gt; did not change a thing, it's just wrapped in its own function now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gi"&gt;+and interpret_seq env exprs =
+  match exprs with
+  | [] -&amp;gt; ok (env, Unit)
+  | [expr] -&amp;gt; interpret env expr
+  | (expr :: exprs') -&amp;gt;
+    interpret env expr &amp;gt;&amp;gt;= fun (env', _) -&amp;gt;
+    interpret env' (Seq exprs')
+
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Lazy evaluation arrives
&lt;/h2&gt;

&lt;p&gt;This is where the interpreter changes get substantial. Up until now, we were eagerly evaluating in multiple parts of the code. But with inner references, we need proper lazy evaluation to avoid infinite loops.&lt;/p&gt;

&lt;p&gt;Consider this pathological case:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;drink&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;x:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;drink.x&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we evaluate &lt;code&gt;drink.x&lt;/code&gt; eagerly, we'd recurse infinitely. The solution is to only evaluate object fields when they're actually accessed.&lt;/p&gt;

&lt;p&gt;Here's the new &lt;code&gt;interpret_local&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ocaml"&gt;&lt;code&gt;&lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;interpret_local&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="n"&gt;vars&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;env'&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nn"&gt;List&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fold_left&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="n"&gt;acc&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;varname&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;acc&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
        &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;expr&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt;
        &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;ObjectFieldAccess&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Self&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;TopLevel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;[]&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
          &lt;span class="c"&gt;(* Eagerly evaluate unchained self/$ references to capture the current object.
            AST example:
            (Ast.Local (3:3, [("drink", (Ast.ObjectFieldAccess (3:3, Ast.Self, [])))])));
          *)&lt;/span&gt;
          &lt;span class="k"&gt;let&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env'&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;evaluated_expr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;interpret&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="n"&gt;expr&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
          &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_local&lt;/span&gt; &lt;span class="n"&gt;varname&lt;/span&gt; &lt;span class="n"&gt;evaluated_expr&lt;/span&gt; &lt;span class="n"&gt;env'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
          &lt;span class="c"&gt;(* Other expressions remain lazy *)&lt;/span&gt;
          &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_local&lt;/span&gt; &lt;span class="n"&gt;varname&lt;/span&gt; &lt;span class="n"&gt;expr&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;vars&lt;/span&gt;
  &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env'&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exception is &lt;code&gt;self&lt;/code&gt; and &lt;code&gt;$&lt;/code&gt; with no field chain -- we need to evaluate those immediately to capture the concrete object ID. For everything else, we store the unevaluated expression.&lt;/p&gt;

&lt;p&gt;I'm pretty sure there's some edge cases hiding in this part as we progress in the implementation, but this is enough to make the current tests to pass--baby steps is my mantra.&lt;/p&gt;

&lt;h2&gt;
  
  
  Simplifying object interpretation
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;interpret_object&lt;/code&gt; function actually got simpler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt; and interpret_object env (pos, entries) =
   let* obj_id = Env.Id.generate () in
&lt;span class="gd"&gt;-  let had_toplevel = Option.is_some (Env.find_opt "$" env) in
-  let self_expr = ObjectPtr (obj_id, Self) in
-  let env' = Env.add_local "self" self_expr env in
-  let env', toplevel_expr = Env.add_local_when_not_present "$" (ObjectPtr (obj_id, TopLevel)) env' in
&lt;/span&gt;&lt;span class="gi"&gt;+  let obj_env = Env.add_local "self" (ObjectPtr (obj_id, Self)) env in
+  let obj_env, _ = Env.add_local_when_not_present "$" (ObjectPtr (obj_id, TopLevel)) obj_env in
+
&lt;/span&gt;   (* First add locals and object fields to env *)
&lt;span class="gd"&gt;-  let* (env', fields) = List.fold_left
&lt;/span&gt;&lt;span class="gi"&gt;+  let* (obj_env, fields) = List.fold_left
&lt;/span&gt;     (fun result entry -&amp;gt;
       let* (env', fields) = result in
       match entry with
&lt;span class="p"&gt;@@ -120,80 +142,87 @@&lt;/span&gt; and interpret_object env (pos, entries) =
           it will add the expr to the environment *)
         let* (env', _) = interpret env' expr in ok (env', fields)
       | ObjectField (name, expr) -&amp;gt;
&lt;span class="gi"&gt;+        (* Object fields are kept lazy -- they will be evaluated only when accessed.
+           This prevents infinite loops from circular references. *)
&lt;/span&gt;         let env' = Env.add_obj_field name expr obj_id env' in
         ok (env', ObjectFields.add name fields)
     )
&lt;span class="gd"&gt;-    (ok (env', ObjectFields.empty))
&lt;/span&gt;&lt;span class="gi"&gt;+    (ok (obj_env, ObjectFields.empty))
&lt;/span&gt;     entries
   in
&lt;span class="gd"&gt;-  (* Then interpret object fields after env is populated *)
-  let* env' = ObjectFields.fold
-    (fun field acc -&amp;gt;
-      let* env' = acc in
-      let* (env', _expr) =
-        Env.get_obj_field field obj_id env'
-          ~succ:(interpret)
-          ~err:(Error.error_at pos)
-      in
-      (* self is removed by object evaluation, for this reason
-         we re-add self and $ to env' on each iteration here *)
-      let env' = Env.add_local "self" self_expr env' in
-      let env' = Env.add_local "$" toplevel_expr env' in
-      ok env'
-    )
-    fields
-    (ok env')
-  in
-
-  (* Remove self and $ from the resulting environment.
-     Posterior interpretations shouldn't have references to them. *)
-  let env' = Env.Map.remove "self" env' in
-  let env' = if had_toplevel then env' else Env.Map.remove "$" env' in
-
-  ok (env', RuntimeObject (pos, obj_id, fields))
&lt;/span&gt;&lt;span class="gi"&gt;+  (* We return env unchanged. RuntimeObject holds its own scoped env. *)
+  ok (env, RuntimeObject (pos, obj_env, fields))
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of evaluating all fields immediately and carefully removing &lt;code&gt;self&lt;/code&gt; and &lt;code&gt;$&lt;/code&gt; references afterward, we just keep the object's environment inside &lt;code&gt;RuntimeObject&lt;/code&gt;. Much cleaner!&lt;/p&gt;

&lt;p&gt;The complexity moved to &lt;code&gt;interpret_object_field_access&lt;/code&gt;, which now handles the actual evaluation when fields are accessed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The gnarly field access logic
&lt;/h2&gt;

&lt;p&gt;This function got... lengthy. The core challenge is handling three different types of scopes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt; and interpret_object_field_access env (pos, scope, chain_exprs) =
&lt;span class="gd"&gt;-  let* obj =
-    match Env.find_opt (string_of_object_scope scope) env with
-    | Some (ObjectPtr _ as obj) -&amp;gt; ok obj
-    | _ -&amp;gt;
-      Error.error_at pos
-        (match scope with
-        | Self -&amp;gt; Error.Msg.self_out_of_scope
-        | TopLevel -&amp;gt; Error.Msg.no_toplevel_object)
&lt;/span&gt;&lt;span class="gi"&gt;+  let* (env', obj) =
+    (* Special case: if this is just `self` or `$` with no field chain,
+     return the ObjectPtr directly. This ensures that when stored in variables,
+     they capture the concrete object ID, not a dynamic scope reference. *)
+    match scope with
+    | Self | TopLevel -&amp;gt;
+      (* For self and $, look them up as scopes in the environment *)
+      (match Env.find_opt (string_of_object_scope scope) env with
+      | Some (ObjectPtr _ as obj) -&amp;gt; ok (env, obj)
+      | Some (RuntimeObject _ as obj) -&amp;gt; ok (env, obj)
+      | _ -&amp;gt;
+        Error.error_at pos
+          (match scope with
+          | Self -&amp;gt; Error.Msg.self_out_of_scope
+          | TopLevel -&amp;gt; Error.Msg.no_toplevel_object
+          | ObjVarRef _ -&amp;gt; Error.Msg.var_not_found "" (* unreachable -- should never happen, TODO: make this unrepresentable *)
+          )
+      )
+    | ObjVarRef varname -&amp;gt;
+      (* For variable references, look up and evaluate the variable *)
+      let* (env', expr) =
+        Env.find_var varname env ~succ:(interpret) ~err:(Error.error_at pos)
+      in
+      match expr with
+      | ObjectPtr (obj_id, (Self | TopLevel)) -&amp;gt;
+        (* If the variable holds a Self/TopLevel reference, the obj_id
+           already captures which object it refers to. We don't need to
+           re-resolve Self/TopLevel in the current environment. *)
+        ok (env', ObjectPtr (obj_id, ObjVarRef varname))
+      | ObjectPtr _ as obj -&amp;gt; ok (env', obj)
+      | RuntimeObject _ as obj -&amp;gt; ok (env', obj)
+      | _ -&amp;gt; Error.error_at pos Error.Msg.must_be_object
&lt;/span&gt;   in
&lt;span class="gi"&gt;+
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tricky part is when a variable holds a &lt;code&gt;Self&lt;/code&gt; or &lt;code&gt;TopLevel&lt;/code&gt; reference -- we need to preserve the object ID that was captured when the variable was bound, not re-resolve it in the current environment.&lt;/p&gt;

&lt;p&gt;Processing the chain involves lazily evaluating fields:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;   List.fold_left
     (fun acc field_expr -&amp;gt;
       let* (env', prev_expr) = acc in
       let get_obj_id =
         match prev_expr with
&lt;span class="gd"&gt;-        | ObjectPtr (obj_id, _) -&amp;gt; ok obj_id
-        | RuntimeObject (_, obj_id, _) -&amp;gt; ok obj_id
&lt;/span&gt;&lt;span class="gi"&gt;+        | ObjectPtr (obj_id, _) -&amp;gt;
+          (* Temporarily add self and $ to env for lazy field evaluation *)
+          let field_env = Env.add_local "self" (ObjectPtr (obj_id, Self)) env' in
+          let field_env = Env.add_local_when_not_present "$" (ObjectPtr (obj_id, TopLevel)) field_env |&amp;gt; fst in
+          ok (obj_id, field_env)
+        | RuntimeObject (_, obj_env, _) -&amp;gt;
+          (match Env.find_opt "self" obj_env with
+          | Some (ObjectPtr (obj_id, _)) -&amp;gt; ok (obj_id, obj_env)
+          | _ -&amp;gt; error Error.Msg.must_be_object
+          )
&lt;/span&gt;         | _ -&amp;gt; Error.error_at pos Error.Msg.must_be_object
       in
&lt;span class="err"&gt;
&lt;/span&gt;       match field_expr with
       | String (pos, field) | Ident (pos, field) -&amp;gt;
&lt;span class="gd"&gt;-        let* obj_id = get_obj_id in
-        Env.get_obj_field field obj_id env'
&lt;/span&gt;&lt;span class="gi"&gt;+        let* (obj_id, field_env) = get_obj_id in
+        Env.get_obj_field field obj_id field_env
&lt;/span&gt;           ~succ:(interpret)
           ~err:(Error.error_at pos)
&lt;span class="gd"&gt;-      | IndexedExpr (pos, field, index_expr) -&amp;gt;
-        let* obj_id = get_obj_id in
-        let* (env', index_expr') = interpret env' index_expr in
-        let* (env', indexable_expr) =
-          Env.get_obj_field field obj_id env'
-            ~succ:(interpret)
-            ~err:(Error.error_at pos)
-        in
-          Result.fold
-            (Indexable.get index_expr' indexable_expr)
-            ~ok:(fun e -&amp;gt; interpret env' e)
-            ~error:(Error.error_at pos)
&lt;/span&gt;&lt;span class="gi"&gt;+      | Number _ as index_expr -&amp;gt;
+        (* Handle array/string indexing: prev_expr[number] *)
+        Result.fold
+          (Indexable.get index_expr prev_expr)
+          ~ok:(fun e -&amp;gt; ok (env', e))
+          ~error:(Error.error_at pos)
&lt;/span&gt;       | _e -&amp;gt;
         Error.error_at pos Error.Msg.interp_invalid_lookup
     )
&lt;span class="gd"&gt;-    (ok (env, obj))
&lt;/span&gt;&lt;span class="gi"&gt;+    (ok (env', obj))
&lt;/span&gt;     chain_exprs
&lt;span class="err"&gt;
&lt;/span&gt; let eval expr = interpret Env.empty expr
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code needs to be simplified to avoid unreachable cases, such as the &lt;code&gt;ObjVarRef&lt;/code&gt;. By leveraging the type system, we can encode this in the types, but this would require a big refactoring. The comments explaining the reason are trade-offs that I accept for the time being.&lt;/p&gt;

&lt;p&gt;I also had to change how the bracketed expressions were being interpreted. This part got simpler and became more readable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Breaking circular dependencies
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;Json&lt;/code&gt; module now needs to actually interpret expressions, which creates a circular dependency. The quick fix is to pass the interpreter function as a parameter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/lib/json.ml b/lib/json.ml
index 57a3be6..c8e2159 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/lib/json.ml
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/lib/json.ml
&lt;/span&gt;&lt;span class="p"&gt;@@ -2,7 +2,15 @@&lt;/span&gt; open Ast
 open Result
 open Syntax_sugar
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;-let rec value_to_yojson (env : expr Env.Map.t) (expr : Ast.expr) : (Yojson.t, string) result =
&lt;/span&gt;&lt;span class="gi"&gt;+(* The interpreter type allows us to break the circular dependency
+   between Json and Interpreter modules.
+   Ideally, the Json module does not need to know anything about the
+   previous step.
+   TODO: Json module fuctions should not receive unevaluated values.
+   Guarantee Interpreter generates a new and evaluated AST. *)
+type interpreter = expr Env.Map.t -&amp;gt; expr -&amp;gt; ((expr Env.Map.t * expr), string) result
+
+let rec value_to_yojson ~(eval: interpreter) (env : expr Env.Map.t) (expr : Ast.expr) : (Yojson.t, string) result =
&lt;/span&gt;   match expr with
   | Number (_, n) -&amp;gt;
     ok (match n with
&lt;span class="p"&gt;@@ -12,22 +20,24 @@&lt;/span&gt; let rec value_to_yojson (env : expr Env.Map.t) (expr : Ast.expr) : (Yojson.t, st
   | Bool (_, b) -&amp;gt; ok (`Bool b)
   | String (_, s) -&amp;gt; ok (`String s)
   | Array (_, values) -&amp;gt;
&lt;span class="gd"&gt;-    let expr_to_list expr' = to_list (value_to_yojson env expr') in
&lt;/span&gt;&lt;span class="gi"&gt;+    let expr_to_list expr' = to_list (value_to_yojson ~eval env expr') in
&lt;/span&gt;     let results = values |&amp;gt; List.map expr_to_list |&amp;gt; List.concat in
     ok (`List results)
&lt;span class="gd"&gt;-  | RuntimeObject (pos, context, fieldset) -&amp;gt; obj_to_yojson env (pos, context, fieldset)
-  | expr -&amp;gt; error ("value type not representable as JSON: " ^ string_of_type expr)
&lt;/span&gt;&lt;span class="gi"&gt;+  | RuntimeObject (pos, obj_env, fieldset) -&amp;gt; obj_to_yojson ~eval env (pos, obj_env, fieldset)
+  | expr -&amp;gt; error (Error.Msg.value_not_represetable_as_json (string_of_type expr))
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;-and obj_to_yojson env (pos, obj_id, fieldset) =
&lt;/span&gt;&lt;span class="gi"&gt;+and obj_to_yojson ~(eval: interpreter) _env (pos, obj_env, fieldset) =
&lt;/span&gt;   let* fields =
     ObjectFields.fold
       (fun field acc -&amp;gt;
&lt;span class="gd"&gt;-        let* (_, expr) =
-          Env.get_obj_field field obj_id env
-            ~succ:(fun _ expr -&amp;gt; ok (env, expr))
-            ~err:(Error.error_at pos)
&lt;/span&gt;&lt;span class="gi"&gt;+        let* obj_id = match Env.find_opt "self" obj_env with
+        | Some (Ast.ObjectPtr (obj_id, _)) -&amp;gt; ok obj_id
+        | _ -&amp;gt; error Error.Msg.must_be_object
+        in
+        let* (env', expr) =
+          Env.get_obj_field field obj_id obj_env ~succ:eval ~err:(Error.error_at pos)
&lt;/span&gt;         in
&lt;span class="gd"&gt;-        let* yo_value = value_to_yojson env expr in
&lt;/span&gt;&lt;span class="gi"&gt;+        let* yo_value = value_to_yojson ~eval env' expr in
&lt;/span&gt;         let* fields = acc in
         ok ((field, yo_value) :: fields)
       )
&lt;span class="p"&gt;@@ -35,6 +45,6 @@&lt;/span&gt; and obj_to_yojson env (pos, obj_id, fieldset) =
       (ok [])
   in ok (`Assoc (List.rev fields))
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;-let expr_to_string (env, expr) =
-  let yojson = value_to_yojson env expr
&lt;/span&gt;&lt;span class="gi"&gt;+let expr_to_string ~(eval: interpreter) (env, expr) =
+  let yojson = value_to_yojson ~eval env expr
&lt;/span&gt;   in Result.map Yojson.pretty_to_string yojson
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gh"&gt;diff --git a/lib/tsonnet.ml b/lib/tsonnet.ml
index d142c9e..6913295 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/lib/tsonnet.ml
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/lib/tsonnet.ml
&lt;/span&gt;&lt;span class="p"&gt;@@ -23,4 +23,4 @@&lt;/span&gt; let run (config : Config.t) (filename: string) : (string, string) result =
     &amp;gt;&amp;gt;= Ast.debug config
     &amp;gt;&amp;gt;= Type.check config
     &amp;gt;&amp;gt;= Interpreter.eval
&lt;span class="gd"&gt;-    &amp;gt;&amp;gt;= Json.expr_to_string
&lt;/span&gt;&lt;span class="gi"&gt;+    &amp;gt;&amp;gt;= Json.expr_to_string ~eval:Interpreter.interpret
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This isn't ideal -- the &lt;code&gt;Json&lt;/code&gt; module shouldn't need to know about interpretation. Ideally, we'd have separate AST types for each compiler phase, with the JSON serializer only receiving fully-evaluated expressions. But that's a big refactoring, and for now this works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Type checker follows suit
&lt;/h2&gt;

&lt;p&gt;The type checker needs similar changes to track variable references:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/lib/type.ml b/lib/type.ml
index 390313f..6334c27 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/lib/type.ml
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/lib/type.ml
&lt;/span&gt;&lt;span class="p"&gt;@@ -264,14 +264,30 @@&lt;/span&gt; and translate_object venv pos entries =
   ok (venv, TruntimeObject (obj_id, entry_types))
&lt;span class="err"&gt;
&lt;/span&gt; and translate_object_field_access venv pos scope chain_exprs =
&lt;span class="gd"&gt;-  let* obj =
-    match Env.find_opt (string_of_object_scope scope) venv with
-    | Some (TobjectPtr _ as obj) -&amp;gt; ok obj
-    | _ -&amp;gt;
-      Error.error_at pos
-        (match scope with
-        | Self -&amp;gt; Error.Msg.self_out_of_scope
-        | TopLevel -&amp;gt; Error.Msg.no_toplevel_object)
&lt;/span&gt;&lt;span class="gi"&gt;+  let* (venv, obj) =
+    match scope with
+    | Self | TopLevel -&amp;gt;
+      (* For self and $, look them up directly *)
+      (match Env.find_opt (string_of_object_scope scope) venv with
+      | Some (TobjectPtr _ as obj) -&amp;gt; ok (venv, obj)
+      | _ -&amp;gt;
+        Error.error_at pos
+          (match scope with
+          | Self -&amp;gt; Error.Msg.self_out_of_scope
+          | TopLevel -&amp;gt; Error.Msg.no_toplevel_object
+          | ObjVarRef _ -&amp;gt; "" (* unreachable *)
+          )
+      )
+    | ObjVarRef varname -&amp;gt;
+      (* For variable references, look up and translate the variable *)
+      Env.find_var varname venv
+        ~succ:(fun venv ty -&amp;gt;
+          match ty with
+          | TobjectPtr _ | TruntimeObject _ as obj -&amp;gt; ok (venv, obj)
+          | Lazy expr -&amp;gt; translate venv expr
+          | _ -&amp;gt; Error.error_at pos Error.Msg.must_be_object
+        )
+        ~err:(Error.error_at pos)
&lt;/span&gt;   in
&lt;span class="err"&gt;
&lt;/span&gt;   List.fold_left
&lt;span class="p"&gt;@@ -291,23 +307,12 @@&lt;/span&gt; and translate_object_field_access venv pos scope chain_exprs =
         Env.get_obj_field field obj_id venv
           ~succ:translate_lazy
           ~err:(Error.error_at pos)
&lt;span class="gd"&gt;-      | IndexedExpr (pos, field, index_expr) -&amp;gt;
-        let* (venv', index_expr_ty) = translate venv index_expr in
-        let* () =
-          match index_expr_ty with
-          | Tnumber | Tstring -&amp;gt; ok ()
-          | ty -&amp;gt; Error.error_at pos (Error.Msg.type_non_indexable_type (to_string ty))
-        in
-        let* obj_id = get_obj_id in
-        let* (venv', ty) =
-          Env.get_obj_field field obj_id venv'
-            ~succ:translate_lazy
-            ~err:(Error.error_at pos)
-        in
-        (match ty with
-        | (Tarray _) as array_ty -&amp;gt; ok (venv', array_ty)
-        | Tstring as ty -&amp;gt; ok (venv', ty)
-        | _ -&amp;gt; Error.error_at pos (Error.Msg.type_non_indexable_field field)
&lt;/span&gt;&lt;span class="gi"&gt;+      | Number (pos, _) -&amp;gt;
+        (* Handle numeric indexing of strings and arrays *)
+        (match prev_ty with
+        | Tstring -&amp;gt; ok (venv, Tstring)
+        | Tarray elem_ty -&amp;gt; ok (venv, elem_ty)
+        | _ -&amp;gt; Error.error_at pos (Error.Msg.type_non_indexable_type (to_string prev_ty))
&lt;/span&gt;         )
       | _ -&amp;gt;
         Error.error_at pos (Error.Msg.type_invalid_lookup_key (string_of_type field_expr))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The field chain processing also simplifies since we don't need the &lt;code&gt;IndexedExpr&lt;/code&gt; case anymore -- bracket notation is now just numeric indexing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Taming more error messages
&lt;/h2&gt;

&lt;p&gt;Found a couple more error messages that needed centralization:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/lib/error.ml b/lib/error.ml
index cdbbada..b518608 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/lib/error.ml
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/lib/error.ml
&lt;/span&gt;&lt;span class="p"&gt;@@ -10,6 +10,7 @@&lt;/span&gt; module Msg = struct
   (* Shared operation messages *)
   let self_out_of_scope = "Can't use self outside of an object"
   let no_toplevel_object = "No top-level object found"
&lt;span class="gi"&gt;+  let var_not_found varname = varname ^ " not found"
&lt;/span&gt;   let invalid_binary_op = "Invalid binary operation"
   let invalid_unary_op = "Invalid unary operation"
   let must_be_object = "Must be an object"
&lt;span class="p"&gt;@@ -31,6 +32,9 @@&lt;/span&gt; module Msg = struct
   let interp_invalid_concat = "Invalid string concatenation operation"
   let interp_invalid_lookup = "Invalid object lookup"
   let interp_cannot_interpret expr = Printf.sprintf "Expression %s cannot be interpreted" expr
&lt;span class="gi"&gt;+
+  (* Others messages *)
+  let value_not_represetable_as_json value = "value type not representable as JSON: " ^ value
&lt;/span&gt; end
&lt;span class="err"&gt;
&lt;/span&gt; let enumerate_error_lines filename position ~highlight_error =
&lt;span class="gh"&gt;diff --git a/lib/error.mli b/lib/error.mli
index 365af96..58d7e8e 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/lib/error.mli
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/lib/error.mli
&lt;/span&gt;&lt;span class="p"&gt;@@ -5,6 +5,7 @@&lt;/span&gt; module Msg : sig
   (* Scope-related messages *)
   val self_out_of_scope : string
   val no_toplevel_object : string
&lt;span class="gi"&gt;+  val var_not_found : string -&amp;gt; string
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;   (* Shared operation messages *)
   val invalid_binary_op : string
&lt;span class="p"&gt;@@ -28,6 +29,9 @@&lt;/span&gt; module Msg : sig
   val interp_invalid_concat : string
   val interp_invalid_lookup : string
   val interp_cannot_interpret : string -&amp;gt; string
&lt;span class="gi"&gt;+
+  (* Other messages *)
+  val value_not_represetable_as_json : string -&amp;gt; string
&lt;/span&gt; end
&lt;span class="err"&gt;
&lt;/span&gt; val trace : string -&amp;gt; Ast.position -&amp;gt; (string, string) result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  And voilà
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;dune &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; tsonnet samples/tutorials/inner-reference.jsonnet
&lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="s2"&gt;"Martini"&lt;/span&gt;: &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"garnish"&lt;/span&gt;: &lt;span class="s2"&gt;"Olive"&lt;/span&gt;,
    &lt;span class="s2"&gt;"ingredients"&lt;/span&gt;: &lt;span class="o"&gt;[&lt;/span&gt;
      &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"kind"&lt;/span&gt;: &lt;span class="s2"&gt;"Farmer's Gin"&lt;/span&gt;, &lt;span class="s2"&gt;"qty"&lt;/span&gt;: 1 &lt;span class="o"&gt;}&lt;/span&gt;,
      &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="s2"&gt;"kind"&lt;/span&gt;: &lt;span class="s2"&gt;"Dry White Vermouth"&lt;/span&gt;, &lt;span class="s2"&gt;"qty"&lt;/span&gt;: 1 &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;]&lt;/span&gt;,
    &lt;span class="s2"&gt;"served"&lt;/span&gt;: &lt;span class="s2"&gt;"Straight Up"&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Inner references required introducing proper lazy evaluation for objects, which turned out to be a bigger change than I expected. The interpreter now properly handles self-references through variables, and objects carry their own environments.&lt;/p&gt;

&lt;p&gt;There are definitely some rough edges -- the unreachable cases in pattern matches, the circular dependency between &lt;code&gt;Json&lt;/code&gt; and &lt;code&gt;Interpreter&lt;/code&gt;, the lengthy field access function. These are all candidates for future refactoring, but they're manageable trade-offs for now. The tests pass, and that's what matters.&lt;/p&gt;

&lt;p&gt;The entire diff can be seen &lt;a href="https://gitlab.com/-/snippets/4910399" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Thanks for reading Bit Maybe Wise! &lt;a href="https://bitmaybewise.substack.com/" rel="noopener noreferrer"&gt;Subscribe&lt;/a&gt; for more compiler complexity that's definitely not a circular dependency (it totally is).&lt;/p&gt;

&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@didsss" rel="noopener noreferrer"&gt;Didssph&lt;/a&gt; on &lt;a href="https://unsplash.com" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>jsonnet</category>
      <category>tsonnet</category>
      <category>compiler</category>
    </item>
    <item>
      <title>Tsonnet #28 - Debugging gets pretty (printed)</title>
      <dc:creator>Hercules Lemke Merscher</dc:creator>
      <pubDate>Thu, 11 Dec 2025 11:06:52 +0000</pubDate>
      <link>https://forem.com/bitmaybewise/tsonnet-28-debugging-gets-pretty-printed-cbf</link>
      <guid>https://forem.com/bitmaybewise/tsonnet-28-debugging-gets-pretty-printed-cbf</guid>
      <description>&lt;p&gt;Welcome to the &lt;a href="https://gitlab.com/bitmaybewise/tsonnet" rel="noopener noreferrer"&gt;Tsonnet&lt;/a&gt; series!&lt;/p&gt;

&lt;p&gt;If you're not following along, check out how it all started in &lt;a href="https://dev.to/bitmaybewise/tsonnet-0-from-zero-to-interpreter-54na"&gt;the first post of the series&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In the previous post, I reorganized function parameters and centralized error messages for consistency:&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/bitmaybewise/tsonnet-27-consistency-consistency-consistency-3nn5" class="crayons-story__hidden-navigation-link"&gt;Tsonnet #27 - Consistency, consistency, consistency&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/bitmaybewise" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F187316%2Faed95d8a-d5c5-4d3e-8dab-513ed83b24c3.jpeg" alt="bitmaybewise profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/bitmaybewise" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Hercules Lemke Merscher
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Hercules Lemke Merscher
                
              
              &lt;div id="story-author-preview-content-2987007" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/bitmaybewise" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F187316%2Faed95d8a-d5c5-4d3e-8dab-513ed83b24c3.jpeg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Hercules Lemke Merscher&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/bitmaybewise/tsonnet-27-consistency-consistency-consistency-3nn5" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Nov 3 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/bitmaybewise/tsonnet-27-consistency-consistency-consistency-3nn5" id="article-link-2987007"&gt;
          Tsonnet #27 - Consistency, consistency, consistency
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/tsonnet"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;tsonnet&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/jsonnet"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;jsonnet&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/compiler"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;compiler&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
            &lt;a href="https://dev.to/bitmaybewise/tsonnet-27-consistency-consistency-consistency-3nn5#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            8 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;




&lt;p&gt;The project is getting more complex and not having an option to debug the AST is becoming cumbersome. We'll quickly explore how to improve this.&lt;/p&gt;

&lt;h2&gt;
  
  
  deriving show
&lt;/h2&gt;

&lt;p&gt;Sometimes we want to see a data type in the console in its raw form. We could implement a function to do that ourselves, but it's repetitive and boring. Enter &lt;code&gt;ppx_deriving.show&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/lib/dune b/lib/dune
index 75a7340..22e9961 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/lib/dune
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/lib/dune
&lt;/span&gt;&lt;span class="p"&gt;@@ -4,7 +4,7 @@&lt;/span&gt;
   (backend bisect_ppx))
  (libraries yojson)
  (preprocess
&lt;span class="gd"&gt;-  (pps ppx_deriving_qcheck)))
&lt;/span&gt;&lt;span class="gi"&gt;+  (pps ppx_deriving.show ppx_deriving_qcheck)))
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt; (menhir
  (modules parser))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This saves you from manually writing boilerplate code to convert your types to strings.&lt;/p&gt;

&lt;p&gt;Then we annotate the &lt;code&gt;expr&lt;/code&gt; and other types, and it automatically generates the &lt;code&gt;show&lt;/code&gt; and &lt;code&gt;pp&lt;/code&gt; (pretty-printer) functions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/lib/ast.ml b/lib/ast.ml
index f7964a8..58c4439 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/lib/ast.ml
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/lib/ast.ml
&lt;/span&gt;&lt;span class="p"&gt;@@ -5,25 +5,33 @@&lt;/span&gt; type bin_op =
   | Subtract
   | Multiply
   | Divide
&lt;span class="gd"&gt;-  [@@deriving qcheck]
&lt;/span&gt;&lt;span class="gi"&gt;+  [@@deriving qcheck, show]
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt; type unary_op =
   | Plus
   | Minus
   | Not
   | BitwiseNot
&lt;span class="gd"&gt;-  [@@deriving qcheck]
&lt;/span&gt;&lt;span class="gi"&gt;+  [@@deriving qcheck, show]
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt; type number =
   | Int of int
   | Float of float
&lt;span class="gd"&gt;-  [@@deriving qcheck]
&lt;/span&gt;&lt;span class="gi"&gt;+  [@@deriving qcheck, show]
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt; type position = {
   startpos: Lexing.position;
   endpos: Lexing.position;
 }
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gi"&gt;+let pp_position fmt pos =
+  Format.fprintf fmt "%d:%d"
+    pos.startpos.pos_lnum
+    pos.endpos.pos_lnum
+
+let show_position pos =
+  Format.asprintf "%a" pp_position pos
+
&lt;/span&gt; let dummy_pos = {
   startpos = Lexing.dummy_pos;
   endpos = Lexing.dummy_pos;
&lt;span class="p"&gt;@@ -34,7 +42,16 @@&lt;/span&gt; module StringSet = struct
   let compare = String.compare
 end
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;-module ObjectFields = Set.Make(StringSet)
&lt;/span&gt;&lt;span class="gi"&gt;+module ObjectFields = struct
+  include Set.Make(StringSet)
+
+  let pp fmt s =
+    Format.fprintf fmt "{%s}"
+      (String.concat ", " (to_list s))
+
+  let show s =
+    Format.asprintf "%a" pp s
+end
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt; type expr =
   | Unit
&lt;span class="p"&gt;@@ -45,8 +62,8 @@&lt;/span&gt; type expr =
   | Ident of position * string
   | Array of position * expr list
   | ParsedObject of position * object_entry list
&lt;span class="gd"&gt;-  | RuntimeObject of position * Env.env_id * ObjectFields.t
-  | ObjectPtr of Env.env_id * object_scope
&lt;/span&gt;&lt;span class="gi"&gt;+  | RuntimeObject of position * (Env.env_id [@opaque]) * ObjectFields.t
+  | ObjectPtr of (Env.env_id [@opaque]) * object_scope
&lt;/span&gt;   | ObjectFieldAccess of position * object_scope * expr list
   | BinOp of position * bin_op * expr * expr
   | UnaryOp of position * unary_op * expr
&lt;span class="p"&gt;@@ -59,9 +76,15 @@&lt;/span&gt; and object_entry =
 and object_scope =
   | Self
   | TopLevel
&lt;span class="gi"&gt;+[@@deriving show]
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt; let dummy_expr = Unit
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gi"&gt;+let debug (config : Config.t) (ast : expr) : (expr, string) result =
+  if config.debug_ast then
+    prerr_endline (show_expr ast);
+  Result.ok ast
+
&lt;/span&gt; let pos_from_lexbuf (lexbuf : Lexing.lexbuf) : position =
   { startpos = lexbuf.lex_curr_p;
     endpos = lexbuf.lex_curr_p;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you might have noticed, we can customize the functions for specific types. Neat!&lt;/p&gt;

&lt;p&gt;This is required for our next step: printing the AST.&lt;/p&gt;

&lt;h2&gt;
  
  
  --debug-ast
&lt;/h2&gt;

&lt;p&gt;We start by adding a new parameter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/bin/main.ml b/bin/main.ml
index e51d36b..64cf7b8 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/bin/main.ml
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/bin/main.ml
&lt;/span&gt;&lt;span class="p"&gt;@@ -1,13 +1,17 @@&lt;/span&gt;
 let usage_msg = "tsonnet &amp;lt;file1&amp;gt; [&amp;lt;file2&amp;gt;] ..."
 let input_files = ref []
 let skip_typecheck = ref false
&lt;span class="gi"&gt;+let debug_ast = ref false
&lt;/span&gt; let anonymous_fun filename = input_files := filename :: !input_files
 let spec_list = [
   ("--skip-typecheck", Arg.Set skip_typecheck, "Skip type checking step");
&lt;span class="gi"&gt;+  ("--debug-ast", Arg.Set debug_ast, "Print abstract syntax tree");
&lt;/span&gt; ]
&lt;span class="err"&gt;
&lt;/span&gt; let run_parser filename =
&lt;span class="gd"&gt;-  match Tsonnet.run ~skip_typecheck:!skip_typecheck filename with
&lt;/span&gt;&lt;span class="gi"&gt;+  let open Tsonnet in
+  let config = Config.make ~skip_typecheck:!skip_typecheck ~debug_ast:!debug_ast () in
+  match run config filename with
&lt;/span&gt;   | Ok stringified_json -&amp;gt; print_endline stringified_json
   | Error err -&amp;gt; prerr_endline err; exit 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of passing multiple options to the library, let's condense the configuration parameters in a &lt;code&gt;Config&lt;/code&gt; module.&lt;/p&gt;

&lt;p&gt;It has this interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ocaml"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;skip_typecheck&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;debug_ast&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;
&lt;span class="k"&gt;val&lt;/span&gt; &lt;span class="n"&gt;make&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="n"&gt;skip_typecheck&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="n"&gt;debug_ast&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;unit&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And this concrete implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ocaml"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;skip_typecheck&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;debug_ast&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;skip_typecheck&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;debug_ast&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;make&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;skip_typecheck&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;debug_ast&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="bp"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;skip_typecheck&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="n"&gt;debug_ast&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now every function call has a clean config record to rely on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/lib/tsonnet.ml b/lib/tsonnet.ml
index 614b5ed..d142c9e 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/lib/tsonnet.ml
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/lib/tsonnet.ml
&lt;/span&gt;&lt;span class="p"&gt;@@ -1,6 +1,8 @@&lt;/span&gt;
 open Result
 open Syntax_sugar
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gi"&gt;+module Config = Config
+
&lt;/span&gt; (** [parse s] parses [s] into an AST. *)
 let parse (filename: string) =
   let input = open_in filename in
&lt;span class="p"&gt;@@ -10,16 +12,15 @@&lt;/span&gt; let parse (filename: string) =
     try ok (Parser.prog Lexer.read lexbuf)
     with
     | Lexer.SyntaxError err -&amp;gt; (Error.trace err (Ast.pos_from_lexbuf lexbuf)) &amp;gt;&amp;gt;= error
&lt;span class="gd"&gt;-    | Parser.Error -&amp;gt; (Error.trace "Invalid syntax" (Ast.pos_from_lexbuf lexbuf)) &amp;gt;&amp;gt;= error
-    | Failure err -&amp;gt; Error.trace ("Invalid token error: " ^ err) (Ast.pos_from_lexbuf lexbuf) &amp;gt;&amp;gt;= error
&lt;/span&gt;&lt;span class="gi"&gt;+    | Parser.Error -&amp;gt; (Error.trace Error.Msg.parse_error (Ast.pos_from_lexbuf lexbuf)) &amp;gt;&amp;gt;= error
+    | Failure err -&amp;gt; Error.trace (Error.Msg.parse_invalid_token err) (Ast.pos_from_lexbuf lexbuf) &amp;gt;&amp;gt;= error
&lt;/span&gt;   in
   close_in input;
   result
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;-let run ?(skip_typecheck = false) (filename: string) : (string, string) result =
-  if skip_typecheck then
-    prerr_endline "Warning: Type checking is skipped. This is not recommended as it may lead to runtime errors.\n";
&lt;/span&gt;&lt;span class="gi"&gt;+let run (config : Config.t) (filename: string) : (string, string) result =
&lt;/span&gt;   parse filename
&lt;span class="gd"&gt;-    &amp;gt;&amp;gt;= (if skip_typecheck then ok else Type.check)
&lt;/span&gt;&lt;span class="gi"&gt;+    &amp;gt;&amp;gt;= Ast.debug config
+    &amp;gt;&amp;gt;= Type.check config
&lt;/span&gt;     &amp;gt;&amp;gt;= Interpreter.eval
     &amp;gt;&amp;gt;= Json.expr_to_string
&lt;span class="gh"&gt;diff --git a/lib/type.ml b/lib/type.ml
index f1f30af..390313f 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/lib/type.ml
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/lib/type.ml
&lt;/span&gt;&lt;span class="p"&gt;@@ -315,10 +315,12 @@&lt;/span&gt; and translate_object_field_access venv pos scope chain_exprs =
     (ok (venv, obj))
     chain_exprs
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;-let check expr =
-  Scope.validate expr
-  &amp;gt;&amp;gt;= fun _ -&amp;gt; translate Env.empty expr
-  &amp;gt;&amp;gt;= fun _ -&amp;gt;
&lt;/span&gt;&lt;span class="gi"&gt;+let check (config : Config.t) expr  =
+  let* _ = Scope.validate expr in
+  if config.skip_typecheck then
+    (prerr_endline Error.Msg.warn_skip_typecheck;
+    ok expr)
+  else
+    let* _ = translate Env.empty expr in
&lt;/span&gt;     Env.Id.reset ();
     ok expr
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As always, cram tests FTW--they function as automated tests with a nice touch of documentation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/bin/help.t b/bin/help.t
index dcf8e30..ebdd411 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/bin/help.t
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/bin/help.t
&lt;/span&gt;&lt;span class="p"&gt;@@ -3,6 +3,7 @@&lt;/span&gt; Display help message with -help flag:
   $ tsonnet -help
   tsonnet &amp;lt;file1&amp;gt; [&amp;lt;file2&amp;gt;] ...
     --skip-typecheck Skip type checking step
&lt;span class="gi"&gt;+    --debug-ast Print abstract syntax tree
&lt;/span&gt;     -help  Display this list of options
     --help  Display this list of options
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="p"&gt;@@ -11,6 +12,7 @@&lt;/span&gt; Display help message with --help flag:
   $ tsonnet --help
   tsonnet &amp;lt;file1&amp;gt; [&amp;lt;file2&amp;gt;] ...
     --skip-typecheck Skip type checking step
&lt;span class="gi"&gt;+    --debug-ast Print abstract syntax tree
&lt;/span&gt;     -help  Display this list of options
     --help  Display this list of options
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="p"&gt;@@ -27,3 +29,45 @@&lt;/span&gt; Run with type checking skipped using long flag:
   Warning: Type checking is skipped. This is not recommended as it may lead to runtime errors.
&lt;span class="err"&gt;
&lt;/span&gt;   42
&lt;span class="gi"&gt;+
+Testing the --debug-ast flag:
+
+Run with AST debugging enabled:
+
+  $ tsonnet --debug-ast ../samples/literals/object.jsonnet
+  (Ast.ParsedObject (1:8,
+     [(Ast.ObjectField ("int_attr", (Ast.Number (2:2, (Ast.Int 1)))));
+       (Ast.ObjectField ("float_attr", (Ast.Number (3:3, (Ast.Float 4.2)))));
+       (Ast.ObjectField ("string_attr", (Ast.String (4:4, "Hello, world!"))));
+       (Ast.ObjectField ("null_attr", (Ast.Null 5:5)));
+       (Ast.ObjectField ("array_attr",
+          (Ast.Array (6:6,
+             [(Ast.Number (6:6, (Ast.Int 1))); (Ast.Bool (6:6, false));
+               (Ast.ParsedObject (6:6, []))]
+             ))
+          ));
+       (Ast.ObjectField ("obj_attr",
+          (Ast.ParsedObject (7:7,
+             [(Ast.ObjectField ("a", (Ast.Bool (7:7, true))));
+               (Ast.ObjectField ("b", (Ast.Bool (7:7, false))));
+               (Ast.ObjectField ("c",
+                  (Ast.ParsedObject (7:7,
+                     [(Ast.ObjectField ("d",
+                         (Ast.Array (7:7, [(Ast.Number (7:7, (Ast.Int 42)))]))
+                         ))
+                       ]
+                     ))
+                  ))
+               ]
+             ))
+          ))
+       ]
+     ))
+  {
+    "array_attr": [ 1, false, {} ],
+    "float_attr": 4.2,
+    "int_attr": 1,
+    "null_attr": null,
+    "obj_attr": { "a": true, "b": false, "c": { "d": [ 42 ] } },
+    "string_attr": "Hello, world!"
+  }
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Error messages
&lt;/h2&gt;

&lt;p&gt;Some leftovers -- untamed error messages from previous changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/lib/error.ml b/lib/error.ml
index e48ed89..cdbbada 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/lib/error.ml
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/lib/error.ml
&lt;/span&gt;&lt;span class="p"&gt;@@ -3,6 +3,10 @@&lt;/span&gt; open Result
 open Syntax_sugar
&lt;span class="err"&gt;
&lt;/span&gt; module Msg = struct
&lt;span class="gi"&gt;+  (* Parameters configuration *)
+  let warn_skip_typecheck = "Warning: Type checking is skipped. This is not recommended as it may lead to runtime errors.\n"
+
+
&lt;/span&gt;   (* Shared operation messages *)
   let self_out_of_scope = "Can't use self outside of an object"
   let no_toplevel_object = "No top-level object found"
&lt;span class="p"&gt;@@ -10,6 +14,10 @@&lt;/span&gt; module Msg = struct
   let invalid_unary_op = "Invalid unary operation"
   let must_be_object = "Must be an object"
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gi"&gt;+  (* Parser messages *)
+  let parse_error = "Parsing error. Invalid syntax:"
+  let parse_invalid_token err = "Invalid token error: " ^ err
+
&lt;/span&gt;   (* Type checker messages *)
   let type_cyclic_reference varname = "Cyclic reference found for " ^ varname
   let type_non_indexable_value ty = ty ^ " is a non indexable value"
&lt;span class="gh"&gt;diff --git a/lib/error.mli b/lib/error.mli
index ddeb318..365af96 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/lib/error.mli
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/lib/error.mli
&lt;/span&gt;&lt;span class="p"&gt;@@ -1,4 +1,7 @@&lt;/span&gt;
 module Msg : sig
&lt;span class="gi"&gt;+  (* Parameters configuration *)
+  val warn_skip_typecheck : string
+
&lt;/span&gt;   (* Scope-related messages *)
   val self_out_of_scope : string
   val no_toplevel_object : string
&lt;span class="p"&gt;@@ -8,6 +11,10 @@&lt;/span&gt; module Msg : sig
   val invalid_unary_op : string
   val must_be_object : string
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gi"&gt;+  (* Parser messages *)
+  val parse_error : string
+  val parse_invalid_token : string -&amp;gt; string
+
&lt;/span&gt;   (* Type checker messages *)
   val type_cyclic_reference : string -&amp;gt; string
   val type_non_indexable_value : string -&amp;gt; string
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gh"&gt;diff --git a/test/cram/errors.t b/test/cram/errors.t
index b70b266..7c117b8 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/test/cram/errors.t
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/test/cram/errors.t
&lt;/span&gt;&lt;span class="p"&gt;@@ -45,7 +45,7 @@&lt;/span&gt;
   [1]
&lt;span class="err"&gt;
&lt;/span&gt;   $ tsonnet ../../samples/errors/unscoped_local.jsonnet
&lt;span class="gd"&gt;-  ../../samples/errors/unscoped_local.jsonnet:1:15 Invalid syntax
&lt;/span&gt;&lt;span class="gi"&gt;+  ../../samples/errors/unscoped_local.jsonnet:1:15 Parsing error. Invalid syntax:
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;   1: local a = local b = 1;
      ^^^^^^^^^^^^^^^^
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;With &lt;code&gt;ppx_deriving.show&lt;/code&gt; and a new &lt;code&gt;--debug-ast&lt;/code&gt; flag, debugging Tsonnet just got a whole lot easier. And consolidating configuration into a &lt;code&gt;Config&lt;/code&gt; module keeps the API clean as more options get added.&lt;/p&gt;

&lt;p&gt;The error message cleanup was long overdue. Having all error strings centralized in &lt;code&gt;Error.Msg&lt;/code&gt; makes them easier to maintain and ensures consistency between the parser, type checker, and interpreter. One less thing to worry about when adding new features!&lt;/p&gt;

&lt;p&gt;The entire diff can be seen &lt;a href="https://gitlab.com/-/snippets/4910568" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Thanks for reading Bit Maybe Wise! &lt;a href="https://bitmaybewise.substack.com/" rel="noopener noreferrer"&gt;Subscribe&lt;/a&gt; to see what else I can get the compiler to write for me.&lt;/p&gt;

&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@timothycdykes" rel="noopener noreferrer"&gt;Timothy Dykes&lt;/a&gt; on &lt;a href="https://unsplash.com" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

</description>
      <category>jsonnet</category>
      <category>tsonnet</category>
      <category>compiler</category>
    </item>
    <item>
      <title>ABEND dump #23</title>
      <dc:creator>Hercules Lemke Merscher</dc:creator>
      <pubDate>Mon, 10 Nov 2025 21:56:52 +0000</pubDate>
      <link>https://forem.com/bitmaybewise/abend-dump-23-3o9j</link>
      <guid>https://forem.com/bitmaybewise/abend-dump-23-3o9j</guid>
      <description>&lt;p&gt;Welcome to the ABEND dump #23.&lt;/p&gt;

&lt;p&gt;If you wanna know what is the "ABEND dump", &lt;a href="https://bitmaybewise.substack.com/i/68092102/what-is-abend-dump" rel="noopener noreferrer"&gt;I've got you covered&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can check the previous ABEND dump here:&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/bitmaybewise/abend-dump-22-f2f" class="crayons-story__hidden-navigation-link"&gt;ABEND dump #22&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/bitmaybewise" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F187316%2Faed95d8a-d5c5-4d3e-8dab-513ed83b24c3.jpeg" alt="bitmaybewise profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/bitmaybewise" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Hercules Lemke Merscher
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Hercules Lemke Merscher
                
              
              &lt;div id="story-author-preview-content-2865208" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/bitmaybewise" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F187316%2Faed95d8a-d5c5-4d3e-8dab-513ed83b24c3.jpeg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Hercules Lemke Merscher&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/bitmaybewise/abend-dump-22-f2f" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Sep 24 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/bitmaybewise/abend-dump-22-f2f" id="article-link-2865208"&gt;
          ABEND dump #22
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag crayons-tag--filled  " href="/t/news"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;news&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/abenddump"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;abenddump&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
            &lt;a href="https://dev.to/bitmaybewise/abend-dump-22-f2f#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            2 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;




&lt;p&gt;It's been a while since I sat down to curate another dump, but here we are: book reading philosophy, browser trust issues, career pendulums, and space BBQ. The usual eclectic mix that somehow makes sense (or probably not) when you're done reading. Let's go!&lt;/p&gt;




&lt;h2&gt;
  
  
  &lt;a href="https://sbaziotis.com/non-cs/on-reading-books.html#introduction" rel="noopener noreferrer"&gt;On reading books&lt;/a&gt;
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://sbaziotis.com/non-cs/on-reading-books.html#if-you-don%E2%80%99t-enjoy-it-don%E2%80%99t-do-it" rel="noopener noreferrer"&gt;If you don't enjoy it, don't do it.&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I tend to agree. BUT, and there's a reason for it being a big "BUT" here, you must try hard when you're reading to acquire knowledge before dropping the book, and potentially returning to it later if you failed in the first try. &lt;/p&gt;

&lt;p&gt;You can totally drop difficult and boring books if you're reading just for fun or curiosity, and I'd be the first one saying "don't do it". For knowledge acquisition, I'd stick to it a bit more, unless there's a better alternative available.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://x.com/GergelyOrosz/status/1981077205502325181?t=ezGD8W9LI8zqPm0DgdsZoQ&amp;amp;s=09" rel="noopener noreferrer"&gt;Gergely on AI browsers&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fituetx7wmbo0v63mcsar.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fituetx7wmbo0v63mcsar.png" alt="AI browsers"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I agree, except that I don't trust any browser with my passwords and credit cards. One more vector to leak important information. No thanks, I pass!&lt;/p&gt;

&lt;p&gt;Speaking of which, I've been trying &lt;a href="https://zen-browser.app/" rel="noopener noreferrer"&gt;Zen Browser&lt;/a&gt;, and it is a refreshing version of Firefox.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://youtu.be/iPKmcLxuS_A?si=LsWe202Yp8F9FrQZ" rel="noopener noreferrer"&gt;Code Complete with Steve McConnell&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;That's an interesting interview:&lt;/p&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/iPKmcLxuS_A"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

&lt;p&gt;I have this classic book on my list to read for a loooong time. Maybe it's about time to finally read it.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://charity.wtf/2017/05/11/the-engineer-manager-pendulum/" rel="noopener noreferrer"&gt;The Engineer/Manager Pendulum&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;This is a nice write-up from &lt;a href="https://www.linkedin.com/in/charity-majors/" rel="noopener noreferrer"&gt;Charity Majors&lt;/a&gt;, that I like to read once a year and reflect whether I should try management or stay as an individual contributor.&lt;/p&gt;

&lt;p&gt;I believe I had the luck of working in places where, in many occasions, I could be a manager of one -- myself. Which is nice. Because I'm not naturally inclined to do managerial work 100% of the time, at least, I don't feel like it full time yet, but still being a manager of one exposes you partially to many things that you should be aware of when coordinating work and expectations.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://prog21.dadgum.com/56.html" rel="noopener noreferrer"&gt;The Recovering Programmer&lt;/a&gt;
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;I realized I was looking at everything backward, from an implementation point of view, not from the perspective of a finished application, not considering the user first. And I realized that the inherent bitterness and negativity of programming arguments and technical defensiveness on the web were making &lt;strong&gt;me&lt;/strong&gt; bitter and negative. I've consciously tried to rewind, to go back to when programming was a tool for implementing my visions, not its own end.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It's interesting how this thought can be flipped depending on the context and biases you're exposed to. I've definitely seen the lack of UX and software design together more often in the realm of web development, and sometimes caused by myself. The important point, IMO, is to be aware of your current context and do your best to assess the right trade-off -- easier said than done, but we can always get better with practice if we are attentive.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://x.com/CNSpaceStation/status/1985569473924121035?s=20" rel="noopener noreferrer"&gt;Chinese astronauts had BBQ in space&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Ok, we can have BBQ in space. Now we are talking going to Mars XD&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8cqpj39h9moes1lq378i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8cqpj39h9moes1lq378i.png" alt="Astronauts BBQ"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Thank you for reading the ABEND dump! &lt;a href="https://bitmaybewise.substack.com" rel="noopener noreferrer"&gt;Subscribe to Bit Maybe Wise&lt;/a&gt; -- there's no risk of prompt injection, just good, interesting, thoughtful, and fun links.&lt;/p&gt;

</description>
      <category>abenddump</category>
    </item>
    <item>
      <title>Tsonnet #27 - Consistency, consistency, consistency</title>
      <dc:creator>Hercules Lemke Merscher</dc:creator>
      <pubDate>Mon, 03 Nov 2025 09:42:56 +0000</pubDate>
      <link>https://forem.com/bitmaybewise/tsonnet-27-consistency-consistency-consistency-3nn5</link>
      <guid>https://forem.com/bitmaybewise/tsonnet-27-consistency-consistency-consistency-3nn5</guid>
      <description>&lt;p&gt;Welcome to the &lt;a href="https://gitlab.com/bitmaybewise/tsonnet" rel="noopener noreferrer"&gt;Tsonnet&lt;/a&gt; series!&lt;/p&gt;

&lt;p&gt;If you're not following the series so far, you can check out how it all started in &lt;a href="https://dev.to/bitmaybewise/tsonnet-0-from-zero-to-interpreter-54na"&gt;the first post of the series&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In the previous post, I added comprehensive testing for object interpretation to prevent reference leaks:&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/bitmaybewise/tsonnet-26-chain-me-maybe-part-2-2onn" class="crayons-story__hidden-navigation-link"&gt;Tsonnet #26 - Chain me maybe, part 2&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/bitmaybewise" class="crayons-avatar  crayons-avatar--l  "&gt;
            &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F187316%2Faed95d8a-d5c5-4d3e-8dab-513ed83b24c3.jpeg" alt="bitmaybewise profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/bitmaybewise" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Hercules Lemke Merscher
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Hercules Lemke Merscher
                
              
              &lt;div id="story-author-preview-content-2971700" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/bitmaybewise" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&gt;
                        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F187316%2Faed95d8a-d5c5-4d3e-8dab-513ed83b24c3.jpeg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Hercules Lemke Merscher&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/bitmaybewise/tsonnet-26-chain-me-maybe-part-2-2onn" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Oct 29 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/bitmaybewise/tsonnet-26-chain-me-maybe-part-2-2onn" id="article-link-2971700"&gt;
          Tsonnet #26 - Chain me maybe, part 2
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/tsonnet"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;tsonnet&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/jsonnet"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;jsonnet&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/compiler"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;compiler&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
            &lt;a href="https://dev.to/bitmaybewise/tsonnet-26-chain-me-maybe-part-2-2onn#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            8 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;




&lt;p&gt;This will be short and sweet post, so let's get to it.&lt;/p&gt;

&lt;p&gt;When I first wrote the type checking function, I made an innocent mistake by having the environment parameter in a different order in the &lt;code&gt;translate&lt;/code&gt; function compared to the interpreter. It's not a big deal, but consistency is important!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/lib/type.ml b/lib/type.ml
index 99f11cc..1ece085 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/lib/type.ml
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/lib/type.ml
&lt;/span&gt;&lt;span class="p"&gt;@@ -111,7 +111,7 @@&lt;/span&gt; and check_object_field_chain_for_cycles venv (pos, scope, exprs) seen =
     (ok ())
     exprs
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;-let rec translate expr venv =
&lt;/span&gt;&lt;span class="gi"&gt;+let rec translate venv expr =
&lt;/span&gt;   match expr with
   | Unit -&amp;gt; ok (venv, Tunit)
   | Null _ -&amp;gt; ok (venv, Tnull)
&lt;span class="p"&gt;@@ -122,7 +122,7 @@&lt;/span&gt; let rec translate expr venv =
     Env.find_var varname venv
       ~succ:(fun venv ty -&amp;gt;
         match ty with
&lt;span class="gd"&gt;-        | Lazy expr -&amp;gt; translate expr venv
&lt;/span&gt;&lt;span class="gi"&gt;+        | Lazy expr -&amp;gt; translate venv expr
&lt;/span&gt;         | _ -&amp;gt; ok (venv, ty)
       )
       ~err:(Error.error_at pos)
&lt;span class="p"&gt;@@ -168,26 +168,26 @@&lt;/span&gt; let rec translate expr venv =
     in ok (venv', Tunit)
   | Seq exprs -&amp;gt;
     List.fold_left
&lt;span class="gd"&gt;-      (fun acc expr -&amp;gt; acc &amp;gt;&amp;gt;= fun (venv, _) -&amp;gt; translate expr venv)
&lt;/span&gt;&lt;span class="gi"&gt;+      (fun acc expr -&amp;gt; acc &amp;gt;&amp;gt;= fun (venv, _) -&amp;gt; translate venv expr)
&lt;/span&gt;       (ok (venv, Tunit))
       exprs
   | BinOp (pos, op, e1, e2) -&amp;gt;
&lt;span class="gd"&gt;-    (let* (venv', e1') = translate e1 venv in
-    let* (venv'', e2') = translate e2 venv' in
&lt;/span&gt;&lt;span class="gi"&gt;+    (let* (venv', e1') = translate venv e1 in
+    let* (venv'', e2') = translate venv' e2 in
&lt;/span&gt;     match op, e1', e2' with
     | _, Tnumber, Tnumber -&amp;gt; ok (venv'', Tnumber)
     | Add, _, Tstring | Add, Tstring, _ -&amp;gt; ok (venv'', Tstring)
     | _ -&amp;gt; Error.trace "Invalid binary operation" pos &amp;gt;&amp;gt;= error
     )
   | UnaryOp (pos, op, expr) -&amp;gt;
&lt;span class="gd"&gt;-    (let* (venv', expr') = translate expr venv in
&lt;/span&gt;&lt;span class="gi"&gt;+    (let* (venv', expr') = translate venv expr in
&lt;/span&gt;     match op, expr' with
     | Plus, Tnumber | Minus, Tnumber | BitwiseNot, Tnumber -&amp;gt; ok (venv', Tnumber)
     | Not, Tbool | BitwiseNot, Tbool -&amp;gt; ok (venv', Tbool)
     | _ -&amp;gt; Error.trace "Invalid unary operation" pos &amp;gt;&amp;gt;= error
     )
   | IndexedExpr (pos, varname, index_expr) -&amp;gt;
&lt;span class="gd"&gt;-    (let* (venv', index_expr') = translate index_expr venv in
&lt;/span&gt;&lt;span class="gi"&gt;+    (let* (venv', index_expr') = translate venv index_expr in
&lt;/span&gt;     match index_expr' with
     | Tnumber -&amp;gt;
       Env.find_var varname venv'
&lt;span class="p"&gt;@@ -195,7 +195,7 @@&lt;/span&gt; let rec translate expr venv =
           match expr' with
           | (Tarray _) as ty -&amp;gt; ok (venv', ty)
           | Tstring as ty -&amp;gt; ok (venv', ty)
&lt;span class="gd"&gt;-          | Lazy expr -&amp;gt; translate expr venv
&lt;/span&gt;&lt;span class="gi"&gt;+          | Lazy expr -&amp;gt; translate venv expr
&lt;/span&gt;           | ty -&amp;gt; error (to_string ty ^ " is a non indexable value")
         )
         ~err:(Error.error_at pos)
&lt;span class="p"&gt;@@ -205,7 +205,7 @@&lt;/span&gt; let rec translate expr venv =
     error ("Invalid type " ^ string_of_type expr')
&lt;span class="err"&gt;
&lt;/span&gt; and translate_lazy venv = function
&lt;span class="gd"&gt;-  | Lazy expr -&amp;gt; translate expr venv
&lt;/span&gt;&lt;span class="gi"&gt;+  | Lazy expr -&amp;gt; translate venv expr
&lt;/span&gt;   | ty -&amp;gt; error ("Invalid type " ^ to_string ty)
&lt;span class="err"&gt;
&lt;/span&gt; and translate_object venv pos entries =
&lt;span class="p"&gt;@@ -222,7 +222,7 @@&lt;/span&gt; and translate_object venv pos entries =
       let* venv = result in
       match entry with
       | ObjectExpr expr -&amp;gt;
&lt;span class="gd"&gt;-        let* (venv', _) = translate expr venv in (ok venv')
&lt;/span&gt;&lt;span class="gi"&gt;+        let* (venv', _) = translate venv expr in (ok venv')
&lt;/span&gt;       | ObjectField (attr, expr) -&amp;gt;
         ok (Env.add_obj_field attr (Lazy expr) obj_id venv)
     )
&lt;span class="p"&gt;@@ -292,7 +292,7 @@&lt;/span&gt; and translate_object_field_access venv pos scope chain_exprs =
           ~succ:translate_lazy
           ~err:(Error.error_at pos)
       | IndexedExpr (pos, field, index_expr) -&amp;gt;
&lt;span class="gd"&gt;-        let* (venv', index_expr_ty) = translate index_expr venv in
&lt;/span&gt;&lt;span class="gi"&gt;+        let* (venv', index_expr_ty) = translate venv index_expr in
&lt;/span&gt;         let* () =
           match index_expr_ty with
           | Tnumber | Tstring -&amp;gt; ok ()
&lt;span class="p"&gt;@@ -317,7 +317,7 @@&lt;/span&gt; and translate_object_field_access venv pos scope chain_exprs =
&lt;span class="err"&gt;
&lt;/span&gt; let check expr =
   Scope.validate expr
&lt;span class="gd"&gt;-  &amp;gt;&amp;gt;= fun _ -&amp;gt; translate expr Env.empty
&lt;/span&gt;&lt;span class="gi"&gt;+  &amp;gt;&amp;gt;= fun _ -&amp;gt; translate Env.empty expr
&lt;/span&gt;   &amp;gt;&amp;gt;= fun _ -&amp;gt;
     Env.Id.reset ();
     ok expr
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yeah, I know what you're thinking: "Is this really worth a whole post?". We can argue that this is not a big deal, and in theory, it isn't, but in practice it's much more comfortable to work in a codebase where you get used to the pattern, and even better when they are replicated across multiple modules and functions. It just feels natural! Our brains just stop fighting the code.&lt;/p&gt;

&lt;p&gt;Consistency, consistency, consistency!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqtl974mq3k97himd6xt2.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqtl974mq3k97himd6xt2.gif" alt="developers"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Speaking of patterns -- this parameter ordering change is actually a small step toward a bigger goal. Eventually, I want to enforce better type checking in-between compiler phases: &lt;code&gt;parsing -&amp;gt; scope validation -&amp;gt; type checking -&amp;gt; interpretation&lt;/code&gt;. As of now, we work with the same AST across multiple phases, but it would be nice to have intermediate representations between each step. This could help removing duplicated checks already present, and making invalid states unrepresentable.&lt;/p&gt;

&lt;p&gt;But that's a story for another day. Before doing such big refactoring, there's something even more mundane that helps moving towards this goal: extracting error messages.&lt;/p&gt;

&lt;p&gt;lib/error.ml:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ocaml"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nc"&gt;Msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;
  &lt;span class="c"&gt;(* Shared operation messages *)&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;self_out_of_scope&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Can't use self outside of an object"&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;no_toplevel_object&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"No top-level object found"&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;invalid_binary_op&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Invalid binary operation"&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;invalid_unary_op&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Invalid unary operation"&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;must_be_object&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Must be an object"&lt;/span&gt;

  &lt;span class="c"&gt;(* Type checker messages *)&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;type_cyclic_reference&lt;/span&gt; &lt;span class="n"&gt;varname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Cyclic reference found for "&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="n"&gt;varname&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;type_non_indexable_value&lt;/span&gt; &lt;span class="n"&gt;ty&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ty&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="s2"&gt;" is a non indexable value"&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;type_expected_integer_index&lt;/span&gt; &lt;span class="n"&gt;ty&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Expected Integer index, got "&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="n"&gt;ty&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;type_invalid_expr&lt;/span&gt; &lt;span class="n"&gt;expr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Invalid type "&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="n"&gt;expr&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;type_non_indexable_type&lt;/span&gt; &lt;span class="n"&gt;ty&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ty&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="s2"&gt;" is a non-indexable type"&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;type_non_indexable_field&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="s2"&gt;" is a non-indexable value"&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;type_invalid_lookup_key&lt;/span&gt; &lt;span class="n"&gt;expr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Invalid object lookup key: "&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="n"&gt;expr&lt;/span&gt;

  &lt;span class="c"&gt;(* Interpreter messages *)&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;interp_invalid_concat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Invalid string concatenation operation"&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;interp_invalid_lookup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Invalid object lookup"&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;interp_cannot_interpret&lt;/span&gt; &lt;span class="n"&gt;expr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sprintf&lt;/span&gt; &lt;span class="s2"&gt;"Expression %s cannot be interpreted"&lt;/span&gt; &lt;span class="n"&gt;expr&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See what I mean with the shared operation messages?! Those &lt;code&gt;invalid_binary_op&lt;/code&gt; and &lt;code&gt;invalid_unary_op&lt;/code&gt; strings appear in both the type checker and interpreter -- operations that should really only happen once, in a single compilation phase.&lt;/p&gt;

&lt;p&gt;Now I can replace the hard-coded error messages everywhere:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gh"&gt;diff --git a/lib/interpreter.ml b/lib/interpreter.ml
index fe7f1a7..77b3ac2 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/lib/interpreter.ml
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/lib/interpreter.ml
&lt;/span&gt;&lt;span class="p"&gt;@@ -30,7 +30,7 @@&lt;/span&gt; let interpret_concat_op env (e1 : expr) (e2 : expr) : (expr, string) result =
   | val1, String (_, s2) -&amp;gt;
     let* s1 = Json.expr_to_string (env, val1) in ok (String (dummy_pos, s1^s2))
   | _ -&amp;gt;
&lt;span class="gd"&gt;-    error "Invalid string concatenation operation"
&lt;/span&gt;&lt;span class="gi"&gt;+    error Error.Msg.interp_invalid_concat
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt; let interpret_unary_op (op: unary_op) (evaluated_expr: expr) =
   match op, evaluated_expr with
&lt;span class="p"&gt;@@ -39,7 +39,7 @@&lt;/span&gt; let interpret_unary_op (op: unary_op) (evaluated_expr: expr) =
   | Minus, Number (pos, Float f) -&amp;gt; ok (Number (pos, Float (-. f)))
   | Not, (Bool (pos, b)) -&amp;gt; ok (Bool (pos, not b))
   | BitwiseNot, Number (pos, Int i) -&amp;gt; ok (Number (pos, Int (lnot i)))
&lt;span class="gd"&gt;-  | _ -&amp;gt; error "Invalid unary operation"
&lt;/span&gt;&lt;span class="gi"&gt;+  | _ -&amp;gt; error Error.Msg.invalid_unary_op
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt; (** [interpret expr] interprets and reduce the intermediate AST [expr] into a result AST. *)
 let rec interpret env expr =
&lt;span class="p"&gt;@@ -63,7 +63,7 @@&lt;/span&gt; let rec interpret env expr =
     | _, Number (pos, v1), Number (_, v2) -&amp;gt;
       ok (env2, Number (pos, interpret_arith_op op v1 v2))
     | _ -&amp;gt;
&lt;span class="gd"&gt;-      Error.trace "Invalid binary operation" pos &amp;gt;&amp;gt;= error
&lt;/span&gt;&lt;span class="gi"&gt;+      Error.trace Error.Msg.invalid_binary_op pos &amp;gt;&amp;gt;= error
&lt;/span&gt;     )
   | UnaryOp (pos, op, expr) -&amp;gt;
     let* (env', expr') = interpret env expr in
&lt;span class="p"&gt;@@ -91,7 +91,7 @@&lt;/span&gt; let rec interpret env expr =
       )
       ~err:(Error.error_at pos)
     | expr -&amp;gt;
&lt;span class="gd"&gt;-      error (Printf.sprintf "Expression %s cannot be interpreted" (string_of_type expr))
&lt;/span&gt;&lt;span class="gi"&gt;+      error (Error.Msg.interp_cannot_interpret (string_of_type expr))
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt; and interpret_array env (pos, exprs) =
   let* (env', evaluated_exprs) = List.fold_left
&lt;span class="p"&gt;@@ -159,8 +159,8 @@&lt;/span&gt; and interpret_object_field_access env (pos, scope, chain_exprs) =
     | _ -&amp;gt;
       Error.error_at pos
         (match scope with
&lt;span class="gd"&gt;-        | Self -&amp;gt; Scope.self_out_of_scope
-        | TopLevel -&amp;gt; Scope.no_toplevel_object)
&lt;/span&gt;&lt;span class="gi"&gt;+        | Self -&amp;gt; Error.Msg.self_out_of_scope
+        | TopLevel -&amp;gt; Error.Msg.no_toplevel_object)
&lt;/span&gt;   in
   List.fold_left
     (fun acc field_expr -&amp;gt;
&lt;span class="p"&gt;@@ -169,7 +169,7 @@&lt;/span&gt; and interpret_object_field_access env (pos, scope, chain_exprs) =
         match prev_expr with
         | ObjectPtr (obj_id, _) -&amp;gt; ok obj_id
         | RuntimeObject (_, obj_id, _) -&amp;gt; ok obj_id
&lt;span class="gd"&gt;-        | _ -&amp;gt; Error.error_at pos "Must be an object"
&lt;/span&gt;&lt;span class="gi"&gt;+        | _ -&amp;gt; Error.error_at pos Error.Msg.must_be_object
&lt;/span&gt;       in
&lt;span class="err"&gt;
&lt;/span&gt;       match field_expr with
&lt;span class="p"&gt;@@ -191,7 +191,7 @@&lt;/span&gt; and interpret_object_field_access env (pos, scope, chain_exprs) =
             ~ok:(fun e -&amp;gt; interpret env' e)
             ~error:(Error.error_at pos)
       | _e -&amp;gt;
&lt;span class="gd"&gt;-        Error.error_at pos "Invalid object lookup"
&lt;/span&gt;&lt;span class="gi"&gt;+        Error.error_at pos Error.Msg.interp_invalid_lookup
&lt;/span&gt;     )
     (ok (env, obj))
     chain_exprs
&lt;span class="gh"&gt;diff --git a/lib/scope.ml b/lib/scope.ml
index d61a9c9..8846f39 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/lib/scope.ml
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/lib/scope.ml
&lt;/span&gt;&lt;span class="p"&gt;@@ -12,9 +12,6 @@&lt;/span&gt; type context = {
   current_locals: string list;
 }
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;-let self_out_of_scope = "Can't use self outside of an object"
-let no_toplevel_object = "No top-level object found"
-
&lt;/span&gt; let empty_context = {
   in_object = false;
   object_depth = 0;
&lt;span class="p"&gt;@@ -61,8 +58,8 @@&lt;/span&gt; let rec _validate expr context =
&lt;span class="err"&gt;
&lt;/span&gt; and validate_ident pos varname context =
   match (varname, context.in_object) with
&lt;span class="gd"&gt;-  | ("self", false) -&amp;gt; Error.trace self_out_of_scope pos &amp;gt;&amp;gt;= error
-  | ("$", false) -&amp;gt; Error.trace no_toplevel_object pos &amp;gt;&amp;gt;= error
&lt;/span&gt;&lt;span class="gi"&gt;+  | ("self", false) -&amp;gt; Error.trace Error.Msg.self_out_of_scope pos &amp;gt;&amp;gt;= error
+  | ("$", false) -&amp;gt; Error.trace Error.Msg.no_toplevel_object pos &amp;gt;&amp;gt;= error
&lt;/span&gt;   | _ -&amp;gt; ok ()
&lt;span class="err"&gt;
&lt;/span&gt; and validate_expression_list exprs context =
&lt;span class="p"&gt;@@ -109,8 +106,8 @@&lt;/span&gt; and validate_object_field_access pos scope context =
   if not context.in_object
   then
     let with_error_msg = match scope with
&lt;span class="gd"&gt;-                        | Self -&amp;gt; self_out_of_scope
-                        | TopLevel -&amp;gt; no_toplevel_object
&lt;/span&gt;&lt;span class="gi"&gt;+                        | Self -&amp;gt; Error.Msg.self_out_of_scope
+                        | TopLevel -&amp;gt; Error.Msg.no_toplevel_object
&lt;/span&gt;     in
     Error.trace with_error_msg pos &amp;gt;&amp;gt;= error
   else ok ()
&lt;span class="gh"&gt;diff --git a/lib/type.ml b/lib/type.ml
index 1ece085..f1f30af 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/lib/type.ml
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/lib/type.ml
&lt;/span&gt;&lt;span class="p"&gt;@@ -56,7 +56,7 @@&lt;/span&gt; let rec to_string = function
 let rec check_cyclic_refs venv varname seen pos =
   if List.mem varname seen
   then
&lt;span class="gd"&gt;-    Error.trace ("Cyclic reference found for " ^ varname) pos &amp;gt;&amp;gt;= error
&lt;/span&gt;&lt;span class="gi"&gt;+    Error.trace (Error.Msg.type_cyclic_reference varname) pos &amp;gt;&amp;gt;= error
&lt;/span&gt;   else
     match Env.find_opt varname venv with
     | Some (Lazy expr) -&amp;gt; check_expr_for_cycles venv expr (varname :: seen)
&lt;span class="p"&gt;@@ -177,14 +177,14 @@&lt;/span&gt; let rec translate venv expr =
     match op, e1', e2' with
     | _, Tnumber, Tnumber -&amp;gt; ok (venv'', Tnumber)
     | Add, _, Tstring | Add, Tstring, _ -&amp;gt; ok (venv'', Tstring)
&lt;span class="gd"&gt;-    | _ -&amp;gt; Error.trace "Invalid binary operation" pos &amp;gt;&amp;gt;= error
&lt;/span&gt;&lt;span class="gi"&gt;+    | _ -&amp;gt; Error.trace Error.Msg.invalid_binary_op pos &amp;gt;&amp;gt;= error
&lt;/span&gt;     )
   | UnaryOp (pos, op, expr) -&amp;gt;
     (let* (venv', expr') = translate venv expr in
     match op, expr' with
     | Plus, Tnumber | Minus, Tnumber | BitwiseNot, Tnumber -&amp;gt; ok (venv', Tnumber)
     | Not, Tbool | BitwiseNot, Tbool -&amp;gt; ok (venv', Tbool)
&lt;span class="gd"&gt;-    | _ -&amp;gt; Error.trace "Invalid unary operation" pos &amp;gt;&amp;gt;= error
&lt;/span&gt;&lt;span class="gi"&gt;+    | _ -&amp;gt; Error.trace Error.Msg.invalid_unary_op pos &amp;gt;&amp;gt;= error
&lt;/span&gt;     )
   | IndexedExpr (pos, varname, index_expr) -&amp;gt;
     (let* (venv', index_expr') = translate venv index_expr in
&lt;span class="p"&gt;@@ -196,17 +196,17 @@&lt;/span&gt; let rec translate venv expr =
           | (Tarray _) as ty -&amp;gt; ok (venv', ty)
           | Tstring as ty -&amp;gt; ok (venv', ty)
           | Lazy expr -&amp;gt; translate venv expr
&lt;span class="gd"&gt;-          | ty -&amp;gt; error (to_string ty ^ " is a non indexable value")
&lt;/span&gt;&lt;span class="gi"&gt;+          | ty -&amp;gt; error (Error.Msg.type_non_indexable_value (to_string ty))
&lt;/span&gt;         )
         ~err:(Error.error_at pos)
&lt;span class="gd"&gt;-    | ty -&amp;gt; Error.trace ("Expected Integer index, got " ^ to_string ty) pos &amp;gt;&amp;gt;= error
&lt;/span&gt;&lt;span class="gi"&gt;+    | ty -&amp;gt; Error.trace (Error.Msg.type_expected_integer_index (to_string ty)) pos &amp;gt;&amp;gt;= error
&lt;/span&gt;     )
   | expr' -&amp;gt;
&lt;span class="gd"&gt;-    error ("Invalid type " ^ string_of_type expr')
&lt;/span&gt;&lt;span class="gi"&gt;+    error (Error.Msg.type_invalid_expr (string_of_type expr'))
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt; and translate_lazy venv = function
   | Lazy expr -&amp;gt; translate venv expr
&lt;span class="gd"&gt;-  | ty -&amp;gt; error ("Invalid type " ^ to_string ty)
&lt;/span&gt;&lt;span class="gi"&gt;+  | ty -&amp;gt; error (Error.Msg.type_invalid_expr (to_string ty))
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt; and translate_object venv pos entries =
   let* obj_id = Env.Id.generate () in
&lt;span class="p"&gt;@@ -270,8 +270,8 @@&lt;/span&gt; and translate_object_field_access venv pos scope chain_exprs =
     | _ -&amp;gt;
       Error.error_at pos
         (match scope with
&lt;span class="gd"&gt;-        | Self -&amp;gt; Scope.self_out_of_scope
-        | TopLevel -&amp;gt; Scope.no_toplevel_object)
&lt;/span&gt;&lt;span class="gi"&gt;+        | Self -&amp;gt; Error.Msg.self_out_of_scope
+        | TopLevel -&amp;gt; Error.Msg.no_toplevel_object)
&lt;/span&gt;   in
&lt;span class="err"&gt;
&lt;/span&gt;   List.fold_left
&lt;span class="p"&gt;@@ -282,7 +282,7 @@&lt;/span&gt; and translate_object_field_access venv pos scope chain_exprs =
         match prev_ty with
         | TobjectPtr (obj_id, _) -&amp;gt; ok obj_id
         | TruntimeObject (obj_id, _) -&amp;gt; ok obj_id
&lt;span class="gd"&gt;-        | _ -&amp;gt; Error.error_at pos "Must be an object"
&lt;/span&gt;&lt;span class="gi"&gt;+        | _ -&amp;gt; Error.error_at pos Error.Msg.must_be_object
&lt;/span&gt;       in
&lt;span class="err"&gt;
&lt;/span&gt;       match field_expr with
&lt;span class="p"&gt;@@ -296,7 +296,7 @@&lt;/span&gt; and translate_object_field_access venv pos scope chain_exprs =
         let* () =
           match index_expr_ty with
           | Tnumber | Tstring -&amp;gt; ok ()
&lt;span class="gd"&gt;-          | ty -&amp;gt; Error.error_at pos (to_string ty ^ " is a non-indexable type")
&lt;/span&gt;&lt;span class="gi"&gt;+          | ty -&amp;gt; Error.error_at pos (Error.Msg.type_non_indexable_type (to_string ty))
&lt;/span&gt;         in
         let* obj_id = get_obj_id in
         let* (venv', ty) =
&lt;span class="p"&gt;@@ -307,10 +307,10 @@&lt;/span&gt; and translate_object_field_access venv pos scope chain_exprs =
         (match ty with
         | (Tarray _) as array_ty -&amp;gt; ok (venv', array_ty)
         | Tstring as ty -&amp;gt; ok (venv', ty)
&lt;span class="gd"&gt;-        | _ -&amp;gt; Error.error_at pos (field ^ " is a non-indexable value")
&lt;/span&gt;&lt;span class="gi"&gt;+        | _ -&amp;gt; Error.error_at pos (Error.Msg.type_non_indexable_field field)
&lt;/span&gt;         )
       | _ -&amp;gt;
&lt;span class="gd"&gt;-        Error.error_at pos ("Invalid object lookup key: " ^ string_of_type field_expr)
&lt;/span&gt;&lt;span class="gi"&gt;+        Error.error_at pos (Error.Msg.type_invalid_lookup_key (string_of_type field_expr))
&lt;/span&gt;     )
     (ok (venv, obj))
     chain_exprs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;That's all for today! Nothing revolutionary, but these little improvements are making the codebase easier to navigate.&lt;/p&gt;

&lt;p&gt;Now let me write down the ideas for a future refactoring.&lt;/p&gt;

&lt;p&gt;The entire diff can be seen &lt;a href="https://gitlab.com/-/snippets/4901556" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Thanks for reading Bit Maybe Wise! &lt;a href="https://bitmaybewise.substack.com/" rel="noopener noreferrer"&gt;Subscribe&lt;/a&gt; to watch me reorganize function parameters like I'm Marie Kondo-ing the compiler -- does it spark consistency? Then it stays!&lt;/p&gt;

</description>
      <category>tsonnet</category>
      <category>jsonnet</category>
      <category>compiler</category>
    </item>
  </channel>
</rss>
