<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Roman Melnikov</title>
    <description>The latest articles on Forem by Roman Melnikov (@neftedollar).</description>
    <link>https://forem.com/neftedollar</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%2F538476%2Ffb34583a-6247-43f4-bdfe-30b66dc7ccf3.jpeg</url>
      <title>Forem: Roman Melnikov</title>
      <link>https://forem.com/neftedollar</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/neftedollar"/>
    <language>en</language>
    <item>
      <title>The 2,600-Line Compiler That Compiles Itself and Emits F#, TypeScript, Python, Java, and C#</title>
      <dc:creator>Roman Melnikov</dc:creator>
      <pubDate>Mon, 18 May 2026 09:34:34 +0000</pubDate>
      <link>https://forem.com/neftedollar/the-2600-line-compiler-that-compiles-itself-and-emits-f-typescript-python-java-and-c-49lh</link>
      <guid>https://forem.com/neftedollar/the-2600-line-compiler-that-compiles-itself-and-emits-f-typescript-python-java-and-c-49lh</guid>
      <description>&lt;h1&gt;
  
  
  The 2,600-Line Compiler That Compiles Itself and Emits F#, TypeScript, Python, Java, and C
&lt;/h1&gt;

&lt;p&gt;&lt;code&gt;ll-lang&lt;/code&gt; started as a language for LLM code generation. The more interesting update now is not the syntax pitch. It is the compiler story: the compiler is written in &lt;code&gt;ll-lang&lt;/code&gt; itself, the bootstrap reached a fixpoint, and the same source can emit several host targets.&lt;/p&gt;

&lt;p&gt;That gives the project two concrete proofs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;compiler₁.fs == compiler₂.fs&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;one source language can emit F#, TypeScript, Python, Java, C#, and an experimental LLVM backend&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That combination moves &lt;code&gt;ll-lang&lt;/code&gt; out of the "interesting syntax demo" category.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the self-hosting milestone matters
&lt;/h2&gt;

&lt;p&gt;A lot of language projects can parse a toy file. Fewer can move their own compiler into the language they are building.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;ll-lang&lt;/code&gt; repo now documents bootstrap as complete. The canonical compiler lives in the self-hosted path, while archived stage0 code stays under &lt;code&gt;obsolete/stage0&lt;/code&gt; only for recovery diagnostics. The README makes the claim explicit: &lt;code&gt;compiler₁.fs == compiler₂.fs&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That fixpoint matters because it means the compiler output stabilizes under its own pipeline. It is not just "the compiler can probably compile itself." It is "the compiler compiles itself, and the emitted artifact stops changing."&lt;/p&gt;

&lt;p&gt;For a project aimed at LLM workflows, that matters even more. The promise is not just smaller syntax. The promise is a closed, testable toolchain.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bootstrap path
&lt;/h2&gt;

&lt;p&gt;The documented bootstrap path is intentionally simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Neftedollar/ll-lang.git
&lt;span class="nb"&gt;cd &lt;/span&gt;ll-lang
./tools/bootstrap-self.sh &lt;span class="nb"&gt;install
&lt;/span&gt;&lt;span class="nv"&gt;BOOTSTRAP_BIN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;./tools/bootstrap-self.sh path&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BOOTSTRAP_BIN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; check &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PWD&lt;/span&gt;&lt;span class="s2"&gt;/lllcself/src/Main.lll"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pinned artifact is verified against &lt;code&gt;bootstrap/lllc-bootstrap.lock.json&lt;/code&gt;, so bootstrap does not depend on a lucky local setup. On success the compiler reports &lt;code&gt;OK&lt;/code&gt; and exits 0.&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%2Fxevzmo9h0igx3kno7fuk.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%2Fxevzmo9h0igx3kno7fuk.gif" alt="Full self-host cycle: bootstrap install, multi-target compile, and  raw `lllc self check` endraw  on the compiler source" width="800" height="514"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There is also a strict launcher:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./tools/lllc-bootstrap.sh check &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PWD&lt;/span&gt;&lt;span class="s2"&gt;/lllcself/src/Main.lll"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That wrapper runs the pinned bootstrap path directly instead of silently falling back to older bridge behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  The multi-target path
&lt;/h2&gt;

&lt;p&gt;Self-hosting is only half the story. A compiler that only proves it can emit itself is interesting to compiler people. A compiler that can also target multiple ecosystems is easier for application teams to try.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ll-lang&lt;/code&gt; exposes that through the same CLI surface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lllc build &lt;span class="nt"&gt;--target&lt;/span&gt; fs   shapes.lll
lllc build &lt;span class="nt"&gt;--target&lt;/span&gt; ts   shapes.lll
lllc build &lt;span class="nt"&gt;--target&lt;/span&gt; py   shapes.lll
lllc build &lt;span class="nt"&gt;--target&lt;/span&gt; java shapes.lll
lllc build &lt;span class="nt"&gt;--target&lt;/span&gt; cs   shapes.lll
lllc build &lt;span class="nt"&gt;--target&lt;/span&gt; llvm shapes.lll
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The repository tutorial shows the same algebraic data type mapped into several host languages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;F# discriminated unions&lt;/li&gt;
&lt;li&gt;TypeScript tagged unions&lt;/li&gt;
&lt;li&gt;Python &lt;code&gt;@dataclass&lt;/code&gt; + pattern matching&lt;/li&gt;
&lt;li&gt;Java 21 sealed interfaces + records&lt;/li&gt;
&lt;li&gt;C# records&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is the practical angle. The source language stays compact for prompt-driven authoring, while the output lands in ecosystems teams already run in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  One source, multiple outputs
&lt;/h2&gt;

