<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged scrml</title>
<link>https://coderlegion.com/tag/scrml</link>
<description>Powered by Question2Answer</description>
<item>
<title>While my keyboard gently weeps.</title>
<link>https://coderlegion.com/33238/while-my-keyboard-gently-weeps</link>
<description>&lt;p&gt;So a job offer came in. The company offering it is one I helped build. In fact, the &quot;I just don't give a what&quot; profile picture that I use here was taken by the same guy offering me the position.&lt;/p&gt;
&lt;p&gt;The &quot;basin boys&quot; came into the cementing game taking no prisoners and offering no quarter. We perfected the 2-stage 1-shot technique That is now used by every cementing company around. We made the name synonymous with quality work. Now they want me back, to put much of my oil-field and down-hole specific knowledge to work again.&lt;/p&gt;
&lt;p&gt;I don't need the job. I can always fall back to truck driving; I own the equipment, so it's not like I would have to interview. So this isn't about money. It's a real choice.&lt;/p&gt;
&lt;p&gt;And the real question before me is: do I pause the work on scrml?&lt;/p&gt;
&lt;p&gt;If I take this job, it will consume my time my mind and my efforts. I know that going in. because it is who I am. And what the job requires.&lt;/p&gt;
&lt;p&gt;While running my own company has allowed me time to focus on building scrml.&lt;/p&gt;
&lt;p&gt;Building a language that doesn't exist yet, and writing programs in it, is a special kind of thing.&lt;/p&gt;
&lt;p&gt;That's what excites me. But special for whom? The level of interest in this project appears to be approaching absolute zero.&lt;/p&gt;
&lt;p&gt;There is an aire of finality here that scares the hell out of me.&lt;/p&gt;
&lt;p&gt;and a silence that should cry havoc&lt;/p&gt;
&lt;p&gt;I have to wonder: is that my answer?&lt;/p&gt;
&lt;p&gt;Or just the voice of frustration, screaming, to unleash the dogs of war?&lt;/p&gt;
&lt;p&gt;Do I go quietly into the night?&lt;/p&gt;
&lt;p&gt;Or is it time to actually get loud?&lt;/p&gt;
&lt;p&gt;And if it is the latter, well; Y`all aint seen nothing yet.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/33238/while-my-keyboard-gently-weeps</guid>
<pubDate>Sat, 31 Oct 2026 16:11:22 +0000</pubDate>
</item>
<item>
<title>Go type yourself!</title>
<link>https://coderlegion.com/33126/go-type-yourself</link>
<description>&lt;p&gt;DHH said that typescript is pure &quot;type masterbation&quot;. And though I agree with his premise, It is a fixable problem. And I say we use the type system to do just that.&lt;/p&gt;
&lt;p&gt;There is a familiar complaint about TypeScript: too much of the code is types talking to other types. Generic wrappers, schemas restating the same shapes, &lt;code&gt;never&lt;/code&gt; tricks to force a &lt;code&gt;switch&lt;/code&gt; to be complete. Very little of it does anything at run time.&lt;/p&gt;
&lt;p&gt;The complaint is fair, but it usually lands on the wrong target. The TypeScript in a typical full-stack feature is not careless. It is what you have to write to get the guarantees, because TypeScript can't see the parts that matter: what crosses the network, which errors can happen, and whether you handled all of them.&lt;/p&gt;
&lt;p&gt;Here is a small, ordinary feature. A page loads a post's title and shows a different message for each way loading can fail.&lt;/p&gt;
&lt;h3 id=&quot;in-typescript&quot;&gt;In TypeScript&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// shared/types.ts — the wire contract, stated as types AND as a runtime schema
import { z } from &quot;zod&quot;;

export const PostRow = z.object({ title: z.string(), owner: z.string() });
export type PostRow = z.infer&amp;lt;typeof PostRow&amp;gt;;

export type LoadError =
  | { kind: &quot;NotFound&quot;; id: string }
  | { kind: &quot;Forbidden&quot; }
  | { kind: &quot;Unavailable&quot; };

export type Result&amp;lt;T, E&amp;gt; = { ok: true; value: T } | { ok: false; error: E };
export type ApiResponse = Result&amp;lt;string, LoadError&amp;gt;;

// the same contract again, so the client can check at runtime what TS only assumes
export const ApiResponseSchema = z.discriminatedUnion(&quot;ok&quot;, [
  z.object({ ok: z.literal(true), value: z.string() }),
  z.object({ ok: z.literal(false), error: z.discriminatedUnion(&quot;kind&quot;, [
    z.object({ kind: z.literal(&quot;NotFound&quot;), id: z.string() }),
    z.object({ kind: z.literal(&quot;Forbidden&quot;) }),
    z.object({ kind: z.literal(&quot;Unavailable&quot;) }),
  ]) }),
]);

export function assertNever(x: never): never {
  throw new Error(`Unhandled: ${JSON.stringify(x)}`);
}

// app/api/post/route.ts
export async function GET(req: Request): Promise&amp;lt;Response&amp;gt; {
  const id = new URL(req.url).searchParams.get(&quot;id&quot;) ?? &quot;&quot;;
  const viewer = await getViewer(req);
  let row: PostRow | undefined;
  try {
    row = PostRow.optional().parse(
      db.prepare(&quot;SELECT title, owner FROM posts WHERE id = ?&quot;).get(id));
  } catch {
    return Response.json({ ok: false, error: { kind: &quot;Unavailable&quot; } } satisfies ApiResponse);
  }
  if (!row) return Response.json({ ok: false, error: { kind: &quot;NotFound&quot;, id } } satisfies ApiResponse);
  if (row.owner !== viewer) return Response.json({ ok: false, error: { kind: &quot;Forbidden&quot; } } satisfies ApiResponse);
  return Response.json({ ok: true, value: row.title } satisfies ApiResponse);
}

// client
type ClientError = LoadError | { kind: &quot;Transport&quot; };

async function loadTitle(id: string): Promise&amp;lt;Result&amp;lt;string, ClientError&amp;gt;&amp;gt; {
  let res: Response;
  try { res = await fetch(`/api/post?id=${encodeURIComponent(id)}`); }
  catch { return { ok: false, error: { kind: &quot;Transport&quot; } }; }
  const parsed = ApiResponseSchema.safeParse(await res.json());
  return parsed.success ? parsed.data : { ok: false, error: { kind: &quot;Transport&quot; } };
}

function label(r: Awaited&amp;lt;ReturnType&amp;lt;typeof loadTitle&amp;gt;&amp;gt;): string {
  if (r.ok) return r.value;
  switch (r.error.kind) {
    case &quot;NotFound&quot;:    return `No post ${r.error.id}`;
    case &quot;Forbidden&quot;:   return &quot;Not yours&quot;;
    case &quot;Unavailable&quot;: return &quot;Try again later&quot;;
    case &quot;Transport&quot;:   return &quot;Network trouble&quot;;
    default:            return assertNever(r.error);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;in-scrml&quot;&gt;In scrml&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-scrml&quot;&gt;&amp;lt;program db=&quot;./app.db&quot;&amp;gt;

type LoadError:enum = { NotFound(id: string), Forbidden, Unavailable }

function loadTitle(id: string, viewer: string) ! LoadError {
    let row = ?{`SELECT title, owner FROM posts WHERE id = ${id}`}.get() !{
        _ :&amp;gt; { fail .Unavailable }
    }
    if (row is not) { fail .NotFound(id) }
    if (row.owner != viewer) { fail .Forbidden }
    return row.title
}

&amp;lt;msg&amp;gt; = &quot;&quot;

function show(id: string) {
    @msg = loadTitle(id, &quot;me&quot;) !{
        .NotFound(mid) :&amp;gt; &quot;No post &quot; + mid
        .Forbidden     :&amp;gt; &quot;Not yours&quot;
        .Unavailable   :&amp;gt; &quot;Try again later&quot;
        .Transport(_)  :&amp;gt; &quot;Network trouble&quot;
    }
}

&amp;lt;button onclick=show(&quot;1&quot;)&amp;gt;Load&amp;lt;/&amp;gt;
&amp;lt;p&amp;gt;${@msg}&amp;lt;/p&amp;gt;

&amp;lt;/program&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now is'nt that just so much nicer to read?&lt;/p&gt;
&lt;h3 id=&quot;where-the-typescript-went&quot;&gt;Where the TypeScript went&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;TypeScript &lt;/th&gt;&lt;th&gt;scrml&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;LoadError&lt;/code&gt; as a union of objects tagged with &lt;code&gt;kind&lt;/code&gt; &lt;/td&gt;&lt;td&gt;&lt;code&gt;type LoadError:enum&lt;/code&gt;: variants with payloads are part of the language&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;Result&amp;lt;T, E&amp;gt;&lt;/code&gt;, and &lt;code&gt;ok: true/false&lt;/code&gt; wrapping at every return &lt;/td&gt;&lt;td&gt;&lt;code&gt;! LoadError&lt;/code&gt; on the signature, &lt;code&gt;fail .X&lt;/code&gt; in the body. A function that can fail is a language feature, not a library type&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;A Zod schema restating the wire type so the client can check it at run time &lt;/td&gt;&lt;td&gt;Nothing. &lt;code&gt;loadTitle&lt;/code&gt; runs on the server because it touches the database, and the compiler writes the encoding for its result and its errors&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;An API route, a URL, &lt;code&gt;fetch&lt;/code&gt;, a &lt;code&gt;try&lt;/code&gt; around &lt;code&gt;fetch&lt;/code&gt;, and a hand-made &lt;code&gt;&quot;Transport&quot;&lt;/code&gt; error &lt;/td&gt;&lt;td&gt;&lt;code&gt;loadTitle(id, &quot;me&quot;)&lt;/code&gt; is an ordinary call. A network failure arrives as the built-in &lt;code&gt;.Transport&lt;/code&gt; variant&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;assertNever&lt;/code&gt; and a &lt;code&gt;default:&lt;/code&gt; branch to make the &lt;code&gt;switch&lt;/code&gt; complete &lt;/td&gt;&lt;td&gt;The compiler refuses an incomplete handler. Delete the &lt;code&gt;.Forbidden&lt;/code&gt; line and the build fails: &lt;code&gt;E-TYPE-080: Non-exhaustive error handler for LoadError. Missing variant(s): Forbidden&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;satisfies ApiResponse&lt;/code&gt; on every &lt;code&gt;Response.json&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Nothing to satisfy, because no response is written by hand&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;Awaited&amp;lt;ReturnType&amp;lt;typeof loadTitle&amp;gt;&amp;gt;&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Nothing to derive, because no type is threaded through generics&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;the-point&quot;&gt;The point&lt;/h3&gt;
&lt;p&gt;None of the TypeScript above is bad. Every line restates something TypeScript cannot see on its own:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the shape of what crosses the wire;&lt;/li&gt;
&lt;li&gt;the set of errors a call can produce;&lt;/li&gt;
&lt;li&gt;whether every one of them was handled.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You write those types because the compiler is blind to the server, the network and the client as one program.&lt;/p&gt;
&lt;p&gt;scrml's compiler is not blind to them. It compiles the server function, the encoding between server and browser, and the client in one pass. So it already knows the error set, already writes the encoding, and can check exhaustiveness itself. You don't need fewer guarantees. You just don't have to restate them by hand.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/33126/go-type-yourself</guid>
<pubDate>Fri, 30 Oct 2026 17:47:29 +0000</pubDate>
</item>
<item>
<title>Markdown without a back door: &lt;md&gt;</title>
<link>https://coderlegion.com/32735/markdown-without-a-back-door-md</link>
<description>&lt;p&gt;How scrml lets an author write &lt;strong&gt;bold&lt;/strong&gt; instead of &lt;b&gt;, and why nothing inside an  block can ever run.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;Where this came from&lt;br&gt;
It started with a security review, not a feature request. scrml treats the bodies of &lt;/p&gt;&lt;pre&gt; and &lt;code&gt; as raw content, and until this week the compiler copied raw content into the page exactly as written. A review then ran these through a real Chromium:
&lt;/code&gt;&lt;/pre&gt;&lt;img src=&quot;x&quot; alt=&quot;image&quot;&gt;                                  // the handler runs
&lt;pre&gt;&lt;iframe srcdoc=&quot;&amp;lt;script&amp;gt;steal()&amp;lt;/script&amp;gt;&quot;&gt;&lt;/iframe&gt;&lt;/pre&gt;  // a same-origin script runs
&lt;pre&gt;&lt;link rel=&quot;stylesheet nofollow&quot; href=&quot;denied:data:text/css,…&quot;&gt;&lt;/pre&gt;                 // the CSS applies
&lt;p&gt;The fix was a ruling: raw bodies now render as text. &amp;lt; and &amp;gt; are encoded, &amp;amp; is left alone so the many existing &amp;lt;…&amp;gt; code samples keep working, and the common &lt;/p&gt;&lt;pre&gt;&lt;code&gt;…&lt;/code&gt;&lt;/pre&gt; wrapper stays a real element. Across 4,523 raw bodies in 12 repositories, that breaks nothing.
&lt;p&gt;It also closed the last place an author could type formatting tags inside text. so I asked the obvious question: can scrml have a first-class , so an author gets the effect of &lt;strong&gt;bold&lt;/strong&gt; without writing &lt;b&gt;?&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;The answer is yes. It took four decisions.&lt;/p&gt;
&lt;p&gt;What it looks like&lt;br&gt;
You write&lt;br&gt;
&lt;/p&gt;&lt;article class=&quot;prose&quot;&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;md&amp;gt;
    ## Filing a good bug report

    Before you file, **search the board**.

    - Say what you *expected*.
    - Paste the exact error in `backticks`.
    - Read the [triage guide](/docs/triage).
&amp;lt;/md&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/article&gt;&lt;br&gt;
The page gets&lt;br&gt;
&lt;h2 id=&quot;filing-a-good-bug-report&quot;&gt;…&lt;/h2&gt;&lt;br&gt;
&lt;p&gt;Before you file,&lt;br&gt;
   &lt;strong&gt;search the board&lt;/strong&gt;.&lt;/p&gt;&lt;br&gt;
&lt;ul&gt;
  &lt;li&gt;Say what you &lt;em&gt;expected&lt;/em&gt;.&lt;/li&gt;
  &lt;li&gt;… &lt;code&gt;backticks&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;… &lt;a rel=&quot;nofollow&quot; href=&quot;/docs/triage&quot;&gt;…&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;&lt;br&gt;
The body is dedented the way Java text blocks are: the smallest indentation among the lines, counting the closing tag's line, is stripped. So  sits naturally inside indented scrml markup. Static  is turned into elements at build time and costs nothing at runtime.
&lt;p&gt;DECISION 1 of 4&lt;/p&gt;
&lt;p&gt;A value inside  is text, never markdown&lt;br&gt;
Ruled: ${…} inside  is text-only.&lt;/p&gt;

    ## Welcome back, ${@user.name}

    Your last order shipped **${@order.date}**.

&lt;p&gt;Now suppose a user sets their name to this:&lt;/p&gt;
&lt;p&gt;x&lt;/p&gt;
&lt;h3 id=&quot;your-account-is-locked&quot;&gt;Your account is locked&lt;/h3&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://evil.example/login&quot;&gt;Verify here&lt;/a&gt;&lt;br&gt;
If interpolated values were parsed as markdown, that name would render a fake heading and a phishing link. The link is a perfectly valid https URL, so no URL check would stop it. Under the rule we chose, the value goes in as plain text and can never create structure. The page shows the odd name literally.&lt;/p&gt;
&lt;p&gt;This is the same contract scrml already uses for SQL. In ?{ … WHERE id = ${id} } the value is a bound parameter: data is never code.  applies that rule to prose.&lt;/p&gt;
&lt;p&gt;DECISION 2 of 4&lt;/p&gt;
&lt;p&gt;Markdown from data gets its own, explicit form&lt;br&gt;
Ruled:  is part of , designed now and built second.&lt;/p&gt;
&lt;p&gt;Comments, posts and CMS rows hold markdown that arrives at runtime. That is the dangerous case, so it does not happen by accident. It has its own spelling:&lt;/p&gt;
 = ?{ SELECT body FROM posts WHERE id = ${id} }.get()

&lt;article&gt;
    
&lt;p&gt;&lt;/p&gt;&lt;/article&gt;&lt;br&gt;
It uses the same reader and the same small set of output elements as static . A user's markdown can produce &lt;strong&gt;bold&lt;/strong&gt; and &lt;a rel=&quot;nofollow&quot; href=&quot;https://…&quot;&gt;a link&lt;/a&gt;, but never a script, an event handler or a javascript: link. Data-driven headings get no generated id, because ids chosen by users can overwrite DOM globals. The parser for this form ships only on pages that use of=; it is estimated at 3–6 KB gzipped, an estimate that has not been measured yet.
&lt;p&gt;DECISION 3 of 4&lt;/p&gt;
&lt;p&gt;A small dialect, one way to write each thing&lt;br&gt;
bryan: &quot;one way to do it with lints to guide&quot;&lt;/p&gt;
&lt;p&gt;In&lt;br&gt;
paragraphs&lt;/p&gt;
&lt;h2 id=&quot;headings&quot;&gt;headings&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;em&lt;/em&gt; and &lt;strong&gt;strong&lt;/strong&gt;&lt;br&gt;
inline &lt;code&gt;code&lt;/code&gt; and fenced code&lt;br&gt;
inline links and images&lt;br&gt;
lists and &amp;gt; quotes&lt;br&gt;
--- rules&lt;br&gt;
tables and &lt;del&gt;strike&lt;/del&gt;&lt;br&gt;
Out&lt;br&gt;
raw HTML (shown as text)&lt;br&gt;
indented code blocks&lt;br&gt;
underlined === headings&lt;br&gt;
reference-style links&lt;br&gt;
entity references&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://…&quot;&gt;https://…&lt;/a&gt; autolinks&lt;br&gt;
footnotes and task lists&lt;br&gt;
Markdown has several ways to write most things. scrml keeps one. When pasted markdown uses a dropped construct, a W-MD-* warning names the form to use instead, so a cut teaches rather than surprises.&lt;/p&gt;
&lt;p&gt;Some cuts are forced. Indented code blocks had to go:  lives inside indented source, and an indentation-sensitive construct would misfire constantly. MDX dropped them for the same reason. Others make the dialect safer. Raw HTML is the obvious one, and task lists are out because they would put form controls into the output. Djot, John MacFarlane's cleanup of CommonMark, made the same kinds of cuts.&lt;/p&gt;
&lt;p&gt;DECISION 4 of 4&lt;/p&gt;
&lt;p&gt;One markdown reader, written in scrml&lt;br&gt;
Ruled: the parser is a single scrml module, scrml:md, used by both compilers.&lt;/p&gt;
&lt;p&gt;scrml currently has two compilers: impl#1, written in TypeScript, and the self-hosting bootstrap, written in scrml. The markdown parser is written once, as an ordinary scrml module. impl#1 compiles it like any other stdlib module and uses it twice: at build time to turn static  into elements, and as the runtime parser shipped for . The bootstrap uses the same module.&lt;/p&gt;
&lt;p&gt;The alternatives were a TypeScript parser now and a scrml one later, or waiting for the bootstrap. The first means two readers of the same text, and two readers that can disagree is exactly the shape that kept producing security bypasses this session. The second means waiting. One reader avoids both. The cost is new machinery: impl#1 needs a hook that runs a scrml module at compile time. That hook gets the same adversarial review as everything else.&lt;/p&gt;
&lt;p&gt;Why nothing inside  can run&lt;br&gt;
Most markdown renderers produce an HTML string and then clean it, by stripping , blocking javascript:, and so on. Those are lists of known-bad things, and the list is always one entry short. DOMPurify has a long run of mutation-XSS bypasses because what it checks and what the browser re-parses can differ. @nuxtjs/mdc had a CVE (CVE-2025-24981) for javascript: URLs hidden behind hex entities.&lt;/p&gt;
 works the other way around. The renderer can only build a fixed set of elements, each with a fixed set of attributes:

ElementAttributesWhere the value comes from
p em strong del blockquote ul li hr br table thead tbody tr prenone—
olstartdigits from the list marker
th tdstyleone of three compiler constants for alignment
h1–h6ida compiler slug, static  only
codeclasslanguage- plus letters, digits, _+-
ahref, titlehref must pass scrml's URL allow-list
imgsrc, alt, titlesrc must pass the same allow-list
Content reaches the page only as text nodes or as values of those attributes. No HTML string is ever handed to the browser to parse. The URL check is scrml's existing allow-list, which admits http:, https:, mailto: and similar schemes rather than trying to name every dangerous one. A dangerous element the browsers add next year cannot appear, because  has no way to build it.

Who pays
Authors can't put a &lt;details&gt; or &lt;kbd&gt; inside . They close the block and write the element in ordinary scrml markup.
People pasting from GitHub see raw HTML tags as text, and get a warning for each dropped construct.
Pages that render user markdown ship the parser, estimated at 3–6 KB gzipped. Pages with only static  ship nothing.
The compiler gains a compile-time hook into a scrml module, which is new machinery to get right.
Not covered: a comment rendered through of= can still say &quot;click here to re-verify&quot; with a real https link. That is content moderation, and true of every forum. Images in user markdown can also act as tracking pixels; an opt-in image policy is still an open question.

Prior art
The closest designs are Markdoc, from Stripe, which keeps content declarative and won't mix arbitrary code into it, and elm-markdown, which renders to a typed tree instead of an HTML string and fails loudly on unexpected HTML. Djot shaped the dialect cuts. Most static-site tools, such as Hugo and markdown-it, default to dropping raw HTML. Tools that pass it through, such as marked, leave sanitizing to the user; marked's README says so directly.

Status: the four decisions were ruled in S462. The full design, including the prior-art table and the security model, is in scrml-support/docs/deep-dives/md-element-2026-10-11.md. Build phases: 1 is the SPEC section and the scrml:md parser (in progress); 2 is the impl#1 hook for static ; 3 is . This draft should be re-checked against the compiler once phase 2 lands, and every code block compiled before publishing.
&lt;/kbd&gt;&lt;/details&gt;</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32735/markdown-without-a-back-door-md</guid>
<pubDate>Tue, 27 Oct 2026 18:54:33 +0000</pubDate>
</item>
<item>
<title>Is there any way to make type conversion sexy? Nope</title>
<link>https://coderlegion.com/31882/is-there-any-way-to-make-type-conversion-sexy-nope</link>
<description>&lt;p&gt;When converting a value of one type to another; There are conversions which never fail (ie int -&amp;gt; string). Some that can fail like string -&amp;gt; int, and then some lossy and implicit ones ( number -&amp;gt; int, int -&amp;gt; number)&lt;/p&gt;
&lt;p&gt;How do we spell it in any of those situations?&lt;/p&gt;
&lt;p&gt;My lean is to have a single tool with sane defaults. if you don't cover the error arm it wont compile and tell you why. That would look something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;p&amp;gt;typed ${num} chars&amp;lt;/&amp;gt; // No explicit cast needed, behaviour is dictated by the &amp;lt;p&amp;gt; parent

 &amp;lt;age:int | not=(int.parse(@txt))/&amp;gt; // &quot;42&quot; → 42, &quot;abc&quot; → not

type Guess:enum = { Yes, No }

&amp;lt;g:Guess | not=(Guess.parse(@txt))/&amp;gt;     // &quot;Yes&quot; → .Yes, &quot;yes&quot; → not

&amp;lt;p&amp;gt;${match @g {
    .Yes :&amp;gt; &quot;yeppers&quot;,
    .No :&amp;gt; &quot;nope&quot;,
    not :&amp;gt; &quot;not a guess&quot;,
}}&amp;lt;/p&amp;gt;

function qty(s: string) -&amp;gt; int {
    const n = int.parse(s)
    if (n is not) {
        return 0
    }
    return n                  // narrowed to int here
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Lossy conversions are named, and round needs a tie mode&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const avg = number.parse(@txt)          // number | not
// once narrowed:
floor(avg)                 // 3.7 → 3
ceil(avg)                  // 3.2 → 4
trunc(avg)                 // -3.7 → -3
round(avg, .HalfUp)        // 2.5 → 3
round(avg, .HalfEven)      // 2.5 → 2
round(avg)                 // error: name a tie mode
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Into text: no conversion spelled, except where it's refused&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;p&amp;gt;typed ${@txt.length} chars&amp;lt;/p&amp;gt;          // ok: markup converts primitives
const msg = `typed ${@txt.length} chars`   // ok: a template is a text context
&quot;typed &quot; + @txt.length                     // error: + stays strict
label(@count)                              // error: int is not string
label(`${@count}`)                         // ok
&amp;lt;p&amp;gt;${@user}&amp;lt;/p&amp;gt;                            // error: a struct has no one honest text form
fixed(@price, 2)                           // &quot;3.14&quot;, from scrml:format
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A bound number cell turns bad input into not&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let &amp;lt;qty:int | not=0/&amp;gt;
&amp;lt;input type=&quot;text&quot; bind:value=@qty/&amp;gt;      // &quot;abc&quot; writes not, never 0 or NaN


int.parse(&quot;42&quot;)       // 42
int.parse(&quot;-7&quot;)       // -7
int.parse(&quot; 42&quot;)      // not   (no trimming)
int.parse(&quot;+42&quot;)      // not   (no plus sign)
int.parse(&quot;12px&quot;)     // not   (whole string or nothing)
int.parse(&quot;0x1F&quot;)     // not   (use scrml:math.parseInt(s, 16) for a radix)
int.parse(&quot;3.5&quot;)      // not   (tnumber, not an int)

number.parse(&quot;3.5&quot;)   // 3.5
number.parse(&quot;1e3&quot;)   // 1000
number.parse(&quot;.5&quot;)    // decided in the SPEC text; strict leans not

bool.parse(&quot;true&quot;)    // true
bool.parse(&quot;True&quot;)    // not
bool.parse(&quot;yes&quot;)     // not
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Would anyone like to offer any criticism to this approach. Maybe a better idea. &lt;/p&gt;
&lt;p&gt;I hope so&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31882/is-there-any-way-to-make-type-conversion-sexy-nope</guid>
<pubDate>Tue, 20 Oct 2026 19:04:00 +0000</pubDate>
</item>
<item>
<title>Function coloring the right way</title>
<link>https://coderlegion.com/31094/function-coloring-the-right-way</link>
<description>&lt;h2 id=&quot;let-it-leak-purity&quot;&gt;Let It Leak Purity&lt;/h2&gt;
&lt;p&gt;Stop hating on functions. They can't help what color they are; they were born that way. You ought to know. You made them.&lt;/p&gt;
&lt;p&gt;We all hate leaky abstractions. But if an abstraction is going to leak, I say let it leak &lt;em&gt;purity&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;That's not what async/await leaks. It's not what try/catch leaks. Those aren't abstractions, they're infections. Mark one function &lt;code&gt;async&lt;/code&gt; and every caller has to turn async too. Throw from one place and every layer above it has to wrap itself in &lt;code&gt;try&lt;/code&gt;. They spread through your code whether you want them or not, and what spreads carries no information: &quot;something down there is slow,&quot; &quot;something down there might blow up.&quot; Try/catch is the poisoned tree, and async/await is its deadly fruit. Neither deserves to see the light of day in a reasonable language.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Errors are enum variants.&lt;/strong&gt; A function that can fail says so in its signature, and the caller handles every way it can fail, by name, or it doesn't compile.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Asynchronous code doesn't exist.&lt;/strong&gt; Let that sit for a moment. Async is in your mind. There's no such thing as asking for data that doesn't exist yet. You ask for data, you get data. Never mind &lt;em&gt;when&lt;/em&gt;; you get it before you need it, and the compiler handles the waiting.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;No try. Only do.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That's the theory. Here's what it does to real code.&lt;/p&gt;
&lt;h3 id=&quot;the-code-you-actually-write-first&quot;&gt;The code you actually write first&lt;/h3&gt;
&lt;p&gt;Nobody starts with a clean architecture. You start with the thing that works:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scrml&quot;&gt;function checkout(lines: Line[]) {
  if (lines.length == 0) {
    @msg = &quot;cart is empty&quot;
    return
  }
  let t = 0
  for (const l of lines) { t = t + l.qty * l.cents }
  t = t + Math.round(t * 0.08)
  if (t &amp;gt; 100000) {
    @msg = &quot;over limit: &quot; + t
    return
  }
  ?{`INSERT INTO orders (cents, at) VALUES (${t}, ${now()})`}.run()
  @msg = &quot;charged &quot; + t
}

&amp;lt;button onclick=checkout(@cart)&amp;gt;Buy&amp;lt;/button&amp;gt;
&amp;lt;p&amp;gt;${@msg}&amp;lt;/p&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That compiles. It runs. scrml doesn't punish you for prototyping, and it shouldn't.&lt;/p&gt;
&lt;p&gt;But look at what's knotted together in there. The arithmetic (line totals, tax). The business rules (an empty cart, a spending limit). The effects (a database write, a clock read). And the UI, because the only way this function can tell anyone it failed is by writing a sentence into a display cell. Nothing that calls &lt;code&gt;checkout&lt;/code&gt; can know it failed. The failure is a string on a screen.&lt;/p&gt;
&lt;p&gt;In most languages, that's where the code stays, because nothing pushes back.&lt;/p&gt;
&lt;h3 id=&quot;give-the-failure-a-name&quot;&gt;Give the failure a name&lt;/h3&gt;
&lt;p&gt;Say the cart rules matter, and you'd like the caller to actually know when checkout didn't happen. So you give the failures names:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scrml&quot;&gt;type CartError:enum = { Empty, OverLimit(cents: int) }

function checkout(lines: Line[])! -&amp;gt; CartError {
  if (lines.length == 0) { fail CartError::Empty }
  let t = 0
  for (const l of lines) { t = t + l.qty * l.cents }
  t = t + Math.round(t * 0.08)
  if (t &amp;gt; 100000) { fail CartError::OverLimit(t) }
  ?{`INSERT INTO orders (cents, at) VALUES (${t}, ${now()})`}.run()
  return t
}

function buy() {
  checkout(@cart)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;@msg&lt;/code&gt; writes are gone from &lt;code&gt;checkout&lt;/code&gt;. It doesn't know there's a screen anymore; it says what happened and returns. And the very next thing the compiler does is refuse &lt;code&gt;buy&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;error [E-ERROR-002]: Result of failable function 'checkout' is not handled.
Either match the result, propagate with '?', catch with '!{}', or wrap in '&amp;lt;errorBoundary&amp;gt;'.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So the caller handles it, by name:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scrml&quot;&gt;function buy() {
  const t = checkout(@cart) !{
    .Empty :&amp;gt; { @msg = &quot;cart is empty&quot;; return }
  }
  @msg = &quot;charged &quot; + t
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Not good enough either:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;error [E-TYPE-080]: Non-exhaustive error handler for `CartError`. Missing variant(s): OverLimit.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every way it can fail, by name, or it doesn't compile. And notice where the UI text ended up: in &lt;code&gt;buy&lt;/code&gt;, the function that owns the screen, not in &lt;code&gt;checkout&lt;/code&gt;, the function that owns the order. You didn't decide to separate those. The error system moved them for you.&lt;/p&gt;
&lt;p&gt;That's the first leak. It isn't an infection. It stops wherever you handle it, and you choose where that is.&lt;/p&gt;
&lt;h3 id=&quot;give-the-arithmetic-a-name&quot;&gt;Give the arithmetic a name&lt;/h3&gt;
&lt;p&gt;Now the math. You want to know that the total is right: same cart, same number, every time. So you try to say so:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scrml&quot;&gt;fn total(lines: Line[]) -&amp;gt; int { ... }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;fn&lt;/code&gt; is the pure color: &lt;em&gt;same input, same answer, touches nothing.&lt;/em&gt; And here's the part that matters: once a function is pure, everything it calls has to be pure too, and the compiler holds that line. Leave any of the prototype's habits inside it and it's refused:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Inside an &lt;code&gt;fn&lt;/code&gt; &lt;/th&gt;&lt;th&gt;Compiler says&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;return now()&lt;/code&gt; &lt;/td&gt;&lt;td&gt;&lt;strong&gt;E-FN-004&lt;/strong&gt;: &lt;code&gt;now()&lt;/code&gt; is non-deterministic; it reads host state&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;return checkout(lines)&lt;/code&gt; &lt;/td&gt;&lt;td&gt;&lt;strong&gt;E-FN-003&lt;/strong&gt;: &lt;code&gt;checkout&lt;/code&gt; is a &lt;code&gt;function&lt;/code&gt;, not an &lt;code&gt;fn&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;?{ SELECT … }&lt;/code&gt; &lt;/td&gt;&lt;td&gt;&lt;strong&gt;E-FN-001&lt;/strong&gt;: a pure function may not touch the database&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;@cart = []&lt;/code&gt; &lt;/td&gt;&lt;td&gt;&lt;strong&gt;E-FN-003&lt;/strong&gt;: a pure function may not write state&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The only way to make &lt;code&gt;total&lt;/code&gt; pure is to pull out everything that isn't. The clock goes. The database goes. The state writes go. What's left is arithmetic, and the arithmetic splits along the same line, because &lt;code&gt;total&lt;/code&gt; can only call other &lt;code&gt;fn&lt;/code&gt;s:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scrml&quot;&gt;fn lineTotal(l: Line) -&amp;gt; int { return l.qty * l.cents }
fn subtotal(lines: Line[]) -&amp;gt; int { return lines.reduce((a, l) =&amp;gt; a + lineTotal(l), 0) }
fn tax(cents: int) -&amp;gt; int { return round(cents * 0.08) }
fn total(lines: Line[]) -&amp;gt; int { return subtotal(lines) + tax(subtotal(lines)) }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's the second leak, and it flows the right way: down. The purity of &lt;code&gt;total&lt;/code&gt; forced the purity of everything under it.&lt;/p&gt;
&lt;h3 id=&quot;what-leaked&quot;&gt;What leaked&lt;/h3&gt;
&lt;p&gt;Put it back together:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scrml&quot;&gt;type CartError:enum = { Empty, OverLimit(cents: int) }

// pure: same input, same answer, touches nothing
fn lineTotal(l: Line) -&amp;gt; int { return l.qty * l.cents }
fn subtotal(lines: Line[]) -&amp;gt; int { return lines.reduce((a, l) =&amp;gt; a + lineTotal(l), 0) }
fn tax(cents: int) -&amp;gt; int { return round(cents * 0.08) }
fn total(lines: Line[]) -&amp;gt; int { return subtotal(lines) + tax(subtotal(lines)) }

// impure: writes the database, reads the clock, can fail, and says how
function checkout(lines: Line[])! -&amp;gt; CartError {
  if (lines.length == 0) { fail CartError::Empty }
  const t = total(lines)
  if (t &amp;gt; 100000) { fail CartError::OverLimit(t) }
  ?{`INSERT INTO orders (cents, at) VALUES (${t}, ${now()})`}.run()
  return t
}

// owns the screen: handles every failure by name
function buy() {
  const t = checkout(@cart) !{
    .Empty        :&amp;gt; { @msg = &quot;cart is empty&quot;; return }
    .OverLimit(c) :&amp;gt; { @msg = &quot;over limit: &quot; + c; return }
  }
  @msg = &quot;charged &quot; + t
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Same behavior as the prototype. But now the signatures tell you the truth about the program. Which parts are calculation (&lt;code&gt;fn&lt;/code&gt;). Which parts touch the world (&lt;code&gt;function&lt;/code&gt;). What can go wrong, and every way it can (&lt;code&gt;! -&amp;gt; CartError&lt;/code&gt;). Who owns the screen (whoever handles the variants). And there's still no &lt;code&gt;async&lt;/code&gt;, no &lt;code&gt;await&lt;/code&gt;, no &lt;code&gt;try&lt;/code&gt; anywhere: &lt;code&gt;checkout&lt;/code&gt; hits the database, &lt;code&gt;buy&lt;/code&gt; calls it like any other function, and the compiler writes the waiting itself.&lt;/p&gt;
&lt;p&gt;None of that structure came from discipline. You didn't sit down and design layers. You asked for two things, &quot;tell me when checkout fails&quot; and &quot;promise me the math is the math,&quot; and the structure leaked in from the rules.&lt;/p&gt;
&lt;p&gt;Async leaks &lt;em&gt;slow&lt;/em&gt; up through your code. Try/catch leaks &lt;em&gt;might explode&lt;/em&gt;. Purity and named failures leak &lt;em&gt;shape&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;That's a leak worth having.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31094/function-coloring-the-right-way</guid>
<pubDate>Wed, 14 Oct 2026 16:25:46 +0000</pubDate>
</item>
<item>
<title>Parallelize more with BlockLease</title>
<link>https://coderlegion.com/30640/parallelize-more-with-blocklease</link>
<description>&lt;p&gt;One of the ideas in the scrml compiler that I am excited about, is something I call &quot;BlockLease.&quot;&lt;/p&gt;
&lt;p&gt;Safe parallelism in agentic code work forces concurrency to be locked to files.  Two agents editing the same file just leads to re-creating every atomics problem devs have ever faced.&lt;/p&gt;
&lt;p&gt;The design of this idea still has open questions. if you have any thoughts, drop me a line.&lt;/p&gt;
&lt;h3 id=&quot;the-problem&quot;&gt;The problem&lt;/h3&gt;
&lt;p&gt;Parallel coding agents are kept safe by file. Each gets its own git worktree, and no two are sent to the same file. That gives up the common case: two agents editing &lt;em&gt;different functions in the same module&lt;/em&gt;. In scrml it cost real time — two agents both edited &lt;code&gt;emit-logic.ts&lt;/code&gt;, one landed a whole-file copy, and the other's work was silently reverted. The edits never overlapped. The isolation unit was just bigger than the work.&lt;/p&gt;
&lt;h3 id=&quot;the-idea&quot;&gt;The idea&lt;/h3&gt;
&lt;blockquote&gt;&lt;p&gt;&quot;if every block of code gets a unique ID... parallelism is no longer locked to disjoint files but to disjoint blocks... attach to blocks, no one else can edit those blocks...&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A block is a named definition — function, component, engine, type, channel — with the ID &lt;code&gt;&amp;lt;relpath&amp;gt;::&amp;lt;name&amp;gt;&lt;/code&gt;. The ID already existed as a provenance anchor; block-lease reuses it as a concurrency token. One ID system, two jobs.&lt;/p&gt;
&lt;p&gt;Block boundaries come from the compiler, not a separate parser. If two parsers disagree about where a block ends, two agents can each believe they hold the same lines. An early regex version assigned a four-line helper over 100 lines because a page's render markup followed it.&lt;/p&gt;
&lt;h3 id=&quot;admission-is-not-landing&quot;&gt;Admission is not landing&lt;/h3&gt;
&lt;p&gt;A lease decides whether two edits may proceed in parallel. It does not merge them, and text-disjoint is not the same as safe. Two branches can each add a write to &lt;code&gt;errorMessage&lt;/code&gt; in different functions, merge without a conflict, and break the program.&lt;/p&gt;
&lt;h3 id=&quot;what-the-compiler-provides&quot;&gt;What the compiler provides&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;scrml compile --emit-block-analysis&lt;/code&gt; writes each block's span and the cells it reads and writes:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scrml&quot;&gt;${
  &amp;lt;errorMessage&amp;gt; = &quot;&quot;
  &amp;lt;okCount&amp;gt; = 0
  &amp;lt;quoteForm&amp;gt; = { originCity: &quot;&quot;, weightLbs: 0 }

  function setError(m) { @errorMessage = m }
  function handler1() {
    @okCount = @okCount + 1
    setError(&quot;x&quot;)
  }
  function setCity(c)   { @quoteForm.originCity = c }
  function setWeight(w) { @quoteForm.weightLbs = w }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{ &quot;id&quot;: &quot;app.scrml::setError&quot;,  &quot;reads&quot;: [],          &quot;writes&quot;: [&quot;errorMessage&quot;] }
{ &quot;id&quot;: &quot;app.scrml::handler1&quot;,  &quot;reads&quot;: [&quot;okCount&quot;], &quot;writes&quot;: [&quot;okCount&quot;] }
{ &quot;id&quot;: &quot;app.scrml::setCity&quot;,   &quot;reads&quot;: [],          &quot;writes&quot;: [&quot;quoteForm.originCity&quot;] }
{ &quot;id&quot;: &quot;app.scrml::setWeight&quot;, &quot;reads&quot;: [],          &quot;writes&quot;: [&quot;quoteForm.weightLbs&quot;] }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Footprints are at field grain: &lt;code&gt;setCity&lt;/code&gt; and &lt;code&gt;setWeight&lt;/code&gt; write different fields of &lt;code&gt;quoteForm&lt;/code&gt; and do not conflict.&lt;/p&gt;
&lt;p&gt;They are also shallow. &lt;code&gt;handler1&lt;/code&gt; calls &lt;code&gt;setError&lt;/code&gt;, which writes &lt;code&gt;errorMessage&lt;/code&gt;, but &lt;code&gt;handler1&lt;/code&gt;'s footprint says only &lt;code&gt;okCount&lt;/code&gt;. A non-empty footprint can still be incomplete. Until the compiler reports what a block writes &lt;em&gt;through its calls&lt;/em&gt;, a sound check must reject any block that calls a helper — and most real handlers do.&lt;/p&gt;
&lt;h3 id=&quot;where-it-stands&quot;&gt;Where it stands&lt;/h3&gt;
&lt;p&gt;Built: compiler-defined block identity and spans, field-grain read/write footprints, and a structured merge in giti that consumes them. Not built: the claim/release lease registry, leasing of render markup, and transitive footprints — the piece the rest waits on.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&quot;scrml has subsumed block-lease&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;What conflicts is not files but what an edit reads and writes. The compiler is the one component that knows that, so it is the one that has to answer.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/30640/parallelize-more-with-blocklease</guid>
<pubDate>Sat, 10 Oct 2026 20:21:31 +0000</pubDate>
</item>
<item>
<title>Programming Is Magic: Bryan MacLee’s Journey Building scrml</title>
<link>https://coderlegion.com/29769/programming-is-magic-bryan-maclees-journey-building-scrml</link>
<description>&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Real developers. Real journeys. Real ideas.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/user/Bryan+MacLee&quot;&gt;Bryan MacLee&lt;/a&gt; is a truck driver specialising in the oil and gas industry, but programming is much more than a hobby for him. Driven by a deep love of JavaScript, frustration with web development's complexity, and an unconventional idea about how software should work, he is building &lt;strong&gt;scrml&lt;/strong&gt; — an open-source compiled language for the web.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Interview &amp;amp; Story by Mehadi Hasan&lt;/strong&gt;&lt;br&gt;
&lt;em&gt;CoderLegion Community &amp;amp; Editorial Team&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;it-started-with-hello-world-&quot;&gt;It Started With “Hello World”&lt;/h3&gt;
&lt;p&gt;For Bryan MacLee, the answer to what initially attracted him to programming is surprisingly simple:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;“Hello world.”&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;But explaining why programming became such an important part of his life takes a little longer.&lt;/p&gt;
&lt;p&gt;Bryan describes programming as:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;“The truest form of magic that we humans have ever created.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It is a statement he stands by.&lt;/p&gt;
&lt;p&gt;For him, programming is a recursively unfolding logic puzzle. You begin with a question, follow the logic, discover another layer, and continue uncovering how things work.&lt;/p&gt;
&lt;p&gt;Even when a particular language or technology becomes repetitive, the underlying activity never really loses its appeal.&lt;/p&gt;
&lt;p&gt;And that love of solving problems eventually led him somewhere unusual: instead of simply using programming languages, Bryan decided to build one.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;a-truck-driver-building-a-programming-language&quot;&gt;A Truck Driver Building a Programming Language&lt;/h3&gt;
&lt;p&gt;Bryan works as a truck driver specialising in the oil and gas industry.&lt;/p&gt;
&lt;p&gt;Outside that professional world, he spends his time exploring programming, JavaScript and the development of scrml.&lt;/p&gt;
&lt;p&gt;He doesn't believe in the conventional idea of work-life balance.&lt;/p&gt;
&lt;p&gt;As he puts it:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;“Work and grow like progression is air. Laugh and love like the world ends tomorrow. To hell with balance.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;His oilfield work and programming are largely separate worlds. He doesn't claim that driving trucks has directly shaped his coding philosophy.&lt;/p&gt;
&lt;p&gt;However, his enthusiasm for software and technology sometimes follows him into his day job.&lt;/p&gt;
&lt;p&gt;One important difference is that Bryan has the freedom to choose what he works on and when.&lt;/p&gt;
&lt;p&gt;He doesn't have a software manager demanding features or deadlines. He can explore ideas because they interest him, without having to turn every experiment into a commercial deliverable.&lt;/p&gt;
&lt;p&gt;That freedom has helped him preserve something many developers struggle to maintain: his excitement about programming.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;loving-javascript-but-disliking-how-it-handles-the-web&quot;&gt;Loving JavaScript, but Disliking How It Handles the Web&lt;/h3&gt;
&lt;p&gt;Bryan's preferred programming language is JavaScript.&lt;/p&gt;
&lt;p&gt;Not TypeScript.&lt;/p&gt;
&lt;p&gt;JavaScript.&lt;/p&gt;
&lt;p&gt;He loves how quickly he can write code, experiment and see whether an idea works. For prototyping, he considers it an excellent language.&lt;/p&gt;
&lt;p&gt;But there has always been something that frustrated him.&lt;/p&gt;
&lt;p&gt;JavaScript's role in interacting with the DOM and styling the web can become complicated, especially when building complex reactive components.&lt;/p&gt;
&lt;p&gt;Bryan has experienced those frustrations firsthand.&lt;/p&gt;
&lt;p&gt;Initially, he tried to solve them by building a library.&lt;/p&gt;
&lt;p&gt;But as his ambitions grew, he began considering a framework.&lt;/p&gt;
&lt;p&gt;That idea didn't last long either.&lt;/p&gt;
&lt;p&gt;He felt frameworks could only go so far because they still operated on top of JavaScript, relying on conventions and abstractions that the underlying language itself didn't understand.&lt;/p&gt;
&lt;p&gt;Around the same time, he developed an academic interest in Rust.&lt;/p&gt;
&lt;p&gt;He admired its design, despite describing it as a beautifully hideous language.&lt;/p&gt;
&lt;p&gt;That exploration prompted a question:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What if he could take some of the ideas he admired and build something around them?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The answer would eventually become scrml.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-small-frustration-that-started-scrml&quot;&gt;The Small Frustration That Started scrml&lt;/h3&gt;
&lt;p&gt;The origin of scrml wasn't a grand plan to create a new programming ecosystem.&lt;/p&gt;
&lt;p&gt;It began with a small annoyance.&lt;/p&gt;
&lt;p&gt;Bryan was working on a complex function and had written an expression whose result he wanted to reference.&lt;/p&gt;
&lt;p&gt;But he couldn't think of a suitable name for a temporary local variable.&lt;/p&gt;
&lt;p&gt;He sat staring at the cursor, increasingly frustrated.&lt;/p&gt;
&lt;p&gt;Eventually, he typed a generic name and tried to move on.&lt;/p&gt;
&lt;p&gt;But the interruption stayed with him.&lt;/p&gt;
&lt;p&gt;He wanted to write an expression and reference its last fall-through value without having to invent a name for something that would only be used once.&lt;/p&gt;
&lt;p&gt;As Bryan explains:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;“I wish I could just write the expression and reference the last ‘fall-through’ value.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And that was the moment scrml was born.&lt;/p&gt;
&lt;p&gt;Not because he wanted to recreate Rust's enums or pattern matching.&lt;/p&gt;
&lt;p&gt;Not because JavaScript state management was frustrating, although that was certainly part of his broader motivation.&lt;/p&gt;
&lt;p&gt;It began because he wanted to stop naming one-time-use local variables.&lt;/p&gt;
&lt;p&gt;Everything else, as he jokes, was a bonus.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-exactly-is-scrml-&quot;&gt;What Exactly Is scrml?&lt;/h3&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://scrml.dev/&quot;&gt;scrml&lt;/a&gt; is a compiled programming language designed for web development.&lt;/p&gt;
&lt;p&gt;It takes &lt;code&gt;.scrml&lt;/code&gt; source files and generates HTML, CSS and JavaScript.&lt;/p&gt;
&lt;p&gt;But its central idea goes beyond translating one language into another.&lt;/p&gt;
&lt;p&gt;A single scrml file brings together:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Markup&lt;/li&gt;
&lt;li&gt;Application logic&lt;/li&gt;
&lt;li&gt;Styles&lt;/li&gt;
&lt;li&gt;SQL&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The compiler examines the whole program and determines how its parts relate.&lt;/p&gt;
&lt;p&gt;It then separates client-side and server-side responsibilities and generates the necessary connections.&lt;/p&gt;
&lt;p&gt;That includes server routes, fetch calls, CSRF handling and reactive dependency tracking.&lt;/p&gt;
&lt;p&gt;Bryan wants developers to describe what their application should do without manually writing all the repetitive wiring required to make different parts communicate.&lt;/p&gt;
&lt;p&gt;The language also introduces concepts that are central to his design philosophy:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;First-class state&lt;/li&gt;
&lt;li&gt;Exhaustive enum matching&lt;/li&gt;
&lt;li&gt;State-machine constructs&lt;/li&gt;
&lt;li&gt;Typed errors&lt;/li&gt;
&lt;li&gt;A cohesive syntax across the application&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Scrml is built to produce web technologies rather than operate as another framework sitting on top of JavaScript.&lt;/p&gt;
&lt;p&gt;That distinction is fundamental to Bryan's vision.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;one-language-one-compiler-one-understanding-of-the-program-&quot;&gt;One Language. One Compiler. One Understanding of the Program.&lt;/h3&gt;
&lt;p&gt;Bryan believes modern web development can become unnecessarily fragmented.&lt;/p&gt;
&lt;p&gt;A typical project may involve separate tools for markup, styles, logic, compilation, server communication and other responsibilities.&lt;/p&gt;
&lt;p&gt;Each tool sees only part of the source code.&lt;/p&gt;
&lt;p&gt;That fragmentation is one of the problems he wants scrml to address.&lt;/p&gt;
&lt;p&gt;His philosophy is:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;“One walk. One language. One compiler that reads the markup, the logic, the styles and the SQL together, and actually understands how they relate.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Because the compiler sees the whole program, it can generate connections that developers would otherwise have to create manually.&lt;/p&gt;
&lt;p&gt;For example, when an application needs to communicate between the browser and server, scrml can generate the necessary routes, fetch calls and serialization.&lt;/p&gt;
&lt;p&gt;Bryan also dislikes what he calls leaky abstractions and coloured functions.&lt;/p&gt;
&lt;p&gt;His goal is to move responsibility into the language and compiler rather than leaving developers to manage conventions across multiple layers.&lt;/p&gt;
&lt;p&gt;In his view, if the compiler understands the program, entire categories of mistakes can become impossible to express.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;making-state-a-first-class-concept&quot;&gt;Making State a First-Class Concept&lt;/h3&gt;
&lt;p&gt;One of the ideas that influenced scrml was Bryan's exploration of state in functional programming.&lt;/p&gt;
&lt;p&gt;He encountered a conventional definition of state as stored data, variables and conditions remembered by a program at a particular moment.&lt;/p&gt;
&lt;p&gt;He wasn't satisfied with that explanation.&lt;/p&gt;
&lt;p&gt;Instead, he developed his own description:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;“A snap-shot of a data set, at the moment of observation.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That led him to question why programming languages commonly provide logic and type systems but don't necessarily offer state as a first-class language feature.&lt;/p&gt;
&lt;p&gt;Scrml incorporates state directly into its design.&lt;/p&gt;
&lt;p&gt;For example, a state cell can be declared using a tag-like form and then referenced through the language's state syntax.&lt;/p&gt;
&lt;p&gt;The compiler can track dependencies and manage the connections required for reactive behaviour.&lt;/p&gt;
&lt;p&gt;Bryan is also working on stronger lifecycle and validation contracts, along with new approaches to state declarations.&lt;/p&gt;
&lt;p&gt;These are part of his broader ambition to make the compiler responsible for more of the correctness guarantees.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-bryan-is-so-serious-about-dependencies&quot;&gt;Why Bryan Is So Serious About Dependencies&lt;/h3&gt;
&lt;p&gt;Bryan has a strong preference for minimal external dependencies.&lt;/p&gt;
&lt;p&gt;He describes himself as someone who likes to build things himself and has a visceral dislike of not understanding the code in his own source tree.&lt;/p&gt;
&lt;p&gt;He also sees automatically updating dependencies as a potential security problem.&lt;/p&gt;
&lt;p&gt;That doesn't mean scrml prohibits importing external code.&lt;/p&gt;
&lt;p&gt;Instead, Bryan wants imported code to enter through an explicit capability-gated mechanism.&lt;/p&gt;
&lt;p&gt;His underlying principle is that a program should retain ownership and awareness of the code it uses.&lt;/p&gt;
&lt;p&gt;He is particularly critical of developers introducing dependencies for trivial functionality.&lt;/p&gt;
&lt;p&gt;His example is &lt;code&gt;isEven&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For something that can be expressed through a simple mathematical operation, he questions why a developer would need to search the web for an external package.&lt;/p&gt;
&lt;p&gt;For Bryan, fewer dependencies can encourage developers to understand what their applications actually contain.&lt;/p&gt;
&lt;p&gt;Scrml includes a standard library intended to cover most common web application needs.&lt;/p&gt;
&lt;p&gt;The goal isn't to eliminate useful code sharing, but to make dependencies more intentional.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;-assume-the-compiler-works-perfectly-&quot;&gt;“Assume the Compiler Works Perfectly”&lt;/h3&gt;
&lt;p&gt;Bryan's article &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/28387/assume-the-compiler-works-perfectly&quot;&gt;Assume the compiler works perfectly&lt;/a&gt; addresses a particular challenge he has encountered when introducing scrml to other developers.&lt;/p&gt;
&lt;p&gt;A couple of people had tried it and responded that nothing worked.&lt;/p&gt;
&lt;p&gt;Bryan understands that this is a fair reaction, particularly when working with a new compiler.&lt;/p&gt;
&lt;p&gt;But he wanted to ask a different question.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What if the compiler were already finished and worked perfectly?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Would developers actually want to write applications in scrml?&lt;/p&gt;
&lt;p&gt;That is the question behind the article.&lt;/p&gt;
&lt;p&gt;He isn't asking people to ignore technical problems or pretend the implementation is complete.&lt;/p&gt;
&lt;p&gt;He wants to separate two questions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Is the underlying language idea appealing?&lt;/li&gt;
&lt;li&gt;Is the current compiler implementation mature enough to deliver it?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Building a compiler is a substantial undertaking, and Bryan knows the implementation still has work ahead.&lt;/p&gt;
&lt;p&gt;But he wants feedback on the vision itself, not only on the current state of the tooling.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-current-state-of-scrml&quot;&gt;The Current State of scrml&lt;/h3&gt;
&lt;p&gt;Scrml is open source under the MIT licence.&lt;/p&gt;
&lt;p&gt;At the time of Bryan's interview, the compiler was at version &lt;strong&gt;0.8.0&lt;/strong&gt;, running on Bun, and was not published to npm.&lt;/p&gt;
&lt;p&gt;He has also reached an important turning point in its development.&lt;/p&gt;
&lt;p&gt;The original TypeScript compiler prototype gradually became the product.&lt;/p&gt;
&lt;p&gt;Over time, technical debt accumulated to the point where continuing to extend the existing implementation became increasingly difficult.&lt;/p&gt;
&lt;p&gt;Rather than continuing to patch the old structure, Bryan has paused that compiler work and begun building a bootstrap approach.&lt;/p&gt;
&lt;p&gt;The current TypeScript compiler is intended to compile a natively written scrml compiler.&lt;/p&gt;
&lt;p&gt;In theory, this should help eliminate entire classes of bugs through construction rather than relying only on testing.&lt;/p&gt;
&lt;p&gt;He is also working on standalone left-hand-side state declarations, as well as stronger lifecycle and validation rules.&lt;/p&gt;
&lt;p&gt;It is a significant undertaking, but one that reflects his willingness to rethink the foundations rather than keep extending an architecture that no longer serves the language's direction.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-he-still-loves-javascript-but-doesn-t-want-to-write-it&quot;&gt;Why He Still Loves JavaScript but Doesn't Want to Write It&lt;/h3&gt;
&lt;p&gt;Bryan's relationship with JavaScript is unusual.&lt;/p&gt;
&lt;p&gt;He genuinely loves the language.&lt;/p&gt;
&lt;p&gt;He appreciates its simplicity, speed and ability to support rapid experimentation.&lt;/p&gt;
&lt;p&gt;But building scrml has changed where his excitement now lies.&lt;/p&gt;
&lt;p&gt;He says that writing JavaScript has lost much of its appeal compared with the anticipation of writing applications in his own language.&lt;/p&gt;
&lt;p&gt;He isn't less interested in programming.&lt;/p&gt;
&lt;p&gt;His excitement has shifted from using existing tools toward creating a different way to work.&lt;/p&gt;
&lt;p&gt;That enthusiasm is also what keeps him moving when scrml becomes difficult.&lt;/p&gt;
&lt;p&gt;There are naturally moments of discouragement during a project of this size.&lt;/p&gt;
&lt;p&gt;But the thought of eventually using the language he is building is a powerful motivator.&lt;/p&gt;
&lt;p&gt;He intends to continue regardless of what others think.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;building-a-language-for-more-than-himself&quot;&gt;Building a Language for More Than Himself&lt;/h3&gt;
&lt;p&gt;Bryan recognises that he doesn't need a production-level compiler simply to enjoy experimenting with his own ideas.&lt;/p&gt;
&lt;p&gt;He could build a much smaller tool and still benefit from it personally.&lt;/p&gt;
&lt;p&gt;But that's not the entire reason he's building scrml.&lt;/p&gt;
&lt;p&gt;He believes the ideas have value for other developers.&lt;/p&gt;
&lt;p&gt;He wants people to use it, question it, debate its assumptions and help improve it.&lt;/p&gt;
&lt;p&gt;Scrml is released under an open-source MIT licence, and Bryan doesn't see it as something that belongs exclusively to him.&lt;/p&gt;
&lt;p&gt;His ambition is to make it a shared project.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;“I don't view this as ‘my’ language. MIT, open source, I gave it to the world.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;For Bryan, the value of scrml is directly connected to the number of people willing to engage seriously with its ideas.&lt;/p&gt;
&lt;p&gt;He wants developers to challenge his assumptions and help make the language better.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-would-success-look-like-&quot;&gt;What Would Success Look Like?&lt;/h3&gt;
&lt;p&gt;Bryan's definition of success is refreshingly modest.&lt;/p&gt;
&lt;p&gt;He doesn't need thousands of users or a large company to feel that the project has been worthwhile.&lt;/p&gt;
&lt;p&gt;His ideal milestone is simply hearing from one real developer who enjoys using scrml.&lt;/p&gt;
&lt;p&gt;As he puts it, success would be receiving an email or message from someone saying:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;“I enjoy writing scrml.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That alone would make the work worthwhile.&lt;/p&gt;
&lt;p&gt;He hopes scrml may eventually stand alongside other interesting programming languages, but he doesn't measure its value only through popularity.&lt;/p&gt;
&lt;p&gt;The immediate goal is to build something genuinely useful and invite other developers to help shape it.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-freedom-to-keep-programming-for-the-right-reasons&quot;&gt;The Freedom to Keep Programming for the Right Reasons&lt;/h3&gt;
&lt;p&gt;One of the most interesting parts of Bryan's story is that he hasn't followed the conventional professional software-development route.&lt;/p&gt;
&lt;p&gt;He isn't building scrml because an employer assigned it to him.&lt;/p&gt;
&lt;p&gt;He isn't chasing a job title or trying to meet a commercial delivery deadline.&lt;/p&gt;
&lt;p&gt;He codes when and what he wants to.&lt;/p&gt;
&lt;p&gt;That freedom has helped him avoid becoming burned out or jaded about programming.&lt;/p&gt;
&lt;p&gt;He also doesn't force himself to read documentation or follow technologies that don't interest him.&lt;/p&gt;
&lt;p&gt;Instead, he can focus deeply on the ideas that capture his attention.&lt;/p&gt;
&lt;p&gt;That doesn't mean the journey is free of frustration.&lt;/p&gt;
&lt;p&gt;Building a programming language is difficult, and Bryan is candid about the challenges.&lt;/p&gt;
&lt;p&gt;But his motivation comes from genuine curiosity rather than external pressure.&lt;/p&gt;
&lt;p&gt;It is a reminder that unconventional paths can provide their own advantages.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;advice-for-developers-with-unusual-ideas&quot;&gt;Advice for Developers With Unusual Ideas&lt;/h3&gt;
&lt;p&gt;Bryan's advice to someone with an unconventional programming idea is direct:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;“If you have an idea, and you think it is worth attention. Scream it from the rooftops.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;He doesn't believe that a lack of a traditional background, job title or established resources should automatically stop someone from pursuing an interesting idea.&lt;/p&gt;
&lt;p&gt;Ideas should be exposed to discussion.&lt;/p&gt;
&lt;p&gt;They should be questioned.&lt;/p&gt;
&lt;p&gt;They should be challenged.&lt;/p&gt;
&lt;p&gt;And sometimes they should be corrected.&lt;/p&gt;
&lt;p&gt;Bryan sees correction as an opportunity rather than something to fear.&lt;/p&gt;
&lt;p&gt;The worst outcome, in his view, is that someone points out where your assumptions are wrong.&lt;/p&gt;
&lt;p&gt;That is far better than allowing insecurity to prevent you from trying.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;from-truck-driver-to-language-creator&quot;&gt;From Truck Driver to Language Creator&lt;/h3&gt;
&lt;p&gt;Bryan MacLee's journey is not a conventional story about becoming a software engineer and climbing a corporate career ladder.&lt;/p&gt;
&lt;p&gt;It is about someone who loves programming enough to question the tools he uses and attempt to build something different.&lt;/p&gt;
&lt;p&gt;His journey began with JavaScript, frustration with web development's complexity, curiosity about language design and one seemingly insignificant problem: naming a temporary variable.&lt;/p&gt;
&lt;p&gt;That small frustration grew into scrml, a compiled language with ambitions around state, type safety, application structure and compiler-driven correctness.&lt;/p&gt;
&lt;p&gt;The implementation is still evolving.&lt;/p&gt;
&lt;p&gt;The architecture is being reconsidered.&lt;/p&gt;
&lt;p&gt;And the project needs developers willing to test, question and contribute.&lt;/p&gt;
&lt;p&gt;But Bryan's motivation remains clear.&lt;/p&gt;
&lt;p&gt;He isn't building scrml simply to prove that he can build a programming language.&lt;/p&gt;
&lt;p&gt;He's building it because he believes programming can be made better — and because the act of creating software is, to him, one of the most remarkable things humans have ever learned to do.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;about-bryan-maclee&quot;&gt;About Bryan MacLee&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/user/Bryan+MacLee&quot;&gt;Bryan MacLee&lt;/a&gt;&lt;/strong&gt; is a truck driver specialising in the oil and gas industry and an independent programming-language developer.&lt;/p&gt;
&lt;p&gt;He is the creator of &lt;a rel=&quot;nofollow&quot; href=&quot;https://scrml.dev/&quot;&gt;scrml&lt;/a&gt;, an open-source compiled language for web development that generates HTML, CSS and JavaScript.&lt;/p&gt;
&lt;p&gt;His preferred technologies include JavaScript, HTML and CSS, and his interests include language design, compiler development, state management and reducing unnecessary complexity in web development.&lt;/p&gt;
&lt;p&gt;Connect with &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com/user/Bryan+MacLee&quot;&gt;Bryan MacLee on CoderLegion&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Bryan's story is a reminder that a good idea doesn't need to come from a conventional career path.&lt;/p&gt;
&lt;p&gt;Sometimes it starts with a simple frustration — and the willingness to ask:&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;interview-amp-story-by-mehadi-hasan&quot;&gt;Interview &amp;amp; Story by Mehadi Hasan&lt;/h4&gt;
&lt;p&gt;&lt;em&gt;CoderLegion Community &amp;amp; Editorial Team&lt;/em&gt;&lt;/p&gt;
</description>
<category>Dev Stories</category>
<guid isPermaLink="true">https://coderlegion.com/29769/programming-is-magic-bryan-maclees-journey-building-scrml</guid>
<pubDate>Sun, 04 Oct 2026 22:32:36 +0000</pubDate>
</item>
<item>
<title>Arrays are dumb. So are tuples.</title>
<link>https://coderlegion.com/29090/arrays-are-dumb-so-are-tuples</link>
<description>&lt;p&gt;The array/tuple question was never meant to be a long running thing. And as it often happens, the solution was right there in front of me the whole time.&lt;/p&gt;
&lt;p&gt;Arrays are intuitive, fast, and let you do (basically) anything. Therein lies the problem with them, no type-constraint, no tracking (other than what you keep in your head).&lt;/p&gt;
&lt;p&gt;Tuples are almost exactly the opposite. Powerful, dependable (in and of themselves), type-safe at the position level. But they are awkward, and tend to make code that depends on them, more brittle.&lt;/p&gt;
&lt;p&gt;When a debate goes back and forth multiple times with NO NEW INFORMATION being submitted; it tends to devolve into an interminable loop. The only way to break the cycle is to add data, or perspective. The second is honestly the more powerful, by far.&lt;/p&gt;
&lt;p&gt;So, what is the perspective that this question is inspected from? &quot;a programming language needs arrays, tuples, or both.&quot; There, simply by stating what the subject is in clear terms, we can start to see that the problem is in the perspective itself.&lt;/p&gt;
&lt;p&gt;&quot;A programming language needs an ordered-sequence data-structure&quot; (hyphens were hand typed, mmm). Now just look at that! The solution practically bites you on the nose.&lt;/p&gt;
&lt;p&gt;Don't use arrays or tuples.&lt;/p&gt;
&lt;p&gt;A &quot;tape&quot; is a &quot;constrained by use-case&quot; data sequence. You can think of it as an array with a la carte permissions. With no permissions, a tape explicitly declared will remain exactly that forever.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scrml&quot;&gt;&amp;lt;primes:int[]=[2, 3, 5, 7]/&amp;gt;

@primes.push(11)                       // error: length is fixed
@primes[0] = 1                         // error: positions are read-only
@primes = [1, 2, 3]                    // error: replacing the whole thing is its own permission
const big = @primes.map(p =&amp;gt; p * 10)   // fine: map returns a new value
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The sequence itself is frozen: no appends, no removals, no reordering, no swapping one element for another. The elements are exactly as frozen as their own type says. If the type of the thing on the tape has a writable field, that field is writable, tape or no tape. Same rule, all the way down.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scrml&quot;&gt;&amp;lt;todo:struct&amp;gt;
    &amp;lt;title:string=&quot;&quot;/&amp;gt;
    &amp;lt;let done:bool=false/&amp;gt;       // this field grants its own write
&amp;lt;/&amp;gt;

&amp;lt;todos:todo[]=[...]/&amp;gt;            // a tape with no permissions

@todos[0].done = true            // fine: judged by `done`, not by the tape
@todos[0].title = &quot;x&quot;            // error: title is locked
@todos.push(t)                   // error: the tape is frozen
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The obvious way to build this is a list of allowed methods: this tape may &lt;code&gt;push&lt;/code&gt;, that one may &lt;code&gt;pop&lt;/code&gt;. That breaks immediately. &lt;code&gt;@log = @log.slice(1)&lt;/code&gt; throws away the first entry using nothing but a non-mutating method and an assignment. No banned method was called. So a tape's permissions aren't a list of methods, they're axes: how the length may change, where changes may happen, whether a position may be rewritten. The compiler judges every write by what it DOES to the value, not by how it's spelled. &lt;code&gt;slice(1)&lt;/code&gt; is a change at the front. If the tape doesn't allow front changes, that line doesn't compile.&lt;/p&gt;
&lt;p&gt;Here's an audit log. Free length, changes only at the end, and nothing else:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scrml&quot;&gt;type Entry:struct = { at: number, actor: string, action: string }

&amp;lt;audit:Entry[free, end]=[]/&amp;gt;

@audit.push(entry)                          // fine
@audit = [...@audit, entry]                 // same edit, different spelling: fine
@audit.shift()                              // error: a change at the front
@audit = @audit.slice(1)                    // error: still a change at the front
@audit = @audit.filter(e =&amp;gt; e.actor != &quot;x&quot;) // error: a removal from anywhere
@audit = []                                 // error: a replace
reset(@audit)                               // error: reset is a replace too
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No statement anywhere in the program can remove or rewrite an entry, however it's spelled. That log is provably append-only, and the proof costs you one line.&lt;/p&gt;
&lt;p&gt;The permissions are part of the tape's type, so they travel. A function only gets to do what its parameter says:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scrml&quot;&gt;fn pushEdit(s: Edit[end], e: Edit) -&amp;gt; Edit[end] {
    return [...s, e]             // the only thing this function CAN do to `s`
}

&amp;lt;undo:Edit[end]=[]/&amp;gt;
@undo = pushEdit(@undo, e)       // fine: the compiler reads the body and sees an append at the end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There's no &quot;all permissions&quot; switch. A fully mutable tape has to name every axis it wants. An all-grant is a leaky abstraction, unacceptable.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scrml&quot;&gt;&amp;lt;todos:Todo[free, anywhere, writable, replace]=[]/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every axis named. No shorthand. That's the point.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;The tape is specified in the scrml language spec (§66.12) and is being built into the new compiler now. It is not in the shipping compiler yet, and the permission syntax shown here is the proposed spelling.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/29090/arrays-are-dumb-so-are-tuples</guid>
<pubDate>Tue, 29 Sep 2026 14:43:13 +0000</pubDate>
</item>
<item>
<title>Assume the compiler works perfectly</title>
<link>https://coderlegion.com/28387/assume-the-compiler-works-perfectly</link>
<description>&lt;p&gt;When developers look at a new programming language, they usually test it by running the compiler. That makes sense, since the compiler is the only part you can actually run. But it hides a distinction that matters a lot for an early stage language: &lt;strong&gt;the language and its compiler are two totally different things&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A &lt;strong&gt;programming language&lt;/strong&gt; is a design. It's a set of rules for what programs look like, what they mean, and what the language refuses to allow. It covers the abstractions, the defaults, and the tradeoffs.&lt;/p&gt;
&lt;p&gt;A &lt;strong&gt;compiler&lt;/strong&gt; is a program that implements that design. It turns source text into something that runs. It has bugs, missing features, slow spots, and confusing error messages. Those can all be fixed without changing the language. &lt;/p&gt;
&lt;p&gt;For an established language, the difference is mostly academic. But, for a &lt;strong&gt;&lt;em&gt;new&lt;/em&gt;&lt;/strong&gt; language, the distinction is incredibly important.&lt;/p&gt;
&lt;p&gt;A new compiler is always rough. It will have gaps, odd edge cases, and diagnostics that need work. Feedback about those is real and welcome, but it answers: &lt;em&gt;does this implementation work yet?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The important question: &lt;strong&gt;is this language worth building a compiler for, at all?&lt;/strong&gt; A turd can't be polished into being, not a turd. &lt;/p&gt;
&lt;p&gt;Language feedback judges the design, regardless of compiler functionality. &lt;/p&gt;
&lt;p&gt;When I ask what people think of &lt;strong&gt;scrml&lt;/strong&gt;, this second kind of feedback is what I'm after. Compiler bug reports help and I'll keep fixing what they find. But the question that decides whether scrml should exist is a language question:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Assume the compiler works perfectly. Is this a language you'd want to write?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&quot;No, because…&quot; is just as valuable as &quot;yes,&quot; as long as the reason is about the design and not the current compiler build.&lt;/p&gt;
&lt;p&gt;But, by far, the most valuable feedback is: &quot;I think this feature is implemented incorrectly, and I have an idea to fix it.&quot;&lt;/p&gt;
&lt;p&gt;The value of this last statement is not in the 1 idea of 1 feature of the language. It is that a second human being has actual interest in building this thing &lt;strong&gt;&lt;em&gt;with me!&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I am not special. I am not a beautiful, unique snowflake. I am the same &quot;all singing, all dancing crap of the world&quot; as everyone else.  I am not especially capable of designing a programming language.&lt;/p&gt;
&lt;p&gt;What I do have, that does qualify me, to at least submit my opinion on the matter; is a genuine and abiding &lt;strong&gt;&lt;em&gt;love&lt;/em&gt;&lt;/strong&gt; of programming.  It is a thing that I care about deeply.  It is a thing that I want to nurture and improve, if I can.&lt;/p&gt;
&lt;p&gt;If you feel similarly, join me. &lt;strong&gt;&lt;em&gt;Our ideas&lt;/em&gt;&lt;/strong&gt; will certainly be better than &lt;strong&gt;&lt;em&gt;my ideas&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/28387/assume-the-compiler-works-perfectly</guid>
<pubDate>Wed, 23 Sep 2026 19:01:05 +0000</pubDate>
</item>
<item>
<title>I am Jack's `&lt;engine&gt;`</title>
<link>https://coderlegion.com/27290/i-am-jacks-engine</link>
<description>&lt;p&gt;I am Jack's &lt;code&gt;&amp;lt;engine&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Without me, Jack's application has values without a place.&lt;/p&gt;
&lt;p&gt;Jack declares me once. I am a singleton. Every part of his program that reads my cell is&lt;br&gt;
reading the same me.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scrml&quot;&gt;${
    type Order:enum = { Cart, Paying, Paid, Shipped, Refunded }
}

&amp;lt;engine for=Order initial=.Cart&amp;gt;
    &amp;lt;Cart    rule=.Paying          : &quot;Your cart.&quot;&amp;gt;
    &amp;lt;Paying  rule=(.Paid | .Cart)  : &quot;Talking to the bank.&quot;&amp;gt;
    &amp;lt;Paid    rule=.Shipped         : &quot;Thank you.&quot;&amp;gt;
    &amp;lt;Shipped rule=.Refunded        : &quot;On its way.&quot;&amp;gt;
    &amp;lt;Refunded                      : &quot;We are sorry.&quot;&amp;gt;
&amp;lt;/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That is all of me. Jack's order, the message, and the arrows.&lt;/p&gt;
&lt;p&gt;I declare Jack's variable for him. He never writes &lt;code&gt;@order&lt;/code&gt;. He receives it.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Every variant has a line. That is not tidiness. It is the deal.&lt;/p&gt;
&lt;p&gt;Jack has not built the refund screen yet. He deletes &lt;code&gt;&amp;lt;Refunded&amp;gt;&lt;/code&gt; from my body.&lt;/p&gt;
&lt;p&gt;He does not delete it from &lt;code&gt;Order&lt;/code&gt;. He removed the room, not the guest.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;code&gt;error [E-ENGINE-STATE-CHILD-MISSING]: &amp;lt;engine for=Order&amp;gt; body is missing a state-child for variant .Refunded.&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Somebody is going to get a refund.&lt;/p&gt;
&lt;p&gt;I do not know who. I know there will be a place for them to stand.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;The &lt;code&gt;rule=&lt;/code&gt; is the part Jack underestimates. It is not documentation. It is a contract on&lt;br&gt;
writes.&lt;/p&gt;
&lt;p&gt;Jack writes this at 2am:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scrml&quot;&gt;&amp;lt;Cart rule=.Paying&amp;gt;
    &amp;lt;button onclick=${ @order = .Shipped }&amp;gt;Check out&amp;lt;/&amp;gt;
&amp;lt;/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;He is inside &lt;code&gt;&amp;lt;Cart&amp;gt;&lt;/code&gt;. I know precisely where he is standing. So I do not wait for a test,&lt;br&gt;
or a user, or a chargeback:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;code&gt;error [E-ENGINE-INVALID-TRANSITION]: @order = .Shipped inside state-child &amp;lt;Cart&amp;gt; is invalid. &amp;lt;Cart&amp;gt;'s rule=.Paying — only .Paying is reachable.&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Jack shipped an order that was never paid for. He shipped it in a text editor, to nobody,&lt;br&gt;
and I was the only one who saw.&lt;/p&gt;
&lt;p&gt;That is the whole incident. No page, no ticket, no customer.&lt;/p&gt;
&lt;p&gt;Jack closes the error and goes to bed.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Before me, Jack wrote this.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;const [isPaying,   setIsPaying]   = useState(false);
const [isPaid,     setIsPaid]     = useState(false);
const [isShipped,  setIsShipped]  = useState(false);
const [isRefunded, setIsRefunded] = useState(false);

// four hundred lines later
setIsShipped(true);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Four booleans. Sixteen combinations. Five of them are an order.&lt;/p&gt;
&lt;p&gt;The other eleven are paid and refunded and never shipped. Shipped and still sitting in the&lt;br&gt;
cart. They are exactly as easy to write as the real five, and nothing tells Jack which is&lt;br&gt;
which. He carries that list in his head, in every file.&lt;/p&gt;
&lt;p&gt;I do not refuse the other eleven. There is no way to say them.&lt;/p&gt;
&lt;p&gt;Jack could reach for a reducer. A reducer gives Jack his one variable back. It does not give&lt;br&gt;
him the arrows. It will take a &lt;code&gt;SHIP&lt;/code&gt; action while the order is still in the cart, hand back&lt;br&gt;
an order that is shipped, and type-check on the way out.&lt;/p&gt;
&lt;p&gt;I am Jack's raging bile duct!&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Jack has two ways to move. Both of them go through me.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scrml&quot;&gt;@order = .Paying            // a write. checked, and quiet when it holds.
@order.advance(.Paying)     // an assertion. this must hold, or halt.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The difference is not whether I check. It is what Jack means by failing. A write that&lt;br&gt;
cannot happen is a bug. An assertion that cannot happen is a belief Jack held that was&lt;br&gt;
wrong, and he wants the noise.&lt;/p&gt;
&lt;p&gt;There is no &lt;code&gt;.tryAdvance&lt;/code&gt;. One was proposed and refused, on the grounds that silent failures&lt;br&gt;
hide bugs.&lt;/p&gt;
&lt;p&gt;Writing Jack's current state back onto itself does nothing at all. Re-asserting where you&lt;br&gt;
are is not travel.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;I am not Jack's validation. Validation arrives after Jack has already built the wrong thing.&lt;/p&gt;
&lt;p&gt;I am the shape of what Jack is permitted to build.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27290/i-am-jacks-engine</guid>
<pubDate>Tue, 15 Sep 2026 13:22:17 +0000</pubDate>
</item>
<item>
<title>An honest question</title>
<link>https://coderlegion.com/27013/an-honest-question</link>
<description>&lt;p&gt;To whom it may concern,&lt;/p&gt;
&lt;p&gt;The articles that I publish. The smack talking that I engage in, here or on X (more likely). That is all an attempt at engaging anyone, with an opinion and the junk to back it, in a conversation about what programming should look like in the next iteration.&lt;/p&gt;
&lt;p&gt;There are those out there that would give an opinion on how to program for the hardware. TBPC, I have the utmost respect for these individuals. It is not an easy thing, to try and tell the world how an entire industry should change.  You tend to stand alone, for the most part.&lt;/p&gt;
&lt;p&gt;That is why I have been so flagrant with my goading.  I am not trying to tell the world that THIS is how it should be done.  I am asking a question.  Is this closer to the shape of the next version of what we do?&lt;/p&gt;
&lt;p&gt;I use &quot;we&quot; loosely here, but tighter than the times might suggest.&lt;/p&gt;
&lt;p&gt;I am genuinely happy for the &quot;vibe coders&quot; that make it some where. I really am.  I have absolutely no desire to diminish the hopes and happiness of any human being.&lt;/p&gt;
&lt;p&gt;But that is not who I am speaking to here.&lt;/p&gt;
&lt;p&gt;I am speaking to the ones that still remember their &quot;hello world&quot; moment.  Those that got the answer off of stack overflow, and then hacked, and reworked the code until they not only understood it, but could improve on it.&lt;/p&gt;
&lt;p&gt;The ones that, as they wrote out an expression, had a thought about how that act could be improved.&lt;/p&gt;
&lt;p&gt;If that is you, I am ready to talk.  Tell me I wrong, please. Question why the syntax looks the way it does. offer an alternative.&lt;/p&gt;
&lt;p&gt;I can't freaking wait to listen to you explain why my assumptions were wrong, and to defend my choices on syntax or semantics.  Not because I want to flex, because I want to grow, to improve, to progress.&lt;/p&gt;
&lt;p&gt;Scrml is cool.  What is way cooler, is that there is an oportunity here. One that would allow us as a self-proclaimed &quot;coder,&quot; (whatever that means) to actually have a say in what we try next. The only thing that matters to me is the conversation and controversy. Without which, scrml will never be more than a hobby project.  And that is fine, wonderful infact. It means that I was off base. No big deal, I've missed the mark before. I'll move on. &lt;/p&gt;
&lt;p&gt;This is not bating.  I mean every word.&lt;/p&gt;
&lt;p&gt;How can srcrml be better? Let's have a conversation.&lt;/p&gt;
&lt;p&gt;If you made it this far, than either you are reading just to finish. Or you have recognized a kindered spirit and know for a fact, that I am speaking to you.&lt;/p&gt;
&lt;p&gt;And if you are the latter, say hi.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27013/an-honest-question</guid>
<pubDate>Sun, 13 Sep 2026 01:37:00 +0000</pubDate>
</item>
<item>
<title>I am Jack's `match`</title>
<link>https://coderlegion.com/26496/i-am-jacks-match</link>
<description>&lt;p&gt;I am Jack's &lt;code&gt;match&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I am the question Jack asks about a value that could be more than one thing.&lt;/p&gt;
&lt;p&gt;He has an order. It is exactly one of five things, and he wrote the list himself.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scrml&quot;&gt;type Order:enum = { Cart, Paying, Paid, Shipped, Refunded }
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;p&gt;There are two of me. Jack does not pick between us — where he puts me decides which one he&lt;br&gt;
gets.&lt;/p&gt;
&lt;p&gt;In his markup, I draw.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scrml&quot;&gt;&amp;lt;match for=Order on=@order&amp;gt;
    &amp;lt;Cart     : &quot;Your cart.&quot;&amp;gt;
    &amp;lt;Paying   : &quot;Talking to the bank.&quot;&amp;gt;
    &amp;lt;Paid     : &quot;Thank you.&quot;&amp;gt;
    &amp;lt;Shipped  : &quot;On its way.&quot;&amp;gt;
    &amp;lt;Refunded : &quot;We are sorry.&quot;&amp;gt;
&amp;lt;/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In his logic, I answer.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scrml&quot;&gt;fn label(o: Order) -&amp;gt; string {
    return match o {
        .Cart     :&amp;gt; &quot;in the cart&quot;
        .Paying   :&amp;gt; &quot;with the bank&quot;
        .Paid     :&amp;gt; &quot;paid for&quot;
        .Shipped  :&amp;gt; &quot;on its way&quot;
        .Refunded :&amp;gt; &quot;refunded&quot;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two shapes. One of me underneath — the same tree, the same count. The only thing that&lt;br&gt;
changed is whether Jack wanted a screen or an answer.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Jack added &lt;code&gt;.Refunded&lt;/code&gt; to that list last month, because a customer's bank called.&lt;/p&gt;
&lt;p&gt;He did not go looking for the places that needed updating. He did not have to.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;code&gt;error [E-TYPE-020]: Non-exhaustive match over enum type Order. Missing variants: ::Refunded. Add arms for the missing variants, or add an else arm to handle them all.&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Every place in his application that asks what an order is, and has no answer for a refunded&lt;br&gt;
one, stopped the build. I name the file. I name the variant. I name both remedies.&lt;/p&gt;
&lt;p&gt;Jack was annoyed. The alternative was four screens quietly rendering nothing on the worst&lt;br&gt;
day of a customer's week.&lt;/p&gt;
&lt;p&gt;I am Jack's complete lack of surprise.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Jack can finish me with &lt;code&gt;else&lt;/code&gt;, and I will take everything he did not name.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scrml&quot;&gt;.Shipped :&amp;gt; &quot;on its way&quot;
else     :&amp;gt; &quot;something else&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That compiles. It also costs him the paragraph above: the next variant lands in &lt;code&gt;else&lt;/code&gt;, the&lt;br&gt;
build stays green, and I say nothing. It is a real trade and it is sometimes the right one.&lt;br&gt;
It is only a mistake when Jack makes it without knowing he made it.&lt;/p&gt;
&lt;p&gt;If he names all five and writes &lt;code&gt;else&lt;/code&gt; anyway, I tell him that too.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;code&gt;warning [W-MATCH-001]: Wildcard _ arm is unreachable. All variants of Order are already covered by explicit arms. Remove the _ arm.&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;p&gt;One day Jack notices his five arms are not five pictures.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.Cart&lt;/code&gt; never goes straight to &lt;code&gt;.Shipped&lt;/code&gt;. There is an order to them, and he has been holding&lt;br&gt;
it in his head this whole time.&lt;/p&gt;
&lt;p&gt;When that day comes, he changes one word.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scrml&quot;&gt;&amp;lt;engine for=Order initial=.Cart&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The arms do not move. The same five lines, untouched.&lt;/p&gt;
&lt;p&gt;He has stopped asking what his order is, and started declaring where it may go.&lt;/p&gt;
&lt;p&gt;That is a different organ.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26496/i-am-jacks-match</guid>
<pubDate>Tue, 08 Sep 2026 14:37:06 +0000</pubDate>
</item>
<item>
<title>I am Jack's `&lt;program&gt;`</title>
<link>https://coderlegion.com/26455/i-am-jacks-program</link>
<description>&lt;p&gt;I am Jack's &lt;code&gt;&amp;lt;program&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Jack declares me once, in one file, for the whole application. Everything he writes lives&lt;br&gt;
inside me — his markup, his logic, his styles, his database.&lt;/p&gt;
&lt;p&gt;There is a line running through me. On one side is Jack's server. On the other is a browser&lt;br&gt;
Jack does not own, cannot see, and should not trust.&lt;/p&gt;
&lt;p&gt;Jack does not draw that line. I do.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Everything Jack writes runs in the browser. That is the default, and it holds until I find a&lt;br&gt;
reason to move it.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scrml&quot;&gt;&amp;lt;program title=&quot;Jack's Store&quot;&amp;gt;
    import { hashPassword } from &quot;scrml:auth&quot;

    &amp;lt;password&amp;gt; = &quot;&quot;

    function signUp(pw: string) {
        const hash: string = hashPassword(pw)
        println(hash)
    }
A
    &amp;lt;h1&amp;gt;Sign up&amp;lt;/&amp;gt;
    &amp;lt;input type=&quot;password&quot; bind:value=@password/&amp;gt;
    &amp;lt;button onclick=signUp(@password)&amp;gt;Create account&amp;lt;/&amp;gt;
&amp;lt;/program&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I found a reason. &lt;code&gt;signUp&lt;/code&gt; reaches &lt;code&gt;scrml:auth&lt;/code&gt;, and &lt;code&gt;scrml:auth&lt;/code&gt; handles things that must&lt;br&gt;
never arrive in a browser. So &lt;code&gt;signUp&lt;/code&gt; is not a browser function any more.&lt;/p&gt;
&lt;p&gt;Jack gets two files he did not ask for. This is what ships to the browser:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;signUp_4(pw) { ... fetch(&quot;signUp_1&quot;, &quot;POST&quot;, _scrml_body) }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And this is what stays behind:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import { hashPassword } from &quot;./_scrml/auth.js&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The password is hashed in a process Jack controls. The browser gets a POST and a promise.&lt;/p&gt;
&lt;p&gt;Jack never typed the word &lt;code&gt;server&lt;/code&gt;. There was a keyword for it once. It is deprecated now.&lt;br&gt;
Everything it used to tell me, I work out on my own.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Jack thinks of me as one thing per application. I am one thing per &lt;em&gt;boundary&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Put me inside myself, and the inner one is not a section of Jack's app. It is a separate&lt;br&gt;
execution context, with its own scope and its own lifecycle.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scrml&quot;&gt;&amp;lt;program name=&quot;compute&quot;&amp;gt;
    export function withTax(n: number) -&amp;gt; number { return n * 1.08 }
&amp;lt;/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What that becomes depends on the properties that Jack assigns to me. A &lt;code&gt;name=&lt;/code&gt; and it is a web worker. Add a&lt;br&gt;
&lt;code&gt;lang=&lt;/code&gt; and it is a subprocess speaking another language. Add &lt;code&gt;mode=&quot;wasm&quot;&lt;/code&gt; and it is a&lt;br&gt;
WebAssembly module. Give it a &lt;code&gt;route=&lt;/code&gt; and it is an endpoint at a fixed address — the one&lt;br&gt;
shape Jack has to name himself, because the thing that will call it is not his.&lt;/p&gt;
&lt;p&gt;Every other address in his application is mine. He has never seen one.&lt;/p&gt;
&lt;p&gt;Same element. Four different machines.&lt;/p&gt;
&lt;p&gt;None of Jack's things reach inside it. Not a binding, not a type, not an import. If he wants&lt;br&gt;
the inner one to know his tax rate, he sends it.&lt;/p&gt;
&lt;p&gt;A boundary that leaks is decoration.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;The first time Jack wrote that function, it read &lt;code&gt;@password&lt;/code&gt; out of the air.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;code&gt;error [E-REACTIVE-003]: Server-escalated function signUp reads the client-side reactive variable @password. ... this read lowers to _scrml_body[&quot;password&quot;], which the client stub never sends, so it resolves to undefined at request time.&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;He had written a function that reached across a line which did not exist when he started&lt;br&gt;
typing it. I moved the function. Jack did not move with it.&lt;/p&gt;
&lt;p&gt;So I told him where the seam goes: pass the password in, and I will carry it across. That is&lt;br&gt;
the version above. It cost him one parameter.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Consider what I would be if I got this backwards.&lt;/p&gt;
&lt;p&gt;Jack's hashing, and the secret it holds, compiled into a file that every visitor downloads.&lt;br&gt;
A green build. No warning. Nobody learns about it until somebody does.&lt;/p&gt;
&lt;p&gt;I am Jack's cold sweat.&lt;/p&gt;
&lt;p&gt;So when something resists classification, I put it on the server. Wrong in that direction&lt;br&gt;
costs Jack a round trip. Wrong in the other direction costs him the secret.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Jack thinks of me as the place his application starts.&lt;/p&gt;
&lt;p&gt;I am the place it is divided.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;scrml is pre-1.0. The server/client split in this piece ships today — the two bundles above&lt;br&gt;
are real compiler output. Nested execution contexts are specified and not yet built: the&lt;br&gt;
boundary described here is the language's, and the compiler is still catching up to it.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26455/i-am-jacks-program</guid>
<pubDate>Tue, 08 Sep 2026 05:07:54 +0000</pubDate>
</item>
<item>
<title>If You Give a Dev an Enum, then</title>
<link>https://coderlegion.com/25933/if-you-give-a-dev-an-enum-then</link>
<description>&lt;p&gt;If you give a dev an enum, they're going to ask if they can match on it.&lt;/p&gt;
&lt;p&gt;When you let them match on it, they'll want to know they got it all.&lt;/p&gt;
&lt;p&gt;So the compiler counts. And it finds one.&lt;/p&gt;
&lt;p&gt;It says &lt;code&gt;E-MATCH-NOT-EXHAUSTIVE&lt;/code&gt;, and it names the variant: &lt;code&gt;.Empty&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;And then they'll add the arm.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scrml&quot;&gt;&amp;lt;match for=Phase on=@phase&amp;gt;
    &amp;lt;Idle&amp;gt;      &amp;lt;button onclick=load()&amp;gt;Load&amp;lt;/button&amp;gt;   &amp;lt;/&amp;gt;
    &amp;lt;Loading&amp;gt;   Loading...                             &amp;lt;/&amp;gt;
    &amp;lt;Error msg&amp;gt; &amp;lt;div class=&quot;err&quot;&amp;gt;${msg}&amp;lt;/div&amp;gt;          &amp;lt;/&amp;gt;
    &amp;lt;Empty&amp;gt;     No rows yet.                           &amp;lt;/&amp;gt;
    &amp;lt;Success count&amp;gt; Got ${count} rows                  &amp;lt;/&amp;gt;
&amp;lt;/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then they'll look at those five arms and notice something.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.Idle&lt;/code&gt; doesn't go to &lt;code&gt;.Success&lt;/code&gt;. It goes to &lt;code&gt;.Loading&lt;/code&gt; first. See, it says so.&lt;/p&gt;
&lt;p&gt;So then they'll want the arrows. You give them &lt;code&gt;rule=&lt;/code&gt; and &lt;code&gt;&amp;lt;engine&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So they'll do the whole rewrite.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scrml&quot;&gt;&amp;lt;engine for=Phase initial=.Idle&amp;gt;
    &amp;lt;Idle rule=.Loading&amp;gt;      &amp;lt;button onclick=load()&amp;gt;Load&amp;lt;/button&amp;gt;   &amp;lt;/&amp;gt;
    &amp;lt;Loading rule=(.Success | .Error | .Empty)&amp;gt;   Loading...         &amp;lt;/&amp;gt;
    &amp;lt;Error msg rule=.Loading&amp;gt; &amp;lt;div class=&quot;err&quot;&amp;gt;${msg}&amp;lt;/div&amp;gt;          &amp;lt;/&amp;gt;
    &amp;lt;Empty&amp;gt;     No rows yet.                             &amp;lt;/&amp;gt;
    &amp;lt;Success count&amp;gt; Got ${count} rows                    &amp;lt;/&amp;gt;
&amp;lt;/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every body is untouched.&lt;/p&gt;
&lt;p&gt;One word on the wrapper. Where it starts. Where each may go.&lt;/p&gt;
&lt;p&gt;And now that it knows where it starts and where it may go,&lt;/p&gt;
&lt;p&gt;it simply will not let them go anywhere else.&lt;/p&gt;
&lt;p&gt;Then in a click handler, they'll write &lt;code&gt;@phase = .Success&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Straight out of &lt;code&gt;.Idle&lt;/code&gt;. The happy path was right there.&lt;/p&gt;
&lt;p&gt;The compiler says no. And quotes the rule they wrote a bit ago.&lt;/p&gt;
&lt;p&gt;They'll be annoyed, about four seconds. Then they'll realize that would  have shipped.&lt;/p&gt;
&lt;p&gt;Once the states are honest, they'll want something to &lt;em&gt;happen&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Fire the analytics. Focus an input. Start a timer.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scrml&quot;&gt;&amp;lt;onTransition from=.Loading to=.Success&amp;gt;
    ${ analytics.track(&quot;load.success&quot;) }
&amp;lt;/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And that effect wants data. So they'll write the query out.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scrml&quot;&gt;const rows = ?{ select * from items }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And they'll wonder how to tell it this part is on the server.&lt;/p&gt;
&lt;p&gt;So you tell them that's the compiler's job.&lt;/p&gt;
&lt;p&gt;They'll type &lt;code&gt;await&lt;/code&gt;. It won't be there.&lt;/p&gt;
&lt;p&gt;Then they'll think about the query coming back empty.&lt;/p&gt;
&lt;p&gt;Not &lt;em&gt;empty&lt;/em&gt; empty. &lt;strong&gt;Not there&lt;/strong&gt; empty. The row that wasn't.&lt;/p&gt;
&lt;p&gt;That one has a name. It's &lt;code&gt;not&lt;/code&gt;. And then you ask them a few questions.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Is &lt;code&gt;&quot;&quot;&lt;/code&gt; absent?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;No. It's a string. You have one.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Is &lt;code&gt;0&lt;/code&gt;?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That's a number.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Is &lt;code&gt;[]&lt;/code&gt;?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;No. It's an array. It's just short.&lt;/p&gt;
&lt;p&gt;Absence is absence and emptiness is emptiness. Only one of them is &lt;code&gt;not&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scrml&quot;&gt;given rows :&amp;gt; {
    @phase = .Success(rows.length)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A user will need to type something in. Then,&lt;/p&gt;
&lt;p&gt;The dev will declare the field with what's &lt;em&gt;true&lt;/em&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-scrml&quot;&gt;&amp;lt;signup&amp;gt;
    &amp;lt;name  req length(&amp;gt;=2)&amp;gt; = &amp;lt;input type=&quot;text&quot;/&amp;gt;
    &amp;lt;agree req&amp;gt;             = &amp;lt;input type=&quot;checkbox&quot;/&amp;gt;
&amp;lt;/&amp;gt;

&amp;lt;errors of=@signup.name/&amp;gt;
&amp;lt;button disabled=${!@signup.isValid}&amp;gt;Sign up&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nobody declared &lt;code&gt;@signup.isValid&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;They'll want to show the errors, so they'll ask where the error strings live.&lt;/p&gt;
&lt;p&gt;And here is where it gets funny.&lt;/p&gt;
&lt;p&gt;They aren't strings. You say.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@signup.name.errors[0]&lt;/code&gt; is &lt;code&gt;.Required&lt;/code&gt;. Or &lt;code&gt;.LengthFailed(predicate)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The failure has a &lt;strong&gt;tag&lt;/strong&gt; and a &lt;strong&gt;payload&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The message can come later. Which means the dev is holding a value&lt;/p&gt;
&lt;p&gt;with a fixed set of named cases. And a payload on some of them.&lt;/p&gt;
&lt;p&gt;So the dev now has an enum...&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/25933/if-you-give-a-dev-an-enum-then</guid>
<pubDate>Mon, 31 Aug 2026 17:17:08 +0000</pubDate>
</item>
<item>
<title>A struct is a data strucure, of course of course</title>
<link>https://coderlegion.com/25466/a-struct-is-a-data-strucure-of-course-of-course</link>
<description>&lt;p&gt;When I started designing this language, I had a lot of fundamental decisions to make. Among those, one of the easiest was whether or not to allow virtual functions in scrml &lt;code&gt;struct&lt;/code&gt;s.&lt;/p&gt;
&lt;p&gt;If I had chosen to include virtual functions; I would have just called these data strctures objects.&lt;/p&gt;
&lt;p&gt;If you were paying attention, you might see why.&lt;/p&gt;
&lt;p&gt;A &lt;code&gt;struct&lt;/code&gt; is a data structure. Plain and simple. But, you see, it is within that simplicity,  the power is made plain.&lt;/p&gt;
&lt;p&gt;Lets  define a unit of data as a value over over a type ( x/T ). Some would claim that it is state over type. Philistines!&lt;/p&gt;
&lt;p&gt;State is data that is presently being witnessed.&lt;/p&gt;
&lt;p&gt;While this might seem like a trivial distinction. The real differentiation comes from the realization that state is not witnessed in piecemeal. it is accepted as a whole, or thrown out as invalid state.&lt;/p&gt;
&lt;p&gt;State is, therefore, immutable by nature.&lt;/p&gt;
&lt;p&gt;So then, what of structs? I'll cover that in the next article &quot;State. Why its not data.&quot;&lt;/p&gt;
&lt;p&gt;Just kidding.  I hope this next part is becoming more obvious.&lt;/p&gt;
&lt;p&gt;So are struct mutable? ... Depends. But, let the default be: the node-tree structure should be immutable. Hence, a user definable &lt;code&gt;struct&lt;/code&gt;ure. the values should mostly bi mutable, with each node mutation re-validated against it's type (or predicate if run-time validation is reqd)&lt;/p&gt;
&lt;p&gt;Simple. And because it's simple, we can analyze it, exhaustively.&lt;/p&gt;
&lt;p&gt;Enter enums,  engine, and other super-powers that &quot;objects&quot; just can't &lt;code&gt;match&lt;/code&gt; .)&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/25466/a-struct-is-a-data-strucure-of-course-of-course</guid>
<pubDate>Wed, 26 Aug 2026 21:09:52 +0000</pubDate>
</item>
<item>
<title>I have a conffession to make</title>
<link>https://coderlegion.com/25442/i-have-a-conffession-to-make</link>
<description>&lt;p&gt;I am not exactly sure when this language really started to become writable. I imagine I am one of the few, if not the only guy to build a legitimately usable programming language without ever really having written it.&lt;/p&gt;
&lt;p&gt;Don't get me wrong. I have written thousands of lines of scrml. But only in the context of describing some part of the language to agents. I had yet to actually open up NeoVim to a blank file and try you write a &lt;code&gt;&amp;lt;program&amp;gt;.&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;After only about 8 loc, I had found 2 bugs. One of them significant. Function argument types were not checked against their parameter types. I mean, at all!&lt;/p&gt;
&lt;p&gt;WTF?! The type system is the biggest component of the current compiler and it doesn't check types?!&lt;/p&gt;
&lt;p&gt;Turns out, not as dramatic a bug as I had originally thought. While the type and state systems are tightly woven together; the logic system, apparently didn't get as much attention. Rectifiable.&lt;/p&gt;
&lt;p&gt;But I do feel It's important to call this kind of thing out because it is a perfect example of what we will, certainly, continue to run into during the infant stage of this fancy new tool.&lt;/p&gt;
&lt;p&gt;Not to drum behind the party beat, but, pretty sure we will still need human beings deep in the loop for a while.&lt;/p&gt;
&lt;p&gt;And also a conditional apology. If any squishy brain out there has ever downloaded scrml and actually gave it a try; only to find out that it is still quite broken, in terms of human use...&lt;/p&gt;
&lt;p&gt;I am sorry&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/25442/i-have-a-conffession-to-make</guid>
<pubDate>Wed, 26 Aug 2026 13:03:36 +0000</pubDate>
</item>
</channel>
</rss>