<?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>DEV Community: Hercules Lemke Merscher</title>
    <description>The latest articles on DEV Community by Hercules Lemke Merscher (@bitmaybewise).</description>
    <link>https://dev.to/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>DEV Community: Hercules Lemke Merscher</title>
      <link>https://dev.to/bitmaybewise</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bitmaybewise"/>
    <language>en</language>
    <item>
      <title>Tsonnet #38 - Call me maybe, but make it typed, part 3</title>
      <dc:creator>Hercules Lemke Merscher</dc:creator>
      <pubDate>Tue, 21 Apr 2026 07:00:00 +0000</pubDate>
      <link>https://dev.to/bitmaybewise/tsonnet-38-call-me-maybe-but-make-it-typed-part-3-2564</link>
      <guid>https://dev.to/bitmaybewise/tsonnet-38-call-me-maybe-but-make-it-typed-part-3-2564</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 implemented closures — both bound to locals and immediately invoked:&lt;/p&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/bitmaybewise/tsonnet-37-call-me-maybe-but-make-it-typed-part-3-e4p" class="crayons-story__hidden-navigation-link"&gt;Tsonnet #37 - Call me maybe, but make it typed, part 3&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-3522309" 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-37-call-me-maybe-but-make-it-typed-part-3-e4p" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Apr 19&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-37-call-me-maybe-but-make-it-typed-part-3-e4p" id="article-link-3522309"&gt;
          Tsonnet #37 - Call me maybe, but make it typed, part 3
        &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-37-call-me-maybe-but-make-it-typed-part-3-e4p#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;I was annoyed by the fact that function type variants were difficult to read, so went on an errand to make it simpler. We can do better!&lt;/p&gt;

&lt;p&gt;Let's see how it turned out.&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%2F05hsrsybfip2sugaq342.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%2F05hsrsybfip2sugaq342.jpeg" alt="Telephone" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A makeover for the AST
&lt;/h2&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 e317b6e..4b1102e 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,10 +90,9 @@&lt;/span&gt; type expr =
   | Local of position * (string * expr) list
   | Seq of expr list
   | IndexedExpr of position * string * expr
&lt;span class="gd"&gt;-  | FunctionDef of position * (string * (string * expr option) list * expr)
-  | FunctionCall of position * string * expr list
-  | Closure of position * ((string * expr option) list * expr)
-  | ClosureCall of position * (string * expr option) list * expr * expr list
&lt;/span&gt;&lt;span class="gi"&gt;+  | FunctionDef of position * function_def
+  | FunctionCall of position * function_call
+  | Closure of position * closure
&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;&lt;code&gt;ClosureCall&lt;/code&gt; is gone. It's basically &lt;code&gt;FunctionCall&lt;/code&gt;, so there's no need to have a duplicated type variant.&lt;/p&gt;

&lt;p&gt;The type variants &lt;code&gt;function_def&lt;/code&gt;, &lt;code&gt;function_call&lt;/code&gt;, and &lt;code&gt;closure&lt;/code&gt; are represented as records, which makes them much easier to reason about since we can have names to represent what they are holding:&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;@@ -102,6 +101,20 @@&lt;/span&gt; and object_scope =
   | Self
   | TopLevel
   | ObjVarRef of string
&lt;span class="gi"&gt;+
+and function_def = {
+  name: string;
+  params: (string * expr option) list;
+  body: expr;
+}
+and function_call = {
+  callee: expr;
+  args: expr list;
+}
+and closure = {
+  params: (string * expr option) list;
+  body: expr;
+}
&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;With this AST change, it became easier to simplify the parsing rules:&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 8b3821e..e7b7771 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;@@ -22,6 +22,7 @@&lt;/span&gt;
 %token DOT
 %token SELF TOP_LEVEL_OBJ
 %token PLUS MINUS MULTIPLY DIVIDE MODULO
&lt;span class="gi"&gt;+%nonassoc FUNCTION
&lt;/span&gt; %left PLUS MINUS
 %left MULTIPLY DIVIDE MODULO
 %token &amp;lt;string&amp;gt; ID
&lt;span class="p"&gt;@@ -43,13 +44,11 @@&lt;/span&gt;
 prog:
   | e = expr; EOF { e }
   | e = expr_seq; EOF { e }
&lt;span class="gd"&gt;-  | e = closure; EOF { e }
&lt;/span&gt;   ;
&lt;span class="err"&gt;
&lt;/span&gt; expr:
   | e = assignable_expr { e }
   | e = vars { e }
&lt;span class="gd"&gt;-  | e = closure_call { e }
&lt;/span&gt;   ;
&lt;span class="err"&gt;
&lt;/span&gt; expr_seq:
&lt;span class="p"&gt;@@ -63,6 +62,7 @@&lt;/span&gt; assignable_expr:
   | e = indexed_expr { e }
   | e = obj_field_access { e }
   | e = funcall { e }
&lt;span class="gi"&gt;+  | e = closure { e }
&lt;/span&gt;   ;
&lt;span class="err"&gt;
&lt;/span&gt; indexed_expr:
&lt;span class="p"&gt;@@ -175,7 +175,6 @@&lt;/span&gt; obj_field_access:
&lt;span class="err"&gt;
&lt;/span&gt; var:
   | varname = ID; ASSIGN; e = assignable_expr { (varname, e) }
&lt;span class="gd"&gt;-  | varname = ID; ASSIGN; e = closure { (varname, e) }
&lt;/span&gt;   ;
&lt;span class="err"&gt;
&lt;/span&gt; vars:
&lt;span class="p"&gt;@@ -196,7 +195,7 @@&lt;/span&gt; fundef:
   | fname = ID;
     LEFT_PAREN; params = separated_nonempty_list(COMMA, fundef_param); RIGHT_PAREN;
     ASSIGN;
&lt;span class="gd"&gt;-    body = fundef_body { (fname, params, body) }
&lt;/span&gt;&lt;span class="gi"&gt;+    body = fundef_body { { name = fname; params = params; body = body } }
&lt;/span&gt;   ;
&lt;span class="err"&gt;
&lt;/span&gt; fundef_body:
&lt;span class="p"&gt;@@ -207,20 +206,23 @@&lt;/span&gt; fundef_body:
 funcall:
   | fname = ID;
     LEFT_PAREN; params = separated_nonempty_list(COMMA, assignable_expr); RIGHT_PAREN
&lt;span class="gd"&gt;-    { FunctionCall (with_pos $startpos $endpos, fname, params) }
&lt;/span&gt;&lt;span class="gi"&gt;+    { FunctionCall
+      (with_pos $startpos $endpos, {
+        callee = Ident (with_pos $startpos(fname) $endpos(fname), fname);
+        args = params;
+      })
+    }
+  | callee = scoped_expr;
+    LEFT_PAREN; params = separated_nonempty_list(COMMA, assignable_expr); RIGHT_PAREN
+    { FunctionCall (with_pos $startpos $endpos, { callee = callee; args = params }) }
&lt;/span&gt;   ;
&lt;span class="err"&gt;
&lt;/span&gt; closure:
   | FUNCTION;
     LEFT_PAREN; params = separated_nonempty_list(COMMA, fundef_param); RIGHT_PAREN;