&lt;p&gt;The multi-target tutorial uses a simple source shape:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module Shapes

Shape = Circle Float | Rect Float Float | Empty

area(s Shape) Float =
  | Circle r  -&amp;gt; 3.14159 * r * r
  | Rect w h  -&amp;gt; w * h
  | Empty     -&amp;gt; 0.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From there, &lt;code&gt;ll-lang&lt;/code&gt; emits target-specific code instead of flattening everything to one runtime model. That is what good cross-target compilers do: preserve the source idea while producing code that still looks native enough for the host ecosystem.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would try first
&lt;/h2&gt;

&lt;p&gt;If you want the fastest way to evaluate the project:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Read the README bootstrap section.&lt;/li&gt;
&lt;li&gt;Open &lt;code&gt;lllcself/src/Main.lll&lt;/code&gt; to see that the compiler entrypoint is in &lt;code&gt;ll-lang&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Read &lt;code&gt;docs/tutorials/04-multi-target.md&lt;/code&gt; and compare the emitted TypeScript, Python, and Java shapes.&lt;/li&gt;
&lt;li&gt;Run the bootstrap installer on a clean machine.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That sequence shows both halves of the project without requiring a deep compiler-internals read first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters for LLM workflows
&lt;/h2&gt;

&lt;p&gt;The original thesis behind &lt;code&gt;ll-lang&lt;/code&gt; is that a smaller, statically checked syntax reduces noise in prompts and makes diagnostics machine-readable. The built-in MCP server ships with the toolchain for agent use.&lt;/p&gt;

&lt;p&gt;What changed is that the implementation now backs up the thesis. The compiler is self-hosting. The stdlib is self-hosted. The project system is real. The codegen path is not locked to one runtime.&lt;/p&gt;

&lt;p&gt;That is a stronger story than "language for LLMs." It is a claim with compiler evidence behind it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the line count actually is
&lt;/h2&gt;

&lt;p&gt;The self-hosted compiler CLI under &lt;code&gt;lllcself/src/&lt;/code&gt; is 2589 lines today. So "about 2600 lines" is the accurate version of the headline, even if "under 3000 lines" is the broader takeaway.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this milestone does not mean
&lt;/h2&gt;

&lt;p&gt;It does not mean every part of the stack is finished forever. The repo is explicit that &lt;code&gt;llvm&lt;/code&gt; is still experimental, and that some self-host routing work is still documented as controlled rollout. That is a positive signal, not a weakness. Compiler projects get more trustworthy when they mark their edges clearly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;p&gt;The easy headline for &lt;code&gt;ll-lang&lt;/code&gt; would be "a typed language for LLMs."&lt;/p&gt;

&lt;p&gt;The better headline now is narrower and more technical:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;ll-lang&lt;/code&gt; reached a self-hosting bootstrap fixpoint, and the same language can already emit multiple mainstream targets.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is the kind of milestone that turns a positioning story into an implementation story.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/Neftedollar/ll-lang" rel="noopener noreferrer"&gt;https://github.com/Neftedollar/ll-lang&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Landing page: &lt;a href="https://neftedollar.com/ll-lang/" rel="noopener noreferrer"&gt;https://neftedollar.com/ll-lang/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Earlier intro post: &lt;a href="https://dev.to/neftedollar/why-we-built-ll-lang-a-statically-typed-functional-language-for-llms-2hg8"&gt;https://dev.to/neftedollar/why-we-built-ll-lang-a-statically-typed-functional-language-for-llms-2hg8&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>compilers</category>
      <category>fsharp</category>
      <category>typescript</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Wire ll-lang into Claude Code, Cursor, or Zed in 30 Seconds</title>
      <dc:creator>Roman Melnikov</dc:creator>
      <pubDate>Sun, 26 Apr 2026 21:34:45 +0000</pubDate>
      <link>https://forem.com/neftedollar/wire-ll-lang-into-claude-code-cursor-or-zed-in-30-seconds-51c6</link>
      <guid>https://forem.com/neftedollar/wire-ll-lang-into-claude-code-cursor-or-zed-in-30-seconds-51c6</guid>
      <description>&lt;h1&gt;
  
  
  Wire ll-lang into Claude Code, Cursor, or Zed in 30 Seconds
&lt;/h1&gt;

&lt;p&gt;If you want an LLM to write ll-lang productively, the best setup is not "open a terminal and hope the model can parse shell output." The better setup is MCP.&lt;/p&gt;

