<?xml version="1.0" encoding="UTF-8"?>
    <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
      <channel>
        <title>Irfan Ali</title>
        <link>https://irfanali.org/blog/</link>
        <description>Irfan Ali's Blog</description>
        <language>en-us</language>
        <atom:link href="https://irfanali.org/rss.xml" rel="self" type="application/rss+xml" />
        <item>
      <title>Open and Continuous Maps in Topology</title>
      <link>https://irfanali.org/blog/opencont</link>
      <guid>https://irfanali.org/blog/opencont</guid>
      <pubDate>Sun, 13 Sep 2026 00:00:00 GMT</pubDate>
      <description><![CDATA[<h1>Open and Continuous Maps in Topology</h1>
<p>I think it would be fair to say that many students in the starting lessons of general topology get a bit puzzled by the definition of continuous functions when they first encounter it:</p>
<p>Any function $f : X \rightarrow Y $ is continuous if the pre-image $f^{-1}(A)$ of every open set $A$ in $Y$ is open in $X$.</p>
<p>Doesn&#39;t it feel like the definition is backward?
Shouldn&#39;t a continuous function send open sets to open sets instead to preserve structure?
Well, functions which do that are called <em>open functions</em> (commonly <em>open maps</em>).</p>
<p>While we learn that continuity and openness are different conditions, some natural questions arise:</p>
<ol>
<li>What do they convey?</li>
<li>How are they different?</li>
</ol>
<p>Even though textbooks contain counterexamples to clarify that they do not imply each other, in many pedagogical settings the lesson ends with a stern warning to not get confused between the two without providing any intuition as to why they are so different.</p>
<p>This post is a mostly non-rigorous intuitive attempt to do that.</p>
<p>We start with some thoughts on the simple real line and then see some general reasons as to why continuity is not just openness.</p>
<h2>Continuity in Real Line</h2>
<p>Take the good-old $\epsilon-\delta$ definition of continuity:</p>
<blockquote>
<p>A function $f : \mathbb{R} \rightarrow \mathbb{R}$ is continuous at a point $c$ if for each $\epsilon \gt 0$ there exists a $\delta \gt 0$ such that $$ |x - c| &lt; \delta \implies |f(x) - f(c)| &lt; \epsilon $$</p>
</blockquote>
<p>It first starts with an $\epsilon$ room around the image $f(c)$ and then says: no matter how small an $\epsilon$ one chooses around the image $f(c)$, there must exist a $\delta$ room around $c$ such that every point in it must map inside the $\epsilon$ room.</p>
<p>We can see that this definition would prohibit any jumps.
Take the step function which takes the value $-1$ at all $x &lt; 0$ and $1$ at all $x \geq 0$.
We can find a room $(1 - \epsilon, 1 + \epsilon)$ around $f(0)$ and easily verify that it is impossible to find a $\delta$ room around $0$ which would map completely inside this target.</p>
<p>The discontinuity occurs because the step function <em>tears</em> the domain at $0$ to map one half of the domain to $-1$ and the rest of the domain to $1$.
It is akin to tearing a strip of paper which represents the number line and mapping one part to $-1$ and another to $1$.
At the risk of losing some rigour, it might be helpful to rely on this analogy as a ladder to develop intuition.</p>
<p>Before we move forward, let&#39;s ponder one minor point.</p>
<p>It is common in intuitive explanations to hear that continuity means “a small change in the input produces only a small change in the output.”
This is correct if we are careful about what it means, but it is best not to lean too much on this framing: it reads as if we start in the domain and push forward.
This hides the fact that the order in which we pick the two rooms matters a lot.</p>
<p>Suppose we flip it, so that we pick the room around $c$ first and only ask for <em>some</em> room around $f(c)$ to catch the outputs:</p>
<blockquote>
<p>A function $f : \mathbb{R} \rightarrow \mathbb{R}$ is continuous at a point $c$ if for each $\epsilon \gt 0$ there exists a $\delta \gt 0$ such that $$ |x - c| &lt; \epsilon \implies |f(x) - f(c)| &lt; \delta $$</p>
</blockquote>
<p>This is the same definition as before but we&#39;ve just switched the $\epsilon$ and $\delta$ in the condition.
Now we choose an $\epsilon$ and try to say that if $x$ stays within the $\epsilon$ room then $f(x)$ must stay in the $\delta$ room.</p>
<p>But since a function does not have to map to every point in the $\delta$ room, this distorted definition does not do anything useful.
We can always satisfy this definition by taking a big enough $\delta$.</p>
<p>This shows that the task of finding a $\delta$ to constrain $x$ so that $f(x)$ is constrained really does tell us something about the function.
But finding a $\delta$ to constrain $f(x)$ given that $x$ is constrained is not really a condition at all.
This happens because a function by definition must map every point in its domain somewhere but is not obligated to cover any point in the codomain</p>
<p>The actual definition works because it runs the other way: the codomain sets a target, and the domain must find a room small enough to land inside it.
A function must map every point of its domain somewhere, but it is not obligated to cover any point in the codomain.
So making a room in the codomain big enough to contain the outputs costs nothing, while shrinking a room in the domain until the outputs fit inside an arbitrary target is a genuine condition.</p>
<p>Coming back to the point in discussion, the definition of an open map is also very similar to our wrong $\epsilon-\delta$ definition, but even more restrictive.
For a map to be open, every open set $A$ must map to some open set $B$ in the codomain.</p>
<p>As mentioned before, this looks like a nice condition, but note again what it proposes: the image of every open set $A$ must be some open set $B$.
This means that every point in the open set $B$ must be mapped to by some point in $A$.</p>
<p>This has a few unintended consequences which make open maps quite restrictive but at the same time do not ensure continuity in general. Let&#39;s see.</p>
<h2>Open is Too Restrictive</h2>
<p>If a function $f$ must map every single $A$ to some open set $B$, it must ensure that whatever $B$ happens to be, it is covered in full.</p>
<p>This immediately disallows any functions which stay constant in any small interval in their domain.
If the function is constant in any interval $(a, b)$, then we can find a smaller open interval $(a + \epsilon, b - \epsilon)$ ($\epsilon &lt; b - a$) which would map to a single point in the codomain, which obviously happens to be not open.</p>
<p>But constant functions are as benign a function as possible. They are certainly continuous, but it looks like they cannot be open.
To borrow the analogy, this really means that for a function to be open, <em>squishing</em> to a point is not allowed, as the function must spread to the whole of $B$ for every $A$, whatever open set $B$ happens to be.</p>
<p>Take another example, say, $f(x) = x^2$. Though this is also a well-behaved continuous function on $\mathbb{R}$, it is not an open map.
If we take an open interval around $0$, say $(-1, 1)$, we can see that the image of this open interval is $[0, 1)$, which is clearly not open in $\mathbb{R}$.
This function is also continuous, but not open.</p>
<p>If we stretch the analogy further, we can see that $x^2$ <em>creases</em> the domain at $0$ in order to create a minimum and revert back.</p>
<p>We can see that for a function to be open, both squishing and creasing of the domain are not allowed while mapping into the codomain.</p>
<p>This means that any nice continuous function which attains an extremum and changes direction in its domain ceases to be open.
It is a common exercise in analysis to prove that any continuous function $f : \mathbb{R} \rightarrow \mathbb{R}$ which happens to be open must be strictly monotonic.</p>
<p>If $f$ is continuous and open on an interval $I$ but not strictly monotonic, then there would exist $$ u &lt; v &lt; w $$ in $I$ such that $f(v)$ is not strictly between $f(u)$ and $f(w)$.
This means that $f$ would have a local extremum at some interior point in $I$.
And we have already seen that we can take a sufficiently small open interval around an extremum which maps to a half-open interval. This would mean $f$ is not open.</p>
<p>So, it is clear that an open map is not a useful way to ensure continuity in functions, as it disallows many nicely behaved continuous functions.</p>
<p>But let&#39;s look at this question from another angle: does the restriction of openness rule out any discontinuities?</p>
<p>It turns out the answer is yes, but mostly accidentally, I think.</p>
<p>It is easy to see that any open map would avoid simple isolated discontinuities like a step function.
Since the step function we supplied above is constant on either side of the jump, it is obviously not open.
But if we look at a strictly monotonic map with a discontinuity, say</p>
<p>$$ f(x) = \begin{cases} x &amp; \text{if } x &lt; 0 \\ x + 1 &amp; \text{if } x \geq 0 \end{cases} $$</p>
<p>We can see that it also fails to be open. For the interval $(-1, 1)$, the image is $(-1, 0) \cup [1, 2)$, which is clearly not open.</p>
<p>If we push this line further, we can convince ourselves that $f$ cannot possibly have isolated discontinuities, by which we mean that $f$ cannot be discontinuous at any $c$ while still being continuous on some $(c-\epsilon, c)$ and $(c, c+\epsilon)$.</p>
<p>To see why that is true, note that $f(c)$ would lie either inside or outside the open images of these two intervals.</p>
<p>It cannot lie outside as an isolated point, because then it would sit by itself in the image of $(c-\epsilon, c+\epsilon)$, making that image non-open as we have seen before.</p>
<p>But if it lies inside the image of either side interval, say the left interval $(c - \epsilon, c)$, we can just find a smaller interval around $c$ to exclude the one point in $(c - \epsilon, c)$ which happened to also map to $f(c)$.</p>
<p>Strict monotonicity ensures that $f$ attains the value $f(c)$ only once inside $(c - \epsilon, c)$.
But with the smaller interval, $f(c)$ would be back to being an isolated point outside both (shrunk) images, and we have already ruled that out.</p>
<p>So $f(c)$ can only sit right at the shared edge between the open images, which just means $f$ is continuous at $c$.</p>
<h2>Open is Possibly Discontinuous</h2>
<p>It looks like disallowing a good chunk of functions also disallows simple types of discontinuity.
But openness does not disallow all types of discontinuities.</p>
<p>We already saw that isolated discontinuities are disallowed if surrounded by continuity.</p>
<p>So, we have seen that open maps are good at ensuring that the function does not squeeze or crease the domain in ways that introduce <em>boundaries</em> in the images.
But open maps are not very helpful in ensuring that the function does not tear up the domain.
While simple tearings like isolated jumps are disallowed because they inevitably introduce boundaries, there are ways to construct open maps which defy continuity in pathological ways.</p>
<p>Since open maps only ensure that open sets map to open sets, if we can somehow manage to map every single open set to the entire real number line, the function does become open but would not be continuous.
A famous example is the Conway base-13 function. It is based on an idea which roughly works like this:</p>
<ol>
<li>In Conway&#39;s construction, the number $x$ is written in base 13 with the usual base-10 digits and other symbols like $+$, $-$, and $.$</li>
<li>Now every number $x$ can be mapped to either $0$ or some other real $r$ based on some rule.</li>
<li>The rule looks like this: if the number when written in base 13 has a <em>tail</em> which looks like a valid base-10 real, then it is mapped to that real. If not, then it is mapped to $0$.</li>
<li>Take some number $x$ which when written in base 13 looks like <code>+-492+1-2+3.14159...</code>; then this number would get mapped to $\pi$.</li>
<li>But if the base-13 expansion never has a tail which looks like a valid real, say <code>+-492+++...</code>, it is mapped to $0$.</li>
</ol>
<p>Now, somewhat strangely, this function does happen to be an open map.
If we take any interval $(a, b)$, no matter how small, we can show that its image under the above function is the entire real line $\mathbb{R}$ (which is open in $\mathbb{R}$).
Take any arbitrary real $r$; all we need to do is to prefix a valid base-13 <em>head</em> to produce a number $c$ such that:</p>
<ol>
<li>$c \in (a, b)$</li>
<li>the tail of $c$ when written in base 13 is equal to $r$</li>
</ol>
<p>Since the head is the most significant part of the number and can be arbitrarily long before the tail begins, we can always find a $c$ which satisfies these.
But even though this function is open, it is obviously not continuous.</p>
<p>So, openness disallows simple enough tears in the number line, because they introduce boundaries in some image and destroy openness.
But if we tear every single point in every single open interval and <em>scatter</em> them like dust particles over the entire number line, then openness is not violated, as the whole real number line does not have any boundaries.</p>
<h2>Beyond the Real Line</h2>
<p>So, now we have some idea of what open maps must be like on the real line.
But what about a general topology where there are no metrics or intervals?
What do continuity and openness mean in a space with a vague notion of distance or proximity, or none at all?</p>
<p>Before we go there, we must notice that open sets in a general topology do not represent any property, unlike on the real line.
In an unspecified general context, open sets are just sets which behave in a certain manner vis-à-vis set-theoretic operations.
Any collection of subsets can be the open sets of a topology as long as it contains the empty set and the whole space, and is closed under arbitrary unions and finite intersections.
This is how the topology is defined in the first place.</p>
<p>Now, the curious thing about the pre-image of a function is that it plays very, very well with set-theoretic operations.
For any function $f : X \rightarrow Y$, any subsets $A$ and $B$ of $Y$, and any family of subsets $A_i$ of $Y$:</p>
<ul>
<li><strong>Union:</strong> $f^{-1}\left(\bigcup_i A_i\right) = \bigcup_i f^{-1}(A_i)$</li>
<li><strong>Intersection:</strong> $f^{-1}(A \cap B) = f^{-1}(A) \cap f^{-1}(B)$</li>
<li><strong>Difference:</strong> $f^{-1}(Y - A) = X - f^{-1}(A)$</li>
</ul>
<p>Regardless of the function, pre-images faithfully preserve unions and intersections.
This is largely due to the fact that a function by definition must map any point in the domain to a unique point in the codomain.</p>
<p>Note from the above equations (together with $f^{-1}(\varnothing) = \varnothing$ and $f^{-1}(Y) = X$) that the set of pre-images in $X$ of all open sets in $Y$ under $f : X \rightarrow Y$ already fulfills the condition of being a topology on $X$.
This means that even before we define continuity or openness, pre-images automatically induce a topology on $X$ just by virtue of their properties.</p>
<p>Said differently, if we consider the sets of points which got transformed into open sets in the codomain $Y$, we already know that they would form a valid topology.
Continuity only requires that they must have come from the topology already endowed on $X$ itself.</p>
<p>This begins to shed some light on what continuity might be trying to portray.
It shows that whatever structure $Y$ has in terms of open sets, if we try to <em>pull back</em> the structure along the function to $X$, we just need to ensure that a similar or finer structure is available in $X$.</p>
<p>But when we talk about images, things are not quite smooth.
We started by thinking that images might propagate or <em>push</em> the structure from $X$ to $Y$, but alas, that is not at all true.
Images simply don&#39;t play well with set-theoretic constructions.
Unions still work, but intersections don&#39;t.
$f(A \cap B)$ is not necessarily equal to $f(A) \cap f(B)$, because functions can map multiple points in the domain to the same point in the codomain.
Also, $f(X - A)$ is not equal to $Y - f(A)$ in general.</p>
<p>So, even if we could ensure that open sets propagate from $X$ to $Y$ via images of open maps, we would never be able to ensure that they induce a topology on $Y$ from the topology of $X$ itself.</p>
<p>This is the reason that imposing conditions on the image of a function does not afford us any special behaviour when it comes to ensuring continuity.</p>
<p>If we try to circle back to the analogy of tearing, which continuity is supposed to disallow, we see that pre-images are the right choice.
If we take a simple step function on $\mathbb{R}$ which jumps at some $c$ in the domain to $f(c)$ in the range, then the pre-image of any sufficiently small open interval $(f(c) - a, f(c) + a)$ would obviously be outside the usual topology of the domain $\mathbb{R}$ (as it is the closed ray $[c, \infty)$).
This just means that the induced topology from all the pre-images of the step function contains at least one element different from the usual topology on $\mathbb{R}$.
This makes the pulled-back set fundamentally alien to the original topology of $\mathbb{R}$.
This is exactly the tear in the domain which creates a set-theoretic boundary in the pre-image that the original domain topology never possessed.
It is detected when the induced topology on the domain is found to be different from, and irreconcilable with, the endowed topology.</p>
<h2>Two Mindsets</h2>
<p>When we first study topology, our natural impulse is to think of functions as transformations which push the structure of a space forward onto another.
That is why requiring open sets to map to open sets might feel like the only logical way to preserve structure.</p>
<p>But as we have seen, open images as a condition are neither necessary nor sufficient, for ensuring continuity.
The condition disallows benign smooth behaviour like local extrema or constancy, but at the same time allows pathological maps to tear and scatter open sets everywhere.</p>
<p>Continuity instead works under a <em>pull</em> mindset under which we ask: does the domain possess <em>enough structural resolution</em> to make sense of the open sets in the codomain?
If the answer is yes, the function is continuous.
This is why, if we have the finest topology on the domain or the coarsest topology on the codomain, all functions become continuous.</p>
<p>But this should not mean that open maps are useless altogether.
If a function which is both continuous and bijective (one-one onto) also happens to be open, then its inverse becomes continuous, which allows us to mark two topological spaces as structurally identical (homeomorphic).</p>
<p>But taken separately, open and continuous maps measure two entirely different features of a transformation:</p>
<p>Continuity prevents <em>tearing</em> by ensuring the structure of the codomain can be pulled back into the topology of the domain.
Openness prevents <em>squishing</em> and <em>creasing</em> by ensuring the room of the domain is distributed adequately across the image.</p>
]]></description>
    </item><item>
      <title>Poor Man's Time Machine</title>
      <link>https://irfanali.org/blog/repmin</link>
      <guid>https://irfanali.org/blog/repmin</guid>
      <pubDate>Mon, 08 Jun 2026 00:00:00 GMT</pubDate>
      <description><![CDATA[<h1>Poor Man&#39;s Time Machine</h1>
<p>Here is a simpler version of the famous repmin puzzle from Richard Bird&#39;s arsenal of functional tricks: Given a non-empty array of numbers <code>a</code>, you have to find the smallest number <code>m</code> <em>and</em> replace every element of <code>a</code> with <code>m</code> in a <em>single traversal</em>.</p>
<h2>Naive Non-Solution</h2>
<p>It is straightforward to build a naive non-solution which calculates the minimum value of <code>a</code> in one pass and then mutates <code>a</code> in another pass:</p>
<pre><code class="language-javascript">function findMin(a) {
  let m = a[0]
  for (let i = 0; i &lt; a.length; i++) {
    if (a[i] &lt; m) {
      m = a[i]
    }
  }
  return m
}

function replaceMin(a, m) {
  for (let i = 0; i &lt; a.length; i++) {
    a[i] = m
  }
}

const m = findMin(a)  // Pass #1
replaceMin(a, m)      // Pass #2
</code></pre>
<p>The question is: how do we perform these <em>temporally dependent</em> tasks in a single pass?
That is, how do we go through <code>a</code> to replace all its elements with <code>m</code> but somehow also calculate <code>m</code> at the same time?</p>
<p>Basically, we need to traverse <code>a</code> to calculate <code>m</code> and simultaneously replace its elements with some value <code>x</code> which we can guarantee is going to be the minimum value.</p>
<p>But wait, won&#39;t that require sending a message to the present from the future?</p>
<p>So, is it necessary to invent a time machine to solve this puzzle?
Or could we do with something more modest: say, a simple <em>function</em> and a tiny <em>leap of faith</em>?</p>
<h2>A Function as an Unevaluated Value</h2>
<p>To see how a simple function can help us, please note that the <em>seemingly temporal</em> dependency arises only because <code>replaceMin(a, findMin(a))</code> forces the <em>traversal</em> in <code>findMin</code> before the <em>replacement</em> in <code>replaceMin</code>.</p>
<p>If we could just find a way to represent the minimum value not as a <code>Number</code> in JavaScript but as a <code>function</code> instead, the temporal dependency would vanish because a function <em>doesn&#39;t need to be evaluated</em> immediately.
We can just replace every element of <code>a</code> with a function which would <em>evaluate</em> to the minimum value <em>later</em>.
This function is then a proxy for <code>m</code> which delays the need to actually calculate <code>m</code> when we enter <code>replaceMin</code>.</p>
<p>Let&#39;s do this with a function <code>findAndReplaceMin</code> which calculates <code>m</code> but also replaces the elements of <code>a</code> by some as-of-yet unrelated function <code>xf</code> in a single pass:</p>
<pre><code class="language-javascript">function findAndReplaceMin(a, xf) {
  let m = a[0]
  for (let i = 0; i &lt; a.length; i++) {
    if (a[i] &lt; m) {
      m = a[i]
    }
    a[i] = xf
  }
  return m
}
</code></pre>
<p>Note that <code>findAndReplaceMin</code> receives <code>xf</code> as an argument and replaces every element of <code>a</code> with it.
We still need to choose <code>xf</code> so that every occurrence of <code>xf</code> eventually yields the minimum value, even though that minimum has not yet been computed.</p>
<p>This seems like a problem.
We&#39;re still stuck in a situation where computing <code>m</code> requires calling <code>findAndReplaceMin</code> with <code>xf</code>, but <code>xf</code> must already know how to produce <code>m</code> before <code>findAndReplaceMin</code> has computed it.</p>
<h2>Make Evidence for Faith</h2>
<p>This seems like a real problem until we realise that we can <em>bootstrap</em> the evidence for our <em>faith</em> by saying this:</p>
<pre><code class="language-javascript">let m = findAndReplaceMin(a, () =&gt; m)
</code></pre>
<p>This seems paradoxical: how can <code>m</code> be used in its own definition?</p>
<p>The answer is subtle: we are NOT <em>using</em> <code>m</code> to calculate <code>m</code> but only <em>denoting</em> <code>m</code> in a function which calculates <code>m</code> independently of the denotation.</p>
<p>More precisely, the closure <code>() =&gt; m</code> captures the variable <code>m</code>, which has not been initialised yet, without forcing its evaluation, which would happen only when we say <code>xf()</code>.
And if you look carefully, we take care not to do that anywhere inside <code>findAndReplaceMin</code> (more on this in a bit).</p>
<p>When the line <code>let m = findAndReplaceMin(a, () =&gt; m)</code> is executed, it does <em>two separate tasks</em> in <em>one single pass:</em></p>
<ol>
<li><strong>Calculate</strong> the minimum value by traversing <code>a</code> and <strong>bind</strong> it to <code>m</code></li>
<li><strong>Replace</strong> every element of <code>a</code> with the function <code>() =&gt; m</code> (without evaluating the function)</li>
</ol>
<p>Contrast the line above with the obviously invalid declaration <code>let m = m</code>, which immediately attempts to read the value of <code>m</code> before it has been initialised.</p>
<p>By comparison, <code>let m = findAndReplaceMin(a, () =&gt; m)</code> never reads <code>m</code> during the construction of the closure.
The expression <code>() =&gt; m</code> merely records how to obtain <code>m</code> in the future.
The actual lookup is postponed until the function is called.</p>
<p>Note that after <code>findAndReplaceMin</code> has run, <code>a</code> is populated by <code>function</code>s and not <code>Number</code>s.
So, when any element of <code>a</code> is needed, we get it by calling <code>a[i]()</code>, which reads the value bound to <code>m</code>.
Note that this doesn&#39;t need to run <code>findAndReplaceMin</code> again because <code>m</code> is already bound to the minimum value from Step 1.</p>
<p>In functional parlance, writing this self-referential declaration is close to what is called <strong>tying the knot</strong>.
It refers to the circular reference to <code>m</code>, which we fill from <code>findAndReplaceMin</code> in the future, but which we can still use inside <code>findAndReplaceMin</code> in the present if we take care not to inspect or evaluate it.</p>
<p>The key to this illusion of time travel is the assignment <code>a[i] = xf</code> without inspecting <code>xf</code>.
We cannot afford to poke <code>xf</code> (say by doing <code>xf()</code>) inside <code>findAndReplaceMin</code>, because that would try to read <code>m</code> while we are still in the middle of computing it.
JavaScript doesn&#39;t play along: it throws a <code>ReferenceError</code> on the spot, complaining that <code>m</code> cannot be accessed before initialisation.</p>
<p>I should reiterate that no law of physics is being broken here and no information is actually travelling backwards in time.
We instinctively think of computation as a sequence of steps: first compute the minimum, then replace the elements.
But tying the knot invites us to think equationally instead.
Rather than saying <em>let me calculate <code>m</code> first,</em> we say <em>here is how I&#39;d like <code>m</code> to be defined given <code>findAndReplaceMin</code></em>, with the added benefit that the definition itself can be self-referential if we build it carefully.</p>
<p>We&#39;ve almost solved this puzzle, but you might now complain: we were asked to create an array of <code>Number</code>s but ended up creating an array of <code>function</code>s instead.</p>
<p>And the answer is that with standard primitives, we can&#39;t solve this perfectly in JavaScript without leaving behind an array of functions.
(We could maybe pull off a solution using some advanced object metaprogramming, but that&#39;s probably a topic for another blog post!)</p>
<h2>The Real Lazy Evaluation</h2>
<p>To see how we can do exactly what we set out to, we look at a language where delayed evaluation is built in: <strong>Haskell</strong>.</p>
<p>Let&#39;s see what <code>findAndReplaceMin</code> would look like in Haskell:</p>
<pre><code class="language-haskell">findAndReplaceMin :: [Int] -&gt; Int -&gt; (Int, [Int])
findAndReplaceMin [y]     x  = (y, [x])
findAndReplaceMin (h : t) x  = (min g h, x : t&#39;)
  where (g, t&#39;) = findAndReplaceMin t x

let (m, b) = findAndReplaceMin a m
</code></pre>
<p>It almost mirrors the JavaScript logic, but recursively and immutably.</p>
<p>After <code>findAndReplaceMin</code> has run, the list <code>b</code> contains <code>Int</code>s in Haskell.
But the same is not possible in JavaScript.
Why?</p>
<p>The main reason is that in Haskell, we do not need to rely on closures to delay evaluation because values are lazy by default.</p>
<p>What that means in practice is that Haskell does not evaluate expressions just because they are passed as arguments or stored inside data structures.
Instead, expressions are represented as unevaluated values (called <strong>thunks</strong>) and evaluated only when their values are demanded in some calculation.
This evaluation strategy is known as <strong>call-by-need evaluation</strong>.
An expression like <code>findAndReplaceMin a m</code> can be written without worrying about <code>m</code> being evaluated before <code>findAndReplaceMin</code> has finished.
So, we don&#39;t need to resort to tricks like <code>() =&gt; m</code> just to delay evaluation of <code>m</code>.
The expression bound to <code>m</code> remains unevaluated until some operation (mostly pattern-matching, or some function which needs <code>m</code>, like <code>+</code> or <code>show</code>) actually demands its value.</p>
<p>Compare this to JavaScript, where if we write <code>findAndReplaceMin(a, m)</code>, both <code>a</code> and <code>m</code> would be evaluated <em>before</em> being passed to the function, which is why we need to <em>wrap</em> <code>m</code> inside <code>() =&gt; ...</code> to prevent JavaScript from poking.
This style of evaluation is called <strong>call-by-value evaluation</strong> and is common in languages like C, Python, and JavaScript.</p>
<p>Also, tying the knot is relatively easier in Haskell.
In Haskell, we can just write <code>m</code> on both sides of <code>let (m, b) = findAndReplaceMin a m</code>.
A similar attempt in JavaScript is bound to fail, which is also why we took care to hide <code>m</code> inside <code>() =&gt; m</code>.
But Haskell can construct cyclic graphs of thunks.
The self-reference in <code>let (m, b) = findAndReplaceMin a m</code> does not require the runtime to know the value of <code>m</code> immediately.
Instead, it creates a network of dependencies in which <code>m</code> and <code>b</code> can mutually refer to each other.
Laziness ensures that these references are only followed when their values are actually needed.</p>
<h2>Don&#39;t Peek Too Soon!</h2>
<p>So it turns out we didn&#39;t need a time machine after all: just a function, a carefully deferred lookup, and the discipline not to peek too soon.
But more importantly, in both languages, the trick explicitly relies on the same <em>leap of faith</em>: while lazy evaluation (Haskell) and the wrapper lambda (JavaScript) prevent automatic evaluation by the runtime, we ourselves cannot afford to peek into them (by pattern-matching on <code>m</code> in Haskell or calling <code>xf</code> in JavaScript) before <code>findAndReplaceMin</code> finishes execution.</p>
<p>If we get too impatient and lose faith, the knot comes undone in both languages.
JavaScript refuses outright with a <code>ReferenceError</code>, while Haskell chases its own tail into an <em>infinite abyss of doubt</em> till time ends, unless GHC notices the loop first and gives up with a terse <code>&lt;&lt;loop&gt;&gt;</code>!</p>
]]></description>
    </item><item>
      <title>A Gentler Introduction to Y and Z Combinators</title>
      <link>https://irfanali.org/blog/zcom</link>
      <guid>https://irfanali.org/blog/zcom</guid>
      <pubDate>Tue, 02 Jun 2026 00:00:00 GMT</pubDate>
      <description><![CDATA[<h1>A Gentler Introduction to Y and Z Combinators</h1>
<p>Let&#39;s try to solve a simple enough puzzle in JavaScript: You have to write a function <code>fact(n)</code> which calculates the factorial of a number <code>n</code>, but you cannot use loops (<code>for</code>, <code>while</code>, etc.), recursion, or even declarations (<code>let</code>, <code>const</code>, etc.).</p>
<h2>Non-Solution: Direct Recursion</h2>
<p>We start with a canonical recursive non-solution to know what we&#39;re aiming for, in terms of behaviour:</p>
<pre><code class="language-javascript">function fact(n) {
  if (n === 0) {
    return 1
  }
  else {
    return n * fact(n - 1)
  }
}
</code></pre>
<p>This is a non-solution because we cannot use recursion, which means we cannot call the function <code>fact</code> from inside the definition of the function <code>fact</code>.</p>
<h2>Non-Solution: Mutual Recursion</h2>
<p>So, running <code>for</code> loops or calling <code>fact</code> from itself is not allowed.
Then, what should we do?
Can we call <code>fact</code> from some other function and try to arrive at the same behaviour?</p>
<p>After some thinking, we can write a function <code>factf</code> which calculates factorial but doesn&#39;t call itself:</p>
<pre><code class="language-javascript">function factf(n) {
  if (n === 0) {
    return 1
  }
  else {
    return n * factg(n - 1)
  }
}

function factg(n) {
  if (n === 0) {
    return 1
  }
  else {
    return n * factf(n - 1)
  }
}
</code></pre>
<p>This works.
<code>factf(n)</code> does calculate the factorial.
So, are we done?
Well, we did avoid an explicit call to <code>factf</code> inside <code>factf</code>, but it&#39;d be a lie to say that this doesn&#39;t use recursion.</p>
<p>This solution calls for a new rule: <strong>mutual recursion</strong> is also not allowed.</p>
<h2>Non-Solution: Recursion via Indirect Self-Reference</h2>
<p>But, can we still learn something from the previous attempt?
We know that if we decide to avoid loops, we definitely need to emulate a call to something like <code>fact</code> inside <code>fact</code>.
What if we don&#39;t call <code>fact</code> inside <code>fact</code> directly, but instead define a generator function <code>factgen</code> and pass it a function which gets called instead?</p>
<pre><code class="language-javascript">function factgen(self, n) {
  if (n === 0) {
    return 1
  }
  else {
    return n * self(self, n - 1)
  }
}

let fact = n =&gt; factgen(factgen, n)
</code></pre>
<p>To see how it works, take <code>fact(4)</code>.
It is just <code>factgen(factgen, 4)</code>, which evaluates to <code>4 * self(self, 3)</code>, which is just <code>4 * factgen(factgen, 3)</code>, which reduces to <code>4 * 3 * factgen(factgen, 2)</code>, and so on.
Every <code>factgen(factgen, n)</code> call essentially becomes <code>n * factgen(factgen, n - 1)</code> until <code>n</code> becomes <code>0</code>.</p>
<p>It seems like we&#39;ve solved our puzzle:</p>
<ol>
<li><code>fact</code> did not call itself in its own definition (explicit recursion).</li>
<li><code>fact</code> also did not call any other function which called <code>fact</code> indirectly (mutual recursion).</li>
</ol>
<p>But alas, we still leaned on declarations.
<code>factgen</code> is a named function, and <code>fact</code> only works because it can refer to <code>factgen</code> by name and hand it to itself.
And if we look closely at our challenge, we should not use a declaration.</p>
<p>If we really vowed to <code>let</code> go of declarations, we should avoid using <code>function</code> too.
This calls for another rule: no declarations at all, including <code>function</code>.</p>
<p>We&#39;d use <strong>only anonymous functions</strong> like <code>(x, y) =&gt; { ... }</code> in our solution.</p>
<h2>Recursion is Self-Reference</h2>
<p>But like before, let&#39;s see if we can get some intuition from the definition above.
If we zoom in on the recursive part, we see that <code>self(self, n)</code> has a curious property.
Evaluating it does not eliminate the self-reference.
Instead, it eventually produces another call involving the same pattern, namely <code>self(self, n - 1)</code> embedded inside a larger expression.
The argument changes, but the self-application survives.
This is not even specific to the definition of <code>fact</code> given here.
If, say, there were a function <code>f</code> defined like this:</p>
<pre><code class="language-javascript">function f(g, n, s) {
  console.log(s)
  if (n &lt; 10) {
    g(g, n + 1, s)
  }
}
</code></pre>
<p>and it were called like this: <code>f(f, 0, &quot;hello, world&quot;)</code>, we&#39;d get <code>&quot;hello, world&quot;</code> printed repeatedly until <code>n</code> reaches <code>10</code> (if stack size allows).
That&#39;s basically a while loop.</p>
<p>So, our venture teaches us that the pattern <code>g(g, ...)</code> captures the core mechanism needed to create recursion.
It <strong>results in itself when it executes</strong>, which allows it to reuse the same behaviour with different inputs to emulate iteration.</p>
<h2>Self-Replication without Self-Reference</h2>
<p>Let&#39;s see if we can extract this pattern into a function to gain more insight:</p>
<pre><code class="language-javascript">let rep = x =&gt; x(x)
</code></pre>
<p>(Don&#39;t worry about the <code>let</code>; this is not an attempt at solving the challenge and we&#39;d remove it later.)</p>
<p>Clearly, <code>rep</code> doesn&#39;t do anything in particular.
It just takes a function <code>x</code> and calls <code>x</code> with argument <code>x</code>.
But something interesting happens when we call <code>rep</code> with itself as an argument.
If we reason purely in terms of expressions and forget evaluation for a moment, then <code>rep(rep)</code> rewrites to <code>rep(rep)</code> again.</p>
<p>(Operationally, however, a JavaScript engine attempting to evaluate this expression would recurse forever and eventually overflow the stack.
We&#39;d handle this problem later.
For now, we are interested only in the equational behaviour of the expression.)</p>
<p>In other words, if written without <code>rep</code>, the code:</p>
<pre><code class="language-javascript">(x =&gt; x(x))(x =&gt; x(x))
</code></pre>
<p>reduces to itself.
Please note that we just obtained self-application without loops, recursion, or declarations.
We just used an anonymous function and literally wrote the function twice by hand.</p>
<p>But what good is self-replicating code like <code>rep</code> if we can&#39;t use this machinery to do some real task, like calculating the factorial?
To get something useful with <code>rep</code>, we&#39;d need to expand on this idea.</p>
<p>How about we do the obvious and stick a function, say <code>h</code> (assumed to be in scope), inside the definition of <code>rep</code>:</p>
<pre><code class="language-javascript">let rep = x =&gt; h(x(x))
</code></pre>
<p>Now, something even more interesting happens if we reduce <code>rep(rep)</code>.
(Again, we just mean reduction in the equational sense, not how it would run on the system.)</p>
<p><code>rep(rep)</code> just reduces to:</p>
<pre><code class="language-javascript">(x =&gt; h(x(x)))(x =&gt; h(x(x)))
</code></pre>
<p>which is nothing but:</p>
<pre><code class="language-javascript">h((x =&gt; h(x(x)))(x =&gt; h(x(x))))
</code></pre>
<p>which, if we look closely, is just <code>h(rep(rep))</code> expanded.
In other words, <code>rep(rep)</code> reduces to <code>h(rep(rep))</code> with our new definition of <code>rep</code>.</p>
<p>This is more interesting.
Now we not only have replication, but we can run some function on the result.
This is very close to the recursion we&#39;re looking for.</p>
<h2>Enter Y</h2>
<p>What if we wrap this whole self-replicating machine inside a function <code>Y</code> which accepts a payload <code>h</code>:</p>
<pre><code class="language-javascript">let Y = (h) =&gt; {
  let rep = x =&gt; h(x(x))
  return rep(rep)
}
</code></pre>
<p>The <code>Y</code> is basically a higher-order function which takes a function <code>h</code> and returns the self-replicating code we wrote before, parametrised by <code>h</code>.</p>
<p>But there are two problems now:</p>
<ol>
<li>It is not clear how to use <code>Y</code> to emulate recursive behaviour.</li>
<li>As many of you might be complaining now, the way everything above is written, from <code>rep</code> to <code>Y</code>, it won&#39;t even work because it would never stop evaluating.</li>
</ol>
<p>Let&#39;s tackle the first problem first as it looks easier.
Assume that the second problem is solved, i.e., assume we have some way through which we can delay the call to <code>rep(rep)</code> till just the right moment.
Now, given a delayable <code>Y</code>, how then would we come up with the factorial function?</p>
<p>To see what <code>Y</code> does, let&#39;s see what <code>Y(f)</code> means.
If evaluation can be delayed at will, we can see that <code>Y(f)</code> would reduce to <code>rep(rep)</code>, which would just evaluate to <code>f(rep(rep))</code>, which is identical to <code>f(Y(f))</code> in terms of behaviour.</p>
<p>So <code>Y(f)</code> reduces to <code>f(Y(f))</code>, which reduces to <code>f(f(Y(f)))</code>, and so on.
Assuming it exists, let&#39;s call the result of <code>Y(f)</code> <code>p</code>.
What we&#39;ve shown is that <code>p</code> satisfies:</p>
<pre><code class="language-javascript">p = f(p)
</code></pre>
<p>Feeding <code>p</code> back through <code>f</code> gives <code>p</code> again.</p>
<p>An object with that property is called a fixed point of <code>f</code>.
So <code>Y(f)</code> is just a fixed point of <code>f</code>.</p>
<p>Now admittedly, this is just a tidy definition and not yet a working program.
Let me get there via a short detour, on the promise that we&#39;re close.</p>
<h2>Fixed Points and Recursion</h2>
<p>Let&#39;s go back to our <code>factgen</code> from earlier and try to write a curried version which takes only a single argument and returns a function:</p>
<pre><code class="language-javascript">function factgen(factaux) {
  return n =&gt; {
    if (n === 0) {
      return 1
    }
    else {
      return n * factaux(n - 1)
    }
  }
}
</code></pre>
<p>This <code>factgen</code> is the curried analogue of the earlier <code>factgen</code>.
It takes a function <code>factaux</code> and returns a function which takes a number <code>n</code> and returns its factorial by calling <code>factaux</code> internally.</p>
<p>If the declaration <code>let fact = factgen(fact)</code> worked in JavaScript, it would define the factorial.
Alas, JavaScript refuses to run it: it evaluates the right-hand side first, and it cannot pass <code>fact</code> into <code>factgen</code> before <code>fact</code> has finished being initialised.
(JavaScript is inadvertently applying the rules of the challenge for us here.)</p>
<p>But like before, every failed attempt is an opportunity to look for a new idea.
In this case, if we observe closely, we can see that the equation <code>fact = factgen(fact)</code> describes something familiar about <code>fact</code>.
Going back to our discussion of <code>Y</code>, if we think of <code>fact</code> as <code>p</code> and <code>factgen</code> as <code>f</code>, then a curious fact reveals itself: <code>fact</code> is nothing but a fixed point of the function <code>factgen</code>!</p>
<p>Note that what was said above is not dependent on the details of the factorial function, which sit cozily inside <code>factgen</code>, untouched by our discussions on fixed points.
In fact, the following observation is true for all recursive functions:</p>
<p>Every recursive function (like <code>fact</code>) can be expressed as a fixed point of an appropriately defined function (like <code>factgen</code>).</p>
<p>To see why, note that any function satisfying <code>fact = factgen(fact)</code> behaves exactly like the recursive <code>fact</code> we wanted to define in the first place: <code>factgen(fact)</code> is just a factorial whose recursive calls have been delegated to <code>fact</code> itself.</p>
<p>While JavaScript rejects this definition, what if we had a way to calculate the fixed point of <code>factgen</code> which doesn&#39;t involve a call to the as-of-yet undefined <code>fact</code>?</p>
<h2>Re-enter Y</h2>
<p>We do.
<code>Y(factgen)</code> is nothing but a fixed point of <code>factgen</code>.
So we could now just define <code>fact</code> like:</p>
<pre><code class="language-javascript">function factgen(factaux) {
  return n =&gt; {
    if (n === 0) {
      return 1
    }
    else {
      return n * factaux(n - 1)
    }
  }
}

let fact = Y(factgen)
</code></pre>
<p>We&#39;ve solved our main problem: <code>fact</code> is no longer defined in terms of itself but as the fixed point of <code>factgen</code>.
(Note that we&#39;re still using <code>let</code> and <code>function</code>, which we&#39;d get rid of in a while.)</p>
<p>Now that we have a solution using <code>Y</code>, let&#39;s come to the second problem.
So we assumed that we could delay the repeated evaluation of <code>Y</code> till it&#39;s needed.
But how can we do that?</p>
<p>Note that in JavaScript, the arguments of a function are evaluated before the function is called.
In technical terms, this is known as eager (or call-by-value) evaluation.
When we try to evaluate <code>Y(factgen)</code>, it reduces to <code>factgen(Y(factgen))</code>.
But this never hands control to <code>factgen</code>, because the argument <code>Y(factgen)</code> must be fully evaluated first, which triggers the exact same loop, looping forever before a single line of <code>factgen</code> actually executes.</p>
<h2>The Lazy Cousin</h2>
<p>What if there was a simple way to introduce delay in the evaluation of a value like <code>Y(factgen)</code>?
How about trying to wrap it inside another function instead?</p>
<p>We tweak <code>Y</code> by wrapping the argument <code>x(x)</code> of the call <code>h(x(x))</code>, turning it into <code>h(v =&gt; x(x)(v))</code>:</p>
<pre><code class="language-javascript">let Z = (h) =&gt; {
  let rep = x =&gt; h(v =&gt; x(x)(v))
  return rep(rep)
}
</code></pre>
<p>If we look closely, <code>Z</code> is almost identical to <code>Y</code>.
The only difference is that the self-application is wrapped inside another function: <code>v =&gt; x(x)(v)</code>.
This wrapping postpones the self-application until it is actually needed.</p>
<p>But conceptually, the behaviour remains the same.</p>
<p><code>Z(f)</code> evaluates to <code>rep(rep)</code>, which expands to <code>f(v =&gt; rep(rep)(v))</code>.</p>
<p>Now, the function <code>v =&gt; rep(rep)(v)</code> is not literally the same object as <code>rep(rep)</code>.
The difference is that it delays the self-application until an argument is supplied.
But for our purposes, <code>v =&gt; rep(rep)(v)</code> plays the same role as <code>rep(rep)</code>.
Whenever it is eventually called, it re-enters the same self-referential computation that <code>rep(rep)</code> would have triggered immediately.
The difference is merely one of timing.</p>
<p>Now, let&#39;s get back to factorial.
Amazingly, <code>Z</code> is so much like <code>Y</code> that we can just replace <code>Y</code> by <code>Z</code> in our definition and it works.</p>
<p><code>Z(factgen)</code> evaluates to <code>factgen(v =&gt; rep(rep)(v))</code>, which is just the function:</p>
<pre><code class="language-javascript">n =&gt; {
  if (n === 0) {
    return 1
  }
  else {
    return n * (v =&gt; rep(rep)(v))(n - 1)
  }
}
</code></pre>
<p>Here <code>(v =&gt; rep(rep)(v))(n - 1)</code> is just <code>rep(rep)(n - 1)</code>, and <code>rep(rep)</code> expands back to <code>factgen(v =&gt; rep(rep)(v))</code>.
The self-reference is recreated, but the computation is delayed because the lambda <code>v =&gt; rep(rep)(v)</code> doesn&#39;t need to be reduced further until it is applied to an argument.</p>
<p>So calling this on <code>4</code> gives <code>4 * rep(rep)(3)</code>, which gives <code>4 * 3 * rep(rep)(2)</code>, and so on down to <code>n === 0</code>, where the recursion bottoms out at <code>1</code> and the <code>else</code> branch is finally skipped.</p>
<p>Here is a visual trace of the evaluation:</p>
<pre><code class="language-text">Z(factgen)(4)

factgen(v =&gt; rep(rep)(v))(4)

4 * (v =&gt; rep(rep)(v))(3)

4 * rep(rep)(3)

4 * factgen(v =&gt; rep(rep)(v))(3)

4 * 3 * (v =&gt; rep(rep)(v))(2)

4 * 3 * rep(rep)(2)

4 * 3 * 2 * (v =&gt; rep(rep)(v))(1)

4 * 3 * 2 * rep(rep)(1)

4 * 3 * 2 * factgen(v =&gt; rep(rep)(v))(1)

4 * 3 * 2 * 1 * (v =&gt; rep(rep)(v))(0)

4 * 3 * 2 * 1 * rep(rep)(0)

4 * 3 * 2 * 1 * factgen(v =&gt; rep(rep)(v))(0)

4 * 3 * 2 * 1 * 1

24
</code></pre>
<p>Notice that in the last step, it doesn&#39;t matter what the argument to <code>factgen</code> is.
No recursion is triggered, and the returned function terminates since <code>n</code> is <code>0</code>.</p>
<p>So, to conclude, the expression <code>Z(factgen)</code> behaves exactly like a function which calculates a factorial.
You can test it by calling <code>console.log(Z(factgen)(4))</code>.</p>
<p>In fancy parlance, the function <code>Y</code> is called the <strong>Y combinator</strong>.
It is a fixed-point combinator: given a function, it produces one of its fixed points.</p>
<p>But the version that actually works in JavaScript is usually called the <strong>Z combinator</strong>.
Conceptually it performs the same task as <code>Y</code>, but introduces an extra layer of indirection so that the construction can survive the eager evaluation strategy.</p>
<h2>The End</h2>
<p>So, after many an ordeal and digression, we have finally defined a factorial without iteration or recursion.
And if you insist, even without declarations:</p>
<pre><code class="language-javascript">(f =&gt; (x =&gt; f(v =&gt; x(x)(v))) (x =&gt; f(v =&gt; x(x)(v)))) (cb =&gt; n =&gt; (n === 0) ? 1 : n * cb(n - 1))
</code></pre>
<p>Case closed.</p>
]]></description>
    </item>
      </channel>
    </rss>