&lt;span class="gd"&gt;-    body = assignable_expr { Closure (with_pos $startpos $endpos, (params, body)) }
-  ;
-
-closure_call:
-  | LEFT_PAREN; FUNCTION;
-    LEFT_PAREN; def_params = separated_nonempty_list(COMMA, fundef_param); RIGHT_PAREN;
-    body = assignable_expr;
-    RIGHT_PAREN;
-    LEFT_PAREN; call_params = separated_nonempty_list(COMMA, assignable_expr); RIGHT_PAREN;
-    { ClosureCall (with_pos $startpos $endpos, def_params, body, call_params) }
&lt;/span&gt;&lt;span class="gi"&gt;+    body = assignable_expr {
+      Closure (with_pos $startpos $endpos, { params = params; body = body })
+    }
+    (* precedence here will transform "function(x) x * x" into "function(x) (x * x)" *)
+    %prec FUNCTION
&lt;/span&gt;   ;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I could finally move &lt;code&gt;closure&lt;/code&gt; out of &lt;code&gt;prog&lt;/code&gt; and &lt;code&gt;expr&lt;/code&gt;. It was never meant to be there. But part of the problem was not having &lt;code&gt;FUNCTION&lt;/code&gt; flagged as non-associative.&lt;/p&gt;

&lt;h2&gt;
  
  
  The interpreter catches up
&lt;/h2&gt;

&lt;p&gt;With &lt;code&gt;ClosureCall&lt;/code&gt; gone and &lt;code&gt;FunctionCall&lt;/code&gt; now carrying a &lt;code&gt;callee: expr&lt;/code&gt;, the interpreter needed a bit more surgery -- resolving the callee before dispatching to &lt;code&gt;apply_function&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 7412fb9..15f9932 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;@@ -27,9 +27,8 @@&lt;/span&gt; let rec interpret env expr =
   | Seq exprs -&amp;gt; interpret_seq env exprs
   | IndexedExpr (pos, varname, index_expr) -&amp;gt; interpret_indexed_expr env (pos, varname, index_expr)
   | FunctionDef (pos, def) -&amp;gt; interpret_function_def env (pos, def)
&lt;span class="gd"&gt;-  | FunctionCall (pos, fname, params) -&amp;gt; interpret_function_call env (pos, fname, params)
&lt;/span&gt;&lt;span class="gi"&gt;+  | FunctionCall (pos, call) -&amp;gt; interpret_function_call env (pos, call)
&lt;/span&gt;   | Closure _ -&amp;gt; ok (env, expr)
&lt;span class="gd"&gt;-  | ClosureCall (pos, def_params, body, params) -&amp;gt; interpret_closure_call env (pos, def_params, body, 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;span class="p"&gt;@@ -417,8 +416,8 @@&lt;/span&gt; and interpret_ident env pos varname =
     result
   end
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;-and interpret_function_def env (pos, (fname, params, body)) =
-  let env' = Env.add_local fname (FunctionDef (pos, (fname, params, body))) env in
&lt;/span&gt;&lt;span class="gi"&gt;+and interpret_function_def env (pos, def) =
+  let env' = Env.add_local def.name (FunctionDef (pos, def)) env in
&lt;/span&gt;   ok (env', Unit)
&lt;span class="err"&gt;
&lt;/span&gt; and apply_function env pos def_params body call_params =
&lt;span class="p"&gt;@@ -468,16 +467,23 @@&lt;/span&gt; and apply_function env pos def_params body call_params =
     in
     ok (env, result)
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;-and interpret_function_call env (pos, fname, call_params) =
-  match Env.find_opt fname env with
-  | Some (Closure (pos, (def_params, body)))
-  | Some (FunctionDef (pos, (_, def_params, body))) -&amp;gt;
-    apply_function env pos def_params body call_params
&lt;/span&gt;&lt;span class="gi"&gt;+and interpret_function_call env (pos, call) =
+  let* (env', callee_val) =
+    match call.callee with
+    | Ident (pos, name) -&amp;gt;
+      (match Env.find_opt name env with
+      | Some expr -&amp;gt; ok (env, expr)
+      | None -&amp;gt; Error.error_at pos (Error.Msg.var_not_found name)
+      )
+    | _ -&amp;gt; interpret env call.callee
+  in
+  match callee_val with
+  | Closure (_, closure) -&amp;gt;
+    apply_function env' pos closure.params closure.body call.args
+  | FunctionDef (_, def) -&amp;gt;
+    apply_function env' pos def.params def.body call.args
&lt;/span&gt;   | _ -&amp;gt;
&lt;span class="gd"&gt;-    Error.error_at pos (Error.Msg.var_not_found fname)
-
-and interpret_closure_call env (pos, def_params, body, call_params) =
-  apply_function env pos def_params body call_params
&lt;/span&gt;&lt;span class="gi"&gt;+    Error.error_at pos (Error.Msg.var_not_found (string_of_type call.callee))
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt; let rec deep_eval expr =
   match expr with
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The type checker gets the memo
&lt;/h2&gt;

&lt;p&gt;The type checker can also be improved by using the new type variants as records:&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 ee912b3..ad4f333 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;@@ -17,10 +17,10 @@&lt;/span&gt; type tsonnet_type =
   | TobjectPtr of Env.env_id * t_object_scope
   | Lazy of expr
   | Tunresolved
&lt;span class="gd"&gt;-  | TfunctionDef of (string * tsonnet_type) list (* params: name * type *) * expr (* body *) * tsonnet_type (* return *)
-  | TfunctionCall of tsonnet_type list * tsonnet_type
-  | Tclosure of (string * tsonnet_type) list * expr
-  | TclosureCall of tsonnet_type list * tsonnet_type
&lt;/span&gt;&lt;span class="gi"&gt;+  | TfunctionDef of t_function_def
+  | TfunctionCall of t_function_call
+  | Tclosure of t_closure
+
&lt;/span&gt; and t_object_entry =
   | TobjectField of string * tsonnet_type
   | TobjectExpr of tsonnet_type
&lt;span class="p"&gt;@@ -28,6 +28,20 @@&lt;/span&gt; and t_object_scope =
   | TobjectSelf
   | TobjectTopLevel
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gi"&gt;+and t_function_def = {
+  params: (string * tsonnet_type) list;
+  body: expr;
+  return: tsonnet_type;
+}
+and t_function_call = {
+  params: tsonnet_type list;
+  return: tsonnet_type;
+}
+and t_closure = {
+  params: (string * tsonnet_type) list;
+  body: expr;
+}
+
&lt;/span&gt; let rec to_string = function
   | Tunit -&amp;gt; "()"
   | Tnull -&amp;gt; "Null"
&lt;span class="p"&gt;@@ -59,25 +73,21 @@&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="gd"&gt;-  | TfunctionDef (params, _, return) -&amp;gt;
&lt;/span&gt;&lt;span class="gi"&gt;+  | TfunctionDef {params; return; _} -&amp;gt;
&lt;/span&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)
&lt;span class="gd"&gt;-  | TfunctionCall (params_type, return) -&amp;gt;
&lt;/span&gt;&lt;span class="gi"&gt;+  | TfunctionCall {params=params_type; return} -&amp;gt;
&lt;/span&gt;     Printf.sprintf "function(%s) -&amp;gt; %s"
       (List.map to_string params_type |&amp;gt; String.concat ", ")
       (to_string return)
&lt;span class="gd"&gt;-  | Tclosure (params, _) -&amp;gt;
&lt;/span&gt;&lt;span class="gi"&gt;+  | Tclosure {params; _} -&amp;gt;
&lt;/span&gt;     Printf.sprintf "function(%s)"
       (List.map (fun (name, ty) -&amp;gt; name ^ ": " ^ to_string ty) params
       |&amp;gt; String.concat ", "
       )
&lt;span class="gd"&gt;-  | TclosureCall (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)
&lt;/span&gt;   | Tunresolved -&amp;gt; "&amp;lt;unresolved&amp;gt;"
&lt;span class="err"&gt;
&lt;/span&gt; let rec collect_free_idents = function
&lt;span class="p"&gt;@@ -95,10 +105,9 @@&lt;/span&gt; let rec collect_free_idents = function
   | ObjectFieldAccess (_, _, exprs) -&amp;gt; List.concat_map collect_free_idents exprs
   | IndexedExpr (_, name, e) -&amp;gt; name :: collect_free_idents e
   | Local (_, vars) -&amp;gt; List.concat_map (fun (_, e) -&amp;gt; collect_free_idents e) vars
&lt;span class="gd"&gt;-  | FunctionCall (_, name, args) -&amp;gt; name :: List.concat_map collect_free_idents args
-  | Closure (_, (_, body)) -&amp;gt; collect_free_idents body
-  | ClosureCall (_, _, body, args) -&amp;gt;
-    collect_free_idents body @ List.concat_map collect_free_idents args
&lt;/span&gt;&lt;span class="gi"&gt;+  | FunctionCall (_, call) -&amp;gt;
+    collect_free_idents call.callee @ List.concat_map collect_free_idents call.args
+  | Closure (_, closure) -&amp;gt; collect_free_idents closure.body
&lt;/span&gt;   | _ -&amp;gt; []
&lt;span class="err"&gt;
&lt;/span&gt; let reachable_bindings bindings initial_idents =
&lt;span class="p"&gt;@@ -192,10 +201,8 @@&lt;/span&gt; let rec translate venv expr =
   | 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)
   | FunctionDef (pos, def) -&amp;gt; translate_function_def venv (pos, def)