&lt;p&gt;ll-lang ships with a built-in MCP server through &lt;code&gt;lllc mcp&lt;/code&gt;, so your editor can call the compiler and project tooling as structured tools.&lt;/p&gt;

&lt;p&gt;That means your agent can ask questions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;does this compile?&lt;/li&gt;
&lt;li&gt;what does &lt;code&gt;E005&lt;/code&gt; mean?&lt;/li&gt;
&lt;li&gt;where is this symbol defined?&lt;/li&gt;
&lt;li&gt;what targets can I build to?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And get structured JSON back instead of scraped terminal text.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Add the MCP server
&lt;/h2&gt;

&lt;p&gt;Use the current config shape from the ll-lang README and MCP user guide:&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;"mcpServers"&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="nl"&gt;"ll-lang"&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="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"lllc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&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="s2"&gt;"mcp"&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="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 &lt;code&gt;lllc&lt;/code&gt; is not on your &lt;code&gt;$PATH&lt;/code&gt;, replace &lt;code&gt;command&lt;/code&gt; with the absolute path to the binary. In a local repo checkout, you can also point to the bootstrap wrapper if that is your preferred install path.&lt;/p&gt;

&lt;p&gt;Typical locations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Claude Code: &lt;code&gt;~/.config/claude/mcp.json&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Cursor: project &lt;code&gt;.cursor/mcp.json&lt;/code&gt; or equivalent MCP settings path&lt;/li&gt;
&lt;li&gt;Zed: your MCP server settings file&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important part is the command itself: &lt;code&gt;lllc mcp&lt;/code&gt;.&lt;br&gt;
The MCP server name is up to you. The README currently shows &lt;code&gt;lllc&lt;/code&gt;; the MCP guide shows &lt;code&gt;ll-lang&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. Restart the client and confirm the server appears
&lt;/h2&gt;

&lt;p&gt;Once the client reloads MCP servers, &lt;code&gt;ll-lang&lt;/code&gt; should show up as an available tool provider.&lt;/p&gt;

&lt;p&gt;At that point your agent can call ll-lang directly instead of inferring behavior from shell commands.&lt;/p&gt;
&lt;h2&gt;
  
  
  3. What tools you get
&lt;/h2&gt;

&lt;p&gt;The current README and &lt;code&gt;docs/user-guide/09-mcp.md&lt;/code&gt; document 30 tools. They cover the workflow you actually want in an AI coding loop:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Core compile/check: &lt;code&gt;compile_source&lt;/code&gt;, &lt;code&gt;check_source&lt;/code&gt;, &lt;code&gt;compile_file&lt;/code&gt;, &lt;code&gt;check_file&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Diagnostics and repair: &lt;code&gt;diagnose_source&lt;/code&gt;, &lt;code&gt;diagnose_file&lt;/code&gt;, &lt;code&gt;explain_error&lt;/code&gt;, &lt;code&gt;fix_suggest&lt;/code&gt;, &lt;code&gt;apply_fix_preview&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Formatting and AST inspection: &lt;code&gt;format_source&lt;/code&gt;, &lt;code&gt;format_file&lt;/code&gt;, &lt;code&gt;parse_source&lt;/code&gt;, &lt;code&gt;typed_ast&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Project-level operations: &lt;code&gt;project_graph&lt;/code&gt;, &lt;code&gt;check_project&lt;/code&gt;, &lt;code&gt;build_project&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Symbol navigation: &lt;code&gt;symbols&lt;/code&gt;, &lt;code&gt;definition&lt;/code&gt;, &lt;code&gt;references&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Dependency helpers: &lt;code&gt;mod_add&lt;/code&gt;, &lt;code&gt;mod_tidy&lt;/code&gt;, &lt;code&gt;mod_why&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;FFI helpers: &lt;code&gt;ffi_inspect&lt;/code&gt;, &lt;code&gt;ffi_validate&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Test helpers: &lt;code&gt;test_list&lt;/code&gt;, &lt;code&gt;test_run&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Catalog/meta: &lt;code&gt;stdlib_search&lt;/code&gt;, &lt;code&gt;list_errors&lt;/code&gt;, &lt;code&gt;lookup_error&lt;/code&gt;, &lt;code&gt;list_targets&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is enough for a serious inner loop. The model does not just "write code"; it can inspect the project, ask for diagnostics, search the stdlib, and repair errors with structured feedback.&lt;/p&gt;
&lt;h2&gt;
  
  
  4. The shortest useful workflow
&lt;/h2&gt;

&lt;p&gt;Once MCP is wired in, the productive loop is simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Ask the model to draft a small ll-lang module.&lt;/li&gt;
&lt;li&gt;Call &lt;code&gt;check_source&lt;/code&gt; or &lt;code&gt;check_file&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If an error comes back, call &lt;code&gt;lookup_error&lt;/code&gt; or &lt;code&gt;explain_error&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Apply the fix and re-run &lt;code&gt;check_source&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;When clean, use &lt;code&gt;build_project&lt;/code&gt; or &lt;code&gt;compile_file&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That loop is much tighter than:&lt;/p&gt;

