<?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: Roman Melnikov</title>
    <description>The latest articles on DEV Community by Roman Melnikov (@neftedollar).</description>
    <link>https://dev.to/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>DEV Community: Roman Melnikov</title>
      <link>https://dev.to/neftedollar</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/neftedollar"/>
    <language>en</language>
    <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://dev.to/neftedollar/wire-ll-lang-into-claude-code-cursor-or-zed-in-30-seconds-51c6</link>
      <guid>https://dev.to/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://dev.to/neftedollar/why-we-built-ll-lang-a-statically-typed-functional-language-for-llms-2hg8</link>
      <guid>https://dev.to/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://dev.to/neftedollar/type-safe-cypher-queries-in-f-introducing-fyper-1m81</link>
      <guid>https://dev.to/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>
