<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>pwlmc.dev</title>
  <subtitle>Notes on software engineering, architecture, functional programming and the craft of writing code.</subtitle>
  <link href="https://pwlmc.dev/feed.xml" rel="self"/>
  <link href="https://pwlmc.dev/"/>
  <updated>2026-07-23T00:00:00.000Z</updated>
  <id>https://pwlmc.dev/</id>
  <author>
    <name>Paweł Maciejewski</name>
  </author>
  <entry>
    <title>Your UI Needs More Than Unit Tests</title>
    <link href="https://pwlmc.dev/posts/unit-testing-ui-is-never-a-good-idea/"/>
    <updated>2026-07-23T00:00:00.000Z</updated>
    <id>https://pwlmc.dev/posts/unit-testing-ui-is-never-a-good-idea/</id>
    <summary>The debate around testing is polarized, and simplistic answers are often applied to a problem that is not simple. In this article, I outline my approach to testing from a front-end architecture perspective and argue that writing only unit tests for UI is probably not what you should be doing.</summary>
    <content type="html"><![CDATA[<p>Testing code is a polarizing topic in the industry, and it always has been. Some
people say that if a line of code is not tested, it should be deleted. This may
sound dogmatic, but you will find the topic of testing popping up in almost
every area of software development. For example, sometimes what is legacy code
is defined by the absence of tests for the code in question. The list of
examples goes on, but the claims are not universally accepted. Sceptics will
argue that they are too radical and aiming for full test coverage is simply not
worth the hustle.</p>
<p>It is clear that Test-Driven Development (TDD) is an important part of software
engineering. That said, I tend to view that kind of simplistic reasoning with
suspicion. The idea that every line of code needs to be tested, or it won’t be
maintainable, does not sound practical. It is an easy solution to a nuanced
problem.</p>
<p>Simple solutions have a special kind of charm. They are easy to explain and
enforce, but they are rarely optimal. In this article, I will explain my testing
strategy for the applications I build. I will focus on the front-end perspective
primarily because testing user interfaces is challenging. I should warn you that
it is fairly involved as it requires learning about various techniques of
testing UI, but before we get there, we need to take a step back and answer a
more fundamental question.</p>
<h2>Should you test your code?</h2>
<p>First, let’s acknowledge that the case for testing is overwhelming. It is widely
accepted that automated testing improves project maintainability, decreases the
number of bugs in production, and prevents regressions. Tests can serve as
reliable documentation of business logic, and indirectly improve the overall
design of code being tested. My experience supports these claims, but for the
sake of brevity, I will skip the details and avoid listing every reason to cover
your code with automated tests. It is a broad topic that could easily become a
separate series of articles. If you are not convinced yet, there is no shortage
of excellent material online explaining the benefits of testing code, and I am
sure you will find strong resources in minutes.</p>
<p>Full disclosure: I have always had an extremely positive attitude toward
automated tests, especially unit tests. For some reason, the idea of automated
testing resonated strongly with me. I genuinely enjoy writing my code using a
strict Test-Driven Development method, and I aim for a comprehensive test suite
in all my personal projects as well. Because of this bias, in most engineering
teams I joined, I advocated for higher test coverage. Something that was obvious
to me was that it should include covering UI code with unit tests. In hindsight,
it was a mistake.</p>
<p>So despite the undoubtedly beneficial effects of automated software testing and
my personal sentiment, today my answer to the question “Should you test all your
code?” is: it depends. While I still consider myself a huge fan of TDD and
automated testing in general, these days I’m also more aware of the trade-offs
we need to consider when making that choice.</p>
<h2>Why unit testing should not be your primary way of testing UI</h2>
<p>When I ask candidates during interviews about their experience with automated
tests, the most common answer I get is: unit testing. Other types of testing are
also mentioned from time to time, integration tests, E2E tests, visual
regression tests, synthetic monitoring/smoke tests, but unit tests seem to be
the most widely adopted.</p>
<p>The main reason why unit tests are so popular is quite clear: they are fast. By
definition, a code unit (function, component, class, etc.) needs to be tested in
isolation, which implies that dependencies of the unit under test often have to
be mocked. Mocking a dependency replaces a real dependency with a fake one,
usually a simplified version prepared specifically for testing. This technique
ensures that a correctly written unit test is independent of potentially heavy
runtimes. In the case of JavaScript web applications, that costly runtime is the
browser environment, so to test it efficiently we need to mock it. Luckily,
mature browser-like environments such as <code>jsdom</code> and <code>happy-dom</code> already exist
and we can simply use them. That allows a correctly written unit test to run in
anything from a couple to a couple hundred milliseconds, and it is normal to run
thousands of unit tests in every test cycle.</p>
<p>The problem with this approach is the mocking itself. Every mock is a
simplification of something larger. It can be inaccurate or incorrect, and it
may miss an obscure edge case, so the more you mock, the less confidence you
have in the test you write. Tests with mocks verify how your code behaves in a
particular mocked environment, not in the real application. Not to mention that
someone needs to write the mock in the first place and keep it up to date as the
mocked dependency changes. Browser mocks aside, frameworks like React can
increase the burden because they often require additional mocking to simulate
the way components render.</p>
<p>In general, testing UI tends to be a frustrating experience. Part of it can be
attributed to the aforementioned browser and framework mocks that developers
have to use in order to test even the simplest component. Things get only worse
when you consider “smarter” components. These are components that import parts
of the application that require additional mocking. For example, when a UI
component imports a function that fetches data from a REST API, that function
will need mocking as well. Correctly splitting the UI from the business logic
can help alleviate the issue, but it can never be fully avoided.</p>
<p>As a result, in many projects UI is left untested or tested sporadically. In
others, an alternative is proposed, a method to fully replace unit testing of UI
components. Most often, that alternative is end-to-end tests.</p>
<h2>E2E tests are not a silver bullet</h2>
<p>End-to-end testing is a type of automated testing that takes a more user-centric
perspective. To run it, we need to launch a browser and have a way to simulate
interactions such as clicking, scrolling, and navigating. While it may sound
like a lot, E2E is a well-established technique for testing user interfaces. The
tooling around it, with frameworks like <code>cypress</code> and <code>playwright</code>, is stable
and mature.</p>
<p>Compared to unit tests, E2E tests can look like a step up that could fully
replace them. While mocking is still required, at least we can benefit from a
real browser environment, which gives us much better confidence in the written
tests. This is especially true for functionality that is practically impossible
to test well with unit tests. One classic example is drag and drop, where the
amount of mocking required for unit tests can get so high that it defies the
purpose. E2E is a much better tool for testing this.</p>
<p>However, the main issue with E2E tests is speed. Browsers are complicated pieces
of software, and running them does not come cheap. While unit tests run in
milliseconds, a single E2E test executes in seconds. Depending on its
complexity, it can take up to half a minute, and that can still be considered OK
for an E2E test. Of course, those times can vary depending on the machine they
are executed on, and in CI they can be even longer. No matter how well
provisioned your environment is, we are often looking at around 100x longer
execution times compared to unit tests.</p>
<p>The fact that E2E tests take seconds to execute means we cannot fully replace
unit tests with them. Let us break down why. We can assume that a single E2E
test gives better confidence than a single unit test, which means one E2E test
replaces multiple unit tests. But how many? If we want to replace unit tests
with E2E tests without increasing CI run times, one E2E test would need to
replace roughly 100 unit tests. Practice shows that this is not the case. It is
highly dependent on the project and the code we are testing, but whenever logic
branches out, we need multiple tests to cover those branches. For example,
testing a web form with complex business logic can easily require a dozen E2E
tests. Our simple math shows that this would be equivalent to running a few
thousand unit tests. Needless to say, you need far fewer unit tests to fully
tests to fully cover a form even if it has very complicated conditional
rendering logic or validation rules.</p>
<p>Another issue is that E2E tests validate whole functionality at once, which
requires putting many independent code units together and running them in a
browser. As a result, they are not the right choice for documenting the intended
behavior of smaller units such as functions and classes. They do an excellent
job at documenting whole user flows, for example, whether a user can sign in to
an application with invalid credentials. But documenting that
<code>signIn(userId: string)</code> should throw a <code>401 Unauthorized</code> error when supplied
credentials are incorrect is a job for a unit test. So the idea that E2E tests
can be a 1:1 replacement for unit tests is fundamentally flawed. They each come
with their own trade-offs and should be treated as complementary tools rather
than alternatives.</p>
<p>All that said, I understand where the idea of replacing unit tests with E2E
tests comes from. Both techniques share one trait: they are good at assessing
code behavior, whether a function returned the expected result or whether a
callback was called when specific conditions were met. They answer the question:
“When the user does X, does Y happen?” They are meant to validate intent.
However, there is another kind of testing that answers a different question and,
if used correctly, can be very efficient for testing user interfaces.</p>
<h2>Validating behavior vs detecting changes in structure</h2>
<p>The other kind of tests we can employ are regression tests. These tests, unlike
unit tests and E2E tests, do not describe the expected behavior. Instead, they
preserve some state and detect if that state has changed. It starts with
creating a snapshot of some testable structure - it can be a serialization of
some large object or a screenshot, but in principle it can be anything. The
first run of a regression test is always a “pass” and the goal is to create a
saved state. Only the subsequent runs of the test may fail. The test will fail
if the tested state is different than the saved state. If the test fails,
however, it doesn’t necessarily mean that the new state is not correct. After
all, regression tests do not encode any expectation. A failed regression test is
a signal for the developer that the state changed and it requires their
attention, and it is for them to decide if the new state is correct or not. If
it’s incorrect, then it should be fixed. If it is correct, then it should be
accepted as a new saved state.</p>
<p>Regression testing is not as good at expressing intent as unit tests and E2E
tests, but they allow for catching unintentional changes in complex structures.
It’s worth noting that each time a regression test fails, we rely on the
developer to decide if the change is intended. If we want it to be reliable, the
snapshot has to have some properties that will help the developer to decide
quickly about the nature of the failure. Let’s keep that in mind as it’s going
to be important in the later part of this section. For now, let’s focus on two
widely adopted types of regression testing: snapshot testing and visual
regression testing. It’s interesting to see how they take the same idea of
regression testing and achieve the completely different level of usability.</p>
<p>Let’s take snapshot testing first. It’s usually implemented by frameworks for
unit testing like <code>vitest</code> and <code>jest</code>. The snapshot they take is some string
representation of the UI that is saved on the filesystem and committed alongside
the test files. That representation is defined by the framework we use to render
our test UI, but with <code>React</code> the most common choice these days is
<code>testing-library</code>, which produces a human-readable string representation that
resembles HTML code.</p>
<p>Even though the snapshot is human-readable, it doesn’t automatically mean it’s
easy to comprehend because it’s still very far from what a user sees on the
screen. For simple components, it may be sufficient, but it’s not enough for
more involved ones. When such a test fails, the developer may have a hard time
assessing if the new state should be accepted or not. The less readable the
representation of the UI is, the bigger the chance of accepting an unexpected
change by mistake. Unfortunately, that is what happens often with snapshot tests
and for this reason I don’t recommend using them. Even though they are
relatively fast and easy to introduce since they come with the popular
unit-testing frameworks, they are not worth it. They can create a false sense of
security which in my opinion is worse than not having any tests at all.</p>
<p>While I don’t recommend snapshot testing, it doesn’t mean regression testing as
a whole does not have its use for the front-end. Quite the contrary. The other
type, visual regression testing, has proven to be far more useful in testing
user interfaces. The key seems to be the right snapshot. Instead of a string
representation of an interface, it produces a screenshot of the actual render of
the component under test. The benefits of such an approach are obvious - it’s
much easier for the developer to decide if the change is a regression by
comparing two screenshots. With snapshot testing we would be limited to a file
diff, while visual regression testing tools usually offer more sophisticated
ways to compare changes like side-by-side view, diff strobing, etc.</p>
<p>Under the hood, visual regression testing needs a browser to render the UI. So
similar to how E2E tests are slower than unit tests, visual regression tests are
slower than snapshot tests. Also, similar to E2E tests, they can’t be a
replacement for unit tests, especially because regression testing isn’t well
suited for testing the behavior. That said, it’s not a flaw. User interfaces are
inherently a mix of presentation and behavior. Therefore, for enforcing that our
UI behaves as we expect, unit and E2E tests are the natural candidates. For
ensuring our interface stays the way we want it to look, we should use visual
regression testing. Using all three testing techniques forms the foundation of
my front-end testing strategy.</p>
<h2>Complete strategy for testing front-end applications</h2>
<p>The core principle of my recommendation and strategy for testing user interfaces
is already highlighted in the previous section:</p>
<ul>
<li>Unit tests and E2E tests should be used to test UI behavior</li>
<li>Visual regression tests should be used for testing UI look</li>
</ul>
<p>That means that in every project I work with, I want to have all three
capabilities ready to be used, even if there are no actual tests written yet. I
want to set up the CI pipeline to have <code>vitest</code> running unit tests, which is
already a common practice in the industry. I will work to set up <code>playwright</code>
running E2E tests, even if there are none yet. Most of the time I will want to
have <code>storybook</code> in good shape so it can be used with one of the visual
regression testing tools like Chromatic. That said, there are other visual
regression testing setups that work well for different use cases. As I mentioned
earlier, the use of snapshot testing, even though it usually comes for free with
<code>vitest</code>, should be discouraged.</p>
<p>All three together make up a solid testing setup for any modern front-end
project. Of course, it should not stop there, as there are more testing methods
you should invest in as the project grows, but the setup above will get you a
long way. That said, there is one very important aspect that we still need to
cover: the rule that complements the front-end testing strategy.</p>
<ul>
<li>Business logic should be separated from the UI.</li>
</ul>
<p>To make myself clear, by saying that, I am not pushing back against modern
libraries like React. The true power of components lies in the fact that they
are a mix of presentation and behavior, which, some may argue, is a direct
violation of separating them by classic models like MVC. I take the position
that this is not a flaw. It is one of the reasons why component-based frameworks
are so popular these days. The key distinction to keep in mind is that not all
behavior is automatically business logic. The definitions may vary, but business
logic is behavior that exists because of the business domain of our application.
It’s invariant to the implementation. And it is, generally speaking, a good idea
to separate it from the presentation not only to make it easier to test.</p>
<p>That effort will eventually result in a growing number of visual regression
tests covering larger portions of the UI. A similar effect will also be seen for
the test coverage of the business logic. After all, when logic is properly
separated from presentation, it is much easier to unit test than when it is
embedded within a UI component. And so, when the discipline of extracting
behavior from UI is combined with the right tooling for testing it, you gain
much-needed confidence in your front-end changes, even if coverage is not 100%.</p>
]]></content>
  </entry>
  <entry>
    <title>Source Code Isn&#39;t Going Anywhere</title>
    <link href="https://pwlmc.dev/posts/source-code-is-not-going-anywhere/"/>
    <updated>2026-03-05T00:00:00.000Z</updated>
    <id>https://pwlmc.dev/posts/source-code-is-not-going-anywhere/</id>
    <summary>LLMs (a.k.a. AI) are still a hot topic at the beginning of 2026. It is definitely a leap for software engineering, but one thing I keep finding over and over again is misunderstood - the idea that LLMs can replace coding completely. It&#39;s not possible, and here&#39;s a quick summary of why.</summary>
    <content type="html"><![CDATA[<p>Generative AI is by far the most discussed topic of 2025, and at the beginning
of 2026 it’s still what everyone is talking about. This post is not meant to
teach you how to use it for building software or how to make money out of it.
I’m not an expert on any of these topics. I’m a software engineer who, like many
of us, has incorporated GenAI tools (like Claude Code or Codex) into my daily
workflow.</p>
<p>One thing can be said for sure: it helps me do my job, and I hope it will only
get better with time. That said, it has some hard limitations and I want to talk
about one of them. Something that I believe is already acknowledged by the
engineers but keeps coming up in discussions, especially with people outside
engineering. The idea that, even if we are not there yet today, the source code
will eventually be replaced with prompts. That one day, talking to your LLM will
be the primary way of building software and that the source code will become an
insignificant artifact of the development pipeline.</p>
<p>That is not possible, and in this article I will quickly explain why. I also
understand that these claims may come across as suspicious. After all, I spent
most of my life learning programming languages and how to use them. It’s not
about job security, though. It’s about the fundamental limitation of the
LLM-based generative AI that we should all be aware of.</p>
<h2>You can’t replace source code with a prompt</h2>
<p>The key argument against replacing source code with prompts is that natural
language used to communicate with LLMs is not precise. There are other problems
with LLMs in their current form, such as the fact that they are not free. Even
if we take open-source models into account, you would still need a powerful
machine to run them. Compared to programming, where the learning material is
available for free online and it doesn’t have any special system requirements,
generative AI is an exclusive technology. But all that aside, the argument about
lack of precision should already be enough to close the discussion.</p>
<p>The reason we have programming languages is to allow engineers to write
instructions in human-readable code. The code that humans write, however, is not
what machines can “read”. To function, machines need what we call machine code
– a sequence of zeros and ones that is unreadable to humans. We couldn’t write
any complex application in the pure machine code if we tried, it’s virtually
impossible. So we invented programming languages and tooling for converting the
source code into machine code.</p>
<p>Getting to where we are today with modern programming languages (like Python,
Rust, Go, TypeScript, etc.) was a slow process. It took us decades, and as a
result we got a lot of programming languages to choose from. Some of the older
ones are still actively used today, while others are forgotten. That said, the
underlying idea behind every single one of them is the same. Have humans read
and write code and let the programming language tooling handle the rest, so that
it can run on the machine. There are two artifacts only: source code and machine
code.<sup class="footnote-ref"><a href="#fn1" id="fnref1">[1]</a></sup></p>
<p>Programming languages can be read and understood by humans, but it doesn’t mean
it comes naturally. Every software engineer had to spend time learning a
programming language they wished to use, and some languages take years to
master. It may seem like a serious limitation but it is there for a good reason.
The key feature of the programming languages is that they are strict and they
leave no room for interpretation. As a consequence, a machine will do whatever
you tell it to. The result may be beautiful, dumb, or plain irresponsible. Tell
it to drive a car into a wall, and it will do so, and this is not a flaw. The
effective one-to-one correspondence between code and execution is the foundation
on which every programming language is built.</p>
<p>Large Language Models change that picture drastically. Before the advent of
GenAI, turning an idea into working software required someone who could write
code. Now, a machine can generate code from a plain-English description. This is
remarkable because it introduces a third stage: an LLM prompt that is turned
into source code. This is exciting, also given the fact that modern AI tools are
capable of doing much more than just generating code. They can scan thousands of
lines of code in seconds, spot patterns, find inconsistencies, and provide the
analysis back in a language that anyone can understand. This is something that
couldn’t be easily done before, so the productivity gains for software engineers
will be unlike anything the industry has seen in a long time.</p>
<p>With all that said, natural language is not precise enough to describe
executable instructions reliably. An inherent property of every human natural
language is that it’s open to interpretation. It works for communication between
humans, but it can be an obstacle in areas where we want no ambiguity. It’s the
reason mathematicians devoted centuries to developing rigorous formal notation.
In that sense, formalism is an achievement of civilization. It is the core
mechanism that enables precision. Programming languages are grounded in the
mathematical formalism and inherit this property. When you understand the source
code, you know for sure how the machine will behave. The same can’t be said
about the natural language.</p>
<h2>You shouldn’t surrender source code, even if you could</h2>
<p>Let us also remember the other aspect of software engineering: maintenance. The
true feat is not writing code that works, but doing it in a way that is easy to
adapt to the ever-changing business conditions.</p>
<p>Imagine that source code is an unimportant middle stage between prompt and
machine code, that no one has to read or understand. Wouldn’t that mean we will
lose our ability to build maintainable systems? Investigating every bug or
making any change to the existing codebase would require involving LLMs to
understand and modify it. It’s an unsettling perspective, but I’m sure it would
be great news for companies whose business is selling tokens.</p>
<p>So no, source code is not going anywhere. It will be read, and modified by
humans. Understanding source code keeps us in control. Surrendering it for
convenience may seem like a good idea until you see the invoice.</p>
<hr class="footnotes-sep">
<section class="footnotes">
<ol class="footnotes-list">
<li id="fn1" class="footnote-item"><p>In reality it’s much more complicated than that. Compilers and interpreters
will use various optimization techniques that may produce many more
intermediate-stage artifacts. In normal circumstances, software engineers
are not required to read or modify them, and the last stage is always the
same: the machine code. <a href="#fnref1" class="footnote-backref">↩︎</a></p>
</li>
</ol>
</section>
]]></content>
  </entry>
  <entry>
    <title>When Breaking The Rules Is Actually OK</title>
    <link href="https://pwlmc.dev/posts/okfp-breaks-one-of-the-most-fundamental-rules-of-fp/"/>
    <updated>2025-12-14T00:00:00.000Z</updated>
    <id>https://pwlmc.dev/posts/okfp-breaks-one-of-the-most-fundamental-rules-of-fp/</id>
    <summary>Here is a lesson I learned while building OKFP, my functional programming library for TypeScript. Sometimes, breaking one of the most fundamental rules of functional programming, one you are taught never to break, can lead to a better developer experience.</summary>
    <content type="html"><![CDATA[<blockquote>
<p>This post is a dev diary about a design decision in <strong>OKFP</strong>, my functional
programming library for TypeScript. You can check it out here:
<a href="https://pwlmc.github.io/ok-fp/">https://pwlmc.github.io/ok-fp/</a></p>
</blockquote>
<p>In classical functional programming, especially in languages like Haskell,
functions are expected to be pure. Let’s recap – a <em>pure function</em>:</p>
<ul>
<li>has no side effects</li>
<li>always returns the same output for the same input</li>
</ul>
<p>In general, <em>pure functions</em> are desirable. They require no mocking, so testing
them is a breeze. They lead to predictable and maintainable programs. That said,
in real-world applications, escaping impurity is seemingly impossible, as there
is always some state to keep and transform. As a matter of fact, it’s quite
common to have programs that are extremely stateful, where accessing state (from
a database, network, or filesystem), updating it, and saving it are a big part
of their business. However, the sheer fact that our program is stateful doesn’t
automatically mean we can’t keep our functions pure.</p>
<h2>Separating Data From Operations</h2>
<p>If you look closely, the definition of a pure function doesn’t forbid
interaction with state. It does, however, forbid <em>pure functions</em> from relying
on implicit persistent external or internal state. To better understand what I
mean by <em>implicit state</em>, take a look at the following function:</p>
<pre class="shiki night-owl" style="background-color:#011627;color:#d6deeb" tabindex="0"><code class="language-ts"><span class="line"><span style="color:#C792EA">function</span><span style="color:#82AAFF;font-style:italic"> createInc</span><span style="color:#D9F5DD">()</span><span style="color:#D6DEEB"> {</span></span>
<span class="line"><span style="color:#C792EA">  let</span><span style="color:#D6DEEB;font-style:italic"> i</span><span style="color:#C792EA"> =</span><span style="color:#F78C6C"> 0</span><span style="color:#D6DEEB">;</span></span>
<span class="line"><span style="color:#C792EA;font-style:italic">  return</span><span style="color:#C792EA"> function</span><span style="color:#82AAFF;font-style:italic"> inc</span><span style="color:#D9F5DD">()</span><span style="color:#D6DEEB"> {</span></span>
<span class="line"><span style="color:#D6DEEB">    i</span><span style="color:#C792EA">++</span><span style="color:#D6DEEB">;</span></span>
<span class="line"><span style="color:#C792EA;font-style:italic">    return</span><span style="color:#D6DEEB"> i;</span></span>
<span class="line"><span style="color:#D6DEEB">  };</span></span>
<span class="line"><span style="color:#D6DEEB">}</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C792EA">const</span><span style="color:#82AAFF;font-style:italic"> inc</span><span style="color:#C792EA"> =</span><span style="color:#82AAFF;font-style:italic"> createInc</span><span style="color:#D6DEEB">();</span></span>
<span class="line"><span style="color:#82AAFF;font-style:italic">inc</span><span style="color:#D6DEEB">(); </span><span style="color:#637777;font-style:italic">// 1</span></span>
<span class="line"><span style="color:#82AAFF;font-style:italic">inc</span><span style="color:#D6DEEB">(); </span><span style="color:#637777;font-style:italic">// 2</span></span>
<span class="line"><span style="color:#82AAFF;font-style:italic">inc</span><span style="color:#D6DEEB">(); </span><span style="color:#637777;font-style:italic">// 3</span></span></code></pre>
<p>The <code>inc</code> function returned from <code>createInc</code> is not pure because it does not
return the same output for the same input. But there’s an easy way to fix it. If
we separate state from the calculations, all of a sudden the function can be
considered pure:</p>
<pre class="shiki night-owl" style="background-color:#011627;color:#d6deeb" tabindex="0"><code class="language-ts"><span class="line"><span style="color:#C792EA;font-style:italic">return</span><span style="color:#C792EA"> function</span><span style="color:#82AAFF;font-style:italic"> inc</span><span style="color:#D9F5DD">(</span><span style="color:#D7DBE0">x</span><span style="color:#7FDBCA">:</span><span style="color:#C5E478"> number</span><span style="color:#D9F5DD">)</span><span style="color:#D6DEEB"> {</span></span>
<span class="line"><span style="color:#C792EA;font-style:italic">  return</span><span style="color:#D6DEEB"> x </span><span style="color:#C792EA">+</span><span style="color:#F78C6C"> 1</span><span style="color:#D6DEEB">;</span></span>
<span class="line"><span style="color:#D6DEEB">};</span></span>
<span class="line"></span>
<span class="line"><span style="color:#C792EA">let</span><span style="color:#D6DEEB;font-style:italic"> x</span><span style="color:#C792EA"> =</span><span style="color:#F78C6C"> 1</span><span style="color:#D6DEEB">;</span></span>
<span class="line"><span style="color:#D6DEEB">x </span><span style="color:#C792EA">=</span><span style="color:#82AAFF;font-style:italic"> inc</span><span style="color:#D6DEEB">(x); </span><span style="color:#637777;font-style:italic">// x = 2</span></span>
<span class="line"><span style="color:#D6DEEB">x </span><span style="color:#C792EA">=</span><span style="color:#82AAFF;font-style:italic"> inc</span><span style="color:#D6DEEB">(x); </span><span style="color:#637777;font-style:italic">// x = 3</span></span></code></pre>
<p>Pure functions don’t depend on any internal or external state. Data and
operations on that data are separated. In TypeScript, however, there is an issue
with this approach, and you don’t have to look far to spot it.</p>
<p>Let’s say we want to build an API that performs operations on a bank account. We
want to be able to withdraw and deposit money, freeze, unfreeze, and close the
account. One important note: any of those operations can fail, and we need to
take that into consideration when designing our API. The traditional OOP way of
modeling failure is to throw an exception, but there is a better option: the
<code>Either</code> effect, and we are going to use that.</p>
<h2>Either Effect</h2>
<blockquote>
<p>Feel free to jump to the next section if you are familiar with the Either
monad.</p>
</blockquote>
<p>The <code>Either&lt;T, E&gt;</code> effect is a wrapper type (here: <code>Either&lt;Error, Account&gt;</code>).
You can think of it as a container holding <code>Error</code> or <code>Account</code>, but while the
value is inside, you don’t need to care which one it is. Every
<code>Either&lt;Error, Account&gt;</code> needs to have a <code>flatMap</code> operation defined. Here, we
are going to name it <code>flatMapE</code> to make it clear it’s a <code>flatMap</code> for <code>Either</code>
effect:</p>
<pre class="shiki night-owl" style="background-color:#011627;color:#d6deeb" tabindex="0"><code class="language-ts"><span class="line"><span style="color:#C792EA">function</span><span style="color:#82AAFF;font-style:italic"> flatMapE</span><span style="color:#D9F5DD">(</span></span>
<span class="line"><span style="color:#D7DBE0">  e</span><span style="color:#7FDBCA">:</span><span style="color:#FFCB8B"> Either</span><span style="color:#D6DEEB">&#x3C;</span><span style="color:#FFCB8B">Error</span><span style="color:#5F7E97">,</span><span style="color:#FFCB8B"> Account</span><span style="color:#D6DEEB">>,</span></span>
<span class="line"><span style="color:#82AAFF;font-style:italic">  fn</span><span style="color:#7FDBCA">:</span><span style="color:#D9F5DD"> (</span><span style="color:#D7DBE0">account</span><span style="color:#7FDBCA">:</span><span style="color:#FFCB8B"> Account</span><span style="color:#D9F5DD">)</span><span style="color:#C792EA"> =></span><span style="color:#FFCB8B"> Either</span><span style="color:#D6DEEB">&#x3C;</span><span style="color:#FFCB8B">Error</span><span style="color:#5F7E97">,</span><span style="color:#FFCB8B"> Account</span><span style="color:#D6DEEB">>,</span></span>
<span class="line"><span style="color:#D9F5DD">)</span><span style="color:#7FDBCA">:</span><span style="color:#FFCB8B"> Either</span><span style="color:#D6DEEB">&#x3C;</span><span style="color:#FFCB8B">Error</span><span style="color:#5F7E97">,</span><span style="color:#FFCB8B"> Account</span><span style="color:#D6DEEB">>;</span></span></code></pre>
<p>The point of <code>flatMapE</code> is that it will run <code>fn</code> only when <code>Either</code> contains
<code>Account</code>. It will unwrap <code>Account</code> from <code>Either</code> automatically and pass it as
an argument to <code>fn</code>. The return value is also <code>Either&lt;Error, Account&gt;</code> which
allows <code>fn</code> to fail the operation by returning <code>Error</code> wrapped in <code>Either</code>.</p>
<p>The important bit to remember for <code>flatMapE</code> is that it will do nothing if the
<code>Either</code> passed as the first argument contains <code>Error</code>. You can also think of it
as mapping or flat-mapping over an empty array. After all,
<code>[].map((x) =&gt; console.log(x))</code> is a no-op as well.</p>
<p>We are not going to dive deep into how to unwrap values from <code>Either</code>. For the
purpose of this article, you should know that wrapping the non-error data in
<code>Either</code> is traditionally done by calling the <code>rightE</code> function:</p>
<pre class="shiki night-owl" style="background-color:#011627;color:#d6deeb" tabindex="0"><code class="language-ts"><span class="line"><span style="color:#C792EA">function</span><span style="color:#82AAFF;font-style:italic"> rightE</span><span style="color:#D6DEEB">&#x3C;</span><span style="color:#FFCB8B">E</span><span style="color:#5F7E97">,</span><span style="color:#FFCB8B"> T</span><span style="color:#D6DEEB">></span><span style="color:#D9F5DD">(</span><span style="color:#D7DBE0">value</span><span style="color:#7FDBCA">:</span><span style="color:#FFCB8B"> T</span><span style="color:#D9F5DD">)</span><span style="color:#7FDBCA">:</span><span style="color:#FFCB8B"> Either</span><span style="color:#D6DEEB">&#x3C;</span><span style="color:#FFCB8B">E</span><span style="color:#5F7E97">,</span><span style="color:#FFCB8B"> T</span><span style="color:#D6DEEB">>;</span></span></code></pre>
<p>What other operations can be defined for the <code>Either</code> effect is beyond the scope
of this article. If you want to learn more about this and other useful effects,
please refer to the OKFP docs: <a href="https://pwlmc.github.io/ok-fp/either.html">https://pwlmc.github.io/ok-fp/either.html</a>.</p>
<p>Now, let’s get back to modeling our Account API.</p>
<h2>Account API with pure functions</h2>
<p>If we model operations as pure functions, we might end up with an API that
resembles the one below:</p>
<pre class="shiki night-owl" style="background-color:#011627;color:#d6deeb" tabindex="0"><code class="language-ts"><span class="line"><span style="color:#C792EA">function</span><span style="color:#82AAFF;font-style:italic"> deposit</span><span style="color:#D9F5DD">(</span><span style="color:#D7DBE0">account</span><span style="color:#7FDBCA">:</span><span style="color:#FFCB8B"> Account</span><span style="color:#D6DEEB">, </span><span style="color:#D7DBE0">amount</span><span style="color:#7FDBCA">:</span><span style="color:#C5E478"> number</span><span style="color:#D9F5DD">)</span><span style="color:#7FDBCA">:</span><span style="color:#FFCB8B"> Either</span><span style="color:#D6DEEB">&#x3C;</span><span style="color:#FFCB8B">Error</span><span style="color:#5F7E97">,</span><span style="color:#FFCB8B"> Account</span><span style="color:#D6DEEB">> {}</span></span>
<span class="line"><span style="color:#C792EA">function</span><span style="color:#82AAFF;font-style:italic"> withdraw</span><span style="color:#D9F5DD">(</span><span style="color:#D7DBE0">account</span><span style="color:#7FDBCA">:</span><span style="color:#FFCB8B"> Account</span><span style="color:#D6DEEB">, </span><span style="color:#D7DBE0">amount</span><span style="color:#7FDBCA">:</span><span style="color:#C5E478"> number</span><span style="color:#D9F5DD">)</span><span style="color:#7FDBCA">:</span><span style="color:#FFCB8B"> Either</span><span style="color:#D6DEEB">&#x3C;</span><span style="color:#FFCB8B">Error</span><span style="color:#5F7E97">,</span><span style="color:#FFCB8B"> Account</span><span style="color:#D6DEEB">> {}</span></span>
<span class="line"><span style="color:#C792EA">function</span><span style="color:#82AAFF;font-style:italic"> freeze</span><span style="color:#D9F5DD">(</span><span style="color:#D7DBE0">account</span><span style="color:#7FDBCA">:</span><span style="color:#FFCB8B"> Account</span><span style="color:#D9F5DD">)</span><span style="color:#7FDBCA">:</span><span style="color:#FFCB8B"> Either</span><span style="color:#D6DEEB">&#x3C;</span><span style="color:#FFCB8B">Error</span><span style="color:#5F7E97">,</span><span style="color:#FFCB8B"> Account</span><span style="color:#D6DEEB">> {}</span></span>
<span class="line"><span style="color:#C792EA">function</span><span style="color:#82AAFF;font-style:italic"> unfreeze</span><span style="color:#D9F5DD">(</span><span style="color:#D7DBE0">account</span><span style="color:#7FDBCA">:</span><span style="color:#FFCB8B"> Account</span><span style="color:#D9F5DD">)</span><span style="color:#7FDBCA">:</span><span style="color:#FFCB8B"> Either</span><span style="color:#D6DEEB">&#x3C;</span><span style="color:#FFCB8B">Error</span><span style="color:#5F7E97">,</span><span style="color:#FFCB8B"> Account</span><span style="color:#D6DEEB">> {}</span></span></code></pre>
<p>Now, if we wanted to perform a series of operations on a single account, we can
do this:</p>
<pre class="shiki night-owl" style="background-color:#011627;color:#d6deeb" tabindex="0"><code class="language-ts"><span class="line"><span style="color:#C792EA">const</span><span style="color:#82AAFF;font-style:italic"> account</span><span style="color:#C792EA"> =</span><span style="color:#C792EA"> {}</span><span style="color:#D6DEEB">; </span><span style="color:#637777;font-style:italic">// account shape left out for brevity</span></span>
<span class="line"><span style="color:#C792EA">let</span><span style="color:#D6DEEB;font-style:italic"> accountE</span><span style="color:#C792EA"> =</span><span style="color:#82AAFF;font-style:italic"> rightE</span><span style="color:#D6DEEB">(</span><span style="color:#D6DEEB;font-style:italic">account</span><span style="color:#D6DEEB">); </span><span style="color:#637777;font-style:italic">// wrap account in Either effect</span></span>
<span class="line"><span style="color:#D6DEEB">accountE </span><span style="color:#C792EA">=</span><span style="color:#82AAFF;font-style:italic"> flatMapE</span><span style="color:#D6DEEB">(accountE, </span><span style="color:#D9F5DD">(</span><span style="color:#D7DBE0">account</span><span style="color:#D9F5DD">)</span><span style="color:#C792EA"> =></span><span style="color:#82AAFF;font-style:italic"> deposit</span><span style="color:#D6DEEB">(account, </span><span style="color:#F78C6C">10</span><span style="color:#D6DEEB">));</span></span>
<span class="line"><span style="color:#D6DEEB">accountE </span><span style="color:#C792EA">=</span><span style="color:#82AAFF;font-style:italic"> flatMapE</span><span style="color:#D6DEEB">(accountE, </span><span style="color:#D9F5DD">(</span><span style="color:#D7DBE0">account</span><span style="color:#D9F5DD">)</span><span style="color:#C792EA"> =></span><span style="color:#82AAFF;font-style:italic"> withdraw</span><span style="color:#D6DEEB">(account, </span><span style="color:#F78C6C">5</span><span style="color:#D6DEEB">));</span></span>
<span class="line"><span style="color:#D6DEEB">accountE </span><span style="color:#C792EA">=</span><span style="color:#82AAFF;font-style:italic"> flatMapE</span><span style="color:#D6DEEB">(accountE, </span><span style="color:#D9F5DD">(</span><span style="color:#D7DBE0">account</span><span style="color:#D9F5DD">)</span><span style="color:#C792EA"> =></span><span style="color:#82AAFF;font-style:italic"> freeze</span><span style="color:#D6DEEB">(account));</span></span>
<span class="line"><span style="color:#D6DEEB">accountE </span><span style="color:#C792EA">=</span><span style="color:#82AAFF;font-style:italic"> flatMapE</span><span style="color:#D6DEEB">(accountE, </span><span style="color:#D9F5DD">(</span><span style="color:#D7DBE0">account</span><span style="color:#D9F5DD">)</span><span style="color:#C792EA"> =></span><span style="color:#82AAFF;font-style:italic"> unfreeze</span><span style="color:#D6DEEB">(account));</span></span></code></pre>
<p>This is not inherently bad, but it’s definitely not ergonomic since we have to
repeat <code>accountE</code> twice for every operation. Another way to perform the same set
of operations on <code>Account</code> is to nest calls to subsequent operations:</p>
<pre class="shiki night-owl" style="background-color:#011627;color:#d6deeb" tabindex="0"><code class="language-ts"><span class="line"><span style="color:#C792EA">const</span><span style="color:#82AAFF;font-style:italic"> account</span><span style="color:#C792EA"> =</span><span style="color:#C792EA"> {}</span><span style="color:#D6DEEB">; </span><span style="color:#637777;font-style:italic">// account shape left out for brevity</span></span>
<span class="line"><span style="color:#C792EA">const</span><span style="color:#82AAFF;font-style:italic"> accountE</span><span style="color:#C792EA"> =</span><span style="color:#82AAFF;font-style:italic"> flatMapE</span><span style="color:#D6DEEB">(</span></span>
<span class="line"><span style="color:#82AAFF;font-style:italic">  flatMapE</span><span style="color:#D6DEEB">(</span></span>
<span class="line"><span style="color:#82AAFF;font-style:italic">    flatMapE</span><span style="color:#D6DEEB">(</span></span>
<span class="line"><span style="color:#82AAFF;font-style:italic">      flatMapE</span><span style="color:#D6DEEB">(</span><span style="color:#82AAFF;font-style:italic">rightE</span><span style="color:#D6DEEB">(</span><span style="color:#D6DEEB;font-style:italic">account</span><span style="color:#D6DEEB">)</span><span style="color:#C792EA">,</span><span style="color:#D9F5DD"> (</span><span style="color:#D7DBE0;font-style:italic">account</span><span style="color:#D9F5DD">)</span><span style="color:#C792EA"> =></span><span style="color:#82AAFF;font-style:italic"> deposit</span><span style="color:#D6DEEB">(</span><span style="color:#D6DEEB;font-style:italic">account</span><span style="color:#C792EA">,</span><span style="color:#F78C6C"> 10</span><span style="color:#D6DEEB">))</span><span style="color:#C792EA">,</span></span>
<span class="line"><span style="color:#D9F5DD">      (</span><span style="color:#D7DBE0;font-style:italic">account</span><span style="color:#D9F5DD">)</span><span style="color:#C792EA"> =></span><span style="color:#82AAFF;font-style:italic"> withdraw</span><span style="color:#D6DEEB">(</span><span style="color:#D6DEEB;font-style:italic">account</span><span style="color:#C792EA">,</span><span style="color:#F78C6C"> 5</span><span style="color:#D6DEEB">)</span><span style="color:#C792EA">,</span></span>
<span class="line"><span style="color:#D6DEEB">    )</span><span style="color:#C792EA">,</span></span>
<span class="line"><span style="color:#D9F5DD">    (</span><span style="color:#D7DBE0;font-style:italic">account</span><span style="color:#D9F5DD">)</span><span style="color:#C792EA"> =></span><span style="color:#82AAFF;font-style:italic"> freeze</span><span style="color:#D6DEEB">(</span><span style="color:#D6DEEB;font-style:italic">account</span><span style="color:#D6DEEB">)</span><span style="color:#C792EA">,</span></span>
<span class="line"><span style="color:#D6DEEB">  )</span><span style="color:#C792EA">,</span></span>
<span class="line"><span style="color:#D9F5DD">  (</span><span style="color:#D7DBE0;font-style:italic">account</span><span style="color:#D9F5DD">)</span><span style="color:#C792EA"> =></span><span style="color:#82AAFF;font-style:italic"> unfreeze</span><span style="color:#D6DEEB">(</span><span style="color:#D6DEEB;font-style:italic">account</span><span style="color:#D6DEEB">)</span><span style="color:#C792EA">,</span></span>
<span class="line"><span style="color:#D6DEEB">);</span></span></code></pre>
<p>I think we can agree that this pyramid of doom is even worse than the previous
example. So the question arises: how do functional programming languages deal
with it?</p>
<h2>FP way of dealing with nested calls</h2>
<p>Many functional programming languages provide language-level support to make
pure functional style ergonomic.</p>
<p>Haskell offers <code>do</code> notation:</p>
<pre class="shiki night-owl" style="background-color:#011627;color:#d6deeb" tabindex="0"><code class="language-haskell"><span class="line"><span style="color:#C792EA">let</span><span style="color:#D6DEEB"> result </span><span style="color:#7FDBCA">=</span></span>
<span class="line"><span style="color:#C792EA;font-style:italic">  do</span></span>
<span class="line"><span style="color:#D6DEEB">    account </span><span style="color:#7FDBCA">&#x3C;-</span><span style="color:#82AAFF"> Right</span><span style="color:#D6DEEB"> account</span></span>
<span class="line"><span style="color:#D6DEEB">    account </span><span style="color:#7FDBCA">&#x3C;-</span><span style="color:#D6DEEB"> deposit account </span><span style="color:#F78C6C">10</span></span>
<span class="line"><span style="color:#D6DEEB">    account </span><span style="color:#7FDBCA">&#x3C;-</span><span style="color:#D6DEEB"> withdraw account </span><span style="color:#F78C6C">5</span></span>
<span class="line"><span style="color:#D6DEEB">    account </span><span style="color:#7FDBCA">&#x3C;-</span><span style="color:#D6DEEB"> freeze account</span></span>
<span class="line"><span style="color:#D6DEEB">    account </span><span style="color:#7FDBCA">&#x3C;-</span><span style="color:#D6DEEB"> unfreeze account</span></span>
<span class="line"><span style="color:#D6DEEB">    pure account</span></span></code></pre>
<p>In Scala we have <code>for-comprehensions</code>:</p>
<pre class="shiki night-owl" style="background-color:#011627;color:#d6deeb" tabindex="0"><code class="language-scala"><span class="line"><span style="color:#C792EA">val</span><span style="color:#C5E478"> result</span><span style="color:#7FDBCA">:</span><span style="color:#FFCB8B"> Either</span><span style="color:#D6DEEB">[</span><span style="color:#FFCB8B">AccountError</span><span style="color:#D6DEEB">, </span><span style="color:#FFCB8B">Account</span><span style="color:#D6DEEB">] </span><span style="color:#C792EA">=</span></span>
<span class="line"><span style="color:#C792EA;font-style:italic">  for</span></span>
<span class="line"><span style="color:#D6DEEB">    account </span><span style="color:#7FDBCA">&#x3C;-</span><span style="color:#FFCB8B"> Right</span><span style="color:#D6DEEB">(account)</span></span>
<span class="line"><span style="color:#D6DEEB">    account </span><span style="color:#7FDBCA">&#x3C;-</span><span style="color:#D6DEEB"> deposit(account, </span><span style="color:#F78C6C">10</span><span style="color:#D6DEEB">)</span></span>
<span class="line"><span style="color:#D6DEEB">    account </span><span style="color:#7FDBCA">&#x3C;-</span><span style="color:#D6DEEB"> withdraw(account, </span><span style="color:#F78C6C">5</span><span style="color:#D6DEEB">)</span></span>
<span class="line"><span style="color:#D6DEEB">    account </span><span style="color:#7FDBCA">&#x3C;-</span><span style="color:#D6DEEB"> freeze(account)</span></span>
<span class="line"><span style="color:#D6DEEB">    account </span><span style="color:#7FDBCA">&#x3C;-</span><span style="color:#D6DEEB"> unfreeze(account)</span></span>
<span class="line"><span style="color:#C792EA;font-style:italic">  yield</span><span style="color:#D6DEEB"> account</span></span></code></pre>
<p>A similar effect can also be achieved in C# with LINQ query syntax.</p>
<p>What is important to note in both examples is that each language has a built-in
syntax for a sequence of transformations, which allows it to take advantage of
the most ergonomic syntax possible, while our previous TypeScript examples are
bloated at best and plain unreadable at worst.</p>
<p>Now, can we bring the same functionality to TypeScript?</p>
<h2>What Happens If We Try To Add It To TypeScript</h2>
<p>Most functional programming libraries in TypeScript that I tried attempt to
replicate the approach known from functional programming. They define free
functions, such as:</p>
<pre class="shiki night-owl" style="background-color:#011627;color:#d6deeb" tabindex="0"><code class="language-ts"><span class="line"><span style="color:#82AAFF;font-style:italic">mapE</span><span style="color:#D6DEEB">(either, fn);</span></span>
<span class="line"><span style="color:#82AAFF;font-style:italic">flatMapE</span><span style="color:#D6DEEB">(either, fn);</span></span>
<span class="line"><span style="color:#82AAFF;font-style:italic">filterE</span><span style="color:#D6DEEB">(either, predicate);</span></span></code></pre>
<p>These functions are pure and live independently from the values they operate on.
It mirrors the design found in Haskell or Scala, but in TypeScript, as we
already saw, it quickly becomes awkward for sequences of data transformations.
To alleviate this issue, we must introduce a custom pipe function to simulate
ergonomic composition:</p>
<pre class="shiki night-owl" style="background-color:#011627;color:#d6deeb" tabindex="0"><code class="language-ts"><span class="line"><span style="color:#82AAFF;font-style:italic">pipe</span><span style="color:#D6DEEB">(</span></span>
<span class="line"><span style="color:#82AAFF;font-style:italic">  rightE</span><span style="color:#D6DEEB">(account),</span></span>
<span class="line"><span style="color:#82AAFF;font-style:italic">  flatMapE</span><span style="color:#D6DEEB">(</span><span style="color:#D9F5DD">(</span><span style="color:#D7DBE0">account</span><span style="color:#D9F5DD">)</span><span style="color:#C792EA"> =></span><span style="color:#82AAFF;font-style:italic"> deposit</span><span style="color:#D6DEEB">(account, </span><span style="color:#F78C6C">10</span><span style="color:#D6DEEB">)),</span></span>
<span class="line"><span style="color:#82AAFF;font-style:italic">  flatMapE</span><span style="color:#D6DEEB">(</span><span style="color:#D9F5DD">(</span><span style="color:#D7DBE0">account</span><span style="color:#D9F5DD">)</span><span style="color:#C792EA"> =></span><span style="color:#82AAFF;font-style:italic"> withdraw</span><span style="color:#D6DEEB">(account, </span><span style="color:#F78C6C">5</span><span style="color:#D6DEEB">)),</span></span>
<span class="line"><span style="color:#82AAFF;font-style:italic">  flatMapE</span><span style="color:#D6DEEB">(</span><span style="color:#D9F5DD">(</span><span style="color:#D7DBE0">account</span><span style="color:#D9F5DD">)</span><span style="color:#C792EA"> =></span><span style="color:#82AAFF;font-style:italic"> freeze</span><span style="color:#D6DEEB">(account)),</span></span>
<span class="line"><span style="color:#82AAFF;font-style:italic">  flatMapE</span><span style="color:#D6DEEB">(</span><span style="color:#D9F5DD">(</span><span style="color:#D7DBE0">account</span><span style="color:#D9F5DD">)</span><span style="color:#C792EA"> =></span><span style="color:#82AAFF;font-style:italic"> unfreeze</span><span style="color:#D6DEEB">(account)),</span></span>
<span class="line"><span style="color:#D6DEEB">);</span></span></code></pre>
<p>Note: for simplicity’s sake, I won’t go into how <code>pipe</code> works. Let’s just focus
on the final effect for the sequential transformations of our <code>account</code> data, as
in the previous examples.</p>
<p>The resulting code looks very similar to what we could see in Haskell and Scala
examples, but is it worth it? The crux of the matter is: when your TS library
functions are pure, you are forced to introduce some form of <code>pipe</code> function to
keep the ergonomics reasonable. Without it, you won’t find many people willing
to use your tool.</p>
<p>In my opinion, it’s a mistake. While the style is mathematically pure, for many
TypeScript engineers it will feel unfamiliar and heavy. It starts to look like
an embedded language, which will eventually become a barrier to entry for your
library. For that reason, OKFP does things differently.</p>
<h2>The Decision in OKFP</h2>
<p>I decided to break the rule. Instead of relying only on free functions, effects
in OKFP are objects with methods. You still construct them with pure functions:</p>
<pre class="shiki night-owl" style="background-color:#011627;color:#d6deeb" tabindex="0"><code class="language-ts"><span class="line"><span style="color:#82AAFF;font-style:italic">right</span><span style="color:#D6DEEB">(</span><span style="color:#F78C6C">5</span><span style="color:#D6DEEB">); </span><span style="color:#637777;font-style:italic">// Either effect containing 5</span></span>
<span class="line"><span style="color:#82AAFF;font-style:italic">left</span><span style="color:#D6DEEB">(</span><span style="color:#7FDBCA">new</span><span style="color:#82AAFF;font-style:italic"> Error</span><span style="color:#D6DEEB">(</span><span style="color:#D9F5DD">"</span><span style="color:#ECC48D">Some error</span><span style="color:#D9F5DD">"</span><span style="color:#D6DEEB">)); </span><span style="color:#637777;font-style:italic">// Either effect with Error failure</span></span></code></pre>
<p>But once you have the value, you operate on it like this:</p>
<pre class="shiki night-owl" style="background-color:#011627;color:#d6deeb" tabindex="0"><code class="language-tsx"><span class="line"><span style="color:#C792EA">const</span><span style="color:#82AAFF;font-style:italic"> result</span><span style="color:#C792EA"> =</span><span style="color:#82AAFF;font-style:italic"> right</span><span style="color:#D6DEEB">(</span><span style="color:#D6DEEB;font-style:italic">account</span><span style="color:#D6DEEB">)</span></span>
<span class="line"><span style="color:#C792EA;font-style:italic">  .</span><span style="color:#82AAFF;font-style:italic">flatMap</span><span style="color:#D6DEEB">(</span><span style="color:#D9F5DD">(</span><span style="color:#D7DBE0">account</span><span style="color:#D9F5DD">)</span><span style="color:#C792EA"> =></span><span style="color:#82AAFF;font-style:italic"> deposit</span><span style="color:#D6DEEB">(account, </span><span style="color:#F78C6C">10</span><span style="color:#D6DEEB">))</span></span>
<span class="line"><span style="color:#C792EA;font-style:italic">  .</span><span style="color:#82AAFF;font-style:italic">flatMap</span><span style="color:#D6DEEB">(</span><span style="color:#D9F5DD">(</span><span style="color:#D7DBE0">account</span><span style="color:#D9F5DD">)</span><span style="color:#C792EA"> =></span><span style="color:#82AAFF;font-style:italic"> withdraw</span><span style="color:#D6DEEB">(account, </span><span style="color:#F78C6C">5</span><span style="color:#D6DEEB">))</span></span>
<span class="line"><span style="color:#C792EA;font-style:italic">  .</span><span style="color:#82AAFF;font-style:italic">flatMap</span><span style="color:#D6DEEB">(</span><span style="color:#D9F5DD">(</span><span style="color:#D7DBE0">account</span><span style="color:#D9F5DD">)</span><span style="color:#C792EA"> =></span><span style="color:#82AAFF;font-style:italic"> freeze</span><span style="color:#D6DEEB">(account))</span></span>
<span class="line"><span style="color:#C792EA;font-style:italic">  .</span><span style="color:#82AAFF;font-style:italic">flatMap</span><span style="color:#D6DEEB">(</span><span style="color:#D9F5DD">(</span><span style="color:#D7DBE0">account</span><span style="color:#D9F5DD">)</span><span style="color:#C792EA"> =></span><span style="color:#82AAFF;font-style:italic"> unfreeze</span><span style="color:#D6DEEB">(account));</span></span></code></pre>
<p>No pipe, no nested functions, just chaining calls. From a strict functional
programming perspective, this approach is controversial because the operations
are implemented as methods on objects, which technically introduces stateful
behavior. That breaks the traditional FP preference for pure free functions. But
in practice, what changes is simply the API surface. The resulting code remains
immutable, data transformations are deterministic, and the flow is explicit.</p>
<h2>Why I Think This Matters</h2>
<p>My goal with OKFP was not to reproduce functional programming patterns from
Haskell or Scala. The goal was simple:</p>
<ul>
<li>Make functional programming approachable to TypeScript engineers</li>
</ul>
<p>JavaScript and TypeScript developers are already comfortable with method
chaining, so leaning into natural patterns makes the library feel native to the
language.</p>
<p>Functional programming has incredible ideas that are still waiting for wider
adoption, but I believe those ideas should adapt to the language they live in.
Sometimes the right choice is not strict purity, but ergonomics and clarity.
OKFP breaks one of the core rules of functional programming, but I believe it
does so for the right reason.</p>
]]></content>
  </entry>
</feed>