&lt;p&gt;write -&amp;gt; run shell command -&amp;gt; inspect mixed stdout/stderr -&amp;gt; guess what failed&lt;/p&gt;
&lt;h2&gt;
  
  
  5. Why this setup matters
&lt;/h2&gt;

&lt;p&gt;ll-lang is designed for LLM code generation, so the compiler output is part of the authoring experience, not just a final gate.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;E005 TagViolation&lt;/code&gt; tells the agent it passed an untagged value where a tagged value was required.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;E004 UnitMismatch&lt;/code&gt; tells it incompatible units met in arithmetic.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;lookup_error&lt;/code&gt; can turn an error code into a short explanation without making the model search docs manually.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because the protocol is structured, the model can route on exact fields instead of trying to interpret prose or terminal formatting.&lt;/p&gt;
&lt;h2&gt;
  
  
  6. Minimal prompt to test it
&lt;/h2&gt;

&lt;p&gt;After MCP is connected, try this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Create a small ll-lang module with &lt;code&gt;tag UserId&lt;/code&gt;, a function that accepts &lt;code&gt;Str[UserId]&lt;/code&gt;, and then check whether it compiles. If it fails, fix it using the ll-lang MCP tools.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If the wiring is correct, the model should use ll-lang tools directly rather than defaulting to shell output parsing.&lt;/p&gt;
&lt;h2&gt;
  
  
  7. Install and repo links
&lt;/h2&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/Neftedollar/ll-lang" rel="noopener noreferrer"&gt;https://github.com/Neftedollar/ll-lang&lt;/a&gt;&lt;br&gt;
Landing page: &lt;a href="https://neftedollar.com/ll-lang/" rel="noopener noreferrer"&gt;https://neftedollar.com/ll-lang/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Bootstrap path from the README:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Neftedollar/ll-lang.git
&lt;span class="nb"&gt;cd &lt;/span&gt;ll-lang
&lt;span class="nv"&gt;LLLC_BOOTSTRAP_REINSTALL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 ./tools/check-selfhost-ci.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then keep the MCP config in place:&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;"mcpServers"&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="nl"&gt;"ll-lang"&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="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"lllc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&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="s2"&gt;"mcp"&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="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>ai</category>
      <category>productivity</category>
      <category>compilers</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Why We Built ll-lang, a Statically Typed Functional Language for LLMs</title>
      <dc:creator>Roman Melnikov</dc:creator>
      <pubDate>Sun, 26 Apr 2026 21:34:34 +0000</pubDate>
      <link>https://forem.com/neftedollar/why-we-built-ll-lang-a-statically-typed-functional-language-for-llms-2hg8</link>
      <guid>https://forem.com/neftedollar/why-we-built-ll-lang-a-statically-typed-functional-language-for-llms-2hg8</guid>
      <description>&lt;h1&gt;
  
  
  Why We Built a Statically Typed Functional Language for LLMs to Write
&lt;/h1&gt;

&lt;p&gt;ll-lang is a language for one narrow, practical job: helping LLMs generate correct code faster by spending fewer tokens on syntax and getting compile-time feedback instead of runtime surprises.&lt;/p&gt;

&lt;p&gt;The problem with "AI coding" is not that models cannot produce code. They clearly can. The problem is that most mainstream languages give them the wrong feedback loop.&lt;/p&gt;

&lt;p&gt;When an LLM writes Python, TypeScript, or Java, two things usually happen at the same time.&lt;/p&gt;

&lt;p&gt;First, the model burns a lot of context on syntax that does not carry much logic. Braces, semicolons, class wrappers, repeated keywords, interface boilerplate, and ceremony-heavy declarations all consume tokens. That matters when your real bottleneck is context budget.&lt;/p&gt;

&lt;p&gt;Second, many important mistakes surface too late. A model can generate a file that looks plausible, passes a quick glance, and still fails only after execution. The signal arrives as a runtime error, a stack trace, or an application-side failure. By then, the model has already spent tokens on the wrong path.&lt;/p&gt;

&lt;p&gt;That is an expensive loop:&lt;/p&gt;

&lt;p&gt;write -&amp;gt; run -&amp;gt; inspect prose error -&amp;gt; regenerate -&amp;gt; run again&lt;/p&gt;

&lt;p&gt;We built ll-lang to change that loop.&lt;/p&gt;

&lt;p&gt;ll-lang is a statically typed functional language designed for LLM code generation. The design goal is narrow on purpose: make it easier for a model to write code that compiles, gets strong feedback quickly, and can still target normal downstream ecosystems like F#, TypeScript, Python, Java, and C#.&lt;/p&gt;

&lt;p&gt;This is not a "replace every language" project. It is a tooling and authoring language for a specific constraint: an LLM agent writing code under token pressure.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four design principles
&lt;/h2&gt;

&lt;p&gt;The current README boils ll-lang down to four principles. They are worth unpacking because together they define the product.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Token-efficient syntax
&lt;/h3&gt;

&lt;p&gt;We wanted a language that spends more tokens on logic and fewer on ceremony.&lt;/p&gt;

