<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Manu Martínez-Almeida — Writing</title><description>Essays, project write-ups, and experiments — compilers, graphics, infrastructure, and open source.</description><link>https://manualmeida.dev/</link><atom:link href="https://manualmeida.dev/rss.xml" rel="self" type="application/rss+xml"/><item><title>NoiseLang: Where N = 5 is a Dirac delta</title><link>https://manualmeida.dev/articles/noiselang/</link><guid isPermaLink="true">https://manualmeida.dev/articles/noiselang/</guid><description>A signals-and-noise course left me writing Monte Carlo math on paper and wishing the notation itself would run. Nine years later, with an AI agent doing the heavy lifting, NoiseLang became a real Monte Carlo language with a fused multi-core JIT and a WASM backend.</description><pubDate>Mon, 06 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;During &lt;a href=&quot;https://www.tel.uva.es/en/studies/degrees/itec.htm&quot;&gt;my telecommunications degree&lt;/a&gt; I took a
course on signals and noise, I spent a lot of evenings writing probability by hand: expectations,
variances, the odds of two random variables landing in some region. It always sucked, when I tried to run it on a computer, so much boilerplate.&lt;/p&gt;
&lt;p&gt;That wish became &lt;a href=&quot;https://noiselang.com&quot;&gt;NoiseLang&lt;/a&gt;. I started it about nine years ago, however, I never finished it.
Only recently, I brought it back thanks to AI tools and something far more ambitious than what I could have built alone
the first time.&lt;/p&gt;
&lt;h2&gt;Everything is a distribution&lt;/h2&gt;
&lt;p&gt;The whole language hangs on one idea, that &lt;strong&gt;every value is a probability distribution&lt;/strong&gt;. A plain number is
a Dirac spike, a distribution with all its weight on a single value. Since constants and random variables are the same kind of object, every operator
in the language maps distributions to distributions.&lt;/p&gt;
&lt;p&gt;Purists will tell you that the Dirac delta belongs to continuous densities and has no business on a
six-sided die, and they are right. What I mean is the Dirac measure, all the weight on one point,
which works fine on a die. Noise never evaluates a density anyway, it draws samples. And a constant
collapses back to a plain integer in the graph, so &lt;code&gt;5&lt;/code&gt; costs nothing.&lt;/p&gt;
&lt;p&gt;A name always refers to one fixed node, the same way &lt;code&gt;X&lt;/code&gt; is the same &lt;code&gt;X&lt;/code&gt; across a whole page of math.
So &lt;code&gt;X + X&lt;/code&gt; is &lt;code&gt;2X&lt;/code&gt; and &lt;code&gt;X - X&lt;/code&gt; is exactly &lt;code&gt;0&lt;/code&gt;. If you want variable independence you
write separate draws, ie, using &lt;code&gt;~&lt;/code&gt; multiple times, or &lt;code&gt;~[N]&lt;/code&gt; to draw &lt;code&gt;N&lt;/code&gt; independent variables into a vector.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;X ~ unif_int(1, 6)
Y ~ unif_int(1, 6)
X + Y                 # two independent dice, a real 2d6 distribution
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nothing runs until you ask for some results, for example, &lt;code&gt;P(X + Y &amp;lt; 10)&lt;/code&gt;, at that moment it forces the runtime to
run millions of simulations (across all cores, if available) and return an estimate with a standard error attached.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Bday = unif_int(1, 365)
days ~[23] Bday          # 23 people in a room
P(has_duplicates(days))  # the birthday paradox, about 0.507
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is much easier to watch than to describe, so I built some cool demos!&lt;/p&gt;
&lt;h2&gt;Why it sat for nine years&lt;/h2&gt;
&lt;p&gt;The design was never the hard part, because a parser and a tree-walking interpreter for this language
is a weekend of work. The problem was everything else, writing an efficient Monte Carlo runtime, instead of a naive interpreter, conditional bayesian inference, and more.&lt;/p&gt;
&lt;p&gt;Current version is a compiler, a JIT (using the amazing &lt;a href=&quot;https://cranelift.dev/&quot;&gt;Cranelift&lt;/a&gt;), a &lt;a href=&quot;https://webassembly.org/&quot;&gt;WASM&lt;/a&gt; backend, and a pile of careful numerical
code, so for a cute-weekend project it stayed permanently out of reach.&lt;/p&gt;
&lt;h2&gt;Building the ambitious version with an agent&lt;/h2&gt;
&lt;p&gt;At my day job and side projects, I am experimenting with the boundaries of what today’s AI agents can do. For example, I am also porting a game I built 15 years ago for iOS, in archaic Objective-C to a modern game engine (with relative success).&lt;/p&gt;
&lt;p&gt;With NoiseLang, I realized, AI is great at building the JIT parts, the runtime parts, the numerical parts, but it sucks at coming up with good language design ideas, many times overriding existing language features for different purposes, or coming up with different syntax for non-orthogonal features.&lt;/p&gt;
&lt;h2&gt;One IR, three backends&lt;/h2&gt;
&lt;p&gt;I did not start with Cranelift. The first plan was to compile the graph into WASM and let a WASM
runtime JIT it for me, because that runtime was surely going to optimize better than any compiler I
could write myself. While exploring that, I realized I could reach for the JIT optimizer directly and
skip the WASM step. That is the reason Cranelift is such a killer library for a project like this,
NoiseLang runs at native speed and I barely wrote any compiler.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;~&lt;/code&gt; and the distribution constructors build an append-only DAG called the RvGraph. This graph is the single source of truth,
which is later converted into three different code paths:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a columnar batch interpreter that works everywhere and acts as the correctness oracle;&lt;/li&gt;
&lt;li&gt;a Cranelift JIT that fuses a whole expression into one native kernel;&lt;/li&gt;
&lt;li&gt;a WASM emitter that does the same for the browser.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;flowchart LR
  src[&quot;X ~ unif(1, 6)&quot;] --&amp;gt; ir[&quot;RvGraph&quot;]
  ir --&amp;gt; interp[&quot;batch interpreter&quot;]
  ir --&amp;gt; jit[&quot;Cranelift JIT&quot;]
  ir --&amp;gt; wasm[&quot;WASM emitter&quot;]
  jit -. fallback .-&amp;gt; interp
  wasm -. fallback .-&amp;gt; interp
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One shared module defines what the graph means, so the two code generators stay thin and cannot drift
apart. Anything a backend can’t successfully compile falls back to the interpreter, and the results stay
identical across backends and core counts. All tests run in all three code paths and compared to be bit-identical.&lt;/p&gt;
&lt;h2&gt;Making the Monte Carlo loop cheap&lt;/h2&gt;
&lt;p&gt;All the performance work is about one loop: draw a few million samples, evaluate the expression on
each, and reduce the results. A handful of techniques carry most of it, while keeping the results deterministic (that was the hard part).&lt;/p&gt;
&lt;p&gt;Kernel fusion keeps every intermediate value in registers, so an arithmetic-heavy expression stays in registers. The PRNG (&lt;a href=&quot;https://prng.di.unimi.it/&quot;&gt;xoshiro256++&lt;/a&gt;) compiles into the
kernel, and the &lt;code&gt;ln&lt;/code&gt;, &lt;code&gt;sin&lt;/code&gt;, and &lt;code&gt;cos&lt;/code&gt; become inline polynomial
approximations, speeding up the kernel by a factor of 2.&lt;/p&gt;
&lt;p&gt;My favorite trick is in the RNG. Generating random numbers is a serial dependency chain, so instead of
fighting that, the kernel runs four independent streams at once and lets the out-of-order core overlap
them.&lt;/p&gt;
&lt;p&gt;On my 14-core M4 Pro, a one-line &lt;code&gt;P(...)&lt;/code&gt; sustains around 5.8 billion samples per second and scales
about 9.6× from one core to all of them. Per core, the generated kernel runs within about 1.15× of
hand-written Rust compiled by LLVM. The same fused loop, emitted as WASM, runs at roughly half to
three-quarters of native speed inside V8.&lt;/p&gt;
&lt;h2&gt;Where Noise sits&lt;/h2&gt;
&lt;p&gt;NoiseLang is a toy language, &lt;strong&gt;you probably should not use it for anything serious&lt;/strong&gt;, however I wish this language existed during my university days.&lt;/p&gt;
&lt;p&gt;For a language nerd, it’s a small expression-based language over a static random-variable algebra,
with forward Monte Carlo and rejection-based conditioning.&lt;/p&gt;
&lt;p&gt;You might ask, how does it compare to NumPy or Stan? NumPy makes you write the simulation yourself, and Stan makes you declare a model and wait for a
sampler. Noise lets you write the probability as math while running Monte Carlo under the hood to get the answer.&lt;/p&gt;
&lt;p&gt;Stan and PyMC beat Noise at the thing they’re built for, fitting a posterior to lots of continuous
data with their HMC/NUTS samplers, and NumPy beats it at raw array crunching. Conditioning in Noise is
rejection-based, so it works great for a handful of discrete observations but becomes useless for ten
thousand continuous measurements, and there is no stateful simulation yet (no Markov chains yet).
Noise wins when you have a probability question and you wanna know the answer without much hassle.&lt;/p&gt;
&lt;p&gt;So use Noise for the whiteboard stage of a problem, when you want to run the math you just wrote, and
move to Stan or PyMC when you need a real posterior, or to NumPy and JAX when you need to go to production.&lt;/p&gt;
&lt;h2&gt;Back to signals and noise&lt;/h2&gt;
&lt;p&gt;Going back to my university days, there was this subject called “Señales Aleatorias Y Ruido”, which is a spanish translation of “Random Signals and Noise”.&lt;/p&gt;
&lt;figure&gt;
  &lt;div&gt;
    &lt;img src=&quot;https://manualmeida.dev/articles/noiselang/senales-aleatorias-y-ruido.jpg&quot; alt=&quot;Cover of the textbook Señales Aleatorias y Ruido&quot; /&gt;
  &lt;/div&gt;
  &lt;figcaption&gt;
    The textbook, in all its glory.
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;This subject was the nightmare of many students, including myself, in fact, I failed it. Truth be told, I didn’t put enough effort into it during the first year, but it changed when I had the take the same subject again in the second year.
The professor was great, and made the subject interesting. The things that blew my mind was how he could model why FM survives a noisy channel when AM doesn’t.&lt;/p&gt;
&lt;p&gt;So, here is my tribute to the subject, a one-screen Noise program that models why FM survives a noisy channel when AM doesn’t.&lt;/p&gt;
&lt;p&gt;One caveat, and &lt;code&gt;roger_&lt;/code&gt; on Hacker News caught it before I did. The program above modulates phase, not
frequency, so technically it is PM. For the single tone I am sending, PM and FM are the same thing up
to a scale factor and a shift, so the demo still shows what I want it to show, but real FM would
differentiate the message before it goes into the phase. Now I want to write that version, because FM
should pull even further ahead once the message stops being a single tone.&lt;/p&gt;
&lt;h2&gt;Run NoiseLang in the browser&lt;/h2&gt;
&lt;p&gt;Everything above runs on &lt;a href=&quot;https://www.npmjs.com/package/@noiselang/core&quot;&gt;&lt;code&gt;@noiselang/core&lt;/code&gt;&lt;/a&gt;, thanks to the Rust engine compiled to WebAssembly.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm install @noiselang/core
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;

const result = await run(`
  X ~ rand::unif(-1, 1);
  Y ~ rand::unif(-1, 1);
  4 * P(X^2 + Y^2 &amp;lt; 1)
`);

console.log(result.value); // &quot;3.1415…&quot; — the last statement&apos;s value
console.log(result.output); // everything Print(...) emitted
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;run&lt;/code&gt; never throws, failures come back on &lt;code&gt;result.error&lt;/code&gt; with a source span. There is also
&lt;code&gt;runWithIntrospection&lt;/code&gt;, the API behind the variable inspector at &lt;a href=&quot;https://noiselang.com&quot;&gt;noiselang.com&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;NoiseLang is playable in the browser at &lt;a href=&quot;https://noiselang.com&quot;&gt;noiselang.com&lt;/a&gt;. Open it, type
&lt;code&gt;X ~ unif(-1, 1); Y ~ unif(-1, 1); 4 * P(X^2 + Y^2 &amp;lt; 1)&lt;/code&gt;, and watch a few million draws estimate π
from your browser tab.&lt;/p&gt;
&lt;h2&gt;Appendix: prior art&lt;/h2&gt;
&lt;p&gt;I posted this on &lt;a href=&quot;https://news.ycombinator.com/item?id=48803791&quot;&gt;Hacker News&lt;/a&gt; and the thread turned
into a reading list, which is the best thing that can happen to you when you ship a toy language.
NoiseLang is not the first language to treat probability as a first-class value, not even close, so
here is what people pointed me to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://monad-bayes.netlify.app/&quot;&gt;monad-bayes&lt;/a&gt;, a Haskell library where the same “everything is a distribution” idea falls out of the monad, courtesy of &lt;code&gt;bradrn&lt;/code&gt;. I had no idea Haskell could express this so directly.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.webppl.org/&quot;&gt;webppl&lt;/a&gt;, &lt;a href=&quot;https://probprog.github.io/anglican/&quot;&gt;Anglican&lt;/a&gt;, &lt;a href=&quot;https://mc-stan.org&quot;&gt;Stan&lt;/a&gt; and &lt;a href=&quot;https://www.pymc.io/&quot;&gt;PyMC&lt;/a&gt;, the real probabilistic programming languages, suggested by &lt;code&gt;chrisra&lt;/code&gt;. If you have an actual model to fit, go there, not here.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://diceplots.com&quot;&gt;diceplots.com&lt;/a&gt; by &lt;code&gt;qdotme&lt;/code&gt;, which keeps the distributions exactly analytical at every step and never samples. The opposite trade-off to mine, and a fun one.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://arxiv.org/pdf/1006.0764&quot;&gt;“General Purpose Convolution Algorithm in S4-Classes by means of FFT”&lt;/a&gt; by Ruckdeschel and Kohl, sent by &lt;code&gt;data-ottawa&lt;/code&gt;. It is the paper behind the R package &lt;code&gt;distr&lt;/code&gt;, and it overloads &lt;code&gt;+&lt;/code&gt;, &lt;code&gt;-&lt;/code&gt;, &lt;code&gt;*&lt;/code&gt;, &lt;code&gt;/&lt;/code&gt; on distributions by computing the convolutions with an FFT instead of sampling them. Exactly the operator algebra I wanted, arrived at from the other direction.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kccqzy&lt;/code&gt;’s &lt;a href=&quot;https://github.com/kccqzy/probabilistic-program-inference&quot;&gt;work on probabilistic program inference&lt;/a&gt;, which handles loops without Monte Carlo at all. He also asked the question I was hoping nobody would ask, which is how NoiseLang handles loops, and the answer is that it does not.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Big thanks also to &lt;code&gt;RossBencina&lt;/code&gt; and &lt;code&gt;thrtythreeforty&lt;/code&gt;, who pushed on the RNG trick until I understood
my own benchmark better, and to &lt;code&gt;roger_&lt;/code&gt;, who noticed that my FM demo is really doing phase modulation.
This is why you post your toys!&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href=&quot;https://manualmeida.dev/articles/noiselang&quot;&gt;This article has interactive figures — read it on manualmeida.dev.&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</content:encoded><dc:creator>Manu Martínez-Almeida</dc:creator><category>Systems</category><category>programming languages</category><category>probability</category><category>compilers</category><category>side project</category></item><item><title>Qwik: Resumability That Feels Like React</title><link>https://manualmeida.dev/articles/qwik-resumability/</link><guid isPermaLink="true">https://manualmeida.dev/articles/qwik-resumability/</guid><description>I joined Miško Hevery&apos;s team and helped turn Qwik from a resumable-but-painful prototype into a framework that feels like React, backed by a Rust compiler that extracts closures and serializes app state into HTML, so the fast path is the default one.</description><pubDate>Tue, 09 May 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Performance is a human problem, not a technology one&lt;/h2&gt;
&lt;p&gt;The thing I came to believe working on Qwik is that the web’s performance problem isn’t a
technology problem. It’s a design and developer-experience problem. Developers, like water, follow the
path of least resistance, and in most frameworks the easy way to build something is also the way that
ships a slow site. We then tell people to go optimize it afterward, as a chore, trading off
capabilities or DX or their weekend to claw the performance back.&lt;/p&gt;
&lt;p&gt;I find it more honest to stop blaming developers and fix the path instead. A framework should be
designed so the path of least resistance lands you on a fast site by default. You shouldn’t have to be
a performance expert to ship a fast page; you should have to work to ship a slow one.&lt;/p&gt;
&lt;p&gt;And the thing worth optimizing is almost always JavaScript. Most sites already handle their images and
CSS reasonably well, so there’s little left on the table there. JavaScript is where the big wins hide.
The difference between a sluggish page and a snappy one is often tens of points of Lighthouse score, and
for anyone running an e-commerce or consumer site, that gap is revenue. Resumability is the lever Qwik
pulls on exactly that.&lt;/p&gt;
&lt;h2&gt;The idea: resume, don’t rebuild&lt;/h2&gt;
&lt;p&gt;Every mainstream framework hydrates. The server renders HTML, then the browser downloads the whole
component tree as JavaScript, re-executes it, and reattaches event listeners to reconstruct state the
server already had. You pay, in bytes and CPU, to rebuild something you were handed seconds ago.&lt;/p&gt;
&lt;p&gt;Islands and partial hydration, the approach Astro popularized, make this better by only hydrating
the interactive bits. But each island still has to download and execute its JavaScript before it’s
ready, and someone has to draw and maintain those island boundaries by hand.&lt;/p&gt;
&lt;p&gt;Resumability skips the rebuild entirely. The server serializes the application’s state and its event
wiring into the HTML itself. The browser ships almost no JavaScript up front. Conceptually the app
becomes a hashmap: an event on an element points at the one chunk of code that handles it. When you
click a button, Qwik reads an attribute on that element, fetches that small chunk, restores the state
it captured, and runs it. There’s no global “boot the app” step, because there’s no app to boot.&lt;/p&gt;
&lt;p&gt;There’s a neat way to see the flip. In a hydrated app, event handlers are the &lt;em&gt;last&lt;/em&gt; thing to become
ready, after the whole tree has downloaded and executed. In a resumable app they’re the &lt;em&gt;first&lt;/em&gt;. The
page is interactive the moment it arrives.&lt;/p&gt;
&lt;h2&gt;The first version worked; writing it was the hard part&lt;/h2&gt;
&lt;p&gt;Proving resumability was one thing. The developer experience was another. In &lt;a href=&quot;https://github.com/QwikDev/qwik/tree/a64a648e6b12ec5acd1e55decd734cb3b474b7af/integration/todo/ui/Header&quot;&gt;that early version&lt;/a&gt;
the philosophy leaked all the way into the API. You split your code into many small files by hand, you
referenced lazy-loadable symbols by name, and you wrote a lot of ceremony to tell the runtime how the
pieces fit together. It worked, and it asked too much of the person typing.&lt;/p&gt;
&lt;p&gt;My bet was that all of that ceremony could become the compiler’s job. A developer should write
something that looks like ordinary &lt;a href=&quot;https://react.dev/&quot;&gt;React&lt;/a&gt;, with components, event handlers, and local state, and the
build step should do the hard work of making it resumable. The point of the whole project, after all,
was to make the fast path the easy path, and an API that demanded this much manual wiring was the
opposite of that.&lt;/p&gt;
&lt;h2&gt;Making it feel like React&lt;/h2&gt;
&lt;p&gt;In the design I led, you write a component with handlers and hooks, the way you already think. The
magic lives in the compiler.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export const Counter = component$(() =&amp;gt; {
  const count = useSignal(0);
  // looks like an ordinary closure; the compiler will extract it
  return &amp;lt;button onClick$={() =&amp;gt; count.value++}&amp;gt;{count.value}&amp;lt;/button&amp;gt;;
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The compiler reads the AST, finds every &lt;code&gt;$&lt;/code&gt;-marked boundary (a &lt;code&gt;component$&lt;/code&gt;, an &lt;code&gt;onClick$&lt;/code&gt;), and
pulls that closure out into its own module with no dependencies except the variables it closed over.
Those captured variables get serialized straight into the HTML. When the user clicks, the runtime
loads that one module, restores the state it captured, and runs the handler. Nothing else downloads.&lt;/p&gt;
&lt;p&gt;The captured state can be a live signal, so the handler reads and updates a reactive &lt;code&gt;count&lt;/code&gt; and the
DOM follows along. &lt;code&gt;useSignal&lt;/code&gt; lets the runtime make a surgical update to exactly the text node that
changed, with no component re-render in the browser. The signal model drew on Solid’s
&lt;a href=&quot;https://www.solidjs.com/&quot;&gt;reactivity&lt;/a&gt;. The result reads like React and behaves like a resumable app,
with none of the manual wiring the first version demanded.&lt;/p&gt;
&lt;p&gt;One thing I like about this model over React Server Components: a Qwik component is universal. The same
component runs on the server or the client, and the developer doesn’t annotate the boundary. With RSC
it’s easy to reach for &lt;code&gt;use client&lt;/code&gt; everywhere out of convenience and quietly opt back into shipping
everything. In Qwik there’s no boundary to get wrong, because the compiler is the one deciding what
crosses it.&lt;/p&gt;
&lt;h2&gt;Rust and SWC&lt;/h2&gt;
&lt;p&gt;The optimizer that does all this is written in Rust, on top of &lt;a href=&quot;https://swc.rs/&quot;&gt;SWC&lt;/a&gt;. It runs on
every build and across large codebases, so it had to stay fast enough to disappear into the dev loop.
Rust kept the AST passes quick as projects grew, which matters when the entire premise is that the
compiler, and not the developer, carries the complexity.&lt;/p&gt;
&lt;h2&gt;QwikCity&lt;/h2&gt;
&lt;p&gt;Later, with Adam Bradley (co-creator of Ionic and Stencil), I worked on the design of QwikCity, the
meta-framework that sits on top: routing, data loading, and the conventions that make Qwik a way to
build whole sites. That part was a close back-and-forth between the two of us.&lt;/p&gt;
&lt;h2&gt;Credit where it’s due&lt;/h2&gt;
&lt;p&gt;I want to be precise about this. The resumability concept, and much of how it works under the hood,
including the runtime and the Solid-inspired signals, came from Miško Hevery, the creator of
&lt;a href=&quot;https://angular.dev/&quot;&gt;Angular&lt;/a&gt;.
What I led was the developer-facing redesign and the compiler beneath it: turning closures into
independent, serializable modules and working out the reachability analysis that decides what to ship.&lt;/p&gt;
&lt;p&gt;The throughline to the rest of my work is that compiler. Make the source look ordinary, push the hard
analysis to build time, and let the runtime stay lazy, so the path of least resistance is also the
fast one. I’d wired the same instinct into
&lt;a href=&quot;https://manualmeida.dev/articles/gin-simple-over-easy&quot;&gt;Gin’s router&lt;/a&gt; years earlier, and I’d reach for it again tomorrow.
If you’re designing a framework, that’s where I’d spend the effort: on the compiler that lets people
write the easy thing while you quietly ship the simple one.&lt;/p&gt;
&lt;p&gt;If you want the longer version of this argument, I gave a
&lt;a href=&quot;https://www.youtube.com/watch?v=HfEDhuZKH7A&quot;&gt;talk on it&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href=&quot;https://manualmeida.dev/articles/qwik-resumability&quot;&gt;This article has interactive figures — read it on manualmeida.dev.&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</content:encoded><dc:creator>Manu Martínez-Almeida</dc:creator><category>Open Source</category><category>qwik</category><category>compilers</category><category>rust</category><category>performance</category><category>web</category></item><item><title>I Broke My Ankle, Then Rendered It in a Browser</title><link>https://manualmeida.dev/articles/sethealth-gpu-ct/</link><guid isPermaLink="true">https://manualmeida.dev/articles/sethealth-gpu-ct/</guid><description>In October 2019 I broke my ankle in Berlin, ended up with my own CT scan on a CD, and started rendering gigabytes of it in a browser. That became Sethealth, a GPU-accelerated medical-imaging platform.</description><pubDate>Wed, 20 May 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In October 2019 I went out for drinks with friends in Kreuzberg, a few minutes from my office in
Berlin. I broke my ankle that night. Everything I’d been building stopped at once, and for the first
time in years I had nothing to ship.&lt;/p&gt;
&lt;h2&gt;Fourteen days to think&lt;/h2&gt;
&lt;p&gt;I spent fourteen days in a Kreuzberg hospital, waiting for the swelling to go down before they could
operate. Once I calmed down, it wasn’t all bad. I read the books I’d been meaning to get to, reached
out to people I’d lost touch with, designed a tattoo for the ankle, and learned enough German to order
a coffee with milk and no sugar.&lt;/p&gt;
&lt;p&gt;Mostly, it switched off the autopilot. I’d gone years without a single week away from code, and the
quiet was the first chance I’d had to think about what I wanted to build next.&lt;/p&gt;
&lt;h2&gt;My data was mine, and I couldn’t reach it&lt;/h2&gt;
&lt;p&gt;The surgery worried me even after the doctors said it had gone well. I wanted a second opinion, and
that meant getting my own scans out of the hospital. It turned into a small ordeal. I left with my
medical imaging burned onto a CD, then ordered a USB CD reader from Amazon so I could open my own body
on my own laptop.&lt;/p&gt;
&lt;p&gt;The second opinion sent me straight back to Spain, where my family is. There were real problems: the
joint spacing was off, there was nerve damage from the suture, an unrepaired tendon, and a screw
sitting in the wrong place. A second surgery fixed it, and I finally found some calm.&lt;/p&gt;
&lt;p&gt;Somewhere in that mess the product idea formed. Patients own their medical data. Reaching it, reading
it, and sharing it is absurdly hard.&lt;/p&gt;
&lt;h2&gt;Visualizing my own ankle&lt;/h2&gt;
&lt;p&gt;With no deadlines left, I opened my laptop and went back to something I hadn’t touched in years:
writing rendering code from scratch, the way I used to build &lt;a href=&quot;https://manualmeida.dev/articles/about&quot;&gt;game engines&lt;/a&gt;. I wanted
to see my own scan in 3D, in a browser, with nothing to install. The first time my ankle rotated on
screen, rendered from the CD I’d fought to get, I knew this was the thing I wanted to work on.&lt;/p&gt;
&lt;figure&gt;
  &lt;div&gt;
    &lt;img src=&quot;https://manualmeida.dev/articles/sethealth/ray-tracing.jpg&quot; alt=&quot;A ray-traced 3D reconstruction of a fractured ankle, the bones reassembled from a CT scan&quot; /&gt;
  &lt;/div&gt;
  &lt;figcaption&gt;&lt;span&gt;Figure 1.&lt;/span&gt; My own ankle, reconstructed from the CT scan on that CD and ray-cast in the browser. The loose fragments along the right are the displaced pieces of the break.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2&gt;Rendering gigabytes of voxels in a browser&lt;/h2&gt;
&lt;p&gt;A CT scan is a 3D grid of density samples, often hundreds of slices deep, which adds up to gigabytes.
You can’t download all of it before drawing anything, and you can’t fit all of it in GPU memory. Yet
a radiologist expects to spin and re-window the volume with no lag.&lt;/p&gt;
&lt;p&gt;The rendering itself is ray casting through a 3D texture. For each pixel on screen, march a ray into
the volume, sample density along the way, map each sample to color and opacity, and composite front to
back until the pixel goes opaque.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// simplified: march a ray through the CT volume and accumulate color + opacity
vec4 raymarch(vec3 origin, vec3 dir) {
  vec4 acc = vec4(0.0);
  for (int i = 0; i &amp;lt; STEPS; i++) {
    vec3 p = origin + dir * (float(i) * stepSize);
    float density = texture(volume, p).r;    // sample one voxel
    vec4 s = transfer(density);              // density -&amp;gt; color + alpha
    acc.rgb += (1.0 - acc.a) * s.a * s.rgb;  // front-to-back compositing
    acc.a   += (1.0 - acc.a) * s.a;
    if (acc.a &amp;gt; 0.99) break;                 // early ray termination
  }
  return acc;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Three decisions made it run in real time on &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API&quot;&gt;WebGL&lt;/a&gt;. We split each volume into smaller blocks, so the
GPU only ever held the parts in view and could skip the empty space between them. We quantized the
data, including the normal vectors used for shading, to shrink what had to live in texture memory. And
we loaded progressively, coarse first and detail as you leaned in, so the first frame appeared fast
even on a multi-gigabyte study.&lt;/p&gt;
&lt;figure&gt;
  &lt;div&gt;
    &lt;img src=&quot;https://manualmeida.dev/articles/sethealth/openview.png&quot; alt=&quot;OpenView Health, a browser viewer showing a brain MRI in four synchronized panes: axial, coronal, sagittal slices and a 3D angiography render, with measurements and metadata&quot; /&gt;
  &lt;/div&gt;
  &lt;figcaption&gt;&lt;span&gt;Figure 2.&lt;/span&gt; OpenView Health, the free viewer we built on the Sethealth SDK. Slices and the 3D render stay in sync, and you can measure, window, and annotate straight in the browser.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2&gt;Keeping the data safe by never seeing it&lt;/h2&gt;
&lt;p&gt;Medical data raises the stakes on security, so we ran end-to-end encryption on the client. A scan
never left the device in the clear, which meant we couldn’t read it, and that was the point. The data
we never held unencrypted is data we could never leak. That one decision collapsed a large amount of
compliance and liability surface into something we could reason about.&lt;/p&gt;
&lt;h2&gt;What it became&lt;/h2&gt;
&lt;p&gt;Sethealth shipped as an SDK and a compliant backend across iOS, Android, and the web, and it ran until
2026. The use case that clicked was custom prosthetics. A doctor could shape an implant against a
patient’s real anatomy, an engineer could validate it, and the two could hold a precise conversation
inside the same 3D view, all running on our SDK.&lt;/p&gt;
&lt;figure&gt;
  &lt;div&gt;
    &lt;img src=&quot;https://manualmeida.dev/articles/sethealth/custom-implants.png&quot; alt=&quot;A custom-implant ordering tool built on the Sethealth SDK, showing a fractured ankle with surgical plates and screws planned over the CT scan, alongside controls to order implants, surgical guides, and 3D-printed bio-models&quot; /&gt;
  &lt;/div&gt;
  &lt;figcaption&gt;&lt;span&gt;Figure 3.&lt;/span&gt; A custom-implant planning and ordering tool a partner built on the SDK: plan plates, screws, and surgical guides against a real fracture, leave notes, and order the printed parts. The tool is no longer online.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;I built it on a simple conviction. Healthcare carries more weight than most software, and patients are
the rightful owners of their data even when the system makes that ownership painful to exercise.
Sethealth was my bottom-up attempt to hand some of that ownership back.&lt;/p&gt;
&lt;p&gt;The deeper lesson came from the ankle. For years I never went a week without coding, and I’d mistaken
motion for progress. Stepping back to ask why, and not only how, paid off more than any sprint I can
remember. You don’t need to break a bone to get there. Close the laptop for a week and notice which
idea refuses to leave you alone.&lt;/p&gt;
</content:encoded><dc:creator>Manu Martínez-Almeida</dc:creator><category>Graphics</category><category>gpu</category><category>webgl</category><category>graphics</category><category>medical imaging</category><category>performance</category></item><item><title>Building Gin: Simple Over Easy</title><link>https://manualmeida.dev/articles/gin-simple-over-easy/</link><guid isPermaLink="true">https://manualmeida.dev/articles/gin-simple-over-easy/</guid><description>A stalled startup in 2014 became Gin: a Go web framework shaped by simple-over-easy design, a radix-tree router, and an API that still works ten years later.</description><pubDate>Tue, 30 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;In 2014 I came back from San Francisco with no plan. I spent a year building SDKs at Joypad and
TinySpark after shipping one of my first games, and that year gave me a good sense of what small
software teams need from their tools. Back in Spain, about to start Telecommunications Engineering,
I had to decide what to build next.&lt;/p&gt;
&lt;p&gt;The answer was Fyve, a social network built around people’s interests. I chose &lt;a href=&quot;https://go.dev/&quot;&gt;Go&lt;/a&gt;
for the backend because the language felt plain in the right way, and &lt;a href=&quot;https://gin-gonic.com/&quot;&gt;Gin&lt;/a&gt;
started as the web framework for that product. The code still lives at &lt;a href=&quot;https://github.com/gin-gonic/gin&quot;&gt;gin-gonic/gin&lt;/a&gt;.&lt;/p&gt;
&lt;figure&gt;
  &lt;img src=&quot;https://manualmeida.dev/articles/gin/golang-framework-gin-gonic.png&quot; alt=&quot;Gin Gonic Go framework illustration&quot; /&gt;
&lt;/figure&gt;
&lt;p&gt;Fyve never took off. Gin, the tool I built along the way, is still going twelve years later.&lt;/p&gt;
&lt;h2&gt;Simple over easy&lt;/h2&gt;
&lt;p&gt;At the time, the Go web framework people kept pointing me to was
&lt;a href=&quot;https://github.com/go-martini/martini&quot;&gt;Martini&lt;/a&gt;. I understood why immediately. The README was small,
the middleware model felt elegant, and you could get a route responding in minutes.&lt;/p&gt;
&lt;p&gt;Martini used reflection-based dependency injection to wire handlers together, which made the first
demo feel smooth but also moved important behavior out of sight. Services appeared in your handlers
without any visible wiring, so when something misbehaved the control flow was hard to trace. And all
of that reflection ran on every single request.&lt;/p&gt;
&lt;p&gt;Around then I watched Rob Pike’s
&lt;a href=&quot;https://www.youtube.com/watch?v=rFejpH_tAHM&quot;&gt;Simplicity is Complicated&lt;/a&gt;, and it gave me vocabulary
for what bothered me about Martini. What stuck with me was the idea that simple software often takes
more work from the person building it so that it can take less work from the person using it.&lt;/p&gt;
&lt;p&gt;That became the design brief for Gin. Easy is about how good the first example looks, and Martini’s
first example looked great. Simple is about how many moving parts you have to understand, and how many
exceptions you have to remember, once the codebase is old enough to surprise you.&lt;/p&gt;
&lt;h2&gt;Finding the middle ground&lt;/h2&gt;
&lt;p&gt;Aristotle’s version of virtue was the middle ground: &lt;strong&gt;not too much, not too little.&lt;/strong&gt; That was the shape
of the framework problem too.&lt;/p&gt;
&lt;p&gt;Martini gave you too much magic. Plain &lt;a href=&quot;https://pkg.go.dev/net/http&quot;&gt;net/http&lt;/a&gt; gives you full control
and no surprises, but it helps you with almost nothing, so you end up writing the same plumbing for
route params, request parsing, validation, and responses in handler after handler. None of it is hard,
but it makes the code noisy.&lt;/p&gt;
&lt;p&gt;Gin was my attempt at the point between the two. The request path stays explicit, nothing on it uses
reflection, and the repetitive plumbing lives in a single object called the Context.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;r := gin.Default()
r.GET(&quot;/users/:id&quot;, func(c *gin.Context) {
    id := c.Param(&quot;id&quot;)          // path params, no reflection
    c.JSON(200, gin.H{&quot;id&quot;: id}) // response rendering, one call
})
r.Run(&quot;:8080&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That &lt;code&gt;*gin.Context&lt;/code&gt; carries the request, the response writer, path parameters, validation helpers,
and rendering, so it’s the only thing you pass around. The common operations sit one method call
away, and behind them is plain Go code you can step into when production gets weird.&lt;/p&gt;
&lt;p&gt;Funny enough, &lt;code&gt;gin.Context&lt;/code&gt; shipped in 2014, two years before the standard library’s
&lt;a href=&quot;https://pkg.go.dev/context&quot;&gt;&lt;code&gt;context.Context&lt;/code&gt;&lt;/a&gt; existed. When the standard one arrived we kept our
name and made &lt;code&gt;gin.Context&lt;/code&gt; satisfy the new interface, so every existing program kept compiling and
you could pass a &lt;code&gt;gin.Context&lt;/code&gt; anywhere a &lt;code&gt;context.Context&lt;/code&gt; was expected.&lt;/p&gt;
&lt;p&gt;That instinct came from the SDK years. When the convenient way to do something is also the right way,
people write better code without noticing.&lt;/p&gt;
&lt;h2&gt;A router built around a radix tree&lt;/h2&gt;
&lt;p&gt;The router is where the simple-over-easy line became concrete. Martini matched requests by walking a
list of regular expressions and asking each one whether it matched. Regexes are flexible, since you
can make a route match only numbers or hide extra rules inside the pattern, but they’re also a second
language living inside your framework.&lt;/p&gt;
&lt;p&gt;Gin’s route language is smaller. You get static segments, named parameters, and catch-alls, and that
restriction is exactly what lets the router use a
&lt;a href=&quot;https://en.wikipedia.org/wiki/Radix_tree&quot;&gt;radix tree&lt;/a&gt;, the same approach
&lt;a href=&quot;https://github.com/julienschmidt/httprouter&quot;&gt;httprouter&lt;/a&gt; made popular in Go. It also had a side
effect I came to appreciate. Since the route language gives you nothing to be clever with, routes
written for Gin tend to be regular and boring.&lt;/p&gt;
&lt;p&gt;Matching &lt;code&gt;/blog/42/comments&lt;/code&gt; walks &lt;code&gt;/blog/&lt;/code&gt; down to the &lt;code&gt;:slug&lt;/code&gt; node, binds &lt;code&gt;42&lt;/code&gt;, and continues into
&lt;code&gt;/comments&lt;/code&gt;. The cost of a lookup depends on the length of the URL, and it stays the same whether the
app has ten routes registered or ten thousand. Routes that share a prefix also share nodes, so a big
route table stays compact in memory.&lt;/p&gt;
&lt;p&gt;For a router holding $n$ routes and a request path of length $k$, a radix-tree lookup runs in&lt;/p&gt;
&lt;p&gt;$$
T_\text{match}(k) = O(k), \quad \text{independent of } n,
$$&lt;/p&gt;
&lt;p&gt;while checking a list of $n$ regexes costs $O(n \cdot m)$ for patterns of length $m$. The tree trades
the per-request scan for a single walk down the shared prefix.&lt;/p&gt;
&lt;p&gt;The allocations follow the same thinking. Path parameters go into a preallocated slice, and Context
objects come out of a &lt;code&gt;sync.Pool&lt;/code&gt; and get reset between requests, so the garbage collector has less
junk to clean up and latency has fewer reasons to wobble.&lt;/p&gt;
&lt;p&gt;I trust this kind of performance work because the speed comes from doing fewer things, and doing
fewer things also leaves the person reading the code with less to understand.&lt;/p&gt;
&lt;h2&gt;Designing for zero breaking changes&lt;/h2&gt;
&lt;p&gt;The other constraint I gave myself was backward compatibility. Go had made a
&lt;a href=&quot;https://go.dev/doc/go1compat&quot;&gt;compatibility promise&lt;/a&gt; for the language itself, and I wanted Gin to
offer its users the same deal.&lt;/p&gt;
&lt;p&gt;That constraint changes how you design. Before anything goes into the public API you ask whether
you’d be happy maintaining it for ten years, because removing it later is off the table. You learn to
reject the clever rename that saves five characters, and to treat every exported function as
something a stranger might have built a company on.&lt;/p&gt;
&lt;p&gt;The constraint held. Some of the first programs ever written against Gin still compile and run today,
more than a decade later, and I’m prouder of that than of any benchmark.&lt;/p&gt;
&lt;h2&gt;Hacker News, and then growth&lt;/h2&gt;
&lt;p&gt;I &lt;a href=&quot;https://news.ycombinator.com/item?id=7966700&quot;&gt;released Gin on Hacker News&lt;/a&gt; at the right moment. Go
was getting attention there, and a framework that fit in one README and benchmarked well was easy for
people to try.&lt;/p&gt;
&lt;p&gt;The growth after that was steady. People used it, filed issues, sent patches, and put it in real
services, and today Gin sits around 88k stars with more than 290k projects depending on it.&lt;/p&gt;
&lt;p&gt;Stars are a vanity metric, and I mostly treat them that way. The dependency count is the number I
care about, because each of those projects is a bet that the API won’t break underneath them, and
that bet has kept paying off long after Fyve disappeared.&lt;/p&gt;
&lt;h2&gt;Letting it graduate&lt;/h2&gt;
&lt;p&gt;A few years in, I stepped back and handed Gin to maintainers who kept improving it without me. I
think of that as the project graduating. Special kudos to &lt;a href=&quot;https://github.com/appleboy&quot;&gt;Bo-Yi Wu&lt;/a&gt; and
&lt;a href=&quot;https://github.com/javierprovecho&quot;&gt;Javier Provecho&lt;/a&gt;, who carried it forward and kept the bar high.&lt;/p&gt;
&lt;p&gt;That’s the part of open source I respect most, when a project stops needing its author. Gin has since
absorbed years of other people’s use cases, priorities, and taste, and it came out better for it.&lt;/p&gt;
&lt;p&gt;If you’re building a library, that’s the bar I’d aim for. Design an API you can imagine keeping for
ten years, and make it simple underneath even when that costs more than making it easy, because with
any luck the thing will outgrow you.&lt;/p&gt;
&lt;p&gt;I tried to do the same thing a few years later on a compiler, which is the story of
&lt;a href=&quot;https://manualmeida.dev/articles/qwik-resumability&quot;&gt;Qwik&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href=&quot;https://manualmeida.dev/articles/gin-simple-over-easy&quot;&gt;This article has interactive figures — read it on manualmeida.dev.&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</content:encoded><dc:creator>Manu Martínez-Almeida</dc:creator><category>Open Source</category><category>go</category><category>performance</category><category>http</category><category>open source</category></item><item><title>Half-Truths</title><link>https://manualmeida.dev/articles/notes-i-keep/</link><guid isPermaLink="true">https://manualmeida.dev/articles/notes-i-keep/</guid><description>A text file of one-liners I&apos;ve muttered at a terminal over the years. Half of them are jokes and several contradict each other on purpose. None of them are advice.</description><pubDate>Tue, 28 Jun 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;On writing as little as possible&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Software is best without classes, or functions, or code. But if you must, write as little as possible.&lt;/li&gt;
&lt;li&gt;The only thing that really matters in software is KISS.&lt;/li&gt;
&lt;li&gt;That is classic over-abstraction.&lt;/li&gt;
&lt;li&gt;When in doubt, add &lt;code&gt;setTimeout&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;No hack is complete without &lt;code&gt;.replace()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;try&lt;/code&gt;/&lt;code&gt;catch&lt;/code&gt; is for cowards.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;On shipping&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Never merge a PR.&lt;/li&gt;
&lt;li&gt;If you’re afraid to release on Friday, the problem isn’t Friday.&lt;/li&gt;
&lt;li&gt;If you can’t release fast, you have tech debt to solve.&lt;/li&gt;
&lt;li&gt;Precommit hooks are the root of all evil.&lt;/li&gt;
&lt;li&gt;The difference between a hobby and a job is that you don’t write documentation for the former.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;On things one if-statement away from disaster&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Almost all software is one if-statement away from disaster.&lt;/li&gt;
&lt;li&gt;If you code just to make things work, one day suddenly nothing will work.&lt;/li&gt;
&lt;li&gt;Dependencies are the root of all evil.&lt;/li&gt;
&lt;li&gt;Developers will always find unexpected ways to use your API.&lt;/li&gt;
&lt;li&gt;There are two hard things in computer science: cache invalidation and symlinks.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;On knowing better&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Debugging skills are underrated.&lt;/li&gt;
&lt;li&gt;You are only an expert in a given technology when you know when &lt;em&gt;not&lt;/em&gt; to use it.&lt;/li&gt;
&lt;li&gt;A good engineer balances not underestimating apparently simple tasks against not overengineering
apparently complex ones.&lt;/li&gt;
&lt;li&gt;An experienced developer is the one who knows when to be volatile and when to be stable. (I figured
it out, by the way: a stable is someone who installs linting build scripts, and a volatile is
someone who disables them.)&lt;/li&gt;
&lt;li&gt;I love monorepos, but I hate lerna.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;If you got this far looking for a coherent philosophy, I’m sorry. The honest version is that good
engineering is mostly knowing which of these to ignore today.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Disclaimer: a good number of these were forged in long, unhinged rants with
&lt;a href=&quot;https://x.com/adamdbradley&quot;&gt;Adam Bradley&lt;/a&gt;. I’ll take half the blame and none of the credit.&lt;/em&gt;&lt;/p&gt;
</content:encoded><dc:creator>Manu Martínez-Almeida</dc:creator><category>Notes</category><category>notes</category><category>opinions</category><category>humor</category></item></channel></rss>