# The Universe of Discourse

Sat, 30 Apr 2022

Freddie DeBoer has an article this week titled “Mental illness doesn't make you special”. Usually Freddie and I are in close agreement and this article is not an exception. I think many of M. DeBoer's points are accurate. But his subtitle is “Why do neurodiversity activists claim suffering is beautiful?” Although I am not a neurodiversity activist and I will not claim that suffering is beautiful, that subtitle stung, because I saw a little bit of myself in the question. I would like to cut off that little piece and answer it.

This is from Pippi Longstocking, by Astrid Lindgren (1945):

'No, I don't suffer from freckles,' said Pippi.

Then the lady understood, but she took one look at Pippi and burst out, 'But, my dear child, your whole face is covered with freckles!'

'I know that,' said Pippi, 'but I don't suffer from them. I love them.'

I suffer from attention deficit disorder. Like Pippi Longstocking suffers from freckles.

M. DeBoer says:

There is, for example, a thriving ADHD community on TikTok and Tumblr: people who view their attentional difficulties not as an annoyance to be managed with medical treatment but as an adorable character trait that makes them sharper and more interesting than others around them.

For me the ADD really is a part of my identity — not my persona, which is what I present to the world, but my innermost self, the way I am actually am. I would be a different person without it. I might be a better person, or a happier or more successful one (I don't know) but I'd definitely be someone different.

And it's really not all bad. I understand that for many people ADD is a really major problem with no upsides. For me it's a major problem with upsides. And after living with it for fifty years, I've found ways to mitigate the problems and to accept the ones I haven't been able to mitigate.

I learned long ago never to buy nice gloves because I will inevitably leave them somewhere, perhaps on a store counter, or perhaps in the pocket of a different jacket. In the winter I only wear the cheapest and most disposable work gloves or garden gloves. They work better than nothing, and I can buy six pairs at a time, so that when I need gloves there's a chance I will find a pair in the pocket of the jacket I'm wearing, and if I lose a pair I can pick up another from the stack by the front door.

I used to constantly forget appointments. “Why don't you get a calendar?” people would say, but then I would have to remember the calendar, remember to check the calendar, and not lose the calendar, all seemingly impossible for me. The arrival of smartphones improved my life in so many ways. Now I do carry my calendar everywhere and I miss fewer appointments.

(Why could I learn to carry a smartphone and not a calendar? For one thing, the smartphone is smaller and fits in my pocket. For another, I really do carry it literally everywhere, which I wouldn't do with a calendar. I don't have to remember to check it because it makes a little noise when I have an appointment. It has my phone and my email and my messages in it. It has the books and magazines I'm reading. It has a calculator in it and a notepad. I used to try to carry all that crap separately and every day I would find that I wanted one that I had left at home that day. No longer.)

There are bigger downsides to ADD, like the weeks when I can't focus on work, or when I get distracted by some awesome new thing and don't do the things I should be doing, or how I lost interest in projects and don't always finish them, blah blah blah. I am not going to complain about any of that, it is just part of being me and I like who I am pretty well. Everyone has problems and mine are less severe than many.

And some of the upsides are just great. When it's working, the focus and intensity I get from the ADD are powerful. Not just useful, but fun. When I'm deep into a blog post or a math paper the intense focus brings me real joy. I love being smart and when the ADD is working well it makes me a lot smarter. I don't suffer from freckles, I love them.

When I was around seventeen I took a Real Analysis class at Columbia University. Toward the end of the year the final was coming up. One Saturday morning I sat down at the dining room table, with my class notes, proving every theorem that we had proved in class, starting from page 1. When I couldn't prove it on my own I would consult the notes or the textbook. By dinner time I had finished going through the semester and was ready to take the final. I got an A.

Until I got to college I didn't understand how people could spend hours a day “studying”. When I got there I found out. When my first-year hallmates were “studying” they were looking out the window, playing with their pencils, talking to their roommates, all sorts of stuff that wasn't studying. When I needed to study I would hide somewhere and study. I think the ability to focus on just one thing for a few hours at a time is a great gift that ADD has given me.

I sometimes imagine that the Devil offers me a deal: I will lose the ADD in return for a million dollars. I would have to think very, very carefully before taking that deal and I don't know whether I would say yes.

But what if the Devil came and offered to cure my depression, and the price was my right arm? That question is easy. I would say “sounds great, but what's the catch?” Depression is not something with upsides and downsides. It is a terrible illness, the blight of my life, the worst thing that has ever happened to me. It is neither an adorable character trait nor an annoyance to be managed with medical treatment. It is a severe chronic illness, one that is often fatal. In a good year it is kept in check by medical treatment but it is always lurking in the background and might reappear any morning. It is the Joker: perhaps today he is locked away in Arkham, but I am not safe, I am never safe, I am always wondering if this is the day he will escape and show up at my door to maim or kill me.

I won't write in detail about how I've suffered from depression in my life. It's not something I want to revisit and it's not something my readers would find interesting. You wouldn't be inspired by my brave resolve in the face of adversity. It would be like watching a movie about someone with a chronic bowel disorder who shits his pants every day until he dies. There's no happy ending. It's not heroic. It's sad, humiliating, and boring.

DeBoer says:

This is what it’s actually like to have a mental illness: no desire to justify or celebrate or honor the disease, only the desire to be rid of it.

I agree 100%. This is what it is like to have a mental illness. In two words: it sucks.

And this is why I find it so very irritating that there is no term for my so-called ⸢attention deficit disorder⸣ that does not have the word “disorder” baked into it. I know what a disorder is, and this isn't one. I want a word for this part of my brain chemistry that does not presume, axiomatically, that it is an illness. Why does any deviation from the standard have to be a disorder? Why do we medicalize human variation?

I understand that for some people it really is a disorder, that they have no desire to justify or celebrate or honor their attention deficit. For those people the term “attention deficit disorder” might be a good one. Not for me. I have a weird thing in my brain that makes it work differently from the way most other people's brains do. In many ways it works less well. I lose hats and forget doctor appointments. But that is not a mental illness. Most people aren't as good at math as I am; that's not a mental illness either. People have different brains.

Some variations from standard are intrinsic problems, but many are extrinsic. Homosexual orientation used to be a mental illness. But almost all the problems that queer people face are extrinsic: when you're queer your main problem is that other people treat you like crap. They hate you and they're allowed to tell you how much they hate you. You're not allowed to love or marry or bring up children. It sucks! But “I'm unhappy because people treat me like crap” is not a mental illness! The correct fix for this isn't “stop being queer”, it's “stop treating queer people like crap!”

DeBoer says:

Today’s activists never seem to consider that there is something between terrible stigma and witless celebration, that we are not in fact bound to either ignore mental illness or treat it as an identity.

I agree somewhat, but that doesn't mean that the stigma isn't a real issue. Take away the stigma from queerness and you solve almost all of the problems queer people have that straight people don't also have. With mental illnesses the problems are deeper and harder to solve, but some of them are caused by stigma and should be addressed. Mentally ill people will still be mentally ill, but at least they wouldn't be stigmatized.

Many of the downsides of ADD would be less troublesome for everyone, if our world was a little more accepting of difference, a little more willing to accommodate people who were stamped in a slightly different shape that the other cogs in the machine. In Pippi Longstocking world, why do people suffer from freckles? Not because of the freckles themselves, but only because other people tell them that their freckles are ugly and unlovable. Nobody has to suffer from freckles, if people would just stop being assholes about freckles.

ADD is a bigger problem than freckles. Some of its problems are intrinsic. It definitely contributes to making me unreliable. I don't think losing gloves (and books and jackets and glasses and bags and wallets and everything else) is a delightful quirk. People depending on me to do work timely are sometimes justifiably angry or disappointed when I don't. I'll accept responsibility for that. I've worked my whole life to try to do better.

But when the world has been willing to let me what I can do in the way that I can do it, the results have been pretty good. When the world has insisted that I do things the way everyone else does them, it hasn't always gone so well. And if you examine the “everyone else” there it starts to look threadbare because almost everyone is divergent in one way or another, and almost everyone needs some accommodation or other. There is no such thing as “the way everyone else does”.

I don't think “neurodivergent” is a very good term for how I'm different, not least because it's vague. But at least it doesn't frame my unusual and wonderful brain as a “disorder”.

Returning to Freddie DeBoer's article:

There is, for example, a thriving ADHD community on TikTok and Tumblr: people who view their attentional difficulties not as an annoyance to be managed with medical treatment but as an adorable character trait that makes them sharper and more interesting than others around them.

Some people do have it worse than others. I'm lucky. But that doesn't change the fact that some of those attentional difficulties are more like freckles: a character trait, perhaps even one that someone might find adorable, that other people are being assholes about. Isn't it fair to ask whether some of the extrinsic problems, the stigma, could be ameliorated if society were a little more flexible and a little more accommodating of individual differences, and stop labeling every difference as a disorder?

Tue, 26 Apr 2022

[ I hope this article won't be too controversial. My sense is that SML is moribund at this point and serious ML projects that still exist are carried on in OCaml. But I do observe that there was a new SML/NJ version released only six months ago, so perhaps I am mistaken. ]

It was apparent that SML had some major problems. When I encountered Haskell around 1998 it seemed that Haskell at least had a story for how these problems might be fixed.

I was curious what the major problems you saw with SML were.

I actually have notes about this that I made while I was writing the first article, and was luckily able to restrain myself from writing up at the time, because it would have been a huge digression. But I think the criticism is technically interesting and may provide some interesting historical context about what things looked like in 1995.

I had three main items in mind. Every language has problems, but these three seemed to me be the deep ones where a drastically different direction was needed.

Notation for types and expressions in this article will be a mishmash of SML, Haskell, and pseudocode. I can only hope that the examples will all be simple enough that the meaning is clear.

### Mutation

#### Reference type soundness

It seems easy to write down the rules for type inference in the presence of references. This turns out not to be the case.

The naïve idea was: for each type α there is a corresponding type ref α, the type of memory cells containing a value of type α. You can create a cell with an initialized value by using the ref function: If v has type α, then ref v has type ref α and its value is a cell that has been initialized to contain the value v. (SML actually calls the type α ref, but the meaning is the same.)

The reverse of this is the operator ! which takes a reference of type ref α and returns the referenced value of type α.

And finally, if m is a reference, then you can overwrite the value stored in its its memory cell by saying with m := v. For example:

    m = ref 4          -- m is a cell containing 4
m := 1 + !m        -- overwrite contents with 1+4
print (2 * !m)     -- prints 10


The type rules seem very straightforward:

    ref   :: α → ref α
(!)   :: ref α → α
(:=)  :: ref α × α → unit


(Translated into Haskellese, that last one would look more like (ref α, α) → () or perhaps ref α → α → () because Haskell loves currying.)

This all seems clear, but it is not sound. The prototypical example is:

     m = ref (fn x ⇒ x)


Here m is a reference to the identity function. The identity function has type α → α, so variable m has type ref(α → α).

     m := not


Now we assign the Boolean negation operator to m. not has type bool → bool, so the types can be unified: m has type ref(α → α). The type elaborator sees := here and says okay, the first argument has type ref(α → α), the second has type bool → bool, I can unify that, I get α = bool, everything is fine.

Then we do

     print ((!m) 23)


and again the type checker is happy. It says:

• m has type ref(α → α)
• !m has type α → α
• 23 has type int

amd that unifies, with α = int, so the result will have type int. Then the runtime blithely invokes the boolean not function on the argument 23. OOOOOPS.

#### SML's reference type variables

A little before the time I got into SML, this problem had been discovered and a patch put in place to prevent it. Basically, some type variables were ordinary variables, other types (distinguished by having names that began with an underscore) were special “reference type variables”. The ref function didn't have type α → ref α, it had type _α → ref _α. The type elaboration algorithm was stricter when specializing reference types than when specializing ordinary types. It was complicated, clearly a hack, and I no longer remember the details.

At the time I got out of SML, this hack been replaced with a more complicated hack, in which the variables still had annotations to say how they related to references, but instead of a flag the annotation was now a number. I never understood it. For details, see this section of the SML '97 documentation, which begins “The interaction between polymorphism and side-effects has always been a troublesome problem for ML.”

After this article was published, Akiva Leffert reminded me that SML later settled on a third fix to this problem, the “value restriction”, which you can read about in the document linked previously. (I thought I remembered there being three different systems, but then decided that I was being silly, and I must have been remembering wrong. I wasn't.)

Haskell's primary solution to this is to burn it all to the ground. Mutation doesn't cause any type problems because there isn't any.

If you want something like ref which will break purity, you encapsulate it inside the State monad or something like it, or else you throw up your hands and do it in the IO monad, depending on what you're trying to accomplish.

Scala has a very different solution to this problem, called covariant and contravariant traits.

### Impure features more generally

More generally I found it hard to program in SML because I didn't understand the evaluation model. Consider a very simple example:

     map print [1..1000]


Does it print the values in forward or reverse order? One could implement it either way. Or perhaps it prints them in random order, or concurrently. Issues of normal-order versus applicative-order evaluation become important. SML has exceptions, and I often found myself surprised by the timing of exceptions. It has mutation, and I often found that mutations didn't occur in the order I expected.

Haskell's solution to this again is monads. In general it promises nothing at all about execution order, and if you want to force something to happen in a particular sequence, you use the monadic bind operator >>=. Peyton-Jones’ paper “Tackling the Awkward Squad” discusses the monadic approach to impure features.

Combining computations that require different effects (say, state and IO and exceptions) is very badly handled by Haskell. The standard answer is to use a stacked monadic type like IO ExceptionT a (State b) with monad transformers. This requires explicit liftings of computations into the appropriate monad. It's confusing and nonorthogonal. Monad composition is non-commutative so that IO (Error a) is subtly different from Error (IO a), and you may find you have the one when you need the other, and you need to rewrite a large chunks of your program when you realize that you stacked your monads in the wrong order.

My favorite solution to this so far is algebraic effect systems. Pretnar's 2015 paper “An Introduction to Algebraic Effects and Handlers” is excellent. I see that Alexis King is working on an algebraic effect system for Haskell but I haven't tried it and don't know how well it works.

#### Arithmetic types

Every language has to solve the problem of 3 + 0.5. The left argument is an integer, the right argument is something else, let's call it a float. This issue is baked into the hardware, which has two representations for numbers and two sets of machine instructions for adding them.

Dynamically-typed languages have an easy answer: at run time, discover that the left argument is an integer, convert it to a float, add the numbers as floats, and yield a float result. Languages such as C do something similar but at compile time.

Hindley-Milner type languages like ML have a deeper problem: What is the type of the addition function? Tough question.

I understand that OCaml punts on this. There are two addition functions with different names. One, +, has type int × int → int. The other, +., has type float × float → float. The expression 3 + 0.5 is ill-typed because its right-hand argument is not an integer. You should have written something like int_to_float 3 +. 0.5.

SML didn't do things this way. It was a little less inconvenient and a little less conceptually simple. The + function claimed to have type α × α → α, but this was actually a lie. At compile time it would be resolved to either int × int → int or to float × float → float. The problem expression above was still illegal. You needed to write int_to_float 3 + 0.5, but at least there was only one symbol for addition and you were still writing + with no adornments. The explicit calls to int_to_float and similar conversions still cluttered up the code, sometimes severely

The overloading of + was a special case in the compiler. Nothing like it was available to the programmer. If you wanted to create your own numeric type, say a complex number, you could not overload + to operate on it. You would have to use |+| or some other identifier. And you couldn't define anything like this:

    def dot_product (a, b) (c, d) = a*c + b*d  -- won't work


because SML wouldn't know which multiplication and addition to use; you'd have to put in an explicit type annotation and have two versions of dot_product:

    def dot_product_int   (a : int,   b) (c, d) = a*c + b*d
def dot_product_float (a : float, b) (c, d) = a*c + b*d


Notice that the right-hand sides are identical. That's how you can tell that the language is doing something stupid.

That only gets you so far. If you might want to compute the dot product of an int vector and a float vector, you would need four functions:

    def dot_product_ii (a : int,   b) (c, d) = a*c + b*d
def dot_product_ff (a : float, b) (c, d) = a*c + b*d
def dot_product_if (a,         b) (c, d) = (int_to_float a) * c + (int_to_float b)*d
def dot_product_fi (a,         b) (c, d) = a * (int_to_float c) + b * (int_to_float d)


Oh, you wanted your vectors to maybe have components of different types? I guess you need to manually define 16 functions then…

#### Equality types

A similar problem comes up in connection with equality. You can write 3 = 4 and 3.0 = 4.0 but not 3 = 4.0; you need to say int_to_float 3 = 4.0. At least the type of = is clearer here; it really is α × α → bool because you can compare not only numbers but also strings, booleans, lists, and so forth. Anything, really, as indicated by the free variable α.

Ha ha, I lied, you can't actually compare functions. (If you could, you could solve the halting problem.) So the α in the type of = is not completely free; it mustn't be replaced by a function type. (It is also questionable whether it should work for real numbers, and I think SML changed its mind about this at one point.)

Here, OCaml's +. trick was unworkable. You cannot have a different identifier for equality comparisons at every different type. SML's solution was a further epicycle on the type system. Some type variables were designated “equality type variables”. The type of = was not α × α → bool but ''α × ''α → bool where ''α means that the α can be instantiated only for an “equality type” that admits equality comparisons. Integers were an equality type, but functions (and, in some versions, reals) were not.

Again, this mechanism was not available to the programmer. If your type was a structure, it would be an equality type if and only if all its members were equality types. Otherwise you would have to write your own synthetic equality function and name it === or something. If !!t!! is an equality type, then so too is “list of !!t!!”, but this sort of inheritance, beautifully handled in general by Haskell's type subclass feature, was available in SML only as a couple of hardwired special cases.

#### Type classes

Haskell dealt with all these issues reasonably well with type classes, proposed in Wadler and Blott's 1988 paper “How to make ad-hoc polymorphism less ad hoc”. In Haskell, the addition function now has type Num a ⇒ a → a → a and the equality function has type Eq a ⇒ a → a → Bool. Anyone can define their own instance of Num and define an addition function for it. You need an explicit conversion if you want to add it to an integer:

                    some_int + myNumericValue       -- No
toMyNumericType some_int + myNumericValue       -- Yes


but at least it can be done. And you can define a type class and overload toMyNumericType so that one identifier serves for every type you can convert to your type. Also, a special hack takes care of lexical constants with no explicit conversion:

    23 + myNumericValue   -- Yes


As far as I know Haskell still doesn't have a complete solution to the problem of how to make numeric types interoperate smoothly. Maybe nobody does. Most dynamic languages with ad-hoc polymorphism will treat a + b differently from b + a, and can give you spooky action at a distance problems. If type B isn't overloaded, b + a will invoke the overloaded addition for type A, but then if someone defines an overloaded addition operator for B, in a different module, the meaning of every b + a in the program changes completely because it now calls the overloaded addition for B instead of the one for A.

In Structure and Interpretation of Computer Programs, Abelson and Sussman describe an arithmetic system in which the arithmetic types form an explicit lattice. Every type comes with a “promotion” function to promote it to a type higher up in the lattice. When values of different types are added, each value is promoted, perhaps repeatedly, until the two values are the same type, which is the lattice join of the two original types. I've never used anything like this and don't know how well it works in practice, but it seems like a plausible approach, one which works the way we usually think about numbers, and understands that it can add a float to a Gaussian integer by construing both of them as complex numbers.

[ Addendum 20220430: Phil Eaton informs me that my sense of SML's moribundity is exaggerated: “Standard ML variations are in fact quite alive and the number of variations is growing at the moment”, they said, and provided a link to their summary of the state of Standard ML in 2020, which ends with a recommendation of SML's “small but definitely, surprisingly, not dead community.” Thanks, M. Eaton! ]

Mon, 25 Apr 2022

A few days ago I demanded a way to construct an unordered pair !![x, y]]!! with the property that $$[x, y] = [y, x]$$ for all !!x!! and !!y!!, and also formulas !!P_1!! and !!P_2!! that would extract the components again, not necessarily in any particular order (since there isn't one) but so that $$[P_1([x, y]), P_2([x, y])] = [x, y]$$ for all !!x!! and !!y!!.

Several readers pointed out that such formulas surely do not exist, as their existence would prove the axiom of binary choice. This is a sort of baby brother of the infamous Axiom of Choice. The full Axiom of Choice (“AC”) can be stated this way:

Let !!\mathcal I!! be some index set.

Let !!\mathscr F!! be a family of nonempty sets indexed by !!\mathcal I!!; we can think of !!\mathscr F!! as a function that takes an element !!i \in \mathcal I !! and produces a nonempty set !!\mathscr F_i!!.

Then (Axiom of Choice) there is a function !!\mathcal C!! such that, for each !!i\in \mathcal I!!, $$\mathcal C(i) \in \mathscr F_i.$$

(From this it also follows that $$\{ \mathcal C(i) \mid i\in \mathcal I \},$$ the collection of elements selected by !!\mathcal C!!, is itself a set.)

This is all much more subtle than it may appear, and was the subject of a major investigation between 1914 and 1963. The standard axioms of set theory, called ZF, are consistent with the truth of Axiom of Choice, but also consistent with its falsity. Usually we work in models of set theory in which we assume not just ZF but also AC; then the system is called ZFC. Models of ZF where AC does not hold are very weird. (Models where AC does hold are weird also, but much less so.)

One can ask about all sorts of weaker versions of AC. For example, what if !!\mathcal I!! and !!\mathscr F!! are required to be countable? The resulting “axiom of countable choice”, is strictly weaker than AC: ZFC obviously includes countable choice as a restricted case, but there are models of ZF that satisfy countable choice but not AC in its fullest generality.

Rather than restricting the size of !!\mathscr F!! itself, we can consider what happens when we restrict the size of its elements. For example, what if !!\mathscr F!! is a collection of finite sets? Then we get the “axiom of finite choice”. This is also known to be independent of ZF.

What if we restrict the elements of !!\mathscr F!! yet further, so that !!\mathscr F!! is a family of sets, each of which has exactly two elements? Then we have the “axiom of binary choice”. Finite choice obviously implies binary choice. But the converse implication does not hold. This is not at all obvious. Binary choice is known to imply the corresponding version of binary choice in which each member of !!\mathscr F!! has exactly four elements, and is known not to imply the version in which each member has exactly three elements. (Further details in this Math SE post.)

But anyway, readers pointed out that, if there were a first-order formula !!P_1!! with the properties I requested, it would prove binary choice. We could understand each set !!\mathscr F_i!! as an unordered pair !![a_i, b_i]!! and then

$$\{ \langle i, P_1([a_i, b_i])\rangle \mid i\in \mathcal I \},$$

which is the function !!i\mapsto P_1(\mathscr F_i)!!, would be a choice function for !!\mathscr F!!. But this would constitute a proof of binary choice in ZF, which we know is impossible.

Thanks to Carl Witty, Daniel Wagner, Simon Tatham, and Gareth McCaughan for pointing this out.

Thu, 21 Apr 2022

A year or two ago I wrote a couple of articles about the importance of pushing back against unreasonable contract provisions before you sign the contract. [1: Strategies] [2: Examples]

My last two employers have unintentionally had deal-breaker clauses in contracts they wanted me to sign before starting employment. Both were examples I mentioned in the previous article about things you should never agree to. One employer asked me to yield ownership of my entire work product during the term of my employment, including things I wrote on my own time on my own equipment, such as these blog articles. I think the employer should own only the things they pay me to create, during my working hours.

When I pointed this out to them I got a very typical reply: “Oh, we don't actually mean that, we only want to own things you produced in the scope of your employment.” What they said they wanted was what I also wanted.

This typical sort of reply is almost always truthful. That is all they want. It's important to realize that your actual interests are aligned here! The counterparty honestly does agree with you.

But you mustn't fall into the trap of signing the contract anyway, with the idea that you both understand the real meaning and everything will be okay. You may agree today, but that can change. The company's management or ownership can change. Suppose you are an employee of the company for many years, it is very successful, it goes public, and the new Board of Directors decides to exert ownership of your blog posts? Then the oral agreement you had with the founder seven years before will be worth the paper it is not printed on. The whole point of a written contract is that it can survive changes of agency and incentive.

So in this circumstance, you should say “I'm glad we are in agreement on this. Since you only want ownership of work produced in the scope of my employment, let's make sure the contract says that.”

If they continue to push back, try saying innocently “I don't understand why you would want to have X in the written agreement if what you really want is Y.” (It could be that they really do want X despite their claims otherwise. Wouldn't it be good to find that out before it was too late to back out?)

Pushing back against incorrect contract clauses can be scary. You want the job. You are probably concerned that the whole deal will fall through because of some little contract detail. You may be concerned that you are going to get a reputation as a troublemaker before you even start the job. It's very uncomfortable, and it's hard to be brave. But this is a better-than-usual place to try to be brave, not just for yourself.

If the employer is a good one, they want the contract to be fair, and if the contract is unfair it's probably by accident. But they have a million other things to do other than getting the legal department to fix the contract, so it doesn't get fixed, simply because of inertia.

If, by pushing back, you can get the employer to fix their contract, chances are it will stay fixed for everyone in the future also, simply because of inertia. People who are less experienced, or otherwise in a poorer negotiating position than you were, will be able to enjoy the benefits that you negotiated. You're negotiating not just for yourself but for the others who will follow you.

And if you are like me and you have a little more power and privilege than some of the people who will come after, this is a great place to apply some of it. Power and privilege can be used for good or bad. If you have some, this is a situation where you can use some for good.

It still scary! In this world of giant corporations that control everything, each of us is a tiny insect, hoping not to be heedlessly trampled. I am afraid to bargain with such monsters. But I know that as a middle-aged white guy with experience and savings and a good reputation, I have the luxury of being able to threaten to walk away from a job offer over an unfair contract clause. This is an immense privilege and I don't want to let it go to waste.

We should push back against unfair conditions pressed on us by corporations. It can be frightening and risky to do this, because they do have more power than we do. But we all deserve fairness. If it seems too risky to demand fair treatment for yourself, perhaps draw courage from the thought that you're also helping to make things more fair for other people. We tiny insects must all support one another if we are to survive with dignity.

[ Addendum 20220424: A correspondent says: “I also have come to hate articles like yours because they proffer advice to people with the least power to do anything.” I agree, and I didn't intend to write another one of those. My idea was to address the article to middle-aged white guys like me, who do have a little less risk than many people, and exhort them to take the chance and try to do something that will help people. In the article I actually wrote, this point wasn't as prominent as I meant it to be. Writing is really hard. ]

Sun, 17 Apr 2022

I just went through an extensive job search, maybe the most strenuous of my life. I hadn't meant to! I wrote a blog post asking where I should apply for Haskell jobs, and I thought three or four people would send suggestions. Instead I was contacted by around fifty people, many of whom ran Haskell-related companies and invited me to apply, after it hit #1 on Hacker News. So I ran with it.

At some point I'll need another job. I would really like it to be Haskell programming…

Sometimes this did touch on some deeper reasons. By the time I learned Haskell I had been programming in SML for a while, and it was apparent that SML had some major problems. When I encountered Haskell around 1998 it seemed that Haskell at least had a story for how these problems might be fixed.

But why did I get interested in SML? I'm not sure how I encountered it but by that time I had been programming for ten or fifteen years and it appeared that strong type systems were eventually going to lead to big improvements. Programming is still pretty crappy, but it is way better than it was when I started.One reason is that languages are much better. I'm interested in programming and in how to make it less crappy.

But none of this really answers the question. Yes, I've wanted to know more for decades. But the question is why do I want to learn Haskell? Sometimes these kinds of questions do have a straightforward answer. For example, “I think it will be a good career move”. That was not the answer in this case. Nor “I think it will pay me a lot of money” or “I'm interested in smart contracts and a lot of smart contract work is done in Haskell”.

I'm remembering something written I think by Douglas Hofstadter (but possibly Daniel Dennett? John Haugeland?) where you have a person (or AI program) playing chess, and you ask them “Why did you move the knight to e4?” The chess player answers “To attack the bishop on g5.”

“Why did you want to attack the bishop on g5?”

“That bishop is impeding development of my kingside pieces, and if I could get rid of it I could develop a kingside attack.”

“Why do you want to develop a kingside attack?”

“Uhh… if it is effective enough it could force the other player to resign.”

“But why do you want to force the other player to resign?”

“Because that's how you win a game of chess, dummy.”

“Why do you want to win the game?”

“…”

You can imagine this continuing yet further, but eventually it will reach a terminal point at which the answer to “Why do you want to…” is the exasperated cry “I just do!” (Or the first person turns five years old and grows out of the why-why-why phase.)

I wonder if the computer also feels exasperation at this kind of questioning? But it has an out; it can terminate the questions by replying “Because that's what I was programmed to do”. Anyway when people asked why I wanted to learn Haskell, I felt that exasperation. Sometimes I tried using the phrase “it's a terminal goal”, but I was never sure that my meaning was clear. Even at the end of the interviewing process I didn't have a good answer ready, and was still stammering out answers like “I just wanna know!”

(I realize now that “because that's what I was programmed to do” sometimes works for non-artifical intelligences also. When Katara was small she asked me why I loved her, and I answered “because that's how I'm made.”)

Now that the job hunt is all over, I think I've thought of a better reply to “why do you want to learn Haskell, anyway?” that might be easier to understand and which I like because it seems like such a good way to explain myself to strangers. The new answer is:

I'm the kind of person who gets on a bus and takes it to the last stop, just to see where it goes.

This is excellent! It not only explains me to other people, it helps explain me to myself. Of course I knew this about myself before but putting it into a little motto like that makes it easier to understand, remember, and reason from. It's useful to understand why you do the things you do and why you want the things you want, and this motto helps me by compacting a lot of information about myself into a pithy summary.

One thing I like about the motto is that it is not just metaphorically true. It's a good metaphor for the Haskell thing. I am still riding the Haskell bus to see where it ends up. But also, I do literally get on buses just to find out where they go.

In Haifa about twenty years ago, I got on a bus to see where it would go. I rode for a while, looking out the window, seeing and thinking many things. When I saw something that looked like a big open-air market I got out to see what it was about. It was a big open-air market, not like anything I had seen before. It was just the kind of thing I wanted to see when I visited a foreign country, but wouldn't have known to ask. Sometimes “what can I see that we don't have where I come from” works, but often the things you don't have at home are so ordinary where you are that your host doesn't think to show them to you. At the Haifa market, I remember seeing fresh dates for the first time. (In the U.S. they are always dried.) I bought some; they were pretty good even though they looked like giant cockroaches.

Another wondeful example of something I wanted to see but didn't know about until I got to it was Reg Hartt's Cineforum. Reg Hartt is a movie enthusiast in Toronto who runs a private movie theatre in his living room. Walking around Toronto one day I saw a poster advertising one of his shows, featuring Disney and Warner Brothers cartoons that had been banned for being too racist, and the post was clearly the call of fate. Of course I'm going to attend a cartoon show in some stranger's living room in Toronto. Hartt handed me a beer on the way in and began a long, meandering rant about the history of these cartoons. One guy in the audience interrupted “just start the show” and Hartt shot back “This is the show!” Reg Hartt is my hero.

In Lisbon I was walking around at random and happened on the train station, so I went in and got on the first train I saw and took it to the end of the line, which turned out to be in Cascais. I looked around, had lunch, and spent time feeding a packet of sugar to some ants. It was a good day.

In Philadelphia I often take the #42 bus, which runs west on Walnut Street from downtown to where I live. The #9 bus runs along the same route part of the way, but before it gets to my neighborhood it turns right and goes somewhere else called Andorra. After a few years of wondering what Andorra was like I got on the #9 bus to find out. It's way out at the city limits, in far Roxborough. Similarly I once took the #34 trolley to the end of the line to see where it went. There was a restaurant there called Bubba's Bar-B-Que, which was pretty good. Since then it has become a Jamaican restaurant which is also pretty good. I have also taken the #42 itself to the end of the line to learn where it turns around.

I once drove the car to Stenton Avenue and drove the whole length of Stenton Avenue, because I kept hearing about Stenton Avenue but didn't know where it was or what was on it.

I used to take SEPTA, the Philadelphia commuter rail, to Trenton, because that was the cheapest way to get to New York. Along the way the train would pass through a station called Andalusia but it would never stop there. The conductor would come through the train asking if anyone wanted to debark at Andalusia but nobody ever did. And nobody was ever waiting on the Andalusia platform, so the train had no reason to stop. I wondered for years what was in Andalusia. Once I got a car, I drove there to see what there was. It was a neighborhood, and I climbed down to the (no longer used) SEPTA station to poke around. Going in the other direction on SEPTA I have visited Marcus Hook and Wilmington just to see what they were like.

One especially successful trip was a few years ago when I decided to drive to Indianapolis. When I told people I was taking a road trip to Indianapolis they would ask “why, what is in Indianapolis?” I answered that I didn't know, and I was going there to find out. And when I did get there I found out that Indianapolis is really cool! I enjoyed walking around their central square which has a very cool monument and also a bronze statue of America's greatest president, William Henry Harrison. I had planned to stay in Indianapolis longer, but while I was eating breakfast I learned that the Indiana state fair was taking place about fifteen minutes south, and I had never been to a state fair, so I went to see that. I saw many things, including an exhibition of antique tractors and a demonstration of veterinary surgery, and I ate chocolate-covered bacon on a stick. After I got back from Indianapolis I had an answer to the question “why, what is in Indianapolis?” The answer was: The Indiana State Fair. (I have a blog post I haven't finished about all the other stuff I saw on that trip, and maybe someday I will finish it.)

On another road trip I decided to drive in a loop around Chesapeake Bay, just to see what there was to see. I started in New Castle which is noteworthy for being the center of the only U.S. state border that is a circular arc. I ate Smith Island cake. I drove over the Chesapeake Bay Bridge-Tunnel-Bridge-Tunnel-Bridge which was awesome, totally awesome, and stopped in the middle for an hour to look around. I made stops in towns called Onancock and Bivalve just for the names. I blundered into the Blackwater National Wildlife Refuge, another place I would never have planned to visit but I'm glad I visited. I took the Oxford-Bellevue ferry which has been running between Oxford and Bellevue since 1683. I'm not much for souvenirs, but my Oxford-Bellvue Ferry t-shirt is a prized possession.

When I was a small child my parents had a British Monopoly board and I was fascinated by the place names. When I got my first toy octopus I named it Fenchurch after Fenchurch Street Station. And when I visited London I took the Underground to the Fenchurch Street stop one night to see what was there. It turned out that near Fenchurch Street is a building that is made inside-out. It has all the fire stairs and HVAC ducts on the outside so that the inside can be a huge and spacious atrium. I had had no idea this building even existed and I probably wouldn't have found out if I hadn't decided to visit Fenchurch Street for no particular reason other than to see what was there.

In Vienna I couldn't sleep, went out for a walk at midnight, and discovered the bicycle vending machines. So I rented a bicycle and biked around Vienna and ran across the wacky Hunderwasserhaus which I had not heard of before. Amazing! In Cleveland I went for a long walk by the river past a lot of cement factories and things like that, but eventually came out at the West Side Market. Then I went into a café and asked if there was a movie theatre around. They said there wasn't but they sometimes projected movies on the wall and would I like to see one? And that's how I saw Indiscreet with Gloria Swanson. I was in Cleveland again a few years ago and wandering around at night I happened across the Little Kings Lounge. The outside of the Little Kings Lounge frightened me but I eventually decided that spending the rest of my life wondering what it was like inside would be worse than anything that was likely to happen if I did go in. The inside was much less scary than the outside. There was a bar and a pool table. I drank apple-flavored Crown Royal. They had a sign announcing their proud compliance with the Cleveland indoor smoking ban, the most sarcastic sign I've ever seen.

In Taichung I spent a lot of time at the science museum, but I also spent a lot of time walking to and from the science museum through some very ordinary neighborhoods, and time walking around at random at night. The Taichung night market I had been to fifteen years before was kind of tired out, but going in a different direction I stumbled into a new, fresher night market. In Hong Kong I was leafing through my guidebook, saw a picture of the fish market on Cheung Chau, and decided I had to go see it. I took the ferry to Cheung Chau with no idea what I would find or where I would stay, and spent the weekend there, one of the greatest weekends of my life. I did find the fish market, and watched a woman cutting the heads off of live fish with a scissors. Spaulding Gray talks about searching for a “perfect moment” and how he couldn't leave Cambodia because he hadn't yet had his perfect moment. My first night on Cheung Chau I sat outside, eating Chinese fish dinner and drinking Negro Modelo, watching the fishing boats come into the harbor at sunset, and I had my perfect moment.

A few months back I wrote about going to the Pennsylvania-Delaware-Maryland border to see what it was like. You can read about that if you want. A few years back I biked out to Hog Island, supposedly the namesake of the hoagie, to find out what was there. It turned out there is a fort, and that people go there with folding chairs to fish in the Delaware River. On the way I got to bike over the George C. Platt Bridge, look out over South Phildelphia (looked good, smelled bad) and pick up a German army-style motorcycle helmet someone had abandoned in the roadway. Some years later I found out that George Platt was buried in a cemetery that was on the way to my piano lesson, so I stopped in to visit his grave. Most interesting result of that trip: Holy Cross cemetery numbers their zones and will tell you which zone someone is in, but it doesn't help much because the zones are not arranged in order.

I was on a cruise to Alaska and the boat stopped in Skagway for a few hours before turning around. I walked around Skagway for a while but there was not much to see; I thought it was a dumpy tourist trap and I walked back to the harbor. There was a “water taxi” to Haines so I went to Haines to see what was in Haines. The water taxi trip was lovely, I looked out at the fjords and the bald eagles. Haines was charming and pretty. In Haines I enjoyed the Alaska summer weather, saw the elementary school, bought an immersion heater at a hardware store, and ate spoon bread at The Bamboo Room restaurant. Then I took the water taxi back again. Years later when I returned to Skagway, this time with Lorrie, I already knew what to do. We went directly to the next dock over to get on the water taxi and get some spoon bread.

Sometimes I do have a destination in mind. When I was in Paris my hosts asked me if there was anything I wanted to see and I said I would like to visit the Promenade Plantée, which I had read about once in some magazine. My hosts had not heard of the Promenade Plantée but I did get myself there and walked the whole thing. (We have something like it in Philadelphia now but it's not as good, yet.) What did I see? A lot of plants, French people, and a view of Paris apartment buildings that is different from the one I would have gotten from street level. Sometimes I do tourist stuff: I spent hours at the Sagrada Família, the Giant's Causeway, and the Holy Sepulchre, all super-interesting. But when I go somewhere my main activity is: walk around at random and see what there is. In Barcelona I also happened across September 11th Street, in Belfast I accidentally attended the East Belfast Lantern Festival, and in Jerusalem I stopped in an internet café where each keyboard was labeled in four different scripts. (Hebrew, Latin, Cyrillic, and Arabic.)

Today a friend showed me this funny picture:

Maybe so! But as the kind of person who gets on a bus and takes it to the last stop, just to see where it goes, I'm fully prepared for the possibility that the last stop is a dead end. That's okay. As my twenty-year-old self was fond of saying “to travel is better than to arrive”. The point of the journey is the journey, not the destination.

[ Addendum 20220422: I think I heard about the Promenade Plantée from this Boston Globe article from 2002. ]

[ Addendum 20220422: Apparently I need to get back to Haines because there is a hammer museum I should visit. ]

[ Addendum 20220426: A reader asked for details of my claim that “SML had some major problems”, so I wrote it up: What was wrong with SML? ]

Sat, 16 Apr 2022

Want to write a new Wikipedia article but can't think of a subject not already covered? Here are some items that are notable and should be easy to source:

• Richard Dattner, U.S. proponent of the “Adventure Playground” movement and designer of playgrounds, including the famous one in New York's Central Park near 67th Street.

• Publications International v. Meredith Corp, U.S. federal law case that held that while cookbooks are protected by copyright, the recipes themselves are not.

• Dorrance H. Hamilton Charitable Trust. Hamilton was heir to the Campbell's Soup fortune and a noted philanthropist. Her name is on many buildings and other institutions around the Philadelphia area. Wikipedia does have an article about Hamilton, but it is sadly incomplete and does not mention the Trust.

• Victor Dubourg, a journalist who angered Louis XV with his criticism, was induced to visit France under some pretext, and then was kidnapped and imprisoned in Mont-Saint-Michel for the rest of his life. Stories about the conditions of his confinement vary, ranging from the awful and terrifying to the terrifying and awful. (French Wikipedia)

• Knox v. United States. Knox was convicted of possession of child pornography for owning videotapes of girls in leotards and swimsuits but not engaged in sexual acts or even simulated acts. The U.S. Solicitor General, in a confession of error, joined in Knox's plea to the Supreme Court to vacate the conviction. The Court agreed and remanded the case to the lower court — which convicted him again. Knox appealed the second conviction, but the Solicitor General again changed their view and this time supported the conviction!

• Branly Cadet, U.S. sculptor, created the Octavius V. Catto memorial at Philadelphia City Hall, and the Jackie Robinson sculpture at Dodger Statdium.

• The Rowland Company, founded 1732, oldest still-operating manufacturing company in the U.S.. There are older businesses, but they are mostly taverns and inns.

• Beth Irene Stroud, ordained Methodist minister who came out as lesbian and was consequently defrocked in 2004. The United Methodist Church “affirms that all people are of sacred worth and are equally valuable in the sight of God” but apparently draws the line at lesbian ministers.

Fri, 15 Apr 2022

A great deal of attention has been given to the encoding of ordered pairs as sets. I lately discussed the usual Kuratowski definition:

$$\langle a, b \rangle = \{\{a\}, \{a, b\}\}$$

but also the advantages of the ealier Wiener definition:

$$\langle a, b \rangle = \{\{\{a\},\emptyset\}, \{ \{ b\}\}\}$$

One advantage of the Wiener construction is that the Kuratowski pair has an odd degenerate case: if !!a=b!! it is not really a pair at all, it's a singleton. The Wiener pair always has exactly two elements.

Unordered pairs don't get the same attention because the implementation is simple and obvious. The unordered pair !![a, b]!! can be defined to be !! \{a, b\}!! which has the desired property. The desired property is:

$$[a, b] = [c, d] \\ \text{if and only if} \\ a=c \land b=d\quad \text{or} \quad a=d\land b=c$$

But the implementation as !!\{a, b\}!! suffers from the same drawback as the Kuratowski pair: if !!a=b!!, it's not actually a pair!

So I wonder:

Is there a set !![a, b]!! with the following properties:

1. !![a, b] = [c, d]!! if and only if !!a=c \land b=d!! or !! a=d\land b=c!!

2. !![a, b]!! has exactly two elements for all !!a!! and !!b!!

Put that way, a solution is $$[a, b] = \{ \{ a, b \}, \emptyset \}\tag{\color{darkred}{\spadesuit}}$$ but that is very unsatisfying. There must be some further property I want the solution to have, which is not possessed by !!(\color{darkred}{\spadesuit})!!, but I don't know yet what it is. Is it that I want it to be possible to extract the two elements again? I am not sure what that means, but whatever it means, if !!\{ a, b\}!! does it, then so does !!(\color{darkred}{\spadesuit})!!.

But that does also suggest another property that neither of those enjoys:

There should be formulas !!F_1!! and !!F_2!! such that for all !![a, b]!!:

1. !!F_1([a, b]) = a!! or !!b!!
2. !!F_2([a, b]) = a!! or !!b!!
3. !!F_1([a, b]) = F_2([a, b])!! if and only if !!a=b!!

I think this can be abbreviated to simply:

!![F_1([a, b]), F_2([a, b])] = [a, b]!!

There may be some symmetry argument why there are no such formulas, but if so I can't think of it offhand. Perhaps further consideration of !![a, a]!! will show that what I want is incoherent.

Today is the birthday of Leonhard Euler. Happy 315th, Lenny!

[ Addendum 20220422: Several readers pointed out that the !!F_i!! formulas are effectively choice functions, so there can be no simple solution. Further details. ]

Mon, 11 Apr 2022

Browsing around Math StackExchange today, I encountered this question, ‘Unique’ doesn't have a unique meaning, which pointed out that the phrase “Every boy has a unique shirt” is at least confusing. (Do all the boys share a single shirt?)

“Aha,” I said. “I know what's wrong there: it should be ‘every boy has a distinct shirt’.” I scrolled down to see if I should write that as an answer. But I noticed that the question had been posted in 2012, and guessed that probably someone had already said what I was going to say. Indeed, when I looked at the comments, I saw that the third one said:

If I meant that no shirt belongs to two boys, I would say "every boy has a distinct shirt".

Okay, that saves me the trouble of replying at least. I went to click the upvote button on the comment, but there was no button,

because

the comment had been posted, in August of 2012, by me.

Mon, 04 Apr 2022

If you're losing the game, try instead playing the different game that is one level up.

I remembered a wonderful example of this: The Chen Sheng and Wu Guang Uprising of 209 BCE. As Wikipedia tells it:

[Chen Sheng] was a military captain along with Wu Guang when the two of them were ordered to lead 900 soldiers to Yuyang to help defend the northern border against Xiongnu. Due to storms, it became clear that they could not get to Yuyang by the deadline, and according to law, if soldiers could not get to their posts on time, they would be executed. Chen Sheng and Wu Guang, believing that they were doomed, led their soldiers to start a rebellion.

The uprising was ultimately unsuccessful, but it at least bought Chen Sheng and Wu Guang some time.

Sun, 03 Apr 2022

When I used to work for ZipRecruiter I would fly cross-country a few times a year to visit the offices. A couple of those times I spent the week hanging around with a business team to learn what what they did and if there was anything I could do to help. There were always inspiring problems to tackle. Some problems I could fix right away. Others turned into bigger projects. It was fun. I like learning about other people's jobs. I like picking low-hanging fruits. And I like fixing things for people I can see and talk to.

One important project that came out of one of those visits was: whenever we took on a new customer or partner, an account manager would have to fill out a giant form that included all the business information that our system would need to know to handle the account.

But often, the same customer would have multiple “accounts” to represent different segments of their business. The account manager would create an account that was almost exactly like the one that already existed. They'd carefully fill out the giant form, setting all the values for the new account to whatever they were for the account that already existed. It was time-consuming and tedious, and also error-prone.

The product managers hadn't wanted to solve this. In their minds, this giant form was going to go away, so time spent on it would be wasted. They had grand plans.

“Okay suppose,” I said, talking to the Account Management people who actually had to fill out this form, “on the page for an existing account, there was a button you could click that said “make another account just like this one”, and it wouldn't actually make the account, it would just take you to the same form as always, but the form would be already filled in with the current values for the account you just came from? Then you'd only need to change the few items that you wanted to change.”

The account managers were in favor of this idea. It would save time and prevent errors.

Doing this was straightforward and fairly quick. The form was generated by a method in the application. I gave the method an extra optional parameter, and account ID, that told the method to pre-fill the form with the data for the specified account. The method would do a single extra database lookup, and pass the resulting data to the page. I had to make a large number of changes to the giant form to default its fields to the existing-account data if that was provided, but they were completely routine. I added a link on the already-existing account information pages, to call up the form and supply the account ID for the correct pre-filling. I don't remember there being anything tricky. It took me a couple of days, and probably saved the AM team hundreds of hours of toil and error.

Product's prediction that the giant form would soon go away did not come to pass for any reasonable interpretation of “soon”. (What a surprise!)

This is the kind of magic that sometimes happens when an engineer gets to talk directly to the users to find out what they are doing. When it works, it works really well. ZipRecruiter was willing to let me do this kind of work and then would reward me for it.

But that wasn't my favorite project from that visit. My favorite was the new menu item I added for an account manager named Olaf.

Every month, Olaf had to produce a report that included how many “conversion transitions” had occurred in the previous month. I don't remember what the “conversion transitions” were or what they were actually called. It was probably some sort of business event, maybe something like a potential customer moving from one phase of the sales process to another. All I needed to know then, and all you need to know now is: they were some sort of events, each with an associated date, and a few hundred were added to a database every each month.

There was a web app that provided Account Management with information about the conversion transitions. Olaf would navigate to the page about conversion transitions and there would be a form for filtering conversion transitions in various ways: by customer name, and also a menu with common date filtering choices: select all the conversion transitions from the current month, select the conversion transitions of the last thirty days, or whatever. Somewhere on the back end this would turn into a database query, and then the app would display “317 conversion transitions selected” and the first pageful of events.

Around the beginning of a new month, say August, Olaf would need to write his July report. He would visit the web app and it would immediately tell him that there had been 9 events so far in August. But Olaf needed the number for July. But there was no menu item for July. There was a menu item for “last 30 days”, but that wasn't what he wanted, since it omitted part of July and included part of August,

What Olaf would do, every month, was select “last 60 days”, page forward until he got to the page with the first conversion transition from July, and hand-count the events on that page. Then he would advance through the pages one by one, counting events, until he got to the last one from July. Then he would write the count into his report.

I felt a cold fury. The machine's job is to collate information for reports. It was not doing its job, and to pick up the slack, Olaf, a sentient being, was having to pretend to be a machine. Also, since my job is to make sure the machine is doing its job, this felt to me like an embarrassing professional failure.

“Olaf,” I said, “I am going to fix this for you.”

Fixing it was not as simple as I had expected. But it wasn't anything out of the ordinary and I did it. I added the new menu item, and then had to plumb the desired functionality through three or four levels of software, maybe through some ad-hoc network API, into whatever was actually querying the database, so that alongside the “last 30 days” and “current month” queries, the app also knew how to query for “previous month”.

Once this was done, Olaf would just select “previous month” from the menu, and the first page of July conversion transitions would appear, with a display “332 conversion transitions selected”. Then he could copy the number 332 into his report without having to look at anything else.

From a purely business perspective, this project probably cost the company money. The programming, which was in a part of the system I had never looked at before, took something like a full day of my time including the code changes, testing, and deployment. Olaf couldn't have been spending more than an hour a month on his hand count of conversion transitions. So the cost-benefit break-even point was at least several months out, possibly many years depending on how much Olaf's time was worth.

But the moral calculus was in everyone's favor. What is money, after all, compared with good and evil? If ZipRecruiter could stop trampling on Olaf's soul every month, and the only cost was a few hours of my time, that was time and money well-spent making the world a better place. And one reason I liked working for ZipRecruiter and stayed there so long was that I believed the founders would agree.