&lt;p&gt;ll-lang keeps the syntax compact:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no braces&lt;/li&gt;
&lt;li&gt;no semicolons&lt;/li&gt;
&lt;li&gt;no &lt;code&gt;fn&lt;/code&gt;, &lt;code&gt;type&lt;/code&gt;, &lt;code&gt;in&lt;/code&gt;, &lt;code&gt;then&lt;/code&gt;, or &lt;code&gt;with&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;only 15 keywords&lt;/li&gt;
&lt;li&gt;declarations use an uppercase/lowercase convention instead of extra syntax&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That matters more than it looks on paper. Every redundant token competes with actual reasoning. If an LLM has to generate an algebraic data type, a few helpers, and a pattern match, the difference between a compact representation and a verbose one compounds quickly.&lt;/p&gt;

&lt;p&gt;The project README reports ll-lang as 8 to 17 percent more compact than F# on real code, and significantly more compact than TypeScript, Python, and Java on type-heavy definitions. That is not an aesthetic choice. It is directly about how much useful logic you can fit inside the same context window.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Static types with inference
&lt;/h3&gt;

&lt;p&gt;A language for LLMs cannot force the model to annotate every line. That just reintroduces verbosity through another door.&lt;/p&gt;

&lt;p&gt;So ll-lang uses Hindley-Milner type inference. You keep the guarantees of a static type system, but you only write annotations where they actually clarify boundaries. The compiler carries the rest.&lt;/p&gt;

&lt;p&gt;This gives you a useful middle ground:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;enough structure for compile-time guarantees&lt;/li&gt;
&lt;li&gt;less annotation overhead for the model&lt;/li&gt;
&lt;li&gt;fewer chances to drift between a declaration and an implementation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For an agent, that is a better authoring environment than either extreme. Fully dynamic code catches mistakes too late. Fully annotation-heavy code spends too much of the budget describing obvious facts.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Compiled equals works
&lt;/h3&gt;

&lt;p&gt;This is the most important principle in the project.&lt;/p&gt;

&lt;p&gt;ll-lang is designed so the compiler catches the classes of mistakes that LLMs make all the time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;type mismatches&lt;/li&gt;
&lt;li&gt;unbound variables&lt;/li&gt;
&lt;li&gt;non-exhaustive matches&lt;/li&gt;
&lt;li&gt;tag violations&lt;/li&gt;
&lt;li&gt;unit mismatches&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In other words, the fast feedback signal is compile-time, not runtime.&lt;/p&gt;

&lt;p&gt;That is a major shift for agent workflows. Instead of asking the model to mentally simulate a whole program and then parse a runtime failure, you can keep it on a tighter loop:&lt;/p&gt;

&lt;p&gt;write -&amp;gt; check -&amp;gt; fix one precise error code -&amp;gt; continue&lt;/p&gt;

&lt;p&gt;That is cheaper, faster, and much easier to automate.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. LLM-readable errors
&lt;/h3&gt;

&lt;p&gt;Even a strong type system is less useful if the diagnostics are written as long human prose that an agent has to scrape.&lt;/p&gt;

&lt;p&gt;ll-lang errors are intentionally compact and machine-readable:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;EXXX line:col ErrorKind details&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Examples from the README include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;E001 12:5 TypeMismatch Str Str[UserId]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;E003 15:1 NonExhaustiveMatch Shape missing:Empty&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;E004 20:9 UnitMismatch Float[m] Float[s]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;E005 7:14 TagViolation Str[Email] Str[UserId]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This matters because an agent can route on the code first and the text second. It does not need a fragile natural-language parser to understand what went wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  A worked example: catching a real bug before runtime
&lt;/h2&gt;

&lt;p&gt;Here is the kind of bug that shows up constantly in AI-generated code: a model mixes up raw strings, tagged identifiers, and measurements that should not compose.&lt;/p&gt;

&lt;p&gt;In a dynamic language, that often survives until the application runs. In ll-lang, it gets rejected immediately.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module ResetFlow

tag UserId
tag Email
tag m
tag s

lookupEmail(id Str[UserId]) Str[Email] = "alice@example.com"[Email]
sendReset(to Str[Email]) = to

bad(rawId Str)(distance Float[m])(elapsed Float[s]) =
  email = lookupEmail rawId
  total = distance + elapsed
  sendReset rawId
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are three distinct mistakes here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;lookupEmail&lt;/code&gt; expects &lt;code&gt;Str[UserId]&lt;/code&gt;, but &lt;code&gt;rawId&lt;/code&gt; is only &lt;code&gt;Str&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;distance + elapsed&lt;/code&gt; tries to add meters and seconds.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sendReset&lt;/code&gt; expects &lt;code&gt;Str[Email]&lt;/code&gt;, but receives a raw string.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Those are not edge cases. They are the exact kind of mistakes that happen when a model is juggling several concepts at once and loses one semantic detail at a callsite.&lt;/p&gt;

&lt;p&gt;With ll-lang, the compiler catches them as first-order feedback. The error stream is not an afterthought. It is the product.&lt;/p&gt;

