<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="/feeds/atom-style.xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://annalhq.vercel.app</id>
    <title>ann</title>
    <updated>2026-11-07T09:53:12.309Z</updated>
    <generator>Feed Generator</generator>
    <author>
        <name>annalhq</name>
        <uri>https://annalhq.vercel.app</uri>
    </author>
    <link rel="alternate" href="https://annalhq.vercel.app"/>
    <link rel="self" href="https://annalhq.vercel.app/atom.xml"/>
    <subtitle>mich</subtitle>
    <rights>Copyright © 2026 annalhq</rights>
    <entry>
        <title type="html"><![CDATA[Monads in Programming]]></title>
        <id>https://annalhq.vercel.app/monad</id>
        <link href="https://annalhq.vercel.app/monad"/>
        <updated>2026-10-08T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[As Douglas Crockford once mentioned in his talk, once you understand the monads, you lose the ability to explain them. The word itself tends to obscure what is, at its core, a remarkably practical ide...]]></summary>
        <content type="html"><![CDATA[<p>As Douglas Crockford once mentioned in his <a href="https://www.youtube.com/watch?v=dkZFtimgAcM">talk</a>, once you understand the monads, you lose the ability to explain them. The word itself tends to obscure what is, at its core, a remarkably practical idea about composing computations.</p>
<h2>Where the word came from</h2>
<p>Monads came out of algebraic topology, not programming. In the late 1950s, Roger Godement described a construction he called the <em>standard construction</em>[^1]: a functor $T$ together with two natural transformations that let you wrap things up and flatten double wrappings. Topologists used it to build resolutions for computing cohomology. It was a tool, not yet a subject.</p>
<p>Through the 1960s the structure got several names. Peter Huber and others called it a <em>triple</em>, because it consists of three things: a functor and two transformations. In 1965 two papers appeared almost back to back, one by Heinrich Kleisli[^2] and one by Samuel Eilenberg and John Moore[^3], and they answered the same question in two different ways: <em>does every monad come from a pair of adjoint functors?</em> (Yes, and we’ll see both answers below.)</p>
<p>The word <em>monad</em> itself is usually credited to Jean Bénabou, and the story that gets told is that it came up around the category theory meetings of the mid-60s, where people were tired of “triple” and “standard construction” and wanted something better. It’s a blend of <em>monoid</em> and <em>triad</em>, and there’s a nod to Leibniz’s <em>Monadology</em> in it too. Saunders Mac Lane picked the name for <em>Categories for the Working Mathematician</em>[^4] in 1971, and that pretty much settled it, though you’ll still find “triple” in older papers.</p>
<p>:::margin
Leibniz’s monads were indivisible units that make up the world. A category theorist’s monad is about as far from that as it gets, but the name stuck.
:::</p>
<p>Then nothing much happened in programming for about twenty years. In 1989 Eugenio Moggi[^5] pointed out that many “impure” features of programs (exceptions, state, nondeterminism, input/output) could be described uniformly as monads, and that this gave a clean semantics for them. Philip Wadler[^6] took Moggi’s idea and showed functional programmers how to <em>write code</em> with it. By 1996, Haskell 1.3 used monads for I/O, and the idea escaped into the wider world.</p>
<h2>What is a monad, informally</h2>
<p>A lot of the time, functions don’t just return a value. They return a value <em>plus something</em>: a value that might be missing, a value with a log attached, a value that depends on some state, a list of possible values. Call that “something” a context, and write $T A$ for “a value of type $A$ in context $T$”.</p>
<p>A monad is what you need to <em>chain</em> functions like this together without the context getting in your way. That needs exactly two things:</p>
<ol>
<li>A way to put a plain value into the context, without doing anything interesting. Call it $\eta$ (in code: <em>return</em> or <em>pure</em>).</li>
<li>A way to take a context-wrapped value and feed it into a function that produces another context-wrapped value. That’s <em>bind</em>, and we’ll spend a whole section on it.</li>
</ol>
<p>Before going further, let’s look at two concrete examples, the abstract version will make much more sense once you’ve seen it in practice.</p>
<h3>The Maybe monad</h3>
<p>The simplest context is “this might not be there”. Mathematically, given a type $A$, we add one extra point to it, call it <em>nothing</em>:</p>
<p>$$
\text{Maybe},A = A + 1
$$</p>
<p>Here $+$ is disjoint union and $1$ is a one-element set. A value of $\text{Maybe},A$ is either $\text{just}(a)$ for some $a \in A$, or $\text{nothing}$.</p>
<p>Putting a value in the context is obvious: $\eta(a) = \text{just}(a)$.</p>
<p>Chaining is where it earns its keep. Suppose I have</p>
<p>$$
f : A \to \text{Maybe},B, \qquad g : B \to \text{Maybe},C
$$</p>
<p>We can’t just compose them as $g \circ f$, because $f$ hands back a $\text{Maybe},B$ and $g$ wants a plain $B$. What we want is: if $f$ gave us $\text{just}(b)$, run $g$ on $b$; if $f$ gave us $\text{nothing}$, stop and return $\text{nothing}$. That rule is the entire Maybe monad. It’s the “early return on null” pattern you’ve written a thousand times, written once.</p>
<h3>The State monad, and a detour through currying</h3>
<p>The second example needs a small detour, because it relies on currying, and currying turns out to matter a lot later.</p>
<p><img src="https://annalhq.vercel.app/_vercel/image?url=_astro%2Fcurrying.BjOUIRiB.png&amp;w=828&amp;q=100" alt="Currying transforms a multi-argument function into a sequence of single-argument function applications, where each intermediate application returns a function awaiting the remaining arguments." /></p>
<p>Currying is the observation that a function of two arguments is the same thing as a function that takes the first argument and returns a function of the second:</p>
<p>$$
\text{Hom}(A \times S,\ C) ;\cong; \text{Hom}(A,\ C^S)
$$</p>
<p>Here $C^S$ means “functions from $S$ to $C$”. Going from left to right is called currying, going back is uncurrying. In category theory language, this says the functor $- \times S$ is <em>left adjoint</em> to the functor $(-)^S$. Keep that word, <em>adjoint</em>, in the back of your mind; it’s coming back.</p>
<p>Now, a stateful computation is one that reads some state $s \in S$, produces a result, and produces a new state. As a pure function:</p>
<p>$$
\text{State}_S,A = S \to (A \times S) = (A \times S)^S
$$</p>
<p>Look at the shape of that: it’s $(- \times S)$ followed by $(-)^S$. It is literally the two halves of the currying adjunction glued together. That is not a coincidence, and it’s my favourite fact in this whole topic: <strong>every adjunction gives you a monad</strong>, and the State monad is the one that currying gives you.</p>
<p>For State, $\eta$ returns a value without touching the state:</p>
<p>$$
\eta(a) = \lambda s.\ (a, s)
$$</p>
<p>and chaining runs the first computation, takes the result and the <em>new</em> state, and hands both to the next one. You get the feeling of mutable state while everything stays a pure function.</p>
<h2>The mathematics</h2>
<h3>Definition</h3>
<p>Let $\mathcal{C}$ be a category; for programmers, think of it as “types and functions”. A <strong>monad</strong> on $\mathcal{C}$ is a triple $(T, \eta, \mu)$ where</p>
<ul>
<li>$T : \mathcal{C} \to \mathcal{C}$ is a functor (it sends a type $A$ to $T A$, and a function $f : A \to B$ to $Tf : TA \to TB$; this is <em>map</em>),</li>
<li>$\eta : \text{Id} \Rightarrow T$ is a natural transformation called the <strong>unit</strong>, with components $\eta_A : A \to TA$,</li>
<li>$\mu : T^2 \Rightarrow T$ is a natural transformation called the <strong>multiplication</strong>, with components $\mu_A : TTA \to TA$ (this is <em>flatten</em> or <em>join</em>).</li>
</ul>
<p>They have to satisfy three laws. Associativity:</p>
<p>$$
\mu_A \circ T\mu_A ;=; \mu_A \circ \mu_{TA} \quad : TTTA \to TA
$$</p>
<p>and the two unit laws:</p>
<p>$$
\mu_A \circ T\eta_A ;=; \text{id}<em>{TA} ;=; \mu_A \circ \eta</em>{TA}
$$</p>
<p>Read them in words. Associativity says that if you have three layers of context, it doesn’t matter whether you flatten the inner two first or the outer two first. The unit laws say that wrapping something in a trivial layer and then flattening gives you back what you started with, whichever side you add the layer on.</p>
<p>If those look like the laws of a monoid ($(xy)z = x(yz)$ and $1x = x = x1$), that’s because they are. A monad is a monoid where the elements are functors, “multiplication” is composition of functors, and the unit is the identity functor. Hence the infamous line: <em>a monad is just a monoid in the category of endofunctors</em>. It sounds like a joke, but it’s actually a correct and fairly compact definition.</p>
<p>:::margin
This line is from Mac Lane, more or less. It became a meme thanks to James Iry’s <em>A Brief, Incomplete, and Mostly Wrong History of Programming Languages</em>.
:::</p>
<p>Checking against Maybe: $\mu$ takes $\text{Maybe}(\text{Maybe},A) = (A + 1) + 1$ and collapses it to $A + 1$ by sending $\text{just}(\text{just}(a))$ to $\text{just}(a)$ and both kinds of $\text{nothing}$ to $\text{nothing}$. You can check the laws by hand in a minute.</p>
<p>For lists: $T A$ is finite lists of $A$, $\eta(a) = [a]$, and $\mu$ concatenates a list of lists. Associativity of $\mu$ is just associativity of concatenation.</p>
<h3>The Kleisli category</h3>
<p>Here’s the first big idea. In programming, we rarely work with $\mu$ directly. We work with functions of the form</p>
<p>$$
f : A \to TB
$$</p>
<p>“functions that return a value in context”. Kleisli’s insight was that these functions form a category of their own, where the context is handled automatically.</p>
<p>The <strong>Kleisli category</strong> $\mathcal{C}_T$ has:</p>
<ul>
<li>the same objects as $\mathcal{C}$,</li>
<li>a morphism $A \to B$ in $\mathcal{C}_T$ is a morphism $A \to TB$ in $\mathcal{C}$,</li>
<li>the identity on $A$ is $\eta_A : A \to TA$,</li>
<li>composition of $f : A \to TB$ and $g : B \to TC$ is</li>
</ul>
<p>$$
g \circ_T f ;=; \mu_C \circ Tg \circ f \quad : A \to TC
$$</p>
<p>Read the composition right to left: run $f$ to get a $TB$; map $g$ over it to get a $TTC$; flatten with $\mu$ to get a $TC$. That’s exactly the “chain these context-returning functions” operation from the Maybe example, now stated once for every monad. People write it as $g \mathrel{&lt;!=!&lt;} f$, or $f \mathrel{&gt;!=!&gt;} g$ in the other order, and call it the <em>fish operator</em>.</p>
<p>The monad laws are exactly what make this a category. Associativity of $\mu$ gives associativity of $\circ_T$, and the unit laws make $\eta$ an identity:</p>
<p>$$
\eta_B \circ_T f = f, \qquad f \circ_T \eta_A = f, \qquad h \circ_T (g \circ_T f) = (h \circ_T g) \circ_T f
$$</p>
<p>This is, I think, the cleanest way to say what monads give programmers: <strong>a monad turns effectful functions into something you can compose like ordinary functions, with the same laws.</strong> That’s all. The rest is notation.</p>
<p>Kleisli also showed how this answers the 1965 question. There’s a functor $F_T : \mathcal{C} \to \mathcal{C}_T$ that sends a plain function $f$ to $\eta \circ f$, and a functor $U_T : \mathcal{C}_T \to \mathcal{C}$ that sends $A$ to $TA$. These are adjoint, $F_T \dashv U_T$, and the monad you get back from them is the original $T$.</p>
<h3>Monad algebras</h3>
<p>The second construction asks a different question. Instead of “what are the functions in context?”, it asks: <em>what does it mean to be able to evaluate a context?</em></p>
<p>An <strong>algebra</strong> for the monad $T$ (often called a $T$-algebra) is an object $A$ together with a morphism</p>
<p>$$
a : TA \to A
$$</p>
<p>that collapses the context, in a way that respects the monad:</p>
<p>$$
a \circ \eta_A = \text{id}_A, \qquad a \circ Ta = a \circ \mu_A
$$</p>
<p>The first law says: if I wrap a value trivially and then evaluate, I get the value back. The second says: if I have two layers, I can evaluate the inner layer then the outer, or flatten first and evaluate once, and I get the same answer.</p>
<p>This is easiest to feel with the list monad. A list-algebra is a set $A$ with a map $a : \text{List},A \to A$ that turns any list of elements into a single element, consistently. Take $A = \mathbb{N}$ and $a = \text{sum}$:</p>
<p>$$
\text{sum}([n]) = n, \qquad \text{sum}([\text{sum}(xs_1), \dots, \text{sum}(xs_k)]) = \text{sum}(xs_1 \mathbin{+!!+} \cdots \mathbin{+!!+} xs_k)
$$</p>
<p>Both laws hold. In fact, list-algebras are <em>exactly</em> monoids: give me a monoid and folding with its operation is an algebra; give me an algebra and $x \cdot y = a([x, y])$ with unit $a([,])$ is a monoid. Similarly, Maybe-algebras are <em>pointed sets</em>: the algebra has to say where $\text{nothing}$ goes, and that’s just picking a default element. If you’ve ever written “value or default”, you’ve written a Maybe-algebra.</p>
<p>A morphism of algebras $h : (A, a) \to (B, b)$ is a function $h : A \to B$ that commutes with evaluation:</p>
<p>$$
h \circ a = b \circ Th
$$</p>
<p>For lists, that’s a monoid homomorphism.</p>
<h3>The Eilenberg–Moore category</h3>
<p>The algebras and their morphisms form a category, written $\mathcal{C}^T$ and called the <strong>Eilenberg–Moore category</strong>. Like Kleisli’s, it comes with an adjunction:</p>
<p>$$
F^T : \mathcal{C} \to \mathcal{C}^T, \quad A \mapsto (TA, \mu_A)
$$</p>
<p>$$
U^T : \mathcal{C}^T \to \mathcal{C}, \quad (A, a) \mapsto A
$$</p>
<p>$U^T$ forgets the algebra structure. $F^T$ sends $A$ to the <strong>free algebra</strong> on $A$, which is $TA$ evaluated by flattening. For lists, the free algebra on $A$ is the free monoid on $A$, which is just lists of $A$ under concatenation. Again $F^T \dashv U^T$, and again the monad you get back is $T$.</p>
<p>So we have two adjunctions that both produce $T$, and here is the beautiful part. Among <em>all</em> adjunctions that give rise to $T$, Kleisli’s is the smallest (it’s the initial one) and Eilenberg–Moore’s is the largest (it’s the terminal one). Every other adjunction producing $T$ sits between them. And the Kleisli category sits inside the Eilenberg–Moore one: it’s equivalent to the full subcategory of free algebras.</p>
<p>:::margin
An adjunction whose comparison functor to the Eilenberg–Moore category is an equivalence is called <em>monadic</em>. Groups over sets are monadic; this is why “a group is an algebra for a monad” is a meaningful statement.
:::</p>
<p>The way I think about it:</p>
<ul>
<li>the <strong>Kleisli</strong> category is about <em>computations</em>: arrows $A \to TB$, things that produce effects,</li>
<li>the <strong>Eilenberg–Moore</strong> category is about <em>handlers</em>: maps $TA \to A$, things that consume effects and give you a plain answer.</li>
</ul>
<p>That split is exactly how modern effect systems and algebraic effects are designed: a program is a Kleisli arrow, a handler is an algebra. Fifty-year-old mathematics, sitting quietly underneath.</p>
<h2>Bind</h2>
<p>Now back to code. Programmers rarely write $\mu$ or Kleisli composition directly. They write <strong>bind</strong>, usually spelled $\gg!=$:</p>
<p>$$
(\gg!=) : TA \to (A \to TB) \to TB
$$</p>
<p>$$
m \gg!= f ;=; \mu_B\big((Tf)(m)\big)
$$</p>
<p>Notice the currying again: bind takes a wrapped value and gives back a function that wants a continuation $f$. It says: <em>here’s a value in context, and here’s what to do with the plain value once you have it; you figure out the context.</em></p>
<p>Bind, $\mu$ and Kleisli composition are all interdefinable, so a monad can be given by any of them. From bind you recover the others:</p>
<p>$$
\mu(mm) = mm \gg!= \text{id}, \qquad Tf(m) = m \gg!= (\eta \circ f), \qquad (f \mathrel{&gt;!=!&gt;} g)(a) = f(a) \gg!= g
$$</p>
<p>And the monad laws, rewritten in terms of bind, become the three laws you’ll see in every Haskell tutorial:</p>
<p>$$
\eta(a) \gg!= f ;=; f(a) \qquad \text{(left identity)}
$$</p>
<p>$$
m \gg!= \eta ;=; m \qquad \text{(right identity)}
$$</p>
<p>$$
(m \gg!= f) \gg!= g ;=; m \gg!= (\lambda x.\ f(x) \gg!= g) \qquad \text{(associativity)}
$$</p>
<p>They’re the same laws as before, just seen from the point of view of someone writing a pipeline. Left and right identity say $\eta$ doesn’t do anything. Associativity says that how you group the steps of a pipeline doesn’t matter, which is what lets you refactor a long chain into helper functions without changing its meaning.</p>
<h2>Bind in TypeScript</h2>
<p>TypeScript doesn’t have higher-kinded types, so we can’t write one generic Monad interface the way Haskell does. That’s fine. Writing a couple of concrete monads by hand is more illuminating anyway.</p>
<p>Here’s Maybe:</p>
<pre><code class="language-ts">type Maybe&lt;A&gt; = { kind: 'just'; value: A } | { kind: 'nothing' }

const just = &lt;A&gt;(value: A): Maybe&lt;A&gt; =&gt; ({ kind: 'just', value })
const nothing: Maybe&lt;never&gt; = { kind: 'nothing' }

// η
const pure = just

// &gt;&gt;=
const bind = &lt;A, B&gt;(m: Maybe&lt;A&gt;, f: (a: A) =&gt; Maybe&lt;B&gt;): Maybe&lt;B&gt; =&gt;
  m.kind === 'nothing' ? m : f(m.value)
</code></pre>
<p>That’s the entire monad. Now some functions that can fail:</p>
<pre><code class="language-ts">const parseNumber = (s: string): Maybe&lt;number&gt; =&gt; {
  const n = Number(s)
  return Number.isNaN(n) ? nothing : just(n)
}

const reciprocal = (n: number): Maybe&lt;number&gt; =&gt;
  n === 0 ? nothing : just(1 / n)

const safeSqrt = (n: number): Maybe&lt;number&gt; =&gt;
  n &lt; 0 ? nothing : just(Math.sqrt(n))
</code></pre>
<p>And chaining them:</p>
<pre><code class="language-ts">const compute = (input: string): Maybe&lt;number&gt; =&gt;
  bind(bind(parseNumber(input), reciprocal), safeSqrt)

compute('4')    // just(0.5)
compute('0')    // nothing   (division by zero)
compute('-4')   // nothing   (negative square root)
compute('hey')  // nothing   (not a number)
</code></pre>
<p>No null checks in the pipeline. Each function only deals with its own failure case, and bind handles the plumbing. If you’ve used optional chaining, or the then method on promises, you’ve been using something monad-shaped already. (Promises aren’t a lawful monad, because they flatten nested promises automatically, but they’re close enough that the intuition carries over.)</p>
<p>Here’s State, which is where the currying story pays off:</p>
<pre><code class="language-ts">type State&lt;S, A&gt; = (s: S) =&gt; [A, S]

const pureS = &lt;S, A&gt;(a: A): State&lt;S, A&gt; =&gt; (s) =&gt; [a, s]

const bindS =
  &lt;S, A, B&gt;(m: State&lt;S, A&gt;, f: (a: A) =&gt; State&lt;S, B&gt;): State&lt;S, B&gt; =&gt;
  (s) =&gt; {
    const [a, s1] = m(s)
    return f(a)(s1)
  }

const get = &lt;S&gt;(): State&lt;S, S&gt; =&gt; (s) =&gt; [s, s]
const put = &lt;S&gt;(s: S): State&lt;S, void&gt; =&gt; () =&gt; [undefined, s]
</code></pre>
<p>A tiny example: generating fresh variable names, the way a compiler would.</p>
<pre><code class="language-ts">const fresh: State&lt;number, string&gt; = bindS(get&lt;number&gt;(), (n) =&gt;
  bindS(put(n + 1), () =&gt; pureS(`x${n}`)),
)

const threeNames = bindS(fresh, (a) =&gt;
  bindS(fresh, (b) =&gt;
    bindS(fresh, (c) =&gt; pureS&lt;number, string[]&gt;([a, b, c])),
  ),
)

threeNames(0) // [['x0', 'x1', 'x2'], 3]
</code></pre>
<p>There’s no mutable counter anywhere. Every function is pure, and the state is threaded through by bind. The nesting is a bit ugly, though, and that ugliness is exactly what Haskell’s syntax was designed to get rid of.</p>
<h2>Monads in Haskell</h2>
<p>In Haskell, monad is a type class, and $\eta$ and bind are its methods:</p>
<pre><code class="language-haskell">class Applicative m =&gt; Monad m where
  (&gt;&gt;=)  :: m a -&gt; (a -&gt; m b) -&gt; m b
  return :: a -&gt; m a
  return = pure
</code></pre>
<p>Compare the type of bind to the math: $TA \to (A \to TB) \to TB$. It’s the same thing, character for character. The Applicative superclass is there because every monad is also a functor (that’s $T$ acting on functions) and an applicative (which lets you combine independent computations), and Haskell asks you to provide those too.</p>
<p>Maybe is in the standard library, but here’s what its instance looks like:</p>
<pre><code class="language-haskell">instance Monad Maybe where
  Nothing &gt;&gt;= _ = Nothing
  Just a  &gt;&gt;= f = f a
</code></pre>
<p>The real treat is <strong>do-notation</strong>, which is syntactic sugar for chains of bind:</p>
<pre><code class="language-haskell">compute :: String -&gt; Maybe Double
compute input = do
  n &lt;- readMaybe input
  r &lt;- reciprocal n
  safeSqrt r
</code></pre>
<p>The compiler turns that into</p>
<pre><code class="language-haskell">readMaybe input &gt;&gt;= \n -&gt; reciprocal n &gt;&gt;= \r -&gt; safeSqrt r
</code></pre>
<p>Each arrow in do-notation is a bind, and the rest of the block is the continuation. It reads like imperative code, but it’s a pure expression, and the monad decides what “next line” means. For Maybe, “next line” means “if we didn’t fail”. For lists, it means “for each possibility”. For State, it means “with the updated state”.</p>
<p>Here’s the fresh-name example again, with a State monad written from scratch:</p>
<pre><code class="language-haskell">newtype State s a = State { runState :: s -&gt; (a, s) }

instance Functor (State s) where
  fmap f (State g) = State $ \s -&gt; let (a, s') = g s in (f a, s')

instance Applicative (State s) where
  pure a = State $ \s -&gt; (a, s)
  State f &lt;*&gt; State g = State $ \s -&gt;
    let (h, s1) = f s
        (a, s2) = g s1
    in (h a, s2)

instance Monad (State s) where
  State g &gt;&gt;= f = State $ \s -&gt;
    let (a, s') = g s
    in runState (f a) s'

get :: State s s
get = State $ \s -&gt; (s, s)

put :: s -&gt; State s ()
put s = State $ \_ -&gt; ((), s)

fresh :: State Int String
fresh = do
  n &lt;- get
  put (n + 1)
  return ("x" ++ show n)

threeNames :: State Int [String]
threeNames = do
  a &lt;- fresh
  b &lt;- fresh
  c &lt;- fresh
  return [a, b, c]

-- runState threeNames 0 == (["x0","x1","x2"], 3)
</code></pre>
<p>Compare this to the TypeScript version. Same idea, same bind, but the nesting is gone.</p>
<p>And finally, the one that made Haskell take monads seriously: IO. A value of type IO a is a <em>description</em> of a program that, when run, interacts with the world and produces an $a$. Bind lets you sequence these descriptions without ever leaving pure code:</p>
<pre><code class="language-haskell">main :: IO ()
main = do
  putStrLn "what's your name?"
  name &lt;- getLine
  putStrLn ("hello, " ++ name)
</code></pre>
<p>Conceptually, IO is the State monad where the state is the entire outside world. You can’t copy the world or go back in time with it, and the monad structure makes sure you never try.</p>
<h2>Further Reading</h2>
<ol>
<li>Eric Lippert’s <a href="https://ericlippert.com/2013/02/21/monads-part-one/">blog series</a> on monad</li>
<li><a href="https://blog.ploeh.dk/2022/03/28/monads/">Monads by Mark Seeman</a> (this is part of his series of blogs on <a href="https://blog.ploeh.dk/2017/10/04/from-design-patterns-to-category-theory/">design patterns and category theory</a>)</li>
<li>Jeremy Bowers’ blog on <a href="https://www.jerf.org/iri/post/2958/">Monads</a></li>
</ol>
<p>[^1]: <a href="https://en.wikipedia.org/wiki/Monad_(category_theory)#History">Godement, R. (1958). <em>Topologie algébrique et théorie des faisceaux</em>. Hermann.</a></p>
<p>[^2]: <a href="https://www.ams.org/journals/proc/1965-016-03/S0002-9939-1965-0177024-4/">Kleisli, H. (1965). Every standard construction is induced by a pair of adjoint functors. <em>Proceedings of the AMS</em>, 16(3), 544–546.</a></p>
<p>[^3]: <a href="https://projecteuclid.org/journals/illinois-journal-of-mathematics/volume-9/issue-3/Adjoint-functors-and-triples/10.1215/ijm/1256068141.full">Eilenberg, S., &amp; Moore, J. C. (1965). Adjoint functors and triples. <em>Illinois Journal of Mathematics</em>, 9(3), 381–398.</a></p>
<p>[^4]: <a href="https://link.springer.com/book/10.1007/978-1-4757-4721-8">Mac Lane, S. (1971). <em>Categories for the Working Mathematician</em>. Springer.</a></p>
<p>[^5]: <a href="https://person.dibris.unige.it/moggi-eugenio/ftp/ic91.pdf">Moggi, E. (1991). Notions of computation and monads. <em>Information and Computation</em>, 93(1), 55–92.</a></p>
<p>[^6]: <a href="https://homepages.inf.ed.ac.uk/wadler/papers/essence/essence.ps">Wadler, P. (1992). The essence of functional programming. <em>POPL '92</em>.</a></p>
]]></content>
        <published>2026-10-05T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[Reversing bits using gf2p8affineqb]]></title>
        <id>https://annalhq.vercel.app/gf2p8affineqb</id>
        <link href="https://annalhq.vercel.app/gf2p8affineqb"/>
        <updated>2026-09-01T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[Instructions from Intel GFNI (Galois Field New instructions) designed to perform affine transformations on packed 8-bit elements. Introduction The instruction name can broken into fairly understandabl...]]></summary>
        <content type="html"><![CDATA[<p>Instructions from Intel <code>GFNI</code> (Galois Field New instructions) designed to perform affine transformations on packed 8-bit elements.</p>
<h2>Introduction</h2>
<p>The instruction name can broken into fairly understandable bits</p>
<p><code>GF2-P8-AFFINE-Q-B</code>, where</p>
<ol>
<li><strong>GF2</strong> stands for <a href="https://en.wikipedia.org/wiki/GF(2)">$\mathbb{GF}(2)$</a>, the Galois field with two elements</li>
<li><strong>P8</strong> is for power of 8 ($2^8$). It specifies the field sizea as it works with 8-bit bytes, so operation operating within the finite field $\mathbb{GF}(2^8)$</li>
<li><strong>Affine</strong> indicates affine transformations, which means it multiplies and 8-bit element by an 8 $\times$ 8 bit matrix and then adds (XORs) and 8-bit vector constant.</li>
<li><strong>Qword variant</strong>, the transformation matrix is supplied as a <em>qword</em> 64-bit; each 64-bit chunk contains the 8 $\times$ 8 matrix representations</li>
<li><strong>B</strong>, the elements being transforrmed are <em>bytes</em>.</li>
</ol>
<p>A high level interpretation is: perform an affine transformations over $\mathbb{GF}(2)$ on packed 8-bit elements.</p>
<p>Here each byte is treated as an 8-bit vector over $\mathbb{GF}(2)$. Mathematically operation can be represented as:
$$
y = Ax \oplus b
$$
,the matmul is operated over Galois field, whichm means instruction is essentially performing 8 XOR equations in parallel for every byte.</p>
<h2>A byte as a vector over 8-bit Galois field</h2>
<p>Consider an 8-bit byte</p>
<p>$$
x = x_7x_6x_5x_4x_3x_2x_1x_0
$$</p>
<p>where each bit satisfies</p>
<p>$$
x_i \in \mathbb{GF}(2)={0,1}
$$</p>
<p>We can represent the byte as an 8-dimensional vector over $\mathbb{GF}(2)$:</p>
<h1>$$
\mathbf{x}</h1>
<p>\begin{bmatrix}
x_7\
x_6\
x_5\
x_4\
x_3\
x_2\
x_1\
x_0
\end{bmatrix}
\in \mathbb{GF}(2)^8
$$</p>
<p>An $8\times8$ matrix whose entries are elements of $GF(2)$ can then operate on this vector:</p>
<p>$$
A \in \mathbb{GF}(2)^{8\times8}
$$</p>
<p>The resulting vector is</p>
<p>$$
\mathbf{y}=A\mathbf{x}
$$</p>
<p>Because the arithmetic is performed over $\mathbb{GF}(2)$, addition is XOR and multiplication is AND.</p>
<p>For example, if</p>
<p>$$
A=
\begin{bmatrix}
1&amp;0&amp;1&amp;0&amp;0&amp;0&amp;0&amp;0\
0&amp;1&amp;0&amp;1&amp;0&amp;0&amp;0&amp;0\
0&amp;0&amp;1&amp;0&amp;1&amp;0&amp;0&amp;0\
0&amp;0&amp;0&amp;1&amp;0&amp;1&amp;0&amp;0\
0&amp;0&amp;0&amp;0&amp;1&amp;0&amp;1&amp;0\
0&amp;0&amp;0&amp;0&amp;0&amp;1&amp;0&amp;1\
1&amp;0&amp;0&amp;0&amp;0&amp;0&amp;1&amp;0\
0&amp;1&amp;0&amp;0&amp;0&amp;0&amp;0&amp;1
\end{bmatrix}
$$</p>
<p>then</p>
<h1>$$
\mathbf{y}</h1>
<p>\begin{bmatrix}
x_7\oplus x_5\
x_6\oplus x_4\
x_5\oplus x_3\
x_4\oplus x_2\
x_3\oplus x_1\
x_2\oplus x_0\
x_7\oplus x_1\
x_6\oplus x_0
\end{bmatrix}
$$</p>
<p>Thus, an $8\times8$ binary matrix can be viewed as eight XOR equations, one for each output bit.</p>
<p>For an affine transformation, an additional constant vector $\mathbf{b}\in \mathbb{GF}(2)^8$ is added:</p>
<p>$$
\mathbf{y}=A\mathbf{x}\oplus\mathbf{b}
$$</p>
<hr />
<p>just a draft, will continue later</p>
]]></content>
        <published>2026-09-01T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[The Gumbel-Softmax Trick: Making Discrete Sampling Differentiable]]></title>
        <id>https://annalhq.vercel.app/gumbel</id>
        <link href="https://annalhq.vercel.app/gumbel"/>
        <updated>2025-06-17T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[A problem that haunted generative modeling for years: how do you backpropagate through a discrete sampling operation? This post explores how a simple reparameterization enables gradients to flow throu...]]></summary>
        <content type="html"><![CDATA[<p>A problem that haunted generative modeling for years: how do you backpropagate through a discrete sampling operation?</p>
<p>This post explores how a simple reparameterization enables gradients to flow through categorical sampling, unlocking end-to-end training with discrete latent variables.</p>
<p>In models like DALL-E[^1], the goal is to maximize the joint likelihood $\ln p_{\theta,\psi}(x,y)$ of images and text. Using the standard ELBO derivation, we get a familiar-looking lower bound:</p>
<p>$$
ln p_{\theta,\psi}(x,y) \geq \mathbb{E}<em>{z \sim q</em>{\phi}(z|x)}[\ln p_{\theta}(x|y,z)] - \beta D_{KL}(q_{\phi}(y,z|x) \parallel p_{\psi}(y,z))
$$</p>
<p>Here’s the twist: unlike a VAE’s continuous latent space, (z) here is sampled from a <em>categorical</em> distribution $q_{\phi}(z|x)$. It’s discrete. And that breaks everything. The sampling operation creates a hard stop in the computational graph—no gradients can flow back through it. So while we could compute the ELBO, we couldn’t optimize it end-to-end.</p>
<p>The solution? A clever reparameterization trick, but for categoricals instead of Gaussians. That’s where the Gumbel-Softmax comes in.</p>
<h3>The Core Idea: Continuous Relaxation</h3>
<p>The key insight comes from Maddison et al.'s Concrete distribution[^2]. Instead of sampling a one-hot vector directly, we sample from a continuous distribution over the simplex $\Delta^{k-1}$ (the set of $k$-dimensional probability vectors). Think of it as sampling <em>near</em> the vertices of a simplex, where each vertex represents a pure category.</p>
<p>The process is surprisingly elegant. Given unnormalized logits $\pi_1, \dots, \pi_k$:</p>
<ol>
<li><strong>Add Gumbel noise</strong>: Sample $g_i \sim \text{Gumbel}(0,1)$ and add it to each $\log \pi_i$. This creates perturbed logits that are stochastic but still differentiable.</li>
<li><strong>Softmax with temperature</strong>: Apply a tempered softmax to get a “soft” one-hot vector:</li>
</ol>
<p>$$
X_i = \frac{\exp\left(\log \pi_i + g_i\right)/\tau}{\sum_{j=1}^k \exp\left(\log \pi_j + g_j\right)/\tau}
$$</p>
<p>The temperature $\tau &gt; 0$ controls how sharply the distribution peaks. As $\tau \to 0$, the samples approach true one-hot vectors; as $\tau$ grows, they become more uniform. During training, you typically anneal $\tau$ from high to low, trading off gradient variance for fidelity.</p>
<p>The resulting Concrete distribution over the simplex has this joint density:</p>
<p>$$
p_{\pi,\tau}(X) = (k-1)! , \tau^{k-1} \prod_{i=1}^k \left(\frac{\pi_i X_i^{-\tau-1}}{\sum_{j=1}^k \pi_j X_j^{-\tau}}\right)
$$</p>
<p>The formula is less important than what it <em>does</em>: it gives us a way to sample from a categorical distribution while keeping the gradients intact.</p>
<p>In practice we often use the Gumbel–Softmax (Concrete) relaxation during training to obtain differentiable, soft one-hot samples (with a temperature schedule), then discretize (argmax) at inference; alternatively, people use a straight-through estimator (hard forward, soft backward) during training. This small hack enables many models with discrete choices VAEs with discrete latents, hard-attention models, etc. though large text-to-image systems sometimes rely on discrete codebooks or other techniques instead of Gumbel relaxations.</p>
<p>[^1]: <a href="https://arxiv.org/pdf/2102.12092.pdf">Ramesh, A., et al. (2021). Zero-Shot Text-to-Image Generation*. OpenAI.</a></p>
<p>[^2]: <a href="https://arxiv.org/pdf/1611.00712.pdf">Maddison, C. J., et al. (2017). The Concrete Distribution: A Continuous Relaxation of Discrete Random Variables.</a></p>
]]></content>
        <published>2025-06-17T00:00:00.000Z</published>
    </entry>
    <entry>
        <title type="html"><![CDATA[DeepSeek's customised CUDA PTX instruction]]></title>
        <id>https://annalhq.vercel.app/deepseek</id>
        <link href="https://annalhq.vercel.app/deepseek"/>
        <updated>2026-09-23T00:00:00.000Z</updated>
        <summary type="html"><![CDATA[DeepSeek recently open-sourced six powerful software libraries to tackle problems in LLM teaining, inference and data infrastructure. At the heart of its open-source DeepEP library lies a robust PTX c...]]></summary>
        <content type="html"><![CDATA[<p>DeepSeek recently open-sourced six powerful software libraries to tackle problems in LLM teaining, inference and data infrastructure. At the heart of its open-source DeepEP library lies a robust PTX customization kernel, designed to maximize efficiency for deep compute-intensive tasks. This blog post explores the intricacies of DeepSeek’s PTX kernel and its role in the DeepEP framework.</p>
<h2>What is PTX and Why Does It Matter?</h2>
<p>PTX, or Parallel Thread Execution, is an intermediate assembly language used by NVIDIA GPUs to execute programs written in CUDA.
By directly interacting with PTX, developers gain fine-grained control over GPU operations, enabling them to bypass some of the inefficiencies introduced by higher-level abstractions like standard CUDA kernels
While working at this level requires expertise, it opens up opportunities for groundbreaking optimizations—something DeepSeek has capitalized on extensively.</p>
<h3>The Role of <code>utils.cuh</code> in DeepEP</h3>
<p>This file contains critical implementations that enable the integration of custom PTX instructions into CUDA workflows. For instance:</p>
<ul>
<li>
<p><strong>Custom PTX instructions :</strong> Within <code>utils.cuh</code>, you'll find code snippets where DeepSeek overrides default CUDA compiler decisions with hand-tuned PTX logic. These modifications target specific hardware features, ensuring optimal utilization of resources.</p>
</li>
<li>
<p><strong>Memory Transfer Optimization :</strong> One of the standout achievements in DeepEP is the reduction of unnecessary memory transfers between host and device. By embedding PTX instructions directly, DeepSeek minimizes latency and boosts overall efficiency</p>
</li>
</ul>
<p>Enhanced Memory Access :</p>
<ol>
<li>Relaxed Memory access: Instructions like <code>ld.relaxed</code> and <code>st.relaxed</code> minimize synchronization overhead by avoiding unnecessary memory barriers.</li>
<li>Non-Allocating Loads: The <code>ld.global.nc.L1::no_allocate</code> instruction bypasses L1 cache allocation, reducing cache pollution for certain workloads.</li>
<li>Volatile Loads: Functions like <code>ld.volatile.global</code> ensure data consistency by bypassing compiler optimizations.</li>
</ol>
<p><em>Atomic Operations</em> Atomic instructions are crucial for ensuring data integrity in parallel environments. DeepEP includes custom atomic functions like <code>atom.add.release.sys.global</code>, which combine atomicity with relaxed or release semantics for fine-grained control over memory ordering.</p>
<h2>How Does It Work?</h2>
<p>The customization kernel relies heavily on inline assembly within CUDA, allowing developers to inject raw PTX instructions directly into their code. For example:</p>
<pre><code class="language-cpp">__device__ __forceinline__ void st_na_relaxed(const int *ptr, int val) {
    asm volatile("st.relaxed.gpu.global.L1::no_allocate.b32 [%0], %1;" : : "l"(ptr), "r"(val));
}
</code></pre>
<p>This function performs a relaxed store operation, bypassing the L1 cache for improved performance in specific scenarios.</p>
<p>Additionally, macros like <code>UNROLLED_WARP_COPY</code> enable optimized data movement within warps by unrolling loops and minimizing instruction latency:</p>
<pre><code class="language-cpp">#define UNROLLED_WARP_COPY(UNROLL_FACTOR, LANE_ID, N, DST, SRC, LD_FUNC, ST_FUNC) \
{ \
    constexpr int kLoopStride = 32 * (UNROLL_FACTOR); \
    typename std::remove_reference&lt;decltype(LD_FUNC((SRC) + 0))&gt;::type unrolled_values[(UNROLL_FACTOR)]; \
    auto __src = (SRC); \
    auto __dst = (DST); \
    for (int __i = (LANE_ID); __i &lt; ((N) / kLoopStride) * kLoopStride; __i += kLoopStride) { \
        _Pragma("unroll") \
        for (int __j = 0; __j &lt; (UNROLL_FACTOR); ++__j) \
            unrolled_values[__j] = LD_FUNC(__src + __i + __j * 32); \
        _Pragma("unroll") \
        for (int __j = 0; __j &lt; (UNROLL_FACTOR); ++__j) \
            ST_FUNC(__dst + __i + __j * 32, unrolled_values[__j]); \
    } \
}
</code></pre>
<p>This macro is particularly useful for optimizing memory-bound operations in deep learning models.</p>
]]></content>
        <published>2025-03-06T00:00:00.000Z</published>
    </entry>
</feed>