&lt;span class="gd"&gt;-  | FunctionCall (pos, fname, params) -&amp;gt; translate_function_call venv (pos, fname, params)
-  | Closure (pos, (params, body)) -&amp;gt; translate_closure venv (pos, params, body)
-  | ClosureCall (pos, def_params, body, call_params) -&amp;gt;
-    translate_closure_call venv (pos, def_params, body, call_params)
&lt;/span&gt;&lt;span class="gi"&gt;+  | FunctionCall (pos, call) -&amp;gt; translate_function_call venv (pos, call)
+  | Closure (pos, closure) -&amp;gt; translate_closure venv (pos, closure)
&lt;/span&gt;   | expr' -&amp;gt;
     error (Error.Msg.type_invalid_expr (string_of_type expr')) 
&lt;span class="p"&gt;@@ -493,7 +500,7 @@&lt;/span&gt; and translate_bin_op venv pos op e1 e2 =
   | _, Tany, _ | _, _, Tany -&amp;gt; ok (venv'', Tany)
   | _ -&amp;gt; Error.error_at pos Error.Msg.invalid_binary_op
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;-and translate_function_def venv (pos, (fun_name, params, body)) =
&lt;/span&gt;&lt;span class="gi"&gt;+and translate_function_def venv (pos, def) =
&lt;/span&gt;   (* For params with defaults, we can infer the type from the default expression;
      params without defaults remain Tunresolved until the first call *)
   let* params_typed = List.fold_left
&lt;span class="p"&gt;@@ -507,36 +514,57 @@&lt;/span&gt; and translate_function_def venv (pos, (fun_name, params, body)) =
         ok (params' @ [(name, Tunresolved)])
     )
     (ok [])
&lt;span class="gd"&gt;-    params
&lt;/span&gt;&lt;span class="gi"&gt;+    def.params
+  in
+  let fun_def = TfunctionDef
+    { params = params_typed;
+      body = def.body;
+      return = Tunresolved;
+    }
&lt;/span&gt;   in
&lt;span class="gd"&gt;-  let fun_def = TfunctionDef (params_typed, body, Tunresolved) in
&lt;/span&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 class="gd"&gt;-  let venv' = Env.add_local fun_name fun_def venv in
&lt;/span&gt;&lt;span class="gi"&gt;+  let venv' = Env.add_local def.name fun_def venv in
&lt;/span&gt;   ok (venv', fun_def)
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;-and translate_function_call venv (pos, fname, call_params) =
-  (* 1. retrieve TfunctionDef from venv *)
-  match Env.find_opt fname venv with
-  | Some (TfunctionDef (def_params, body_expr, return_type)) -&amp;gt;
-    (* check arity: allow fewer args if defaults exist *)
-    let num_call = List.length call_params in
&lt;/span&gt;&lt;span class="gi"&gt;+and translate_function_call venv (pos, call) =
+  match call.callee with
+  | Ident (_, name) -&amp;gt;
+    translate_named_function_call venv (pos, name, call.args)
+  | Closure (_, closure) -&amp;gt;
+    translate_closure_call venv (pos, closure.params, closure.body, call.args)
+  | _ -&amp;gt;
+    let* (venv', callee_ty) = translate venv call.callee in
+    (match callee_ty with
+    | Tclosure { params = closure_params; body = body_expr } -&amp;gt;
+      let def_params = List.map (fun (name, _ty) -&amp;gt; (name, None)) closure_params in
+      translate_closure_call venv' (pos, def_params, body_expr, call.args)
+    | _ -&amp;gt;
+      Error.error_at pos (Error.Msg.type_invalid_expr (string_of_type call.callee))
+    )
+
+and translate_named_function_call venv (pos, name, args) =
+  match Env.find_opt name venv with
+  | Some (TfunctionDef { params = def_params;
+                         body = body_expr;
+                         return = return_type;
+                       }) -&amp;gt;
+    let num_call = List.length args in
&lt;/span&gt;     let num_def = List.length def_params in
     if num_call &amp;gt; num_def
     then
       Error.error_at pos
         (Error.Msg.wrong_number_of_params num_def num_call)
     else
&lt;span class="gd"&gt;-      (* 2. type check each positional parameter passed in the function call *)
&lt;/span&gt;       let* (venv', resolved_params) =
         List.fold_left
           (fun acc (index, (param_name, def_param_type)) -&amp;gt;
             let* (venv', params') = acc in
             if index &amp;lt; num_call
             then
&lt;span class="gd"&gt;-              let call_param = List.nth call_params index in
&lt;/span&gt;&lt;span class="gi"&gt;+              let call_param = List.nth args index in
&lt;/span&gt;               let* (venv'', call_param_type) = translate venv' call_param in
               match def_param_type with
               | Tunresolved -&amp;gt;
&lt;span class="p"&gt;@@ -549,26 +577,22 @@&lt;/span&gt; and translate_function_call venv (pos, fname, call_params) =
                     ~expected:(to_string expected)
                     ~got:(to_string call_param_type))
             else
&lt;span class="gd"&gt;-              (* default arg — resolve type from the original AST default expression *)
&lt;/span&gt;               ok (venv', params' @ [(param_name, def_param_type)])
           )
           (ok (venv, []))
           (List.mapi (fun i p -&amp;gt; (i, p)) def_params)
       in
&lt;span class="gd"&gt;-      (* 3. type check return *)
&lt;/span&gt;       let body_venv = List.fold_left
         (fun env (name, ty) -&amp;gt; Env.add_local name ty env)
         venv'
         resolved_params
       in
&lt;span class="gd"&gt;-      (* translate the body with resolved param types in scope *)
&lt;/span&gt;       let* (_, body_type) = translate body_venv body_expr in
&lt;span class="gd"&gt;-      let* resolved_return = match return_type with
&lt;/span&gt;&lt;span class="gi"&gt;+      let* resolved_return =
+        match return_type with
&lt;/span&gt;         | Tunresolved -&amp;gt;
&lt;span class="gd"&gt;-          (* 3a. first call: infer return type from body *)
&lt;/span&gt;           ok body_type
         | expected -&amp;gt;
&lt;span class="gd"&gt;-          (* 3b. subsequent calls: check body type matches *)
&lt;/span&gt;           if body_type = expected
           then ok expected
           else Error.error_at pos
&lt;span class="p"&gt;@@ -576,23 +600,26 @@&lt;/span&gt; and translate_function_call venv (pos, fname, call_params) =
               ~expected:(to_string expected)
               ~got:(to_string body_type))
       in
&lt;span class="gd"&gt;-      (* 4. update env with the now-resolved function type *)
-      let resolved_fun = TfunctionDef (resolved_params, body_expr, resolved_return) in
-      let venv_with_resolved_fun = Env.add_local fname resolved_fun venv' in
&lt;/span&gt;&lt;span class="gi"&gt;+      let resolved_fun =
+        TfunctionDef {
+          params = resolved_params;
+          body = body_expr;
+          return = resolved_return;
+        }
+      in
+      let venv_with_resolved_fun = Env.add_local name resolved_fun venv' in
&lt;/span&gt;       ok (venv_with_resolved_fun, resolved_return)
   | Some (Lazy expr) -&amp;gt;
     let* (venv', resolved) = translate venv expr in
&lt;span class="gd"&gt;-    (* Re-dispatch with the resolved type *)
-    let venv'' = Env.add_local fname resolved venv' in
-    translate_function_call venv'' (pos, fname, call_params)
-  | Some (Tclosure (def_params, body_expr)) -&amp;gt;
-    translate_closure_call venv (pos, 
-      List.map (fun (name, _ty) -&amp;gt; (name, None)) def_params,
-      body_expr, call_params)
&lt;/span&gt;&lt;span class="gi"&gt;+    let venv'' = Env.add_local name resolved venv' in
+    translate_named_function_call venv'' (pos, name, args)
+  | Some (Tclosure { params = closure_params; body = body_expr }) -&amp;gt;
+    let def_params = List.map (fun (name, _ty) -&amp;gt; (name, None)) closure_params in
+    translate_closure_call venv (pos, def_params, body_expr, args)
&lt;/span&gt;   | _ -&amp;gt;
&lt;span class="gd"&gt;-    Error.error_at pos (Error.Msg.var_not_found fname)
&lt;/span&gt;&lt;span class="gi"&gt;+    Error.error_at pos (Error.Msg.var_not_found name)
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;-and translate_closure venv (_pos, params, body) =
&lt;/span&gt;&lt;span class="gi"&gt;+and translate_closure venv (_pos, closure) =
&lt;/span&gt;   let* params_typed = List.fold_left
     (fun acc (name, default) -&amp;gt;
       let* params' = acc in
&lt;span class="p"&gt;@@ -604,11 +631,11 @@&lt;/span&gt; and translate_closure venv (_pos, params, body) =
         ok (params' @ [(name, Tunresolved)])
     )
     (ok [])
&lt;span class="gd"&gt;-    params
&lt;/span&gt;&lt;span class="gi"&gt;+    closure.params
&lt;/span&gt;   in
&lt;span class="gd"&gt;-  ok (venv, Tclosure (params_typed, body))
&lt;/span&gt;&lt;span class="gi"&gt;+  ok (venv, Tclosure { params = params_typed; body = closure.body })
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;-and translate_closure_call venv (pos, def_params, body_expr, call_params) =
&lt;/span&gt;&lt;span class="gi"&gt;+and translate_closure_call venv (pos, def_params, body, call_params) =
&lt;/span&gt;   let num_call = List.length call_params in
   let num_def = List.length def_params in
   let num_required =
&lt;span class="p"&gt;@@ -641,7 +668,7 @@&lt;/span&gt; and translate_closure_call venv (pos, def_params, body_expr, call_params) =
       venv'
       resolved_params
     in
&lt;span class="gd"&gt;-    let* (_, body_type) = translate body_venv body_expr in
&lt;/span&gt;&lt;span class="gi"&gt;+    let* (_, body_type) = translate body_venv body in
&lt;/span&gt;     ok (venv, body_type)
&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;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Switching from tuples to named records across the AST, type checker, and interpreter was one of those refactors that feels almost mechanical — until it isn't. The real win was collapsing &lt;code&gt;ClosureCall&lt;/code&gt; into &lt;code&gt;FunctionCall&lt;/code&gt; and generalising the callee to an arbitrary expression. The grammar conflict that made &lt;code&gt;closure&lt;/code&gt; a second-class citizen in the previous post? Gone. A bit of precedence annotation and a small restructuring of &lt;code&gt;funcall&lt;/code&gt; was all it took.&lt;/p&gt;

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

&lt;p&gt;Next up, methods!&lt;/p&gt;




&lt;p&gt;Thanks for reading Bit Maybe Wise! Tuples are out, records are in. Your inbox deserves named fields too — &lt;a href="https://bitmaybewise.com/" rel="noopener noreferrer"&gt;subscribe&lt;/a&gt; and stop guessing what position two holds.&lt;/p&gt;

&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@quinoal" rel="noopener noreferrer"&gt;Quino Al&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 #37 - Call me maybe, but make it typed, part 3</title>
      <dc:creator>Hercules Lemke Merscher</dc:creator>
      <pubDate>Sun, 19 Apr 2026 10:40:23 +0000</pubDate>
      <link>https://dev.to/bitmaybewise/tsonnet-37-call-me-maybe-but-make-it-typed-part-3-e4p</link>
      <guid>https://dev.to/bitmaybewise/tsonnet-37-call-me-maybe-but-make-it-typed-part-3-e4p</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 default function arguments:&lt;/p&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/bitmaybewise/tsonnet-36-call-me-maybe-but-make-it-typed-part-2-18fn" class="crayons-story__hidden-navigation-link"&gt;Tsonnet #36 — Call me maybe, but make it typed, 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" 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-3512061" 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-36-call-me-maybe-but-make-it-typed-part-2-18fn" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Apr 17&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-36-call-me-maybe-but-make-it-typed-part-2-18fn" id="article-link-3512061"&gt;
          Tsonnet #36 — Call me maybe, but make it typed, 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-36-call-me-maybe-but-make-it-typed-part-2-18fn#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;
            5 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 implement &lt;strong&gt;closures&lt;/strong&gt;.&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%2Frtwv36viik628amgtwxn.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%2Frtwv36viik628amgtwxn.jpeg" alt="Telephone cabins" width="800" height="534"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Captured and ready to be called
&lt;/h2&gt;

&lt;p&gt;We should accept a closure that is going to be assigned to a local, and immediately called closures:&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/closure.jsonnet&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;binding&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;closure&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;id&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;function(x)&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;immediately&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;called&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;closure&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;(function(x)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;id(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;id(x))(&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Parsing: teaching the grammar new tricks
&lt;/h2&gt;

&lt;p&gt;We need two 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 f4c1472..e317b6e 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;@@ -92,6 +92,8 @@&lt;/span&gt; type expr =
   | IndexedExpr of position * string * expr
   | FunctionDef of position * (string * (string * expr option) list * expr)
   | FunctionCall of position * string * expr list
&lt;span class="gi"&gt;+  | Closure of position * ((string * expr option) list * expr)
+  | ClosureCall of position * (string * expr option) list * expr * 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;Then we update the lexer to recognise the new keyword:&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 a4c3745..4d65f55 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;@@ -80,6 +80,7 @@&lt;/span&gt; rule read =
   | "self" { SELF }
   | "$" { TOP_LEVEL_OBJ }
   | "in" { IN }
&lt;span class="gi"&gt;+  | "function" { FUNCTION }
&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;And the parser:&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 290b280..8b3821e 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;@@ -13,6 +13,7 @@&lt;/span&gt;
 %token NULL
 %token &amp;lt;bool&amp;gt; BOOL
 %token &amp;lt;string&amp;gt; STRING
&lt;span class="gi"&gt;+%token FUNCTION
&lt;/span&gt; %token LEFT_SQR_BRACKET RIGHT_SQR_BRACKET
 %token LEFT_PAREN RIGHT_PAREN
 %token COMMA
&lt;span class="p"&gt;@@ -42,11 +43,13 @@&lt;/span&gt;
 prog:
   | e = expr; EOF { e }
   | e = expr_seq; EOF { e }
&lt;span class="gi"&gt;+  | e = closure; EOF { e }
&lt;/span&gt;   ;
&lt;span class="err"&gt;
&lt;/span&gt; expr:
   | e = assignable_expr { e }
   | e = vars { e }
&lt;span class="gi"&gt;+  | e = closure_call { e }
&lt;/span&gt;   ;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I stumbled upon a shift-reduce issue trying to make closure part of the &lt;code&gt;assignable_expr&lt;/code&gt; parsing rule — it would make more semantic sense there, but the grammar conflicts beat me. I'll come back to fix it later.&lt;/p&gt;

&lt;p&gt;Here's the short version of why it's not feasible as-is: if &lt;code&gt;closure&lt;/code&gt; lives in &lt;code&gt;assignable_expr&lt;/code&gt;, then &lt;code&gt;(function(x) x)&lt;/code&gt; becomes reachable via two paths simultaneously — as a parenthesised closure via &lt;code&gt;scoped_expr&lt;/code&gt;, and as the first half of a &lt;code&gt;closure_call&lt;/code&gt;. When the parser sees &lt;code&gt;( function(x) body . )&lt;/code&gt; and hits &lt;code&gt;)&lt;/code&gt;, it can't decide whether to reduce into &lt;code&gt;scoped_expr&lt;/code&gt; or shift &lt;code&gt;)&lt;/code&gt; as part of &lt;code&gt;closure_call&lt;/code&gt; — that would require two tokens of lookahead, which LR(1) doesn't have. The only clean fix would be generalising &lt;code&gt;funcall&lt;/code&gt; to accept any expression as the callee, so &lt;code&gt;(function(x) x)(5)&lt;/code&gt; parses naturally as "call this expression with these args" — but that's a bigger refactor touching the AST, type checker, and interpreter.&lt;/p&gt;

&lt;p&gt;Here's how the new &lt;code&gt;closure&lt;/code&gt; and &lt;code&gt;closure_call&lt;/code&gt; rules are implemented for 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="p"&gt;@@ -171,7 +174,9 @@&lt;/span&gt; obj_field_access:
   ;
&lt;span class="err"&gt;
&lt;/span&gt; var:
&lt;span class="gd"&gt;-  varname = ID; ASSIGN; e = assignable_expr { (varname, e) };
&lt;/span&gt;&lt;span class="gi"&gt;+  | varname = ID; ASSIGN; e = assignable_expr { (varname, e) }
+  | varname = ID; ASSIGN; e = closure { (varname, e) }
+  ;
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt; vars:
   | LOCAL; vars = separated_nonempty_list(COMMA, var) { Local (with_pos $startpos $endpos, vars) }
&lt;span class="p"&gt;@@ -203,4 +208,19 @@&lt;/span&gt; funcall:
   | fname = ID;
     LEFT_PAREN; params = separated_nonempty_list(COMMA, assignable_expr); RIGHT_PAREN
     { FunctionCall (with_pos $startpos $endpos, fname, params) }
&lt;span class="gi"&gt;+  ;
+
+closure:
+  | FUNCTION;
+    LEFT_PAREN; params = separated_nonempty_list(COMMA, fundef_param); RIGHT_PAREN;
+    body = assignable_expr { Closure (with_pos $startpos $endpos, (params, body)) }
+  ;
+
+closure_call:
+  | LEFT_PAREN; FUNCTION;
+    LEFT_PAREN; def_params = separated_nonempty_list(COMMA, fundef_param); RIGHT_PAREN;
+    body = assignable_expr;
+    RIGHT_PAREN;
+    LEFT_PAREN; call_params = separated_nonempty_list(COMMA, assignable_expr); RIGHT_PAREN;
+    { ClosureCall (with_pos $startpos $endpos, def_params, body, call_params) }
&lt;/span&gt;   ;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Type checking: the elephant in the room
&lt;/h2&gt;

&lt;p&gt;The new type variants mirror the ones in &lt;code&gt;ast.ml&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 05bc187..ee912b3 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;@@ -19,6 +19,8 @@&lt;/span&gt; type tsonnet_type =
   | Tunresolved
   | TfunctionDef of (string * tsonnet_type) list (* params: name * type *) * expr (* body *) * tsonnet_type (* return *)
   | TfunctionCall of tsonnet_type list * tsonnet_type
&lt;span class="gi"&gt;+  | Tclosure of (string * tsonnet_type) list * expr
+  | TclosureCall of tsonnet_type list * tsonnet_type
&lt;/span&gt; and t_object_entry =
   | TobjectField of string * tsonnet_type
   | TobjectExpr of tsonnet_type
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;collect_free_idents&lt;/code&gt; function needs updating for the new variants — I also noticed I'd forgotten &lt;code&gt;FunctionCall&lt;/code&gt; here earlier (oops!):&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;@@ -84,6 +95,10 @@&lt;/span&gt; let rec collect_free_idents = function
   | ObjectFieldAccess (_, _, exprs) -&amp;gt; List.concat_map collect_free_idents exprs
   | IndexedExpr (_, name, e) -&amp;gt; name :: collect_free_idents e
   | Local (_, vars) -&amp;gt; List.concat_map (fun (_, e) -&amp;gt; collect_free_idents e) vars
&lt;span class="gi"&gt;+  | FunctionCall (_, name, args) -&amp;gt; name :: List.concat_map collect_free_idents args
+  | Closure (_, (_, body)) -&amp;gt; collect_free_idents body
+  | ClosureCall (_, _, body, args) -&amp;gt;
+    collect_free_idents body @ List.concat_map collect_free_idents args
&lt;/span&gt;   | _ -&amp;gt; []
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we pattern-match on the new AST nodes in &lt;code&gt;translate&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;@@ -178,6 +193,9 @@&lt;/span&gt; let rec translate venv expr =
   | IndexedExpr (pos, varname, index_expr) -&amp;gt; translate_indexed_expr venv (pos, varname, index_expr)
   | 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 class="gi"&gt;+  | Closure (pos, (params, body)) -&amp;gt; translate_closure venv (pos, params, body)
+  | ClosureCall (pos, def_params, body, call_params) -&amp;gt;
+    translate_closure_call venv (pos, def_params, body, call_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;I also added a catch-all for &lt;code&gt;Tany&lt;/code&gt; in binary operations — if either side is &lt;code&gt;Tany&lt;/code&gt;, we let it through rather than erroring:&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;@@ -472,6 +490,7 @@&lt;/span&gt; and translate_bin_op venv pos op e1 e2 =
   | 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 class="gi"&gt;+  | _, Tany, _ | _, _, Tany -&amp;gt; ok (venv'', Tany)
&lt;/span&gt;   | _ -&amp;gt; Error.error_at pos Error.Msg.invalid_binary_op
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;translate_function_call&lt;/code&gt; also needs to handle &lt;code&gt;Tclosure&lt;/code&gt; — if a bound name resolves to a closure, we re-dispatch to &lt;code&gt;translate_closure_call&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;@@ -561,9 +580,70 @@&lt;/span&gt; and translate_function_call venv (pos, fname, call_params) =
       let resolved_fun = TfunctionDef (resolved_params, body_expr, resolved_return) in
       let venv_with_resolved_fun = Env.add_local fname resolved_fun venv' in
       ok (venv_with_resolved_fun, resolved_return)
&lt;span class="gi"&gt;+  | Some (Lazy expr) -&amp;gt;
+    let* (venv', resolved) = translate venv expr in
+    (* Re-dispatch with the resolved type *)
+    let venv'' = Env.add_local fname resolved venv' in
+    translate_function_call venv'' (pos, fname, call_params)
+  | Some (Tclosure (def_params, body_expr)) -&amp;gt;
+    translate_closure_call venv (pos, 
+      List.map (fun (name, _ty) -&amp;gt; (name, None)) def_params,
+      body_expr, call_params)
&lt;/span&gt;   | _ -&amp;gt;
     Error.error_at pos (Error.Msg.var_not_found fname)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;translate_closure&lt;/code&gt; is pretty similar to &lt;code&gt;translate_function_def&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_closure&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;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="o"&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="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;default&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;params'&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;default&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;default_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;default_ty&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;default_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;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;name&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default_ty&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="n"&gt;ok&lt;/span&gt; &lt;span class="p"&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;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="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;params&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="nc"&gt;Tclosure&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="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And &lt;code&gt;translate_closure_call&lt;/code&gt; is pretty similar to &lt;code&gt;translate_function_call&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_closure_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;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;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;let&lt;/span&gt; &lt;span class="n"&gt;num_call&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;length&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;num_def&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;length&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="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;num_required&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;length&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;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;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&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;Option&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_none&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;def_params&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;if&lt;/span&gt; &lt;span class="n"&gt;num_call&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;num_required&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;num_call&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;num_def&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;wrong_number_of_params&lt;/span&gt; &lt;span class="n"&gt;num_def&lt;/span&gt; &lt;span class="n"&gt;num_call&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;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_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;index&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;default&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;if&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;num_call&lt;/span&gt;
          &lt;span class="k"&gt;then&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;call_param&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;call_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="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="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="k"&gt;else&lt;/span&gt;
            &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;default&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;default_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;venv''&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default_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;default_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;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;default_type&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="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;wrong_number_of_params&lt;/span&gt; &lt;span class="n"&gt;num_def&lt;/span&gt; &lt;span class="n"&gt;num_call&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="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;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;i&lt;/span&gt; &lt;span class="n"&gt;p&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;i&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="n"&gt;def_params&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;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="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="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;body_type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's room for simplification here, but let's move on for now.&lt;/p&gt;

&lt;p&gt;It's worth pausing on a behaviour difference between Tsonnet and standard Jsonnet. Given this sample:&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;Jsonnet fails at runtime after evaluating the first call, because &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; expects a boolean and gets a number first:&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/semantics/invalid_function_call_type.jsonnet
RUNTIME ERROR: Unexpected &lt;span class="nb"&gt;type &lt;/span&gt;number, expected boolean
    samples/semantics/invalid_function_call_type.jsonnet:2:1-38 &lt;span class="err"&gt;$&lt;/span&gt;
    During evaluation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tsonnet instead type-checks the function calls before even getting to the &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt;, and catches the type mismatch earlier:&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/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;I think that's a more sensible error. Worth noting: I want to support polymorphic parameters and return types eventually, but for this prototype, this is enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  Interpreting: where closures actually close over things
&lt;/h2&gt;

&lt;p&gt;During interpretation, a closure definition is returned as-is — it just gets stored in the environment. The actual work happens at call time:&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 6ad259e..7412fb9 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;@@ -28,6 +28,8 @@&lt;/span&gt; let rec interpret env expr =
   | IndexedExpr (pos, varname, index_expr) -&amp;gt; interpret_indexed_expr env (pos, varname, index_expr)
   | 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 class="gi"&gt;+  | Closure _ -&amp;gt; ok (env, expr)
+  | ClosureCall (pos, def_params, body, params) -&amp;gt; interpret_closure_call env (pos, def_params, body, params)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The function application logic was straightforward to generalise into &lt;code&gt;apply_function&lt;/code&gt;, which both &lt;code&gt;interpret_function_call&lt;/code&gt; and &lt;code&gt;interpret_closure_call&lt;/code&gt; now share:&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;apply_function&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;def_params&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="n"&gt;call_params&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;num_call&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;length&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;num_def&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;length&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="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;num_required&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;length&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;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;_&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&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;Option&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_none&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;def_params&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;if&lt;/span&gt; &lt;span class="n"&gt;num_call&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;num_required&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;num_call&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;num_def&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;wrong_number_of_params&lt;/span&gt; &lt;span class="n"&gt;num_def&lt;/span&gt; &lt;span class="n"&gt;num_call&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="n"&gt;evaluated_call_params&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;param&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;params&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;_&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="n"&gt;interpret&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="n"&gt;param&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;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;v&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;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="o"&gt;*&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;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;index&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;default&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;bindings&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;if&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;num_call&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;bindings&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="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;evaluated_call_params&lt;/span&gt; &lt;span class="n"&gt;index&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;match&lt;/span&gt; &lt;span class="n"&gt;default&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;default_expr&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;bindings&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;default_expr&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="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;wrong_number_of_params&lt;/span&gt; &lt;span class="n"&gt;num_def&lt;/span&gt; &lt;span class="n"&gt;num_call&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="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;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;i&lt;/span&gt; &lt;span class="n"&gt;p&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;i&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="n"&gt;def_params&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;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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;interpret_function_call&lt;/code&gt; and &lt;code&gt;interpret_closure_call&lt;/code&gt; now just delegate to it:&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_function_call env (pos, fname, call_params) =
   match Env.find_opt fname env with
&lt;span class="gi"&gt;+  | Some (Closure (pos, (def_params, body)))
&lt;/span&gt;   | Some (FunctionDef (pos, (_, def_params, body))) -&amp;gt;
&lt;span class="gd"&gt;-    ...
&lt;/span&gt;&lt;span class="gi"&gt;+    apply_function env pos def_params body call_params
&lt;/span&gt;   | _ -&amp;gt;
     Error.error_at pos (Error.Msg.var_not_found fname)
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gi"&gt;+and interpret_closure_call env (pos, def_params, body, call_params) =
+  apply_function env pos def_params body call_params
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I also used this opportunity to fix a leftover hard-coded error string I'd missed during an earlier refactoring:&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
&lt;/span&gt;&lt;span class="gd"&gt;-  let type_wrong_number_of_params expected got =
-    Printf.sprintf "Expected %d argument(s), got %d" expected got
&lt;/span&gt;&lt;span class="gi"&gt;+  let wrong_number_of_params expected got =
+    Printf.sprintf "Expected %d argument(s), got %d" expected got
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rename also moves the message out of the &lt;code&gt;type_&lt;/code&gt;-prefixed block, since both the type checker and the interpreter now share it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does it work? (Yes it does)
&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/functions/closure.jsonnet
25
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the 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/functions.t b/test/cram/functions.t
index fa29868..5c77837 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/test/cram/functions.t
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/test/cram/functions.t
&lt;/span&gt;&lt;span class="p"&gt;@@ -6,3 +6,6 @@&lt;/span&gt;
&lt;span class="err"&gt;
&lt;/span&gt;   $ tsonnet ../../samples/functions/default_args.jsonnet
   12
&lt;span class="gi"&gt;+
+  $ tsonnet ../../samples/functions/closure.jsonnet
+  25
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Closures are in. Binding them to locals, calling them immediately, passing them around — it all works. The type checker was the trickier part, requiring extra plumbing to re-dispatch when a bound name turns out to be a closure, but the interpreter side practically wrote itself once &lt;code&gt;apply_function&lt;/code&gt; was factored out. The shift-reduce issue in the parser is still there, nagging at me from the grammar file — but that's a problem for future me, probably involving a refactor to let &lt;code&gt;funcall&lt;/code&gt; accept any expression as callee.&lt;/p&gt;

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

&lt;p&gt;Next up, I think this can be simplified, and I will do so.&lt;/p&gt;




&lt;p&gt;Thanks for reading Bit Maybe Wise! Closures capture their environment. This newsletter captures compilers being built in the open. &lt;a href="https://bitmaybewise.com/" rel="noopener noreferrer"&gt;Subscribe&lt;/a&gt; before the environment gets GC'd.&lt;/p&gt;

&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@thenowtime" rel="noopener noreferrer"&gt;The Now Time&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 #36 — Call me maybe, but make it typed, part 2</title>
      <dc:creator>Hercules Lemke Merscher</dc:creator>
      <pubDate>Fri, 17 Apr 2026 07:00:00 +0000</pubDate>
      <link>https://dev.to/bitmaybewise/tsonnet-36-call-me-maybe-but-make-it-typed-part-2-18fn</link>
      <guid>https://dev.to/bitmaybewise/tsonnet-36-call-me-maybe-but-make-it-typed-part-2-18fn</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 basic function support — positional parameters, multiline bodies, and type inference that resolves at call time:&lt;/p&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/bitmaybewise/tsonnet-35-call-me-maybe-but-make-it-typed-part-1-58i2" class="crayons-story__hidden-navigation-link"&gt;Tsonnet #35 — Call me maybe, but make it typed, part 1&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-3501867" 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-35-call-me-maybe-but-make-it-typed-part-1-58i2" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Apr 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-35-call-me-maybe-but-make-it-typed-part-1-58i2" id="article-link-3501867"&gt;
          Tsonnet #35 — Call me maybe, but make it typed, part 1
        &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-35-call-me-maybe-but-make-it-typed-part-1-58i2#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;Now let's make functions a little more forgiving. In this post, we'll implement &lt;strong&gt;default function arguments&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's on the menu?
&lt;/h2&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/default_args.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;y=&lt;/span&gt;&lt;span class="mi"&gt;10&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;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;my_function(&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The call passes only one argument. &lt;code&gt;y&lt;/code&gt; falls back to its default value of &lt;code&gt;10&lt;/code&gt;, so the result is &lt;code&gt;12&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Teaching the parser about optionals
&lt;/h2&gt;

&lt;p&gt;The parser needs a small adjustment to accept an optional default expression per parameter. Previously, each parameter was just an &lt;code&gt;ID&lt;/code&gt;. Now it can optionally include &lt;code&gt;= &amp;lt;expr&amp;gt;&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/parser.mly b/lib/parser.mly
index 102d202..290b280 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;@@ -182,9 +182,14 @@&lt;/span&gt; single_var:
   | LOCAL; var_expr = var { Local (with_pos $startpos $endpos, [var_expr]) }
   ;
&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gi"&gt;+fundef_param:
+  | name = ID { (name, None) }
+  | name = ID; ASSIGN; default = assignable_expr { (name, Some default) }
+  ;
+
&lt;/span&gt; fundef:
   | fname = ID;
&lt;span class="gd"&gt;-    LEFT_PAREN; params = separated_nonempty_list(COMMA, ID); RIGHT_PAREN;
&lt;/span&gt;&lt;span class="gi"&gt;+    LEFT_PAREN; params = separated_nonempty_list(COMMA, fundef_param); RIGHT_PAREN;
&lt;/span&gt;     ASSIGN;
     body = fundef_body { (fname, params, body) }
   ;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the AST type updates to carry the optional default alongside the parameter name:&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 106f37c..f4c1472 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,7 +90,7 @@&lt;/span&gt; type expr =
   | Local of position * (string * expr) list
   | Seq of expr list
   | IndexedExpr of position * string * expr
&lt;span class="gd"&gt;-  | FunctionDef of position * (string * string list * expr)
&lt;/span&gt;&lt;span class="gi"&gt;+  | FunctionDef of position * (string * (string * expr option) list * expr)
&lt;/span&gt;   | FunctionCall of position * string * expr list
&lt;span class="err"&gt;
&lt;/span&gt; and object_entry =
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each parameter goes from a plain &lt;code&gt;string&lt;/code&gt; to a &lt;code&gt;string * expr option&lt;/code&gt; — &lt;code&gt;None&lt;/code&gt; for required, &lt;code&gt;Some expr&lt;/code&gt; for optional with a default.&lt;/p&gt;

&lt;h2&gt;
  
  
  Filling in the blanks
&lt;/h2&gt;

&lt;p&gt;The arity check is a bit more involved now, since we have to distinguish between required and optional parameters:&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 d512c0f..6ad259e 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;@@ -422,16 +422,30 @@&lt;/span&gt; and interpret_function_def env (pos, (fname, params, body)) =
 and interpret_function_call env (pos, fname, call_params) =
   match Env.find_opt fname env with
   | Some (FunctionDef (pos, (_, def_params, body))) -&amp;gt;
&lt;span class="gd"&gt;-    if List.compare_lengths call_params def_params &amp;lt;&amp;gt; 0
&lt;/span&gt;&lt;span class="gi"&gt;+    let num_call = List.length call_params in
+    let num_def = List.length def_params in
+    let num_required =
+      List.length (
+        List.filter (fun (_, default) -&amp;gt; Option.is_none default) def_params
+      )
+    in
+    if num_call &amp;lt; num_required || num_call &amp;gt; num_def
&lt;/span&gt;     then Error.error_at pos "wrong number of param(s)"
     else
&lt;span class="gd"&gt;-      let bindings =
-        List.mapi
-          (fun index value -&amp;gt;
-            let param_name = List.nth def_params index in
-            (param_name, value)
&lt;/span&gt;&lt;span class="gi"&gt;+      let* bindings =
+        List.fold_left
+          (fun acc (index, (param_name, default)) -&amp;gt;
+            let* bindings = acc in
+            if index &amp;lt; num_call
+            then
+              ok (bindings @ [(param_name, List.nth call_params index)])
+            else
+              match default with
+              | Some default_expr -&amp;gt; ok (bindings @ [(param_name, default_expr)])
+              | None -&amp;gt; Error.error_at pos "wrong number of param(s)"
&lt;/span&gt;           )
&lt;span class="gd"&gt;-          call_params
&lt;/span&gt;&lt;span class="gi"&gt;+          (ok [])
+          (List.mapi (fun i p -&amp;gt; (i, p)) def_params)
&lt;/span&gt;       in
       let env' = List.fold_left
         (fun env (k, v) -&amp;gt; Env.add_local k v env)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key change: instead of checking for an exact match, we now compute both the total number of defined parameters (&lt;code&gt;num_def&lt;/code&gt;) and the number of required ones (&lt;code&gt;num_required&lt;/code&gt;). The call is valid if the number of supplied arguments falls anywhere in the range &lt;code&gt;[num_required, num_def]&lt;/code&gt;. For arguments not supplied by the caller, we fall back to the default expression.&lt;/p&gt;

&lt;h2&gt;
  
  
  Knowing the type before the call
&lt;/h2&gt;

&lt;p&gt;Default arguments give the type checker a nice bonus: when a parameter has a default value, we can infer its type right at declaration time instead of waiting for the first 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/type.ml b/lib/type.ml
index bdbf357..05bc187 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;@@ -475,9 +475,21 @@&lt;/span&gt; and translate_bin_op venv pos op e1 e2 =
   | _ -&amp;gt; Error.error_at pos Error.Msg.invalid_binary_op
&lt;span class="err"&gt;
&lt;/span&gt; and translate_function_def venv (pos, (fun_name, params, body)) =
&lt;span class="gd"&gt;-  (* As of now, we don't know the input types at declaration *)
-  let params_typed = List.map (fun name -&amp;gt; (name, Tunresolved)) params in
-  (* We also don't know the result type *)
&lt;/span&gt;&lt;span class="gi"&gt;+  (* For params with defaults, we can infer the type from the default expression;
+     params without defaults remain Tunresolved until the first call *)
+  let* params_typed = List.fold_left
+    (fun acc (name, default) -&amp;gt;
+      let* params' = acc in
+      match default with
+      | Some default_expr -&amp;gt;
+        let* (_, default_ty) = translate venv default_expr in
+        ok (params' @ [(name, default_ty)])
+      | None -&amp;gt;
+        ok (params' @ [(name, Tunresolved)])
+    )
+    (ok [])
+    params
+  in
&lt;/span&gt;   let fun_def = TfunctionDef (params_typed, body, Tunresolved) in
   (* So, function declaration will have an unresolved type definition,
      that only later it will be translated: before function call translation!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Parameters without defaults stay &lt;code&gt;Tunresolved&lt;/code&gt; and get resolved at the call site, same as before. Parameters with defaults get their type resolved immediately. In our example, &lt;code&gt;y=10&lt;/code&gt; means &lt;code&gt;y&lt;/code&gt; is typed as &lt;code&gt;Number&lt;/code&gt; from the moment the function is declared.&lt;/p&gt;

&lt;p&gt;The arity check at the call site follows the same logic as the interpreter — allow fewer arguments than the total, as long as the mandatory ones are all there:&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;@@ -490,35 +502,39 @@&lt;/span&gt; and translate_function_call venv (pos, fname, call_params) =
   (* 1. retrieve TfunctionDef from venv *)
   match Env.find_opt fname venv with
   | Some (TfunctionDef (def_params, body_expr, return_type)) -&amp;gt;
&lt;span class="gd"&gt;-    (* check arity *)
-    if List.compare_lengths call_params def_params &amp;lt;&amp;gt; 0
&lt;/span&gt;&lt;span class="gi"&gt;+    (* check arity: allow fewer args if defaults exist *)
+    let num_call = List.length call_params in
+    let num_def = List.length def_params in
+    if num_call &amp;gt; num_def
&lt;/span&gt;     then
       Error.error_at pos
&lt;span class="gd"&gt;-        (Error.Msg.type_wrong_number_of_params
-          (List.length def_params) (List.length call_params))
&lt;/span&gt;&lt;span class="gi"&gt;+        (Error.Msg.type_wrong_number_of_params num_def num_call)
&lt;/span&gt;     else
       (* 2. type check each positional parameter passed in the function call *)
       let* (venv', resolved_params) =
&lt;span class="gd"&gt;-        List.fold_left2
-          (fun acc call_param (param_name, def_param_type) -&amp;gt;
&lt;/span&gt;&lt;span class="gi"&gt;+        List.fold_left
+          (fun acc (index, (param_name, def_param_type)) -&amp;gt;
&lt;/span&gt;             let* (venv', params') = acc in
&lt;span class="gd"&gt;-            let* (venv'', call_param_type) = translate venv' call_param in
-            match def_param_type with
-            | Tunresolved -&amp;gt;
-              (* 2a. unresolved: accept and record the concrete type *)
-              ok (venv'', params' @ [(param_name, call_param_type)])
-            | expected -&amp;gt;
-              (* 2b. resolved: type check against the concrete type *)
-              if call_param_type = expected
-              then ok (venv'', params' @ [(param_name, expected)])
-              else Error.error_at pos
-                (Error.Msg.type_mismatch
-                  ~expected:(to_string expected)
-                  ~got:(to_string call_param_type))
&lt;/span&gt;&lt;span class="gi"&gt;+            if index &amp;lt; num_call
+            then
+              let call_param = List.nth call_params index in
+              let* (venv'', call_param_type) = translate venv' call_param in
+              match def_param_type with
+              | Tunresolved -&amp;gt;
+                ok (venv'', params' @ [(param_name, call_param_type)])
+              | expected -&amp;gt;
+                if call_param_type = expected
+                then ok (venv'', params' @ [(param_name, expected)])
+                else Error.error_at pos
+                  (Error.Msg.type_mismatch
+                    ~expected:(to_string expected)
+                    ~got:(to_string call_param_type))
+            else
+              (* default arg — resolve type from the original AST default expression *)
+              ok (venv', params' @ [(param_name, def_param_type)])
&lt;/span&gt;           )
           (ok (venv, []))
&lt;span class="gd"&gt;-          call_params
-          def_params
&lt;/span&gt;&lt;span class="gi"&gt;+          (List.mapi (fun i p -&amp;gt; (i, p)) def_params)
&lt;/span&gt;       in
       (* 3. type check return *)
       let body_venv = List.fold_left
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the caller omits an argument that has a default, we skip the type-checking step for that parameter and carry its already-resolved type forward. No extra work needed — the type was inferred when the function was declared.&lt;/p&gt;

&lt;h2&gt;
  
  
  The moment of truth
&lt;/h2&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
index 0ad7e43..fa29868 100644
&lt;/span&gt;&lt;span class="gd"&gt;--- a/test/cram/functions.t
&lt;/span&gt;&lt;span class="gi"&gt;+++ b/test/cram/functions.t
&lt;/span&gt;&lt;span class="p"&gt;@@ -3,3 +3,6 @@&lt;/span&gt;
&lt;span class="err"&gt;
&lt;/span&gt;   $ tsonnet ../../samples/functions/multiline.jsonnet
   [ 6, 7 ]
&lt;span class="gi"&gt;+
+  $ tsonnet ../../samples/functions/default_args.jsonnet
+  12
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;my_function(2)&lt;/code&gt; with &lt;code&gt;y=10&lt;/code&gt; gives &lt;code&gt;12&lt;/code&gt;. Working as expected.&lt;/p&gt;

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

&lt;p&gt;Default arguments are in. With a surprisingly contained change across parser, AST, interpreter, and type checker, &lt;code&gt;my_function(x, y=10)&lt;/code&gt; now works as you'd expect — and the type checker even gets to resolve &lt;code&gt;y&lt;/code&gt;'s type at declaration time rather than waiting for the first call.&lt;/p&gt;

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

&lt;p&gt;Next up, functions get a serious upgrade: closures.&lt;/p&gt;




&lt;p&gt;Thanks for reading Bit Maybe Wise! Default arguments are optional. Subscribing isn't — but I'd never force you. &lt;a href="https://bitmaybewise.com/" rel="noopener noreferrer"&gt;Subscribe&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@comparefibre" rel="noopener noreferrer"&gt;Compare Fibre&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 #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://dev.to/bitmaybewise/tsonnet-35-call-me-maybe-but-make-it-typed-part-1-58i2</link>
      <guid>https://dev.to/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://dev.to/bitmaybewise/abend-dump-26-47eh</link>
      <guid>https://dev.to/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://dev.to/bitmaybewise/tsonnet-34-dabbling-with-untouched-bindings-i5l</link>
      <guid>https://dev.to/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://dev.to/bitmaybewise/tsonnet-33-the-arithmetic-tutorial-must-go-on-1694</link>
      <guid>https://dev.to/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://dev.to/bitmaybewise/tsonnet-32-done-but-getting-there-4h7m</link>
      <guid>https://dev.to/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://dev.to/bitmaybewise/tsonnet-31-taking-back-control-of-equality-5gn6</link>
      <guid>https://dev.to/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://dev.to/bitmaybewise/abend-dump-25-5640</link>
      <guid>https://dev.to/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://dev.to/bitmaybewise/abend-dump-24-45oc</link>
      <guid>https://dev.to/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://dev.to/bitmaybewise/tsonnet-29-making-inner-references-work-502o</link>
      <guid>https://dev.to/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>
  </channel>
</rss>