&lt;p&gt;You get precise signals like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;E005 TagViolation&lt;/code&gt; for passing an untagged string where &lt;code&gt;Str[UserId]&lt;/code&gt; or &lt;code&gt;Str[Email]&lt;/code&gt; is required&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;E004 UnitMismatch&lt;/code&gt; for combining values that do not share compatible units&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The fix is explicit and local:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module ResetFlow

tag UserId
tag Email
tag m
tag s

lookupEmail(id Str[UserId]) Str[Email] = "alice@example.com"[Email]
sendReset(to Str[Email]) = to
speed(distance Float[m])(elapsed Float[s]) = distance / elapsed

good(rawId Str)(distance Float[m])(elapsed Float[s]) =
  userId = rawId[UserId]
  email = lookupEmail userId
  rate = speed distance elapsed
  sendReset email
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That difference is exactly why "compiled equals works" is not just a slogan. It is the basis for a better agent loop.&lt;/p&gt;

&lt;p&gt;If the model is going to make mistakes, which it will, we want those mistakes to collapse into compact compiler diagnostics instead of delayed runtime behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters specifically for LLMs
&lt;/h2&gt;

&lt;p&gt;Humans can often compensate for a weak signal. They read between the lines, trace a stack, remember a convention from elsewhere in the codebase, and infer what the system probably meant.&lt;/p&gt;

&lt;p&gt;LLMs are different. They are much better when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the syntax is regular&lt;/li&gt;
&lt;li&gt;the error shape is stable&lt;/li&gt;
&lt;li&gt;the repair target is local&lt;/li&gt;
&lt;li&gt;the feedback arrives before execution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ll-lang leans into that reality instead of pretending models code the same way humans do.&lt;/p&gt;

&lt;p&gt;A good LLM authoring language should make the happy path easy, but it should also make the failure path legible. That is where ll-lang spends most of its design budget.&lt;/p&gt;

&lt;h2&gt;
  
  
  Self-hosting is proof, not branding
&lt;/h2&gt;

&lt;p&gt;A lot of language projects make ambitious claims early. We wanted something harder to fake.&lt;/p&gt;

&lt;p&gt;ll-lang is self-hosting. The compiler pipeline is implemented in ll-lang, including the lexer, parser, elaborator, type inference, code generation, module system, and MCP server. The bootstrap fixpoint in the README is a strong statement of maturity:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;compiler1.fs == compiler2.fs&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;That matters because it proves a few things at once:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the language can handle real compiler work, not just toy examples&lt;/li&gt;
&lt;li&gt;the stdlib and module system are usable at meaningful scale&lt;/li&gt;
&lt;li&gt;changes that break core semantics show up inside the language's own build loop&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For DevRel, self-hosting also changes the story from "interesting experiment" to "working system with operational evidence."&lt;/p&gt;

&lt;h2&gt;
  
  
  The MCP layer turns the compiler into an agent tool
&lt;/h2&gt;

&lt;p&gt;The language alone is only half the story. The other half is how an agent interacts with it.&lt;/p&gt;

&lt;p&gt;ll-lang ships with an MCP server through &lt;code&gt;lllc mcp&lt;/code&gt;. That means Claude Code, Cursor, Zed, and other MCP-capable clients can call compiler functionality as structured tools instead of shelling out and scraping terminal output.&lt;/p&gt;

&lt;p&gt;The current README and user guide document 30 tools across:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;compile and check flows&lt;/li&gt;
&lt;li&gt;diagnostics and repair helpers&lt;/li&gt;
&lt;li&gt;formatting and AST inspection&lt;/li&gt;
&lt;li&gt;project graph and build operations&lt;/li&gt;
&lt;li&gt;symbol navigation&lt;/li&gt;
&lt;li&gt;dependency helpers&lt;/li&gt;
&lt;li&gt;test helpers&lt;/li&gt;
&lt;li&gt;FFI helpers&lt;/li&gt;
&lt;li&gt;catalog and metadata lookups&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is important because it lets the model stay in a structured loop:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;write source&lt;/li&gt;
&lt;li&gt;call &lt;code&gt;check_source&lt;/code&gt; or &lt;code&gt;check_file&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;inspect structured diagnostics&lt;/li&gt;
&lt;li&gt;call &lt;code&gt;lookup_error&lt;/code&gt; or repair-oriented tools&lt;/li&gt;
&lt;li&gt;retry&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is a much cleaner architecture than "ask the agent to guess what a shell command meant."&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not just use TypeScript or Python with better prompts?
&lt;/h2&gt;

&lt;p&gt;Prompting helps, but it does not solve the underlying signal problem.&lt;/p&gt;

&lt;p&gt;You can absolutely get useful code from mainstream languages. But for the specific case of LLM-authored logic, they still impose tradeoffs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;more syntax overhead&lt;/li&gt;
&lt;li&gt;weaker compile-time guarantees in common workflows&lt;/li&gt;
&lt;li&gt;noisier runtime failures&lt;/li&gt;
&lt;li&gt;error messages optimized for people, not agents&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ll-lang changes the default environment instead of asking the prompt to carry the entire burden.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where ll-lang fits
&lt;/h2&gt;

