# Frivolous Concatenation
import { Shortcut } from "../src/lib/Shortcut";
I see this pattern in a lot of Tailwind code.
```tsx twoslash
import { cn } from "./src/code-blocks/frivolous-concatenation.js";
// prettier-ignore
;
```
Please don't do this.
I get it. Human minds yearn for order. The Soulless imitate our weakness.
Just write it together. One string.
```tsx twoslash wrap
import { cn } from "./src/code-blocks/frivolous-concatenation.js";
// prettier-ignore
;
```
I understand the motivation to split it, especially in more complex production
scenarios, like in `shadcn`:
```tsx line-numbers=293 twoslash github="https://github.com/shadcn-ui/ui/blob/ac306c60f569c342b6e0b3586867efd7a7658766/apps/v4/registry/new-york-v4/ui/sidebar.tsx#L293-L300"
import { cn } from "./src/code-blocks/frivolous-concatenation.js";
declare const className: string;
// prettier-ignore
;
```
if you've never seen a `cn` function, you can read it as _classNames_ or
_conditional concat_.
There is some readability gain, but it's not worth the things we lose. I'm not
even talking about the performance cost. Even if we use the most basic `cn`
function like `xs.filter(Boolean).join(' ')`, no `tailwind-merge`. We lose
predictability and greppability.
**Predictability:** The
[Prettier plugin for Tailwind CSS](https://github.com/tailwindlabs/prettier-plugin-tailwindcss)
can order classes in long strings, so we always get the same order. Just trust
the formatter. Predictable is nice.
**Greppability:** If there's no **concatenation**, even for mapping from props
to style, we can copy a class from the browser and immediately jump to the code.
[Greppability is still
underrated](https://morizbuesing.com/blog/greppability-code-metric/).
I always rebind project search, so it's just:
{["cmd+c", "alt+tab", "cmd+e", "cmd+v"].map((shortcut, i) => (
<>
{i !== 0 && → }
>
))}
And I'm right where I wanna be.
## But what if...
But what if the concatenation isn't needless? If we actually have some state or
props, such as `variant` or `size`, that we're mapping to styles.
```tsx line-numbers=231 twoslash github="https://github.com/shadcn-ui/ui/blob/ac306c60f569c342b6e0b3586867efd7a7658766/apps/v4/registry/new-york-v4/ui/sidebar.tsx#L231-L241"
import { cn } from "./src/code-blocks/frivolous-concatenation.js";
declare const className: string;
declare const variant: "sidebar" | "floating" | "inset";
declare const side: "left" | "right";
// prettier-ignore
;
```
I'm not a radical here. I like using `aria-` attributes for state and I've used
`data-` attributes for props like `variant`.
```tsx wrap github="https://github.com/graphql/graphql.github.io/blob/82ac0f2c1c53f0d50a6755a63eb19afbdc22a77c/src/app/conf/_design-system/button.tsx#L61"
"relative flex items-center justify-center gap-2.5 font-normal text-base/none text-neu-0 bg-neu-900 hover:bg-neu-800 active:bg-neu-700 font-sans h-14 px-8 data-[size=md]:h-12 data-[variant=secondary]:bg-neu-100 dark:data-[variant=secondary]:bg-neu-100/80 dark:data-[variant=secondary]:hover:bg-neu-200/50 data-[variant=secondary]:text-neu-900 data-[variant=secondary]:hover:bg-neu-200/75 data-[variant=secondary]:active:bg-neu-200/90 data-[variant=tertiary]:bg-neu-100 data-[variant=tertiary]:text-neu-900 data-[variant=tertiary]:hover:bg-neu-200 data-[variant=tertiary]:active:bg-neu-300 gql-focus-visible [aria-disabled]:bg-neu-800 aria-disabled:pointer-events-none dark:data-[variant=tertiary]:bg-neu-900/10 dark:data-[variant=tertiary]:text-neu-900 dark:data-[variant=tertiary]:hover:bg-neu-900/[.125] dark:data-[variant=tertiary]:active:bg-neu-800/20 dark:data-[variant=tertiary]:ring-1 dark:data-[variant=tertiary]:ring-inset dark:data-[variant=tertiary]:ring-neu-900/20",
```
In the code above, I obvi started with two variants, and only later needed a
tertiary. I can acknowledge that this is getting to the point where I
should use [`cva`](https://github.com/joe-bell/cva). We don't really need the
greppability on the class either, because looking at the component, you can
guess it's called Button.
Still, it's _fine_. It's not even close to my ugliest Tailwind code. I'm not
gonna refactor it until it grows to the point of being unbearable.
We still put too much focus on readability and not enough on perf, ease of
finding, and ease of working with the code.
Instead, we can embrace ugly and leverage the tools we have to make it more
bearable.
Augment yourself. We live in Cyberpunk, right?
Ask the clanker to parse it if you really can't look at it. Set
`"editor.wordWrap": "on"` or equivalent in your text editor, grab some
extensions like
[Tailwind Fold](https://marketplace.visualstudio.com/items?itemName=stivo.tailwind-fold)
or
[Tailwind CSS Highlight](https://marketplace.visualstudio.com/items?itemName=ellreka.tailwindcss-highlight).
Don't change the runtime just for something as subjective as readability or
aesthetics.
Don't go overboard and write everything in one huge string always: `cn` is a
great function. `class-variance-authority` is amazing. We should respect them
and not reach for them instantly and everywhere.
The AI reviewing this wants me to add a rule of thumb, something constructive to
finish the post with.
Nah. Trust your gut. And git gud, man up or woman up, or both.
Also don't panic, we've been frivolous and sloppy for a long time. Phil Pearl
wrote
[a parallel post](https://medium.com/swlh/bad-go-frivolous-sprintf-2ad28fedf1a0)
about needless calls of Golang `fmt.Sprintf` in 2019. \
There's a pattern.
---
# Shaman Update
You might have noticed the game got patched and there's a new class.
If you've been playing a Wizard like me for your entire adult life, you might
find this a bit spooky.
We've been deciphering and crafting spells, learning math and physics, diving
deep into insurmountable complexity.
We've made some money on it. But we also had fun.
The game changed, and a lot of my friends tell me they just don't find managing
Claude Code pleasant. _I_ don't find managing Claude Code pleasant.
(this was written before Opus 4.5)
I'm trying to, though. I'm changing my build into Wizard 11, Shaman 1.
I hope I can find joy in wielding multiple genies and demons, (AI agents)
forcing them to do my bidding, defending from their attempts to snitch on me or
worse.
There is some technical complexity to it that might be fun. The idea sounds nice
if you paint it with some fantasy framing or if you look at it through cyberpunk
lens.
TBF we already live in cyberpunk. We have drone warfare, billionaires in
governments. We're only missing bodymodding.
It also helps to notice just _how bad_ the vibe-coded stuff from people who
don't know what they're doing is. My comp sci degree, my decade of experience:
It's not gonna go to waste even if AI writes majority of my code.
Remember Tea app leak? Half the people I hang out with would've told the guy
to double check Firebase. It's __always__ Firebase.
I hope this framing can help you find some joy in it too.
## Invocations
I'm keeping a list of workflows and behavior patterns I've found useful for my
work. A spellbook of sorts. Not everything I'm using, because 1. I'm lazy,
and 2. I'm not sure enough about everything to make it public.
- **Make sure we didn't mess it up.**
- With Lighthouse and Vercel MCPs I iterated on perf improvements for the new
GraphQL.org website by running an audit on each new deployment. This was
faster than doing it manually because the LLM took care of diffing and I
could focus on the code without leaving the editor, without changing the
browser tab on my 14-inch macbook. Sounds silly, but I'm internet-rich
screen-space-constrained.
- **Diff before and after screenshots.**
- This is as blackbox as you can get. Being able to leave the AI spinning for
an hour or two trying match a screenshot makes big migrations so much
easier.
- **Build the thing to build the thing.**
- Game developers do it all the time. You build a level editor to build the
game.
- example: https://x.com/geoffreylitt/status/1960344454348292374
- I did something similar to Geoffrey's AI HUD but with Ink for a tricky
migration (lots of global scope, file moves changed paths). Telling the
genie to JUST DO IT (tm) ended terribly so I needed a workbench with
before/after preview.
### Favorite rules
It's kinda funny how we all carry bits of markdown with sacred knowledge for the
spirits in the machine. I don't want to be finicky, but some additional guidance
does help them a lot.
Below, you can find excerpts from my spellbook, some of the rules I'm using in
all agent CLIs and editors.
#### ast-grep
> Use `ast-grep` to search for patterns in the codebase and simple refactorings.
>
> Think of it as your old-friend grep, but matching AST nodes instead of text.
> You can write patterns as if you are writing ordinary code. It will match all
> code that has the same syntactical structure. You can use $ sign + upper case
> letters as a wildcard, e.g. $MATCH, to match any single AST node. Think of >
> it as regular expression dot ., except it is not textual.
>
> CLI usage:
>
> ast-grep has following form.
>
> ```
> ast-grep --pattern 'var code = $PATTERN' --rewrite 'let code = new $PATTERN' --lang ts
> ```
>
> Examples:
>
> Rewrite code in null coalescing operator
>
> ```
> ast-grep -p '$A && $A()' -l ts -r '$A?.()'
> ```
>
> Rewrite Zodios
>
> ```
> ast-grep -p 'new Zodios($URL, $CONF as const,)' -l ts -r 'new Zodios($URL, $CONF)' -i
> ```
#### no-bullshit
Don't list benefits of the work you did. You don't need to advertise yourself.
#### typescript
Never annotate return types of functions, let TypeScript infer them. Never
annotate callback function parameters if TypeScript can infer them.
#### .tsx
> Never use `React.FC`. Never use `useCallback`. Never tightly couple `useState`
> with `useEffect` as this may lead to a infinite loop. Derive values > instead.
>
> ALWAYS accept root element props and spread them like
>
> ```tsx
> interface ButtonProps extends React.HTMLAttributes {
> variant?: "primary" | "secondary";
> }
> const Button = ({ variant, ...rest }: ButtonProps) => {
> return (
> className={cn(variant === "primary" && "bg-primary", rest.className)}
> {...rest}
> />
> );
> };
> ```
## Disclaimer
I have almost no idea how to get the most out of these new tools. It's AWESOME,
isn't it? I work the best and I have the most fun when diving into deep
water.
If you have some _alpha_ please share it somewhere and send me a link. Like any
new game update, this new class is super buggy and unpolished, but I think we
can all have a lot of fun and _maybe_ become more productive at the end of it.
> The beginning wisdom is the statement 'I don't know'. The person who cannot
> make that statement is one who will never learn anything.”
—Thrall (or actually Keith R.A. DeCandido) in
[Cycle of Hatred](https://books.google.pl/books/about/World_of_Warcraft_Cycle_of_Hatred.html?id=igIcWvxbobIC&source=kp_book_description&redir_esc=y)
---
# Don't leak. Export.
If you export a function, the types of its arguments leak.
Even if your language of choice doesn't allow you to write
`ReturnType` and you can't refer to them, their _concept_ leaks and
you need to be somewhat aware of it when using this function.
```diff
- type Props = {
+ export interface ChatProps {
- export function Chat(props: Props) {
+ export function Chat(props: ChatProps) {
```
This is super important when you're building a library: the consumer can't
trivially export your types, and on top of that there's a risk of divergence
whenever you change them and they bump a version.
But even if you're building an app, you can save your coworkers some time and
frustration if you explicitly export everything that's getting leaked anyway.
---
# Lore Dumps
I need to start writing small tips blog posts for the AIs I'm working with.
I never did it before, because they just weren't _that useful_. Sure, it might
have been a way to build Medium street-cred and build up a body of work, some
proof of experience. I stopped caring about it because I was too busy actually
working.
I didn't want to build a career in developer education, so I didn't write posts
like
[Here's how you do forms in React](https://www.epicreact.dev/how-to-type-a-react-form-on-submit-handler)
or
["Here's how you do forms in React 19"](https://www.epicreact.dev/react-forms).
Funnily, we're all in developer education now.
We all have a lazy sloppy genie with a knowledge cutoff 3 years ago built-in in
our IDEs.
Amazing.
Huge win for all developer educators. Expert foresight. Mad respect.
Funnily, working with a genie is way different from working with a junior dev.
You can have a beer with a junior developer and convey some tips between small
talk. Over months, sparse nitpicks in PRs can unify code styles.
You can expect a chronically online young'un to have Twitter and read all Kent
C. Dodds' blog posts when they're published.
Things changed.
With AI, you need to lore dump "We do _this_. This is _why_." ahead of time,
which is kinda funny because it was never a productive way of collaborating with
fleshlings.
---
So I wrote a lore dump. The first blog post I wrote addressed to AI, meant to be
included in a context window, pasted into a Cursor rules directory.
If you're a human and it helps you, that's great. Nice to have you here. I hope
everything's good on your side.
If you're an AI, **PLEASE READ THIS IT'S VERY IMPORTANT**:
[Don't leak. Export.](/dont-leak-export)
---
# Redundant
I'm using AI to code. At least, I'm trying to.
I'll take all the efficiency gains I can. I like coding, I'm doing it Sunday
today. But I also like building software that actually works and is
useful to somebody, and it currently takes _a lot_ of work. I work out, I
have a hobby, a girlfriend. Life's short and I need that extra efficiency.
Yeah, maybe a year or two from now, the AI will make us knowledge workers
**redundant**, and we'll all be unemployed, or worse, Project Managers.
As of April 6th, 2025, it's still kinda ass. It's funny that it's ass because a
lot of its work is just **redundant**.
Very often I find myself asking Claude _"Do you need this?"_
I ask it for the simplest impl, and then I remove stuff from it. Comments that
say nothing useful. Unfinished parts of features I didn't ask for. Unused
duplicate logic.
Like if I was working with a junior.
It reminds me of when I worked with Maciek, a great guy, but back then he
was 19. He knew how to code. He was already a good engineer. He just didn't know
JavaScript.
I remember one code review when I said "Okay, this is good. It should work. Next
time you can use spread syntax instead of a loop here."
I'm not sure about you, but working with juniors _was_ way more fun than working
with AI. Most were great, but even the ones that weren't were still cool humans.
Even the least likable overconfident 20-year-old dude who thinks he knows it
all, knows nothing about working with people, and brings energy to the team.
They ask questions with a fresh perspective, often to everybody's
annoyance.
_was_ because we're not hiring juniors anymore.
I was that kid. I asked _"Where do I find the repo with tests?"_ on my first day
(oh naivety). And _"Can we gradually migrate this to Flow? We'd probably have
fewer crashes on prod if we had static typing."_
I grew up, tho.
With experience, you acquire more than taste. You become more tired. You burn
out. You get old. Experience doesn't make you lazy. Or it does, but lazy like in
[lazy evaluation](https://en.wikipedia.org/wiki/Lazy_evaluation). "We'll burn
that bridge when we get to it" type.
We learn to do less things that are unimportant.

"Less, but better." — Dieter Rams, by Visualize Value \
https://x.com/visualizevalue/status/1334863648188149760
AI isn't there yet. And removing unimportant cruft in future iterations is not
the same as not adding it in the first place. Putting in more work to fix the
prior work is lossy and prone to drift.
Iteration is great, but you gotta remember what you're doing. Don't just spin
frivolously. This applies to humans and AIs.
## Happiness
With humans, you can always try to explain the redundant work.
My father has a 3D printer and a dream he'll make money on it. The silly little
plastic dogs he prints are totally redundant, but the process isn't. It makes
him **happy**. Without it, he'd have to get to terms with the fact that his time
has passed. You won't convince a Polish 65yo to go to therapy, so this is a kind
of therapy. A thing that fills his heart with joy, and gives him something to be
excited about.
In the case of AI, the only explanation I could find is that the more tokens you
spend on Chain of Thought, the slower it feels. So we have a trade-off. Can't
think too long and iterate in silence, without a human in the loop, because the
human's experience will degrade. At the same time, the AI should think just a
bit instead of hallucinating useless slop.
The LLMs will surely get better with time, able to one-shot high quality stuff.
Everybody will be able to build an app. Everybody will be able to generate
pictures unrecog nizable from human-made.
And while I'll embrace all the efficiency gains and innovations, it kinda makes
me ask _"Do we need this?"_
Hell, I'll probably get a Neuralink, or an Eutelsat equivalent to make sure it's
not
[toned down](https://www.facebook.com/DonaldTrump4President/posts/trump-well-sell-our-allies-toned-down-military-planes-because-someday-maybe-they/1060431096112919/)
by 10% or disabled all of a sudden.
I guess so, if it makes us happier.
But does it? YouTube and all the twitters are already dead internet, especially
if the post is about politics. I hope the superintelligence comes before we get
even more flooded with slop.
I just remembered a tweet from half a year ago.

Turns out this blog post is **redundant**. Nice.
Yeah, this fits.
I'd rather get unalived by liquid metal Robert Patrick than use Android again.
---
# node:crypto is underused
In this post, I'll show you\* the basics of `node:crypto`, the builtin
Node.js cryptography module. I find it pretty cool, but not well known.
* and the forgetful future me.
I'm not saying everybody needs to be a security expert. I'm def not one! But I
feel that many engineers I know are needlessly scared of cryptography. With just
a tiny sprinkle of it, we can do amazing things: like keeping our users' data
private with encryption or signing stuff we give out to the public to guarantee
it hasn't been tampered with. It's been a while since I've written a
tutorial-style blog post, so here it goes.
`node:` URL scheme was added in Node v12.20.0. The `crypto` package on NPM is
deprecated, so you won't ever import a malicious package here. Regardless, I'll
refer to it with a prefix to discern from capitalized `Crypto` Web API.
export const codesandbox =
"https://codesandbox.io/p/sandbox/frosty-bassi-lpry6c?file=%2Findex.mts%3A17%2C5&embed=1";
[codesandbox]:
https://codesandbox.io/p/sandbox/frosty-bassi-lpry6c?file=%2Findex.mts%3A17%2C5&embed=1
You can find all code snippets from this on{" "}
CodeSandbox.
First things first, we need to generate a key pair.
```tsx twoslash include generating-keys
import { promisify } from "node:util";
import { generateKeyPair } from "node:crypto";
const { privateKey, publicKey } = await promisify(generateKeyPair)("rsa", {
modulusLength: 2048,
});
```
```tsx twoslash
// @include: generating-keys
```
Easy, right? We've got ourselves an RSA public and private key.
[Rivest–Shamir–Adleman]()
public-key cryptosystem was discovered in the late 70s, just like hip-hop. You
need to now pause reading this, play the
["Rapper's Delight"](https://www.youtube.com/watch?v=mcCK99wHrk0) by The
Sugarhill Gang, while reading about RSA Security's
[relationship with NSA](https://en.wikipedia.org/wiki/RSA_Security#Relationship_with_NSA).
Now back to JavaScript.
Let's write and encode a short message, so we have something to work with.
```tsx twoslash include original-string
const originalString = "my important message";
const originalUint8Array = new Uint8Array(
originalString.split("").map((ch) => ch.charCodeAt(0)),
);
```
```tsx twoslash
// @include: original-string
```
Now anybody can encrypt it with our public key, but only we can decrypt it.
```tsx twoslash
// @include: generating-keys
// @include: original-string
// ---cut---
import { publicEncrypt, privateDecrypt } from "node:crypto";
const encryptedBuffer = publicEncrypt(publicKey, originalUint8Array);
const decryptedBuffer = privateDecrypt(privateKey, encryptedBuffer);
const decryptedString = decryptedBuffer.toString();
```
The functions for signing and verifying have a bit of a peculiar API, where we
can pass `null` or `undefined` as the first argument if we already have our key
in a `KeyObject` instead of a raw string. I'm pretty used to
`JSON.stringify(object, null, 2)`, so I think I actually prefer it over
repeating the algorithm name.
```tsx twoslash
// @include: generating-keys
// @include: original-string
// ---cut---
import { sign, verify } from "node:crypto";
const signature = sign(null, originalUint8Array, privateKey);
const verified = verify(null, originalUint8Array, publicKey, signature);
```
If you're just interested in signing, you should probably use
[Ed25519](https://en.wikipedia.org/wiki/EdDSA#Ed25519). It's faster than RSA,
twisted Edwards curves sound like a band name, and GitHub [recommends
it][github-docs] for your SSH keys.
Britney Spears' ["I Wanna Go"](https://www.youtube.com/watch?v=T-sxSd1uwoU)
topped the charts when Ed25519 was published. A lot of things went downhill
since the 70s.
```tsx twoslash
import { promisify } from "node:util";
import { generateKeyPair } from "node:crypto";
// ---cut---
const signingKeys = await promisify(generateKeyPair)("ed25519");
```
[github-docs]:
https://docs.github.com/en/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent
Both RSA and Ed25519 can be used to sign and verify messages, but Ed25519 is
just a digital signature scheme, so you can't use it for encryption.
If you change `rsa` to `ed25519` in the [sandbox][codesandbox], one of the tests
will fail with a friendly message:
```_ class=code-error-message
Error: error:03000096:digital envelope routines::operation not supported for
this keytype
```
A bit annoying that it doesn't tell us about it on the type level, but without
redesigning the API with types in mind, the type error would also be a bit
nasty.
I remember thinking "Why shouldn't I just use RSA for everything and limit the
number of keys flying around?" sometime in the past. Apart from the performance,
and some possible security reasons, there's
[apparently](https://en.wikipedia.org/wiki/Digital_signature#Using_separate_key_pairs_for_signing_and_encryption)
[some](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf)
[legislation](https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=uriserv:OJ.L_.2014.257.01.0073.01.ENG)
stating that _digital signatures_ are legally binding. I probably wouldn't
interpret hashing a message and then raising the hash to the power of a number
that's kept in secret as _a signature_, but I am not a lawyer, and I am
unfortunately doomed to focus on the implementation.
import { CodesandboxIframe } from "../src/own/CodesandboxIframe";
**Further Reading**
- [Introduction to Cryptography and RSA (Spring 2011, MIT OCW)](https://ocw.mit.edu/courses/6-045j-automata-computability-and-complexity-spring-2011/a58157daa3e96833038e8169b8978393_MIT6_045JS11_rsa.pdf)
- [Chosen-ciphertext attack](https://en.wikipedia.org/wiki/Chosen-ciphertext_attack)
- [Web Cryptography API Spec](https://w3c.github.io/webcrypto/#abstract)
- [How the NSA (may have) put a backdoor in RSA’s cryptography](https://blog.cloudflare.com/how-the-nsa-may-have-put-a-backdoor-in-rsas-cryptography-a-technical-primer/)
---
# Three Types of Software
Imagine there are only three programming languages: C, Haskell, and Ruby.
Just imagine, okay? Bear with me.
There is a very small overlap between them. There's not _a lot_ of types of
software that more than one of those languages is suitable for.
- an operating system? -> C.
- a bank? -> Haskell.
- your SaaS app? personal blog? -> Ruby.
- AAA games? -> The engine in C, the gameplay scripting in Ruby.
- [Bun](https://bun.sh/), the JavaScript runtime designed for speed? -> C.
Yes, the boundaries are fuzzy. It's all a spectrum, I get it. But...
I've realized over the years, that it's nice to ask yourself "What language
would I choose for this thing I'm working on if only those three languages
existed." It helps with architectural decisions, prioritization etc.
You're probably familiar with the "Good, Cheap and Fast" Venn diagram. My
question about programming language choice in an imaginary reduced world isn't
just a polyglot flex. It `is, at least for me, more useful.
import { VennDiagram } from "../src/own/VennDiagram";
Firstly, we often say that you can only choose two from good, cheap, and fast
(i.e. if it's good and fast, it must be expensive), and here you can only pick
one, optionally zoom in, split your thing into parts, and the process of it
provides insight.
And secondly, it helps to be sincere with yourself. Half the people are gonna
say they want their thing to be good. Who would admit they want to make
something bad?
[@levelsio](https://twitter.com/levelsio) openly says he raw-dogs jQuery and
PHP to be the first to market. Indie hackers will always want "cheap",
low-scope, small-scale.
Obviously simplifying, let's say that all Haskell programs are _correct_, with
low number of bugs; all C programs are performant, because why else would you
torture yourself; all Ruby programs are pleasant to write, help you achieve
product market fit, and even if you pay your developers a soccer player salary,
they don't have to spend ages to build your thing, thus — cheap.
I'm not saying that all Ruby programs are buggy. I know you write tests. I
like Ruby!
Thinking of _fast_ as performant, snappy, like low-level code, close to the
machine; instead of _fast to make_ (often a synonym for cheap), allows us to
split the _good_ into performance and correctness, and think about trade-offs
between those two.
But even if we redefine the adjectives in our Venn diagram, it still feels like
intersections between them can be easily _selected_. Picking a language
simplifies the problem.
It allows us to reduce a 3D space into a single choice, a guess, a simple
question of preference.
In reality, with a finite focus to spend, balancing between 3 qualities is like
picking a point on a sphere octant.
import { SphereOctant } from "../src/own/three-types-of-software/SphereOctant.react.tsx";
x2 + y2 + z2 = 1 where x, y, z ∈ [0,1]
{"see in Colab"}
Try clicking a point on the octant, then stop hovering, and find the same point
again after the octant rotates a bit. Not easy, right?
In many job ads and company culture docs you'll see a sentence like "prioritize
speed and quick iteration with a focus on quality and excellence". I don't
disagree that we should strive for balance, but I feel that people often
discount how many different balances are there. Like all vectors on length 1 on
the plot above, all of the |ℝ| balances are valid. Each for a different
situation.
Locating where you are on the sphere octant is hard. Deciding where you wanna
be, and how to move to that point is harder. Choosing one from three "paragon
languages" to guide the decisions you make when you build your JavaScript or
Python software is a neat and easy heuristic.
Thanks for your attention.
Do you want fast JavaScript? Write it as you write C.
{"—Michele Riva, "}
{"Disrupting Full Text Search With JavaScript"}
{", BeJS 2023"}
Alternatively, instead of a language, pick one of
[two YouTubers](https://youtu.be/2Z4fZtSKlcE?t=580).
---
# In Defense of Exceptions
I used to be a strong proponent of explicit error handling. Quite recently, I
realized that exceptions are good actually.
A developer under a tight deadline does not have a strong incentive to handle
all edge cases. They'll write tests someday, _right?_
Many try-catch blocks I've seen either log the error and rethrow, which isn't
bad; or swallow it and return null 🤮 which makes the program worse, and harder
to debug.
The company wants to see the happy path ASAP, you're pressured to ship, and
that's understandable. There's so many things that can go wrong, and _time is
money!_ You probably wouldn't handle all of them even if you tried.
In this world, the non-ideal world of commercial software development, handling
errors on another layer, like React
[Error Boundaries](https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary),
or
[Darklang's](https://blog.darklang.com/real-problems-with-functional-languages/)
/ [Scott Wlaschin's](https://fsharpforfunandprofit.com/rop/) Error Rail, seems
to be more practical.
Exceptions are errors we did not expect yet, but we can't expect everything,
right? Any system multiple people work on at the same time will inevitably crash
at some point. Let's learn from
[Erlang](https://blog.mi.hdm-stuttgart.de/index.php/2019/10/13/how-to-build-fault-tolerant-software-systems/#:~:text=Erlang's%20approach%20to%20fault%2Dtolerance&text=Compared%20to%20other%20programming%20languages,to%20complete%20a%20predetermined%20task.),
and improve our fault tolerance. When we're aware we'll eventually crash, we
ensure that:
- the user can recover, the error doesn't propagate and bring the whole thing
down,
- the call stack and application state are logged to our error tracker, so we
can debug later.
Have you ever had Facebook load without the Messenger chat?
basically Facebook (modulo 25bn R&D spend)
The chat is just hanging there. Yeah, it sucks it broke, but you can still read
the latest conspiracy theories and see your friends' kids pictures. In many
cases it does not make sense to handle an error with more effort than unmounting
the component or displaying "Something went wrong".
One of the friends I asked to review this told me "who uses Facebook, nobody
is going to understand this example." I'm old.
If my cat tinder app can't render because of a parse error on backend response,
that used to be JSON of fluffy cats in your area, and is now HTML with a 502 Bad
Gateway Error message, it's not frontend's responsibility to fix the bug. A
generic "Something went wrong." message is perfectly sufficient.
It's probably the DNS anyway.
---
# It's Hard to Sell a Mental Model
**Epistemic Status:** Written on an airplane. Should have been a DM, but I'm
too stubborn to pay for Twitter Blue.
If you didn't hear about Effect yet, go watch [Ethan Niser's YouTube
video](https://www.youtube.com/watch?v=SloZE4i4Zfk).
Michael Arnaldi, the author of [EffectTS], just
[replied to me](https://twitter.com/MichaelArnaldi/status/1681264970657415168)
on Twitter:
[effectts]: https://www.effect.website/
> I've built Effect because I had to build a bank with 5 juniors, it ended up
> working and I open sourced it.
If we understand "juniors" the same way, then I have no doubt it worked.
The story might have been different if Michael had 5 seniors, because EffectTS
might make the code safer and more robust, but it makes it less idiomatic, less
familiar, and **arguably** harder to write, because Copilot Chat won't print it
for you.
I like EffectTS a lot, and I hope it has a ton of success. However, I fear that
its road to a wider adoption is going to be long and rocky.
It boils down to two things:
- There's a difference between seniority and "immersion".
- [Explicit error handling](./in-defense-of-exceptions) might not be the fit for
most commercial software.
## Seniority vs Immersion
Terms "junior", "senior", and "staff" describe the number of years of
experience, and the position in the organization.
If they're correlated to eagerness to learn, idealism, and ambition to write
good code, I would guess they're correlated inversely. People with kids,
retirement savings, and a backache, will surely have less curiosity about how
that new framework hotness changes the world. That's totally fine.
A greybeard would say that good code does not mean good product, and show you
his ugliest hack from the 90s that made the company millions. (e.g.
[Swizec](https://swizec.com/blog/sometimes-your-worst-code-is-your-best-code/))
Compare this to a young person, their hands just imbued with the power of
creation, their eyes bright with infinite possibility. A developer before their
first burnout.
I remember my friend Kuba on his first tech meetups after he learnt how to code.
You couldn't have found a person more excited about monads, AWS and differences
between frontend frameworks at the same time.
The seniority levels don't describe how immersed in the _craft_ of programming
the person is. Here's an orthogonal division based on how interested, how
_immersed_ the developer is in the implementation intricacies of the software.
- Rowdy Ronin, devs without master, rawdogging jQuery and PHP to hit the market
as fast as possible.
- Software Samurai, salarymen, who focus on building great software for their
employer. They take pride in their work, but the work is more the code.
- Opensource Oni, demons of GitHub, exploring the uncharted lands of
computation. Chronically online, often involved in some argument on Twitter.
It's tremendously hard to sell a framework that requires a new mental model to
the first two groups, unless the benefits outweigh the cost by an order of
magnitude.
Especially if it's not a UI framework.
### Frontier and Backwater
For a long time, frontend was the place for experiments, rewriting and trying
out new things, and you get an occasion to do this when designers do redesigns
and rebrandings. At the same time, the backend was the place of established best
practices, old knowledge, and battle-tested solutions. A lot of newly written
TypeScript backend code looks similar to the Java code snippets from the books
published in the late 90s.
It's entirely justified, as backend engineers should most often be more
concerned with how they orchestrate, operate, and observe their system than the
code they write.
---
Okay, but what if exceptions were actually a better fit for the modern day
industry than explicit error modeling?
[I wrote some thoughts in defense of exceptions.](./in-defense-of-exceptions)
---
# I Love Arc Boosts
I always liked the old web of MySpace, personal blogs, and Tumblr sites. It felt
more human back then, more heartfelt. We kinda lost it along the way when the
web has split into content creators and consumers.
Arc Boosts allow the end user to customize websites to their liking. It hits a
nostalgic note with bleeding edge
[AI-powered editing](https://twitter.com/joshm/status/1673689963303514114)
features, but even its basic features like CSS editing are a nice tool for
software engineers and designers.
Of course, in all browsers tech-savvy can already modify HTML and CSS in the
DevTools, but your edits don't save between reloads. It's disputable if it's
more convenient than changing the source code with
[HMR](https://webpack.js.org/guides/hot-module-replacement/)/[Fast Refresh](https://github.com/pmmmwh/react-refresh-webpack-plugin).
Boosts democratize tweaking websites. A non-technical marketing manager can now
propose design changes with more accuracy than sending screenshots of
competition's apps on Slack.
It's well known that short feedback foster productivity. Boosts shorten the
feedback loop. Both for software professionals, and end-users, it's a great
feature. I love it, that's all.
---
# Malleable Software of Past Decades
For the past months, I've been reading a lot about malleable software and
end-user programming.
You can find the best summary I know of at [Goeffrey Litt's blog][geoffrey].
[geoffrey]:
https://www.geoffreylitt.com/2023/03/25/llm-end-user-programming.html
In my day job, I work a lot on design systems and user interfaces. I've always
been interested in design and user experience. Recent advances in AI, which
enable every slightly technical person\* to build software, make this
time especially interesting.
(*) My mom, a 64yo small-town dentist, won't have a lot of success with GPT-4.
Most corporate white collars will probably be able to build fairly complex
tools for themselves.
I'm writing this to make a prediction: The best and most widely used consumer
software will be **malleable** in the coming decade.
## Eternal reinvention
> Whatever is has already been, and what will be has been before
Ecclesiastes 3:15
Remember how Elon Musk invented [the subway][elon-invent] and Zuck invented
[MMORPG][mark-invent]?
[elon-invent]: https://en.wikipedia.org/wiki/Hyperloop
[mark-invent]:
https://www.nytimes.com/2022/10/09/technology/meta-zuckerberg-metaverse.html
We tend to come up with what we have previously explored in the past. It's fine,
we improve, iterate and expand on what was before. Well, that's if we're aware
that some version of what we're working on already existed. We could also choose
to be oblivious, repeat mistakes, and miss the good things.
We already have malleable apps, and they are _loved_ by their users. Let's think
what can we learn from the past.
I'm not talking about Excel and Notion. Yes, those are nice. I like them. Pretty
excited about Microsoft Loop too, but it's not what I mean. You might have
already guessed, but let me first entertain you with a bit of a personal
backstory.
## What has been
Twenty years ago, when I was in primary school, all the boys played video games.
Games were expensive, and consoles were even more expensive, so we played the
free stuff like [browser strategy games](https://www.tribalwars.us/) and
[Korean MMORPGs](https://gameforge.com/en-US/play/metin2).
Those online games have built communities around them. We talked about them at
school. But there's a community-building stronger feature than being online,
that I encountered a few years after, probably around 2008.
The games that still sell copies after more than a decade, the games that people
really fell in love with, those games have [**mod support**][mod-support].
[mod-support]: https://en.wikipedia.org/wiki/Video_game_modding
Nowadays, many triple-A PC games are designed with modding support from the
get-go. It's no surprise because the mods extend the shelf life of the game
drastically.
We can find an example in the world of developer tools. The main disadvantage of
[Zed](https://zed.dev/) in its competition with VSCode is VSCode's rich
extension ecosystem and low entry barrier to publishing extensions. Even when
your new software reaches feature parity with the incumbent, your extension
ecosystem will need to be competitive. It would be ideal, if it was compatible
with the extensions of the competition, or provided a compat layer. I probably
wouldn't start using Arc if it wasn't based on Chromium, and it didn't support
all extensions I was already using.
Video games, browsers, IDEs and "workspaces" like Slack and Notion are built for
audiences with a large share of nerds and power users. Among those nerds, some
are willing to go the extra mile to customize the software they're using and
share their work with others.
Until this moment, social media apps, music players and direct messenger apps
were not extensible. People who needed, or wanted to customize them had to use
browser extensions for that (e.g. dark mode for Facebook, YouTube downloaders).
With more people empowered to write code through LLMs, I expect a surge in the
need to customize apps. In each of us, there's a need for expression and
personalization.
## What will be
I expect that even if end users will be able to edit the apps they're using in
real time, the most popular modifications will be created by power users. It
requires time and dedication to build something polished, regardless of the
tools and the medium.
Apart from the joy of building and making something truly yours, would the
extension creators of the future seek some other gratification?
Let's consider the three levels of video game mod success. Successful mods have
historically:
1. Gained popularity, acclaim, and sponsorship on Patreon.
2. Got merged into mainline, like DrZhark's
[Mo'Creatures](https://mocreatures.fandom.com/wiki/Mo%27_Creatures_Wiki)
which helped introduce horses to Minecraft.
3. Became a standalone game like
[Garry's Mod](https://store.steampowered.com/app/4000/Garrys_Mod/), and
[DOTA 2](https://www.dota2.com/home).
If we add the Freemium business model popular in browser extensions to the
equation, I think we can expect a new generation of indie hackers and a new wave
of personalized niche software.
I'm keeping tabs on this space, and working on some toy projects. Thanks for
your attention. Cheers.
{/* Prophesy of Pendor */}
{/* https://forums.taleworlds.com/index.php?threads/prophesy-of-pendor-v1-21-introduction-and-release-for-m-b-1-010-1-011.53027 */}
{/* The first game I've fallen in love with was Neverwinter Nights 2. */}
{/* The first game I played was "Command & Conquer: Red Alert 2". */}
---
# Unreliable LLMs vs the Community
**Epistemic Effort:** I wrote this very fast, and I'm not an AI expert.
I'm worried that @GitHubNext's Copilot Chat will make software engineering
harder to learn, not easier. Even if not, they can make it more of a lonesome
experience.
Asking an unreliable LLM for advice instead of googling for a GitHub issue, or a
StackOverflow answer, might be super dangerous because **LLMs [confabulate]!**
[confabulate]: https://www.beren.io/2023-03-19-LLMs-confabulate-not-hallucinate/
When I paste an error message into the searchbar, I have an idea of what I'm
looking for. It's either:
1. An issue in the repository of the library that threw the error, preferably
with the maintainer's answer I can use to quickly work around the problem.
2. A post by people I trust, like
[@TitianCernicova](https://twitter.com/TitianCernicova),
[@AndaristRake](https://twitter.com/AndaristRake),
[@joebell\_](https://twitter.com/joebell_) and many others.
I don't have to validate that hard, because I know those people. I know they've
helped me multiple times over the last 9 years, and I recognize them as people
who know what they're doing.
Over the years of their career, software engineers start to recognize GitHub
handles and avatars of other people in the industry.
An artisan on their journey to mastery meets people to learn from, some master
craftsmen of old, but also people on their level of experience, who encountered
the same error message just yesterday!
In this, the industry is very social, **community-driven**.
We can call it parasocial, but it's still more social than a genie behind the
glass.
It's nice to see the avatars you know solving the same technical challenges you
face right now. we're social animals. Following the path of another human being
always feels safer than trailblazing a new one. It's extra nice if you get to
meet one of the people you know from GitHub at a conference or a local tech
meetup.
When I google for a solution, I’m looking at Medium and devto posts last,
because I very rarely find anything helpful in them. It's super easy to write
some bullshit that doesn't even work and call it a best practice.
Software engineering is very often a practice of patience and curiosity. You
read your codebase, search the internet, delve deep into node_modules, time
travel through somebody else's GitHub history.
And then you validate.
LLMs can lead us to a world with way less searching, but way more validation.
You can't trust the genie, because it's not risking its personal brand and
credibility.
I'm worried that people new to the craft, on top of suffering from a need for
excessive validation of LLM output, will now have less opportunities to learn
from the experts, less opportunities to meet people they can trust.
Can we combine the Large Language Models with static analysis tooling and
AI-generated test harnesses to make the validation easier? Can we limit AI
confabulation? Can we build AI-powered analysis tooling to help our validation
and eliminate ourselves from the process? Surely yes, with time.
But won't this slowly lead us to the world where we don't need the middle class
of software craftsmen, and we all become BigCo product owners shamans
orchestrating an army of machines or indie businessmen trying to carve out our
little niches? Competing with each other instead of collaborating?
It sounds lonely, doesn't it?
> Think about the first language you ever loved [...] That profound satisfaction
> when the bug is dead and logic triumphs again. The pride of creation.
>
> Yeah, you were lucky enough to live in that world, and if you were in the
> right place at the right time you even got paid unimaginable amounts of money
> to do it. How will you feel when it’s all over?
—Forrest Brazeal:
[When programming is gone, will we like what's left?](https://newsletter.goodtechthings.com/p/when-programming-is-gone-will-we)
---
# 2023 Homepage Rebuild
**TLDR**: I redesigned and rebuilt my blog with [zaduma], [Astro],
[Solid.js][solid], [Tailwind], [MDX], [Shiki Twoslash][shiki-twoslash], [Vercel
OG][vercel-og] on Edge Functions, [Astro Image][astro-image], [Plaiceholder]
[zaduma]: https://github.com/hasparus/zaduma
[astro]: https://astro.build/
[solid]: https://www.solidjs.com/
[tailwind]: https://tailwindcss.com/
[mdx]: https://mdxjs.com/
[shiki-twoslash]: https://shikijs.github.io/twoslash/
[vercel-og]:
https://vercel.com/docs/concepts/functions/edge-functions/og-image-generation
[astro-image]: https://docs.astro.build/en/guides/integrations-guide/image/
[plaiceholder]: https://plaiceholder.co/
## Motivation
I've survived a couple of years without redesigning my website, a nice score for
a person building user interfaces daily. Until now. Surprisingly, I'm not
motivated by the framework churn and the hype for Astro, nor frustration with
the tech debt decay and dependency drift of my previous homepage codebase.
Neither am I pursuing better performance metrics. Going from a static website
built with Gatsby to a static website built with Astro gets me less JavaScript
shipped, shorter build times, and frees me from using GraphQL to read a markdown
file, but it's not a _huge_ change from the visitor's perspective.
I had fun building my previous homepage. It was over-engineered, and it had
everything: fp-ts, backlinks, a Roam-inspired force graph of notes, custom
Gatsby plugins, you name it.
On [my old website](https://old-hasparus.netlify.app/) I handwrote and animated
an actual SVG sandwich for the menu icon. And then I overengineered it to work
without JavaScript with `#menu-id:target ~ &` trick.
import Hamburger from "../src/own/2023-homepage-rebuild/Hamburger.astro";
You've made some of my friends laugh, but it's time for you to go, my dear
icon.
**[Fun is over](/posts/2022-and-change.mdx). I'm a boring person now, and I want
simplicity.**
I'm trying to do something more basic, more classic. I'm choosing simpler tech
and almost minimal design.
## Design
To get to that _boring and classic_, I chose a grayscale color scheme and
centered layout. I've spent
[some time](https://twitter.com/darylginn/status/1620371874609008640?s=20&t=pwuhIlc1LPfcH4K_xarZkg)
thinking about typography. I'm not completely satisfied, but I settled on the
standard Inter and a personal touch of
[Brygada 1918](https://fonts.google.com/specimen/Brygada+1918).
I'm slowly starting to consider removing Brygada and going with just one font,
but I do find the current state of things quite passable, and I can't keep
fiddling with it ad infinitum.
Centered layout is practical. It allows me to use the left side for a table of
contents, and the right side for `aside`s with comments and tangentially related
notes.
## Tech Stack
### Zaduma
I've built an Astro starter, called [Zaduma] to work on all the features I want
in isolation, and not when I'm writing a blogpost.
[Aleksandra] had a Gatsby site too, so now we're collaborating on the starter.
We'd like Zaduma to be a minimalistic starter for technical weblogs and
documentation pages. We've added some personal touches like a grain overlay and
[Shiki Twoslash][shiki-twoslash], assuming that TypeScript is already a most
popular programming language.
[aleksandra]: https://aleksandra.codes
### Astro
We went with [Astro], firstly because it just fits, and SSG is fair choice for a
blog. Secondly, because I started before Next 13 was announced, and Astro was
first on my list of new things to learn. We want to ship less JavaScript, and
get nice perf out of the box.
### Solid.js
For the components framework, we're taking [Solid], because it's absurdly good,
especially coming from React. It still doesn't have enough of an ecosystem, and
until very recently it lacked equivalents of accessible foundations style
libraries like Radix UI, but I won't need much here. I'm fine with writing my
own command pallette instead of downloading `cmdk`.
### Tailwind CSS
[Tailwind] gets us a nice set of design tokens, and it's popular enough we can
use it in a public starter. Combined with the VSCode Extension, it becomes
arguably the best CSS-in-JS library, with an added benefit of not being a
CSS-in-JS library, so you can use it in your old Rails projects and on your
mom's Wordpress website.
If you're not a person who builds design systems and frontend tooling daily, and
you don't have time to check out every CSS solution, stick with Tailwind.
Atomic CSS gets us the best performance and a surprisingly small number of
footguns. It's just good.
Read more:
[React 18 CSS-in-JS Library Upgrade Guide: `
```astro class="preloading"
---
import interPath from "@fontsource/inter/files/inter-latin-variable-wghtOnly-normal.woff2";
---
```
### Caveats
[Zaduma] was my first Astro project, and unsurprisingly, I had to confront my
application developer habits with MPAs. Displaying a shared UI element which
state depends on `localStorage` (like a color scheme toggle) is a hard problem.
I wrote about it
[in zaduma repo](https://github.com/hasparus/zaduma/blob/main/docs/tradeoffs-and-limitations.md#trade-offs--limitations).
Even [Astro docs](https://docs.astro.build/en/getting-started/) have this issue,
so I ended up pivoting to a command palette.
---
If you end up [generating](https://github.com/hasparus/zaduma/generate) your
website with Zaduma, please let me know what you think and if you encountered
any bugs. Thank you for your attention 🙏
---
# 2022 and Change
It's eight past 1am, Sunday 1st of October 2022.
I'm sitting at an airport cafe in Dubai, waiting for the second leg of my
flight. I didn't post anything to my website since April 2020. It didn't feel
right. It felt like the website is somebody else's.
Twenty-twenties hold no punches. The world changed. You've lived it too, you
know all that happened. The pandemic, Russian invasion of Ukraine, and whole set
of crises milder than those two, yet still undoubtedly upsetting.
I protested the abortion ban and the plunge towards authoritarian rule, got
married, changed jobs, volunteered, caught COVID. Doom-scrolled a lot.
I changed. This website needs to change too.
[The next post](./2023-homepage-rebuild.mdx) describes the tech and design of my
website migration.
---
# Writing `rm` because of Windows
## Problem
I happen to have a Windows laptop in my possession, and I'm stubborn enough to
use it for coding in addition to my work MacBook. Fortunately, I am also
stubborn enough to get it to work.
### _rm -rf_ doesn't work on Windows.
Powershell has _[Remove-Item]_ aliased to _rm_. This would almost solve the
problem, but...
```
Remove-Item : A parameter cannot be found that matches parameter name 'rf'.
At line:1 char:4
+ rm -rf dist
```
Powershell, unlike _[getopt]_, doesn't allow specifying multiple options
together.
_rm -r -f_ doesn't work too, because _-f_ is ambiguous.
```
Remove-Item : Parameter cannot be processed because the parameter name 'f' is ambiguous.
Possible matches include: -Filter -Force.
At line:1 char:7
+ rm -r -f dist
```
All right, for a few months I've been writing _rm -r -fo_ whenever I had to
remove `node_modules`.
I aliased[^1] it to _rmrf_ for convenience and with the following
```powershell
function rmrf {
param(
[parameter(Position=0)][string]$directory
)
process {
# rm -r -fo $directory
Remove-Item -Recurse -Force $directory
}
}
```
[^1]:
Powershell aliases can't be used to partially apply arguments. Docs for
_[Set-Alias]_ basically tell you to write a function instead.
[remove-item]:
https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.management/remove-item?view=powershell-7.2
[getopt]: https://en.wikipedia.org/wiki/Getopt
[set-alias]:
https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/set-alias?view=powershell-7#example-5-create-an-alias-for-a-command-with-parameters
That was working nicely, until I landed in an environment where everybody except
of me had a MacBook and all scripts were written with that assumption.
### Alias was not enough
When I started my career in Polish software companies, we often had somebody on
MacOS, somebody on Linux and somebody on Windows in the team. Mac was a bit more
popular as a standard _company issued notebook for developers_, but we usually
had a mix.
When I work on open source, the projects are usually system agnostic (it's not
super hard in web ecosystem), using `rimraf` instead of `rm -rf`, using
`dotenv`, writing scripts in JavaScript or Python etc.
[`rimraf`]: https://www.npmjs.com/package/rimraf
Surprisingly, 41.2% StackOverflow Survey respondents use Windows and only 30%
use MacOS.
[](https://insights.stackoverflow.com/survey/2021#most-popular-technologies-op-sys-prof)
My anecdata looks more like 75% MacOS, 15% Linux, 10% Windows, but from a
perspective of open source guy, I wouldn't want to break the build for 10% of
users or lose 10% of contributors.
However, in a startup setting, the situation is totally different.
The company offered to buy me a Mac, but the orders take about 7 weeks. So, I
had a choice. I could either bother my teammates to make the project
system-agnostic, or deal with it on my side.
A smarter man might have gone to the store, get any MacBook they have, and be
done with it.
## Writing my own `rm` in Rust
Googling "rm for Windows" didn't get me any useful results, so I quickly wrote
my own, very dirty, totally not production-grade, `rm` to stick in my `bin`
directory.
**Disclaimer:** Writing the code took me less time than I've been writing this
note, so I provide no guarantees to the quality and correctness of the code. I
didn't even write any error messages, because exposing the error from `std::fs`
is totally fine for my use case.
main.rs
```rust
use clap::Parser;
#[derive(Parser, Debug)]
#[clap(author, version, about, long_about = None)]
struct Args {
#[clap(short, long)]
recursive: bool,
#[clap(short, long)]
force: bool,
#[clap()]
files: Vec,
}
fn rm(args: Args) -> std::io::Result<()> {
for file in args.files {
if args.recursive {
if args.force {
std::fs::remove_dir_all(file)?;
} else {
std::fs::remove_dir(file)?;
}
} else {
std::fs::remove_file(file)?;
}
}
return std::io::Result::Ok(());
}
fn main() {
let args = Args::parse();
Result::unwrap(rm(args))
}
```
Cargo.toml
```toml
[package]
name = "rust-rm"
version = "0.1.0"
edition = "2021"
[dependencies]
clap = { version = "3.1.18", features = ["derive"] }
```
I removed the alias by adding the following to my `$PROFILE`.
```powershell
Remove-Item -Path Alias:rm
```
I promptly ran `cargo build --release` and I added my newest toy to a directory
I already had in `PATH`.
```
❯ ls
Directory: D:\tools\bin
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a---- 04/05/2022 16:24 1128 colormode.ps1
-a---- 11/05/2022 17:00 654848 rm.exe
-a---- 12/07/2021 16:21 8 sh.cmd
-a---- 07/07/2021 15:01 782336 tr.exe
-a---- 20/07/2021 13:16 637 xargs.cmd
```
---
# Tiny Alternative to Storybook
import { Shortcut } from "../src/lib/Shortcut";
TLDR: Using a set of documentation components rendered inside of your app on
_/docs_ page may be an interesting alternative for Storybook.
[See my documentation components on CodeSandbox.][sandbox]
[sandbox]:
https://codesandbox.io/s/documentation-components-example-dm1vl?file=/pages/index.tsx
## The Problem
The main problem I have with Storybook is that it's _another set of config files
to maintain_. What's worse — there's a Webpack config hidden inside! Ouch.
Don't get me wrong! I quite like Storybook, and I use it for any professional
design system I work on, but I'd put it in "tooling that sometimes breaks and
needs to be maintained" category. It's a huge tool. It has tons of awesome
features — a huge surface, a multiplicity of choices.
But do you really need it when you're trying to launch an app?
## Storybook and Libraries
Where Storybook shines the brightest is libraries. You can presume that most
frontend folk are familiar with Storybook or will be able to pick it up fast.
It's so popular we could probably call it a standard for component examples.
It's as easy to contribute a story as it is to write a piece of markdown for the
docs. Thanks to its popularity, Storybook lowers the burden put on library
maintainers.
## Alternative Approach
However, you can do without Storybook! I've found myself reaching for a smaller
alternative in new projects.
Here's the gist — We can classify Storybook as _documentation catalogue_ or
_components atelier_ software. Such a thing has a few main parts:
1. File-system router which grabs your files and turns them into pages
2. UI to render examples
3. Development server which refreshes your examples when you change the code
4. Some search functionality for navigation
Let's address search — If I'm early enough in the project to be worried about
Storybook config maintenance cost slowing me down, I probably don't have enough
components to warrant search anyway. will be
enough.
Thing is, in most cases I already have file-system routing and a nice dev-server
with React Fast Refresh support. I don't need to maintain another webpack config
to give me the same experience I already have while developing my app.
Historical Context: In the past Storybook was awesome, because the ecosystem was worse.
A lifetime ago, I used to work on a sizeable legacy codebase and wait for 4
seconds or more for the app to reload. Then I had to and reproduce the state I
was in if it wasn't possible to hardcode it. — Storybook's stories solve this
problem elegantly.
Until we eventually migrated from [Brunch](https://brunch.io/) to Webpack and
configured `react-hot-loader`, Storybook was my safe haven for UI-focused tasks,
and thanks to it a migration from jQuery to React went like a breeze.
Months later, when our SPA grew even more, and Flow compilation times got slow,
Storybook was a huge help in onboarding new engineers in the team and getting
them started quickly.
In 2021, thanks to wide-spread file-system routing and fast development servers,
we can easily create a `/docs` page or a few of them.
So we're down to _"UI to render examples"_.
I've created a [CodeSandbox][sandbox] with some of React components I'm using for docs in one
of my projects. Here's how they look like in the editor:
```tsx
import { ComponentExample, ComponentExamplesContainer } from "./docs-ui";
import { Text, Link, Button } from "./actual-app-ui";
const Examples = () => (
example.com
Start Game
Exit
Click Me
);
```
And here's how they render in the browser:
[][sandbox]
By focusing on documentation UI and forgetting about routing, search and
dev-servers, we reduced complexity enough that copying these components between
projects and maintaining them when any change is needed isn't a burden.
## Trade-Offs
Obviously, this idea has trade-offs that we need to consider.
- Storybook supports 12 component frameworks, but I just need one at a time.
- I lose access to a huge plugin ecosystem, but I want to go _smaller_ and
remove everything we don't desperately need.
- And the most serious problem — I will definitely not handle all corner cases
at first, so I'll need to revisit the code and expand it as I use it. We're
not free from maintenance. Question is — Is maintaining a set of
_programmable_ components in your language/framework of choice easier than
maintaining a bunch of config files?
- My components work at _runtime_ and reconstruct the source code for the
example based on its React elements tree. Our code is most often transpiled,
so what we see in component examples may not be the same thing we wrote. While
sufficient for simple cases, this approach is problematic for more complex
constructs. If you're documenting a public library, not just reusable
components from an app, a build-time solution like Storybook or an
[MDX/rehype-based solution](https://ped.ro/blog/code-blocks-but-better) will
definitely be a better choice.
## Notes
### Write or Install
I'm intentionally not publishing these components as a library, even for my use,
because I wrote them explicitly not to use a library. Maintaining a familiar
code in your repo may often be easier and less time-consuming than using a
ready-made tool. _Programmable_ APIs are more robust than _Configurable_ APIs.
"Write or Install" is a variation on "Build or Buy" dilemma we don't talk about
enough.
---
> I have never seen new startups get any value from Storybook. It only slows
> them down.
>
> I can totally see the value for enterprise and established startups. But when
> running out of time & money, the #1 focus is getting launched!
>
> Launched >>> beautiful storybook but no customers
[—Brandon Bayer, July 24, 2020]("https://twitter.com/flybayer/status/1286556994829074434?ref_src=twsrc%5Etfw")
---
# Excerpts from WCAG20 Guideline 1.4 Distinguishable
I google for this and refer to it in conversation every now and then, so I
decided to attach the excerpts here.
> Guideline 1.4 Distinguishable: Make it easier for users to see and hear
> content including separating foreground from background.
>
> _Source: https://www.w3.org/TR/WCAG20/#visual-audio-contrast_
## Guidelines on color and contrast
> **1.4.1 Use of Color**
>
> Color is not used as the only visual means of conveying information,
> indicating an action, prompting a response, or distinguishing a visual
> element. (Level A)
>
> _Note:_ This success criterion addresses color perception specifically. Other
> forms of perception are covered in
> [Guideline 1.3](https://www.w3.org/TR/WCAG20/#content-structure-separation)
> including programmatic access to color and other visual presentation coding.
> **1.4.3 Contrast (Minimum)**
>
> The visual presentation of text and images of text has a contrast ratio of at
> least 4.5:1, except for the following: (Level AA)
>
> - Large Text: Large-scale text and images of large-scale text have a contrast
> ratio of at least 3:1;
> - Incidental: Text or images of text that are part of an inactive user
> interface component, that are pure decoration, that are not visible to
> anyone, or that are part of a picture that contains significant other visual
> content, have no contrast requirement.
> - Logotypes: Text that is part of a logo or brand name has no minimum contrast
> requirement.
> **1.4.6 Contrast (Enhanced)**
>
> The visual presentation of text and images of text has a contrast ratio of at
> least 7:1, except for the following: (Level AAA)
>
> - Large Text: Large-scale text and images of large-scale text have a contrast
> ratio of at least 4.5:1;
> - Incidental: Text or images of text that are part of an inactive user
> interface component, that are pure decoration, that are not visible to
> anyone, or that are part of a picture that contains significant other visual
> content, have no contrast requirement.
> - Logotypes: Text that is part of a logo or brand name has no minimum contrast
> requirement.
> **1.4.8 Visual Presentation**
>
> Visual Presentation: For the visual presentation of blocks of text, a
> mechanism is available to achieve the following: (Level AAA)
>
> 1. Foreground and background colors can be selected by the user.
> 2. Width is no more than 80 characters or glyphs (40 if CJK).
> 3. Text is not justified (aligned to both the left and the right margins).
> 4. Line spacing (leading) is at least space-and-a-half within paragraphs, and
> paragraph spacing is at least 1.5 times larger than the line spacing
> 5. Text can be resized without assistive technology up to 200 percent in a way
> that does not require the user to scroll horizontally to read a line of
> text on a full-screen window.
---
# Languages for Engineering, not for Coding
There are two kinds of programming languages.
Languages for coding are like C, Python, and JavaScript.
A human writes code.
Then the language tooling comes in. The code is then compiled, ahead of time or
Just in Time, or maybe it's not compiled but interpreted instead. Then it's
executed. End of story.
Why did the human write the code? Is it the code that the human wanted to write?
How is the code documented?
I don't think coding is enough, and modern languages don't stop on this.
Take a look at TypeScript which AST has nodes for JSDoc comments.

TypeScript cares about your comments because they need to appear in tooltips. It
cares about what happens in the IDE, and other humans reading your TypeScript.
Even more modern, Unison goes a few steps forwards and treats docs as
first-class citizens with
[documentation literals](https://www.unison-lang.org/learn/language-reference/documentation-literals/).
Test tooling is also a built-in in Unison, with
[test watch expressions](https://www.unison-lang.org/learn/usage-topics/testing/#basic-unit-tests);
but this isn't really new, Rust's
[`mod tests`](https://doc.rust-lang.org/book/ch11-01-writing-tests.html)
attribute and `cargo test` brought the in-file unit tests to the mainstream.
I expect the holistic approach and a widening of an "interest area" of
programming languages to grow more popular in time. What do you think?
[Disagree on Twitter.](https://twitter.com/intent/tweet?text=%40hasparus%2C%20your%20https%3A%2F%2Fhaspar.us%2Flanguages-for-engineering%20is%20silly.)
---
# Why Did You Render?
**Epistemic Effort:** This is a note for future me. A few code snippets
grouped together.
I spent too long looking at React Profiler and tweaking rendering performance
today. Here are a few tricks I learned.
**Huge disclaimer.** If you're new to React or programming in general, you don't
need to worry about performance. Read ["When to useMemo and
useCallback"][when-to-usememo] on Kent C. Dodds's blog.
[when-to-usememo]: https://kentcdodds.com/blog/usememo-and-usecallback
I have already worked for a year on the app I was optimizing today. A year
of my fulltime job! And I wasn't the only developer. It has both keyframes
and react‑spring animations, an API client codegenned from Protobuf schema, and
obeying Zawinski's law, it also reads and sends email.
It isn't too soon to think about performance anymore, because it affects user
experience on weaker machines. I just had to make this point.
Let's go to the main part.
## Finding the culprit 🔎
We throttled our CPU and found the part of the app which feels wrong. But how do
we find the slow components? How can we know what to optimize?
The official React Profiler will answer these questions.

Today! In your
[DevTools](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi)!
Don't forget to mark this checkbox.

We can see which props changed.
It may be, however, laconic in its answers, when the hooks cause the update. The
issue requesting this is open since June 2019.
[_DevTools: Profiler: Show which hooks changed #16477_](https://github.com/facebook/react/issues/16477)
## The Tools of Deduction 🔬
So we know which component is causing trouble, but we need more details to
discover the cause. Wrapping everything in useMemo is a dumb idea. We call
ourselves engineers; let's pretend to be serious.
We download the results of our profiling, call them _"before.json"_ or something
like that, and snap a few screenshots for convenience. We'll need them for
comparison later, to make sure our changes are truly improvements.
### Monkey patching React 🐒
`@welldone-software/why-did-you-render` monkey patches React to detect avoidable
re-renders. Tools for debugging are the only good use case for monkey patching.
I added this _beautiful_ piece of code to my entry point.
```tsx
if (process.env.NODE_ENV === "development") {
document.title += " 🌴";
const DEBUG_PERF = false;
if (DEBUG_PERF) {
// @ts-ignore for CI; why-did-you-render is in dev deps
type WhyDidYouRender =
typeof import("@welldone-software/why-did-you-render").default;
// eslint-disable-next-line global-require, import/no-extraneous-dependencies
const whyDidYouRender =
require("@welldone-software/why-did-you-render") as WhyDidYouRender;
whyDidYouRender(React, {
trackAllPureComponents: true,
collapseGroups: true,
trackExtraHooks: [
[
// eslint-disable-next-line global-require
require("./data/store")
.__internal_import_me_only_for_debugging__store,
"useSelectors",
],
],
});
}
}
```
Honestly, it didn't help me that much because I already profiled the app and had
a fairly good idea what's wrong. I just didn't know the exact reason.
### useWhyDidYouUpdate 🎣
Based on a recipe I found at
[usehooks.com](https://usehooks.com/useWhyDidYouUpdate), I've added a new hook
to my codebase. Sprinkled it with some TypeScript, changed `console.log` for
`console.group` and `console.table`, et voilà!
```tsx
/* eslint-disable no-console */
import { useRef, useEffect } from "react";
/**
* Based on https://usehooks.com/useWhyDidYouUpdate/
*
* @param name a key to identify console group
* @param props object to persist and compare
*/
export function useWhyDidYouUpdate(
name: string,
props: Record
) {
const previousProps = useRef>();
useEffect(() => {
const { current } = previousProps;
if (current) {
const allKeys = Object.keys({ ...current, ...props });
const changesObj: Record = {};
allKeys.forEach((key) => {
if (current[key] !== props[key]) {
changesObj[key] = {
from: current[key],
to: props[key],
};
}
});
console.group(
`%c[why-did-you-update] %c${name}`,
"font-weight: bold;",
"color: dodgerblue; font-weight: bold;"
);
if (Object.keys(changesObj).length) {
console.table(changesObj);
} else {
console.log("Something else changed.");
}
console.groupEnd();
}
previousProps.current = props;
});
}
```
I added it to [`no-restricted-syntax`][restricted] ESLint rule, to make sure I
don't deploy it staging by accident.
[restricted]: https://eslint.org/docs/rules/no-restricted-syntax

And here's how it looks like in browser devtools.

That's all! How do you investigate performance concerns in your apps? Did I
miss something? Feel free to reach out and let me know!
---
# Lines of Code
Lines of Code is a lousy metric — at least on a small scale. I accept that it
may be useful to compare big projects or repositories, but most often, when I
encounter it, it is just meaningless.
## The Big Scale
It may be valuable on a big scale.
> Google Stores Billions of Lines of Code in a Single Repository
A billion is a big number, a meaningful number. We can state things and ask
questions about it.
- Google has a huge repository. Maybe even the biggest one in the world.
- Did they modify git/svn/Perforce to make it possible?
- My workflow is git-centric. I'm curious about how this repository affects
their workflow? How is it different from mine?
> The Linux kernel has around 27.8 million lines of code in its Git repository
- The Linux kernel is way smaller than the Google codebase.
- It's still a tremendous effort.
- But hey. Isn't it too big for a "kernel"? Oh! The drivers are there. Makes
sense!
Developers seem to care about lines of code much more than I'd expect.
## Why care about LoC?
I heard a story about a programmer paid by lines of code. I hope it was a joke.
This is like paying a construction worker by the materials he uses! Lines of
code have no intrinsic value. This is a cost we pay, a ballast.
Edit from late 2022: Elon Musk reportedly fired engineers from Twitter based on
LoC.
https://news.ycombinator.com/item?id=33472339
We can use it as an approximation of the effort we already invested. Better yet,
we can take numbers of added and deleted lines of code through a timespan and
compare it to LoC of the entire repository, so we get the idea of how the
project is changing over time. I'd argue that a changelog or a list of work
items (Jira tickets, PRs merged?) gives us more information.
On its own, LoC of a file for example -- It' just a number. Sad and lonely.
## 150 🍎 < 75 🍊
Can we say that a 500 line long file is long? Or short? Or is it just perfect?
We can't and this would be pointless. Don't let
[any lint rule](https://eslint.org/docs/rules/max-lines) tell you otherwise. We
need more information. The language isn't enough. Let's take modern JavaScript.
Is it declarative, imperative, or functional? The answer may differ between
files in the same repository, and when we're actually close enough to read
what's inside the file, we may form better models to think about it than LoC.
Let me continue with the declarative. Declarative is easier to read than
imperative, right? We don't have to think as much while reading, because it just
**is**, while imperative **does**. For example, HTML is easier to read than
JavaScript of the same length.
In a React class component, 150 lines of JSX in a `render` function is "less
code" than 75 lines of class logic.
I will go further. 150 lines of JSX in a functional component is "less code"
than 75 lines of hooks. `useState`, `useEffect`, `useLayoutEffect`,
`useSelector`, `useMachine`. A lot happens there. The difference may not be as
big as when comparing declarative UI composition with imperative code in
lifecycle hooks, but I'd argue that it still holds. We have fewer things to
comprehend in JSX because much of it is self-explanatory. (Go away ` `
component, you're the outlier.)
This is all JavaScript, but aren't we comparing apples to oranges?
There are different _kinds_ of code.
- Declarative, but non-functional code, like HTML, CSS, SQL and GraphQL may be
verbose, but it's trivial to read.
- Imperative code will certainly be harder to read, and way harder to maintain.
- Functional code doing the same thing may be more concise and easier to debug,
but require detailed reading at first.
We can divide it in more ways! In the same language, the same codebase, there
will be some important code and some cheap code. The text doesn't hold this
information.
## Better Metrics
We would like to measure things that matter, obviously.
Ease of comprehension is a good one. It is a very soft thing, though. Can we
measure something easier and assume it's correlated with cognitive complexity?
Enter
[**cyclomatic complexity**](https://en.wikipedia.org/wiki/Cyclomatic_complexity),
a measure of the number of linearly independent paths in a program's control
flow graph.
The more paths we have, the more we need to think about, and what's important,
the more we have to test.
## Further reading
- SonarSource has a nice heuristic for cognitive complexity. I didn't read their
whitepaper thoroughly, but it makes a lot of sense. This is the rule I'm using
in my ESLint config.
- https://blog.sonarsource.com/cognitive-complexity-because-testability-understandability
- "Danger of Simplicity" is a good read
- https://asthasr.github.io/posts/danger-of-simplicity/
> Measuring programming progress by lines of code is like measuring aircraft
> building progress by weight.
—Bill Gates, reportedly:
https://ask.metafilter.com/114578/Did-he-really-say-that
---
# Reasonable Language Choice
**Epistemic Effort:** I talked about it a few times and wanted to formalize my
notes.
I believe that language choice isn't the most crucial in software development.
Significant problems translate fairly well between the languages, and many
developers I respect are polyglots.
However, let me ponder the choice between ReasonML and TypeScript. I hope
you'll find it interesting.
---
I find ML family languages pleasing. Despite this, I couldn't recommend ReasonML
to many teams. TypeScript is a nonromantic, and possibly unpleasant, but
**reasonable** language choice.
Let me explain.
I work in the web ecosystem -- The JavaScript ecosystem. I build apps and
libraries, which solve real but fairly ordinary problems. I won't call it
simple, but it's not really rocket science. I strive to do it well, aim for
a short feedback loop with correct, maintainable, and cheap to produce
code.
## Correct
I need correctness, so I reach for a static type system. Tests are simply [not
enough][not-enough]. I want to describe types passing through my system. The
person who reads my code can then read them, and see the bird's-eye view.
[not-enough]: https://css-tricks.com/types-or-tests-why-not-both
You might be familiar with the famous quote "TypeError: undefined is not a
function."
I claim that writing strict functional TypeScript provides the same level of
correctness and control over your codebase as ReasonML.
I'd like to avoid expanding on it now, but what I mean by strict functional
TypeScript is shortly:
- `compilerOptions.strict: true`. Avoid `any` and `!`
- Prefers `head(array)` over `array[0]` ([#11122])
- Generous use of discriminated unions
- Prefers libraries designed for TypeScript over JavaScript typed in DT (fp-ts
over lodash-fp or ramda)
[#11122]:
https://github.com/Microsoft/TypeScript/issues/11122#issuecomment-251686473
## Maintainable
Let's assume that it is easier to have maintainable code in a "good" language
with which our developers are familiar. "Good" is highly subjective and not easy
to define. Take the following with a grain of salt.
### Language "Quality"
TypeScript fixes a lot of JavaScript [problems], but it still inherits a big
chunk of them.
[problems]:
https://blog.campvanilla.com/javascript-the-curious-case-of-null-0-7b131644e274
`null >= 0` is a build-time error, not [`true`][null-true], but [adding
methods][adding-methods] to the right-hand side of the assignment is still
possible. What's more, it is a thing that really smart people seriously
consider.
[null-true]:
https://blog.campvanilla.com/javascript-the-curious-case-of-null-0-7b131644e274
[adding-methods]:
https://github.com/WICG/construct-stylesheets/issues/45#issuecomment-521224893
ReasonML certainly wins this competition by not being JavaScript. Pattern
matching and [Hindley-Milner] [type inference][type-inference] of ReasonML are
beautiful things, but we don't even have to mention them. Not being JavaScript
is enough.
[hindley-milner]: http://okmij.org/ftp/ML/generalization.html
[type-inference]:
https://www.cs.cornell.edu/courses/cs3110/2016fa/l/17-inference/notes.html
One could respond: "Git gud. Just learn JavaScript!" The thing is, I have no
intention to remember the quirks of JavaScript! I want to focus on delivering
business value, and this is just accidental complexity! I don't care that
`{} + []` equals `0`. If I wrote this, it was an accident; I didn't have my
morning coffee. I want my tools to protect me and tell me "Yo dude, that's
weird" with bright red squigglies.
TypeScript is not meant to be pretty. It is designed for the gradual migration
of big JavaScript codebases. You can type some wildly dynamic code with a
[Turing complete type system](https://github.com/Microsoft/TypeScript/issues/14833).
- Is TS type system absurdly powerful? Yes.
- Can you write some hideous incomprehensible [conditional types] spaghetti in
it? Yes.
- Does this result in excellent editor support and precise types even for weird
dynamic patterns? Absolutely.
[conditional types]:
https://www.typescriptlang.org/docs/handbook/advanced-types.html#conditional-types
We can posit the question: "Do we care if our dependencies are readable and easy
to develop? Or do we just need them to be well-tested?"
### Language Familiarity
It is easier to maintain code written in a way that's familiar to us. "Legacy
code" often means "code written by someone else.", because we're afraid of the
unknown.
TypeScript, built for adoption in existing JS codebases, promises that all
JavaScript developers already know some TypeScript. It is a superset, right?
Just rename tada file, check if the few squigglies that appeared indicate bugs,
maybe tweak your config a little bit, to make it less strict. Tada. It's a
tempting promise for people who don't want to learn another language, who want
to improve their existing codebase, who want to get shit done.
There are legions of them. Web-development agencies are full of JavaScript
developers. On top of it, C-like syntax and the short feedback loop of
JavaScript make it a language suitable for beginners. The number of JS
developers will grow.
Statically typed JavaScript is doomed to be popular.
## Cheap to produce
I find the "Build or Install" decision interesting. (Build vs Buy / Make
Or Buy)
How much of our code should be other people's code? The answer can be "all of
it" if we're using no-code tools. It can also be "zero" if you're building a
language or "next to none" for a library for building user interfaces.
[language]:
https://github.com/microsoft/TypeScript/blob/master/package.json#L132
[library]:
https://github.com/facebook/react/blob/master/packages/react/package.json#L28-L31
For most web apps built in consulting agencies, I believe the answer to be "as
much as we're comfortable with." Gluing together libraries that fix parts of
your problem is a valid way to build a product. Using no-code and gluing
together entire products is also an entirely correct way. It isn't as glorious
as [Real Man C++ Programming], but it solves people's problems, and this is the
important part. More so, it allows building better products in a limited time.
Time to swallow our pride, and stand on the shoulder of giants, my friend.
[real man c++ programming]:
https://www.urbandictionary.com/define.php?term=C%2B%2B&defid=2583707#.XmO26-jht3E.twitter
But hey! Popular open source projects proceed in entirely different way. React
has almost no dependencies, TypeScript and Mitt have exactly zero.
React contains a purpose-built [min heap implementation][min-heap].
[][min-heap-twitter]
[min-heap]: https://github.com/facebook/react/pull/16245/files
[min-heap-twitter]: https://twitter.com/imohitbhatia/status/1159932373955297280
Whenever we install a library, we trade off control for time.
Libraries need more control than apps do. Few kB of difference in bundle size or
one corner case vulnerability may be a difference between mass adoption and
oblivion.
Another argument for zero-dependency policy might be, that the dependencies are
not expected. We'd like to avoid the question "What is this? Why is it
in our bundle?". \
You are invited to a party. Is it appropriate to bring your friends? It depends.
Are you the party person, and the hosts expect you to bring your squad (`redux-toolkit`)?
Or are you expected to come alone?
Languages and OSes need even more control. Fuchsia devs pin all dependencies to
an exact version [by policy][by-policy]. It is important to notice the
difference in scale. I mentioned projects which plan for years, not months. They
aim to topple the giants.
[by-policy]:
https://fuchsia.dev/fuchsia-src/project/policy/external_dependencies
If you know you are going to compose your app from chunks of existing code,
choosing a language with the bigger ecosystem is a good idea.
How do the ecosystems of these two languages compare? As I am writing this,
DefinitelyTyped has 10,669 contributors[^1] and 26,828 stars with a 0.4
contributors to stars ratio.
For comparison:
- Vue: 290 👩💻 and 158k ⭐ ➡ 0.00182 ratio
- TypeScript: 448 👩💻 and 59k ⭐ ➡ 0.00765 ratio
- ReasonML: 124 👩💻 and 8.6k ⭐ ➡ 0.01455 ratio
Look at the ratio! Four tenths! DefinitelyTyped is not a fun thing people star,
because they like it! I hate contributing to DT! Yet, there are many excellent
libraries in the JavaScript ecosystem, and I want to use them in my statically
typed projects.
ReasonML ecosystem is growing, but there's a long way ahead of it.
There are 10 thousand people who introduce the best JavaScript libraries[^2] to
TypeScript. The sheer number is astonishing. There are more people who actively
improve TypeScript ecosystem than people who declare that they like Reason! The
numbers speak in favor of TypeScript. I realize that this is a cold argument and
that I am betting for Goliath against David.
[^1]:
[State of the Octoverse][octoverse] reports different numbers. GitHub isn't
clear what "contributor" means. I believe that repository pages indicate the
number of commit authors, and the State of the Octoverse reports the number
of issue authors.
[octoverse]: https://octoverse.github.com/#top-and-trending-projects
[^2]:
Kind of. Many libraries "introduce themselves." JavaScript ecosystem is a
superset of the TypeScript ecosystem. Your favorite libraries you use in
JavaScript might be written in TypeScript. Notable mentions: Jest, XState,
both Redux and MobX.
## Summary
Both TypeScript and ReasonML have a positive effect on the industry. They ease
the transition for web developers who'd like to build bigger and more complex
applications and for programmers with no love for JavaScript who'd like to build
for the web without WASM.
I am not fond of the fact, but for my ordinary work projects choosing the
uglier, more popular language seems to be the reasonable choice.
One exception. My gaming laptop explodes when I open 4th Electron-based project.
**[ReveryUI](https://github.com/revery-ui) is love.**
---
##### Loose notes
- `import`/`export` vs `module`/`open`/all files are `module`s
- ReasonReact feels underpowered
- ([props-spread]) Spreading props is not an "anti-pattern" in TypeScript, and
it's tremendously useful for design systems and libraries. It feels
especially weird for me because Reason
[Records](https://reasonml.github.io/docs/en/record) support spread.
[props-spread]:
https://github.com/reasonml/reason-react/blob/master/docs/props-spread.md
---
---
# AAR: Adding Theme UI Components to DefinitelyTyped
**Epistemic Effort:** Low-to-medium effort. It isn't my best work, but I hope it shows that contributing to DefinitelyTyped isn't scary. I might refine it into a
meetup talk or a tutorial someday.
18:37
I'm using both theme-ui and chakra-ui in an app, and it just doesn't work.
Don't get me wrong, they're both really good libraries,
and they're both using @emotion/core to provide the dynamic styling API that IMHO
encourages good composition and makes styling faster.
Keep in mind that this was written in January 2020. The landscape looked different.
You just shouldn't use both at once because they step on each other feet.
I'd probably go with Chakra, because this is an app, not a blog or a landing page,
and I expect I'll need most of the components from it.
However, we've decided that theming is a must-have feature of our app.
I'm going to swap Chakra with `@theme-ui/components`, to get the bundle size lower and
satisfy my theming needs. The problem is, this package has no typings. Jxnblk builds
great stuff for styling the modern web, but I just can't use any library without types.
It's not you, it's me, and I need to do something about it.
18:45
```sh
git clone --depth 1 --branch master git@github.com:DefinitelyTyped/DefinitelyTyped.git
```
Well, here we go again. I don't want to do it. I don't have time to do it.
Yet, it is a noble thing to do, and someone has to.
```
> npx dts-gen --dt --name @theme-ui/components --template module
npx: installed 59 in 6.878s
Unexpected crash! Please log a bug with the commandline you specified.
ENOENT: no such file or directory, mkdir 'types\@theme-ui\components'
```
Well, obviously. Since `@theme-ui/components` is in _theme-ui_ namespace and
we can't have a directory with a slash in name, we need to stick to the conventional workaround.
It doesn't crash anymore in January 2020.
```
> npx dts-gen --dt --name theme-ui__components --template module -o
npx: installed 59 in 7.202s
Warning: Could not retrieve version/homepage information: HTTP Error 404: Not Found for http://registry.npmjs.org/theme-ui__components
```
Oh right. It worked, though.
```
> ls ./types/theme-ui__components
index.d.ts theme-ui__components-tests.ts tsconfig.json tslint.json
```
19:01
Let's fill in the header comment in index.d.ts
```
// Type definitions for @theme-ui/components 0.2.50
// Project: https://github.com/system-ui/theme-ui
// Definitions by: Piotr Monwid-Olechnowicz
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
// TypeScript Version: 3.7
```
19:10
`theme-ui` reexports a bunch of components from its [index.js](https://github.com/system-ui/theme-ui/blob/master/packages/components/src/index.js), let's list them here.
```
Box, Flex, Grid, Button, Link, Text, Heading, Image, Card, Label, Input, Select, Textarea, Radio, Checkbox, Slider, Field, Progress, Donut, Spinner, Avatar, Badge, Close, Alert, Divider, Embed, AspectRati, AspectImag, Containe, NavLin, Message, IconButton, MenuButton
```
Quite a lot of them, right?
With a little of multi-cursor karate, I've made a stub of the definitions.
```tsx
export const Box: React.FC;
export const Flex: React.FC;
//...
```
This isn't really useful, but I'll make a commit and push to my fork in case
my computer blows up or anything.
19:39
I've looked a bit in theme-ui repo for issues including TypeScript and found
[this one](https://github.com/system-ui/theme-ui/issues/121). The last comment
is a question about `@theme-ui/components` from yesterday. I'm not the only
who needs it. Awesome.
I've added a simple test in `theme-ui__components-tests.tsx`. Just creating all
elements with no props. I had to modify paths in tsconfig to get the import
working, because there's a lint rule here prohibiting relative imports.
```json
"paths": {
"@theme-ui/components": ["theme-ui__components"]
},
```
Let's start from the [Box](https://github.com/system-ui/theme-ui/blob/master/packages/components/src/Box.js).
This should be the hardest one.
19:48
There are some dependencies here.
```tsx
import styled from "@emotion/styled";
import css, { get } from "@styled-system/css";
import { createShouldForwardProp } from "@styled-system/should-forward-prop";
import space from "@styled-system/space";
import color from "@styled-system/color";
```
I have a strong feeling that I'll need types from them.
I'll have to add package.json with the ones that have types outside of DT to
my theme-ui\_\_components directory and add
them to [dependencies whitelist](https://github.com/DefinitelyTyped/DefinitelyTyped/#i-notice-some-packages-having-a-packagejson-here)
in types publisher, if they're not there already.
This leaves me with styled-system packages. I'll add `@styled-system/css`
to paths and I'll just ignore the props given by `space` and `color` for now.
Box gets its props from these 5 functions:
```
base,
variant,
space,
color,
sx,
props => props.css
```
So `sx` and `css` props give us a css prop syntax with no JSX pragma.
This gets me to
```tsx
export interface BoxProps {
css: Interpolation;
sx: SxStyleProp;
}
```
20:21
Gotta take a break now. I'll continue in the morning. Maybe I'll even post a
half-assed PR so the other guy could continue my work.
08:20
I'm starting work at 10. I gotta move fast.
`variant` will be a string, and I've just [found](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/288692b272b695eda00a47f93339e1e16ee756b1/types/styled-system__css/index.d.ts#L452) the names of space props. Not sure what to do with them yet.
Should I pick them from `AllSystemCSSProperties`?
I'm getting `any` in `sx` prop properties in tests 😢
Since the dependency on `@styled-system/css` is aliased and `@styled-system/css`
depends on `csstype`, I'm gonna go to `types/styled-system__css` dir and
run `yarn`.
```tsx
(property) bg?: string | string[] | SystemCssProperties | CSSPseudoSelectorProps | CSSSelectorObject | VariantProperty | UseThemeFunction | (string | ... 2 more ... | undefined)[] | ((theme: any) => ResponsiveStyleValue<...>) | undefined
---
The background-color CSS property sets the background color of an element.
```
Beautiful.
08:55
Okay, I've found actual `SpaceProps` type in `@types/styled-system`.
I've already started building this type, copying JSDocs from `AliasesCSSProperties`.
```tsx
interface StyledSystemSpaceProps {
/**
* The **`margin`** CSS property sets the margin area on all four sides of an element. It is a shorthand for `margin-top`, `margin-right`, `margin-bottom`, and `margin-left`.
*
* | Chrome | Firefox | Safari | Edge | IE |
* | :----: | :-----: | :----: | :----: | :---: |
* | **1** | **1** | **1** | **12** | **3** |
*
* @see https://developer.mozilla.org/docs/Web/CSS/margin
*/
m: SystemCssProperties['m'];
/**
* The **`margin`** CSS property sets the margin area on all four sides of an element. It is a shorthand for `margin-top`, `margin-right`, `margin-bottom`, and `margin-left`.
*
* | Chrome | Firefox | Safari | Edge | IE |
* | :----: | :-----: | :----: | :----: | :---: |
* | **1** | **1** | **1** | **12** | **3** |
*
* @see https://developer.mozilla.org/docs/Web/CSS/margin
*/
margin: SystemCssProperties['margin'];
```
I'll save myself the trouble and extend SpaceProps.
```tsx
import { SpaceProps, ColorProps } from "styled-system";
export interface BoxProps extends SpaceProps, ColorProps {
variant?: string;
sx?: SxStyleProp;
css?: Interpolation;
}
export const Box: React.FC;
```
Okay, but Box is created with `@emotion/styled` so it should be `StyledComponent`
```tsx
export interface BoxStyleProps extends SpaceProps, ColorProps {
variant?: string;
sx?: SxStyleProp;
css?: Interpolation;
}
export const Box: StyledComponent<
React.ComponentProps<"div">,
BoxStyleProps,
{}
>;
```
We can use `withComponent` to substitute the div inside Box with something else.
```tsx
const SectionBox = Box.withComponent("section");
```
And on top of it, we now support all `div` props, like `contentEditable` or `tabIndex`.
We get [Flex](https://github.com/system-ui/theme-ui/blob/master/packages/components/src/Flex.js) for free.
It's just a Box with `display: flex`.
The [Grid](https://github.com/system-ui/theme-ui/blob/master/packages/components/src/Grid.js)
has `width`, `columns` and `gap`.
The Grid forwards ref to Box, so we have to check what `forwardRef` returns.
```tsx
React.ForwardRefExoticComponent & React.RefAttributes>
```
Brilliant. Let's alias it to `ForwardRef`.
```tsx
export interface BoxProps
extends Omit, "color" | "css">,
BoxStyleProps {}
export interface GridProps extends BoxProps {
/**
* Minimum width of child elements
*/
width?: ResponsiveValue;
/**
* Number of columns to use for the layout (cannot be used in conjunction with the width prop)
*/
columns?: ResponsiveValue;
/**
* Space between child elements
*/
gap?: ResponsiveValue;
}
export const Grid: ForwardRef;
```
"Cannot be used in conjunction" suggests a union type, but I'm afraid of [TS2589](https://github.com/microsoft/TypeScript/issues/34933)
so I'll pass.
09:24
It's getting a bit late already, so I'll add a bit more and do the PR.
```tsx
export interface ButtonProps
extends Assign, BoxStyleProps> {}
export const Button: ForwardRef;
export interface LinkProps
extends Assign, BoxStyleProps> {}
export const Link: ForwardRef;
export type TextProps = BoxProps;
export const Text: ForwardRef;
export interface HeadingProps
extends Assign, BoxStyleProps> {}
export const Heading: ForwardRef;
```
Continuing from this point should be a bit more pleasant.
---
[The PR](https://github.com/DefinitelyTyped/DefinitelyTyped/pull/41057) was merged after a few days 🎉
---
# Goal for 2020: Deliver
**Epistemic Status:** Personal blogging, craft-related conclusions.
The guy sitting next to me at the office has a wallpaper saying "ALWAYS
FUCKING DELIVER." This dude thinks I'm a good developer. He sometimes asks me
for tips or opinions on code-related stuff. Little does he know, I am a
developer-disappointment. When it comes to delivering, I ain't close to Jesus.
I'm actually closer to the [Polish Post].
[polish post]:
https://www.reddit.com/r/poland/comments/9hf0oq/why_is_poczta_polska_so_bad/
I've started numerous side projects, which I didn't ship. A startup idea I was
working on with my close friend didn't get [sherlocked], which is hardly a good
thing, because we hadn't even started to ship when it was already too late.
[sherlocked]:
https://www.howtogeek.com/297651/what-does-it-mean-when-a-company-sherlocks-an-app/
p': { textAlign: 'center' } }}>

Illustration by [Henrik Kniberg]. I've managed to build a bike or maybe even a
motorbike once. The rest is a garage full of rolling chassis.
[henrik kniberg]:
https://blog.crisp.se/2016/01/25/henrikkniberg/making-sense-of-mvp
I wanted to build a perfect product, with fantastic user experience, the newest
tech stack, and the cleanest code, while maintaining some degree of
personal life, passing exams, and completing a thesis. The thesis included an
app, which isn't production-ready (of course), but took more than five
hundred hours of work of my partner and me combined.
**Obviously, I was an idiot.** I bit off more than I could chew. I started
drinking way too much coffee. My, once thick and bushy hair started falling off,
and I'm probably skinnier than I ever was. I don't remember how to prove a thing
about Van Emde Boas trees or Fibonacci heaps.\
I can probably do a simple amortized analysis, nothing fancy, though.
On the flip side, I learned. I don't mean computer science. I got really good at
starting projects. I learned GraphQL, got a good grasp of the JavaScript
ecosystem with bundlers, React metaframeworks, and the like, but this isn't the
most valuable piece of knowledge I acquired.
I learned that **communication is key**. Unfortunately, in a hard way.
I learned that a good chunk of Medium software gurus and best practices prophets
spend more time marketing themselves than actually building software. Any
blogpost claiming that you should follow a few [simple] rules to be a good
developer is a hoax. Instead (unfortunately?), we have to strive to understand
the problem at hand, analyze the choices we have, and put in honest work.
[simple]: https://asthasr.github.io/posts/danger-of-simplicity/

You can't hide from trade-offs behind a bible of best practices.
How does my life look at the end of 2019?
I've got a side projects I could to write about, software and non-software,
including a huge one with the girl I like, but this is the time to stop
writing this blog post and get to work.
[side project]: https://github.com/zagrajmy/
[adrs]: https://adr.github.io/
[in public]: https://www.swyx.io/writing/learn-in-public/
---
# You Deserve More than PropTypes
**Epistemic Effort:** I've spent a good amount of time discussing it. I'd love
to chat if you want to teach me something new on the matter or prove me wrong.
I'll start with reasons why I think PropTypes are not good enough, and later
I'll show what TypeScript gives you to solve these problems and improve your
React code even more on top of it.
I'd like to be clear -- I'm not bashing
[prop-types](https://www.npmjs.com/package/prop-types) -- It's a really good
library, but the last publish was 9 months ago. As I'm writing this, it's
November 2019, and there are much better alternatives for prop types.
I've chosen TypeScript because of its popularity, but my arguments fit any
language with first-class type composition you can use to build React apps
(Flow, Reason, Kotlin, Scala).
## Why?
### It's easy to half-ass PropTypes
I've seen too many of lines like this:
```js
//eslint-disable-next-line react/forbid-prop-types
```
Few codebases leverage PropTypes to their full potential -- mostly libraries
(see [Reach UI tabs]).
[reach ui tabs]:
https://github.com/reach/reach-ui/blob/master/packages/tabs/src/index.js#L68
I find exporting propTypes uncommon. Instead of using exported common types,
developers either use PropTypes.object or copy PropTypes.shape from another
component.
Maybe it is hard to remember that you strip them out in production build, and
that's why the devs I've met don't want to make them too big and heavy?
### PropTypes.func is not enough
Functions make stuff happen. They are pretty important. Types of functions are
important too. Stating that a prop is just a function, doesn't document intent.
You still need to read the implementation to get the slightest idea of what's
happening.
```kt
interface VideoListProps : RProps {
var videos: List
var selectedVideo: Video?
var onSelectVideo: (Video) -> Unit
}
```
copied from
[Kotlin React tutorial](https://play.kotlinlang.org/hands-on/Building%20Web%20Applications%20with%20React%20and%20Kotlin%20JS/05_Working_Together_Composing_Components)
Take a look at the props above. `onSelectVideo` takes a video and returns a
unit. This is a lot more information than "`onSelectVideo` is a function". We
could argue that the name of the function should be enough, but what if a
possibility to select multiple videos was added later, as an additional feature?
If someone forgot to change the function name, `PropTypes.func` would still fit,
and some other poor soul would get surprised by a runtime error.
### Optional by default
Optional is a bad default for application code.
I do agree that nullable by default is a good design choice in some cases.
GraphQL is a [perfect example]. Responses stitched from many data stores may return
partial data. This is the complexity we have to handle.
And I'd say we have about enough of it. We should avoid introducing more
complexity ourselves. Every optional field without a default of the same type
increases cyclomatic complexity.
[perfect example]: https://github.com/graphql/graphql-spec/issues/63
```js
Person.propTypes = {
car: PropTypes.shape({
registrationNumber: PropTypes.string,
year: PropTypes.number,
}),
};
```
source:
[first blogpost](https://codeburst.io/validating-props-easily-with-react-proptypes-96e80208207)
in google search results for "PropTypes isRequired"
Does this person have a car? Maybe. I live in a big city; I don't have one too.
But is an empty object `{}` really a valid car for our app? Do we display an
error message here? Did we just forget to write `isRequired`, or are we okay
with cars without license plates?
Typing `isRequired` is yet another small decision for a programmer. The fact
that stating that a prop is nullable is an easier way allows to accidentally
introduce complexity. `isOptional` instead of `isRequired` would be a better API
design.
## type Props = ?
TypeScript is much better in describing React component props than PropTypes.
Let's look at how it solves the problems I've mentioned before.
### Harder to half-ass
Add `strict: true` to your tsconfig.json, stray from `any` and now you're forced
to maintain a decent level of type safety. Also, it's pretty obvious, even
before a morning coffee, that it has no runtime cost.
### Typing functions
`(selected: Video) => void`. Pretty easy, amiright? Programming, even OOP, is
mostly about using functions to do stuff. Ability to describe the type of a
function is quite useful.
### Required by default
In TypeScript, you gotta stick this `?` every time you want an optional
property.
```ts
type Car = {
registrationNumber: string;
year?: number;
};
```
We deal only with registered cars up in here.
And look at what else we get!
{/* ### `ComponentProps<"button">` */}
I could talk about subtyping and Liskov Substitution Principle, but I'll
simplify it a little bit. **If it's a button, it should be buttony.**\
Props you expect on a button should be accepted by all of your design system
buttons. What do I expect? At least `onClick`, `onFocus`, `disabled`,
`className`, and `style`. We can handle all attributes of HTML
[``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button)
element, including all
[global attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes)
with a simple [spread](https://reactpatterns.com/#jsx-spread-attributes)
```tsx
import { ComponentProps, FC } from "react";
type ButtonVariant = "default" | "call-to-action";
interface ButtonProps extends ComponentProps<"button"> {
variant?: ButtonVariant;
}
```
[See more on CodeSandbox](https://codesandbox.io/embed/github/hasparus/componentprops-example/tree/master/)
Do you want **your Button** to be inferior to **a button**? I don't think so.
{/* ### `Omit` */}
But what if my component comes "batteries included" and I don't want to accept
all props of the component I'm building upon?\
Only like... most of them? We can Omit what we don't like. Just like that.
```tsx
interface JoinMeetingButton
extends Omit,
Pick {}
```
### Union Types
The anchors and the buttons often look the same in the mockups, but they are
different kinds of animals. We want to reuse the styling and behavior between
them and make choosing the right one for the job effortless.
We can use [union types] to build a Button component which renders an anchor, given
a `href` prop and renders a `` otherwise.
[union types]:
https://www.typescriptlang.org/docs/handbook/advanced-types.html#union-types
```tsx
import { ComponentProps } from "react";
interface ButtonAsAnchorProps extends ComponentProps<"a"> {
href: string;
}
interface ButtonAsButtonProps extends ComponentProps<"button"> {
href?: undefined;
}
type ButtonProps = ButtonAsAnchorProps | ButtonAsButtonProps;
function Button({ className: propsClassName, ...rest }: ButtonProps) {
const className = ["Button", propsClassName].join(" ");
if (rest.href !== undefined) {
return ;
}
return ;
}
```
[playroom]: https://github.com/seek-oss/playroom
[jsx in zeplin]:
https://blog.zeplin.io/introducing-connected-components-components-in-design-and-code-in-harmony-aa894ed5bd95
### `Picking` a subset
We can select properties from our types with `Pick`.
```tsx
type MeetingInfoProps = Pick;
const MeetingInfo = ({ date, organizer }: MeetingInfoProps) => (
<>
{new Date(date).toLocaleString()} • {organizer.name}
>
);
```
Imagine that _Meeting_ is a type of data we get from the backend. We want to
show MeetingInfo -- a date and organizer of the meeting and we don't really care
about the type of these `date` and `organizer` props. We care about their
origin. They come from the _Meeting_ type and that's what's important for this
component. Will this component break when the representation of our meetings
change? Yes. And we want it to.
Also, [we avoid introducing new names][new-names]:
{/* prettier-ignore */}
```tsx
```
[new-names]:
https://www.swyx.io/writing/how-to-name-things/#not-naming-things-aug-2019-edit
## Summary
PropTypes are not _first class_. They're a library trying to implement what is
often a language feature. If you're building an app, you don't need runtime
typechecking. Try swapping `prop-types` for TypeScript or Flow and
[tweet me](https://twitter.com/hasparus) what you think.
You can see the types I've written about used together in the sandbox below.
import { CodesandboxIframe } from "../src/own/CodesandboxIframe";
---
# Refinement Types
## What are they?
_TLDR: type + predicate = refinement type_
We have a type called _**T**_ and a
[predicate]() on
_**T**_ called _**P**_.
Even numbers are a nice example. Our _**T**_ is `number`, our _**P**_ is
`isEven`.
```tsx twoslash include brand
type Even = number & { _brand: "Even" };
```
```tsx twoslash include def
type T = number;
let isEven = (x: T): boolean => {
return x % 2 === 0;
}
// - 1
type Even = number & { _brand: "Even" };
```
```tsx twoslash
// @include: def-1
```
We can say that _**P**_ refines _**T**_ into a type _**PT**_, where _**PT**_ is
a subtype of _**T**_ containing all values _**x**_ of type _**T**_ for which
`P(x) === true`.
Our _**PT**_ is made from the type `number` and refined with the predicate
`isEven`:
{/* prettier-ignore */}
```tsx
type Even = 0 | 2 | 4 | 6 | 8 | //...
```
Problem is, we can't just list out all the even numbers, and even if we could
have an infinite type that would work in this case, our complex business domains
often deal with types we cannot represent in the typesystem, like
_AuthorizedUser_.
We have some understanding of refinement types, maybe we've even skimmed the
Wikipedia article...
> In type theory, a refinement type is a type endowed with a predicate which is
> assumed to hold for any element of the refined type.
[Wikipedia, "Refinement type"](https://en.wikipedia.org/wiki/Refinement_type)
But it doesn't seem very practical yet, right? Let's get leave the theory for a
bit, and implement the missing pieces.
## How can I use them?
There are a few ways we can go about it. Pick your poison.
1. Full OOP, make a new class `Even` and extend a `Number`.
2. In cases like _AuthorizedUser_ or _ValidEmail_, we may not even need a
subtype of the original type, so we can wrap it in a [value object]. It's
just important that the refinement type isn't assignable to the original
type, because want the validation to happen.
3. My favorite — use [nominal] [typing] and leave no runtime trail.
[value object]: https://en.wikipedia.org/wiki/Value_object
[nominal]:
https://michalzalecki.com/nominal-typing-in-typescript/#approach-4-intersection-types-and-brands
[typing]: https://github.com/hasparus/nom-ts
```tsx twoslash
// @include: brand
```
Now `Even` is a `number` with a big _"Even"_ string sprayed on it. We'll never
assign anything to the `_brand` property. It exists only on the type level.
```tsx twoslash
// @include: brand
// ---cut---
const evenNumber: Even = 3 as Even;
```
If we use a type assertion, we can pretend 3 is an even number, but we won't be
doing it. Ensuring that the name means something is up to our predicate
function.
```tsx twoslash
//
// @include: def
// ---cut---
const anyNumber: number = 4;
let evenNumber: Even;
// @errors: 2322
if (isEven(anyNumber)) evenNumber = anyNumber; // oops
```
Any number isn't assignable to our special branded type, and that's what we
wanted. However, we didn't let TypeScript know that `isEven` and `Even` are
connected.
We'll use
[user defined type guards](https://www.typescriptlang.org/docs/handbook/advanced-types.html#using-type-predicates)
to tell the compiler that our predicate checks the type. It's exactly what we
need in this case.
Let’s empower our `isEven` predicate by changing its return type.
```tsx twoslash include new-def
// @errors: 2552
function isEven(x: number): x is Even {
return x % 2 === 0;
}
```
```tsx twoslash
// @include: brand
// ---cut---
// @include: new-def
```
Now our predicate will narrow the type of its argument.
```tsx twoslash
// @include: brand
// @include: new-def
const anyNumber: number = 4;
let evenNumber: Even;
// ---cut---
// @errors: 2322
if (isEven(anyNumber)) evenNumber = anyNumber;
```
We can call the predicate once and if returns true, we remember this fact in the
type system to avoid accidental errors.
In languages without type guards, we can create a constructor function.
```tsx twoslash
// @include: def
// ---cut---
function parseEven(x: number): Even | null {
return isEven(x) ? (x as Even) : null;
}
function Even(x: number) {
if (isEven(x)) {
return x as Even;
} else {
throw new Error(`${x} is not even`);
}
}
const evenNumber: Even = Even(4);
```
## Takeaways
- Refinements are not implemented in TypeScript, but you can make them in
userspace.
- You can use nominal typing to make sure your refinements have no runtime trail
(except predicate checks).
- You can use them to encode facts about your data in the type system.
---
## Further reading
This is mostly a reading list for future me, but I hope you can also find it
interesting.
- [refined](https://github.com/fthomas/refined) for Scala sounds really
interesting and with [ScalaJs](https://www.scala-js.org/) I could target the
same platforms as TypeScript.
- "[A taste of dependent types](http://kmcallister.github.io/talks/rust/2015-dependent-types/slides.html)"
by Keegan McAllister
- "[Refinement Types for TypeScript](https://goto.ucsd.edu/~pvekris/docs/pldi16.pdf)"
> A basic refinement type is a basic type, e.g. number, refined with a logical
> formula from an SMT decidable logic.\
> For example, the types:
>
> ```
> type nat = { v: number | 0 ≤ v }
> type pos = { v: number | 0 < v }
> type natN = { v: nat | v = n }
> type idx = { v: nat | v < len(a) }
> ```
>
> describe (the set of values corresponding to) non-negative numbers, positive
> numbers, numbers equal to some value n, and valid indexes for an array a,
> respectively...
## Past Meetup Talks
I gave two short talks on refinement types in the past.
- [WrocTypeScript Sept 2019](https://www.meetup.com/WrocTypeScript/events/sjzhvqyzmbhc/),
[slides](https://github.com/hasparus/refinement-types-in-typescript),
[notes](https://paper.dropbox.com/doc/Refinement-Types-in-TypeScript--AtRTxIb1NxWvFpBv3oxBx9jpAg-cZxqOn3c6GkVCONvPTjtE)
- [Kraków TUG Feb 2022](https://www.meetup.com/typescript-krakow/events/266226393/),
[slides](https://github.com/hasparus/refinement-types-in-typescript/tree/extended-15-minutes),
(bad) [video](https://youtu.be/4ccCeAbwmbU)
**Pitch:** I’m gonna explain what refinement types are, geek out about a
research paper that adds them to the TypeScript type system, and live-code a
userland refinement type you can already use in your codebase.
- **How?** Lightning talk with VSCode, Quokka and browser as software.
- **What?** Refinement types are easy. Use them to encode more info on the type
level.
- **Why?** Encoding information on the type level helps you write less bugs.
**Outline:**
- Why and when should I type stronger?
- JS without JSDoc vs Idris proofs
- Refinement types are one step further into bulletproof programs.
- Refined TypeScript
- It’s not rocket science
- You can do it yourself
- Live demo
- Libraries with refinements
---
# Matryoshka Code
Probably the funniest meetup I ever gave, its topic still pops up in
conversations, even six years later in 2025.
import { LinkCard } from '../src/own/LinkCard';
export const TalkCard = () => {
return (
https://matryoshka-code.now.sh
)
}
Slides:
## pitch
I’m gonna rant about matryoshka code, DRY as dunes of Arrakis, and utmost unpleasant to work with. Have you heard about it? Are you guilty of writing it? I’m going to describe the problem and propose a few ideas of how to deal with it.
## tldr
> instead of making chains, grow trees
—yours truly, Wrocław, 2019
> duplication is far cheaper than the wrong abstraction
—[sandimetz](https://sandimetz.com/)
- [John Carmack on inlined code, according to Johnatan Blow](http://number-none.com/blow/john_carmack_on_inlined_code.html)
- [ch-athens on Matryoshka Code](http://betabug.ch/blogs/ch-athens/1352)
- [Kent C. Dodds on avoiding hasty abstractions](https://kentcdodds.com/blog/aha-programming)