&lt;p&gt;ll-lang is a good fit when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an LLM agent is generating typed business logic&lt;/li&gt;
&lt;li&gt;compile-time correctness matters more than framework breadth&lt;/li&gt;
&lt;li&gt;the same source may need to target multiple runtimes&lt;/li&gt;
&lt;li&gt;token efficiency is a practical constraint&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is not trying to be the answer to every programming problem. It is a sharper tool for a narrower one.&lt;/p&gt;

&lt;p&gt;That is usually a good sign.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/Neftedollar/ll-lang" rel="noopener noreferrer"&gt;https://github.com/Neftedollar/ll-lang&lt;/a&gt;&lt;br&gt;
Landing page: &lt;a href="https://neftedollar.com/ll-lang/" rel="noopener noreferrer"&gt;https://neftedollar.com/ll-lang/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Bootstrap path from the current README:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Neftedollar/ll-lang.git
&lt;span class="nb"&gt;cd &lt;/span&gt;ll-lang
&lt;span class="nv"&gt;LLLC_BOOTSTRAP_REINSTALL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 ./tools/check-selfhost-ci.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want to see the agent story directly, wire the MCP server into your client:&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;"mcpServers"&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="nl"&gt;"ll-lang"&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="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"lllc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&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="s2"&gt;"mcp"&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="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 MCP server label is arbitrary. Some docs show &lt;code&gt;lllc&lt;/code&gt;, others &lt;code&gt;ll-lang&lt;/code&gt;. What matters is &lt;code&gt;command: "lllc"&lt;/code&gt; with &lt;code&gt;args: ["mcp"]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Then ask your editor or agent a simple question: "Does this compile?"&lt;/p&gt;

&lt;p&gt;That is the core bet behind ll-lang. Give the model a language with less ceremony, stronger guarantees, and better diagnostics, and the quality of the coding loop improves materially.&lt;/p&gt;

&lt;p&gt;For human-first languages, runtime is often where truth appears.&lt;/p&gt;

&lt;p&gt;For agent-first workflows, that is too late.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>ai</category>
      <category>compilers</category>
      <category>functional</category>
    </item>
    <item>
      <title>Type-Safe Cypher Queries in F# — Introducing Fyper</title>
      <dc:creator>Roman Melnikov</dc:creator>
      <pubDate>Fri, 03 Apr 2026 02:37:26 +0000</pubDate>
      <link>https://forem.com/neftedollar/type-safe-cypher-queries-in-f-introducing-fyper-1m81</link>
      <guid>https://forem.com/neftedollar/type-safe-cypher-queries-in-f-introducing-fyper-1m81</guid>
      <description>&lt;p&gt;Every F# developer who's worked with Neo4j knows the pain: raw Cypher strings, no IntelliSense, no compile-time checks, and the constant fear of typos in property names.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The old way — string-based, error-prone&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;cypher&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"MATCH (p:Preson) WHERE p.agee &amp;gt; 30 RETURN p"&lt;/span&gt;
&lt;span class="c1"&gt;// Two typos. You'll find out at runtime. Maybe in production.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I built &lt;a href="https://github.com/Neftedollar/fyper" rel="noopener noreferrer"&gt;Fyper&lt;/a&gt; to fix this. It's a type-safe Cypher query builder that uses F# computation expressions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;Person&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nc"&gt;Age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&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;query&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cypher&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;where&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Age&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// Generates: MATCH (p:Person) WHERE p.age &amp;gt; $p0 RETURN p&lt;/span&gt;
&lt;span class="c1"&gt;// Parameters: { p0: 30 }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Typo in &lt;code&gt;p.Agee&lt;/code&gt;? Compile error. Wrong type name? Compile error. Forgot to parameterize a value? Impossible — Fyper parameterizes everything by default.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;p&gt;Plain F# records are your schema. No attributes, no base classes, no code generation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;Person&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nc"&gt;Age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;Movie&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;Title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nc"&gt;Released&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;ActedIn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;Roles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="kt"&gt;list&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fyper conventions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Type name → node label (&lt;code&gt;Person&lt;/code&gt; → &lt;code&gt;:Person&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;PascalCase field → camelCase property (&lt;code&gt;FirstName&lt;/code&gt; → &lt;code&gt;firstName&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Relationship type → UPPER_SNAKE_CASE (&lt;code&gt;ActedIn&lt;/code&gt; → &lt;code&gt;ACTED_IN&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Relationships
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;findActors&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cypher&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Movie&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;matchRel&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="n"&gt;edge&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ActedIn&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;--&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;where&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Age&lt;/span&gt; &lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Released&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;2000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;orderBy&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Released&lt;/span&gt;
    &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// MATCH (p:Person) MATCH (m:Movie)&lt;/span&gt;
&lt;span class="c1"&gt;// MATCH (p)-[:ACTED_IN]-&amp;gt;(m)&lt;/span&gt;
&lt;span class="c1"&gt;// WHERE (p.age &amp;gt; $p0) AND (m.released &amp;gt;= $p1)&lt;/span&gt;
&lt;span class="c1"&gt;// ORDER BY m.released&lt;/span&gt;
&lt;span class="c1"&gt;// RETURN p.name, m.title&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Variable-length paths work too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="n"&gt;matchPath&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="n"&gt;edge&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Knows&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;--&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Between&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;5&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
&lt;span class="c1"&gt;// MATCH (p)-[:KNOWS*1..5]-&amp;gt;(q)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Mutations
&lt;/h2&gt;

&lt;p&gt;F# record update syntax for SET — only changed fields generate Cypher:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Birthday: increment age&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;birthday&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cypher&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;where&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Tom"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;set&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;p&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;Age&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Age&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// SET p.age = (p.age + $p0)&lt;/span&gt;

&lt;span class="c1"&gt;// MERGE with ON MATCH / ON CREATE&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;ensurePerson&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cypher&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;merge&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;Name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Tom"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nc"&gt;Age&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="n"&gt;onMatch&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;p&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;Age&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt; &lt;span class="o"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;onCreate&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;p&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;Age&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt; &lt;span class="o"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Multi-Backend: Same Query, Different Database
&lt;/h2&gt;

&lt;p&gt;Write once, run on Neo4j or Apache AGE (PostgreSQL):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Neo4j&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;neo4j&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Neo4jDriver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nn"&gt;GraphDatabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Driver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"bolt://localhost:7687"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nn"&gt;AuthTokens&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Basic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"neo4j"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"password"&lt;/span&gt;&lt;span class="o"&gt;)))&lt;/span&gt;

&lt;span class="c1"&gt;// Apache AGE (PostgreSQL)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AgeDriver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nn"&gt;NpgsqlDataSource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Host=localhost;Database=mydb;..."&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;graphName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"movies"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// Same query, different backend&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;people&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Cypher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;executeAsync&lt;/span&gt; &lt;span class="n"&gt;neo4j&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;people&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Cypher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;executeAsync&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each driver declares which Cypher features it supports. Unsupported features (like OPTIONAL MATCH on AGE) are rejected at query construction time — not at the database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Inspect Without Executing
&lt;/h2&gt;

&lt;p&gt;Debug queries without a database connection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;cypher&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="p"&gt;|&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Cypher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;toCypher&lt;/span&gt;
&lt;span class="n"&gt;printfn&lt;/span&gt; &lt;span class="s2"&gt;"%s"&lt;/span&gt; &lt;span class="n"&gt;cypher&lt;/span&gt;
&lt;span class="n"&gt;printfn&lt;/span&gt; &lt;span class="s2"&gt;"%A"&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Cypher Parser (Bonus)
&lt;/h2&gt;

&lt;p&gt;Fyper includes a zero-dependency Cypher parser. Parse any Cypher string into a typed AST:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;open&lt;/span&gt; &lt;span class="nn"&gt;Fyper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Parser&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;parsed&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;CypherParser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse&lt;/span&gt;
    &lt;span class="s2"&gt;"MATCH (p:Person)-[:ACTED_IN]-&amp;gt;(m:Movie) RETURN p.name"&lt;/span&gt;
&lt;span class="c1"&gt;// parsed.Clauses = [Match(RelPattern(...)); Return(...)]&lt;/span&gt;

&lt;span class="c1"&gt;// Roundtrip: parse → compile&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;compiled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;CypherCompiler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compile&lt;/span&gt; &lt;span class="n"&gt;parsed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Performance
&lt;/h2&gt;

&lt;p&gt;Benchmarked on Apple M1 Pro:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Compile simple query&lt;/td&gt;
&lt;td&gt;890 ns&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compile complex query (8 clauses)&lt;/td&gt;
&lt;td&gt;3.2 μs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Parse Cypher string&lt;/td&gt;
&lt;td&gt;1.2 μs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Full roundtrip (parse → compile)&lt;/td&gt;
&lt;td&gt;2.0 μs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Get Started
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dotnet add package Fyper
dotnet add package Fyper.Neo4j    &lt;span class="c"&gt;# or Fyper.Age&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub&lt;/strong&gt;: &lt;a href="https://github.com/Neftedollar/fyper" rel="noopener noreferrer"&gt;github.com/Neftedollar/fyper&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docs&lt;/strong&gt;: &lt;a href="https://neftedollar.github.io/fyper/" rel="noopener noreferrer"&gt;neftedollar.github.io/fyper&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NuGet&lt;/strong&gt;: &lt;a href="https://www.nuget.org/packages/Fyper" rel="noopener noreferrer"&gt;nuget.org/packages/Fyper&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;250 tests (unit + property-based + integration). MIT license. Zero dependencies in core.&lt;/p&gt;




&lt;p&gt;If you've been writing raw Cypher strings from F# — try Fyper. I'd love feedback: &lt;a href="https://github.com/Neftedollar/fyper/issues" rel="noopener noreferrer"&gt;open an issue&lt;/a&gt; or leave a comment here.&lt;/p&gt;

</description>
      <category>fsharp</category>
      <category>neo4j</category>
      <category>graphdatabase</category>
      <category>dotnet</category>
    </item>
  </channel>
</rss>
