← Back to Blog

Finding Joy in Programming

Sean Grove ·
Finding Joy in Programming

This post is based on my talk at Øredev 2017. Watch the video.

I’ve been thinking about this for a while now - what actually makes me happy when I’m programming? Because here’s the thing: I’m pretty sure I’m intrinsically motivated to code. I genuinely enjoy it. There are plenty of fun things I could be doing with my time, and yet I’d often rather be programming, creating something.

And yet
 there are times when I feel deeply unhappy programming. Sometimes for very long periods of time. So what’s going on?

I figured if I could tease apart what was making me happy versus unhappy, I could communicate my decisions and preferences more clearly to my coworkers. Maybe even help others think about their own choices.

The “Best Tool for the Job” Problem

There’s this mantra that goes around at conferences: “use the right tool for the job” or “use the best tool for the job.” I particularly dislike this statement.

I was at a CSS-in-JS talk where the speaker had a slide with literally 60 different libraries. And he said, “use the best tool for the job.” I thought: I have no idea what the best tool for the job is. You’ve imparted no information to me.

At the very least, if you’re not going to tell me what the best tool is, tell me how to decide. What are the criteria?

This matters because when we’re sharing information at talks or in blog posts, we’re rarely sharing understanding. Understanding is an embodied experience. You’ve tried something, you’ve felt the good parts and the bad parts. And it’s part of why reasoning by analogy is so important - if you can give me an analogy, I can figure out the differences from there.

JavaScript Developer View

Selling Power vs. Selling Restraint

Selling power to developers is a much, much easier proposition. “Look how powerful this thing is! Look how quickly I can do this! Why wouldn’t I want all this power?”

This includes me - especially if I look back at a much younger me who was much more confident in his ability to program. I was quite arrogant, maybe. I thought I’m not the one that’s going to make mistakes. I deserve this power. Why wouldn’t I want it?

It’s only after shooting yourself in the foot several dozen times that you realize maybe there are trade-offs inherent with this power.

The challenge when selling restraint is that it almost entirely relies on the audience having some experience and understanding what the trade-offs are. The market for selling restraint is, by definition, smaller than the market for selling power. And this devolves into “just trust me, you’ll try it for a while and then you’ll love it.”

That’s not a compelling argument. I listen to you for three weeks, and it turns out to be a total waste of my time? No thanks.

Three Dimensions of Programming Happiness

So what makes me happy when I’m programming? I’d argue it’s when I hit success points. And success - whatever that means to you - has three dimensions that matter:

  1. Time to initial success - How quickly can I get something working?
  2. Intensity of success - When I do succeed, how good does it feel?
  3. Regularity/interval between success points - How often do I keep hitting that success state?

This sounds like a tautology - “happiness is success” is like saying good things are great and water is wet. But actually, I think it’s really powerful to apply precise language to specific concepts.

If you’ve seen Rich Hickey’s “Simple Made Easy” talk, he differentiates between what is simple and what is easy. This separation makes conversations much more effective because we’re no longer talking past each other with fuzzy terms.

Haskeller View of JavaScript

The Chemical Reality of Success

Here’s something important: success is a chemical process in our brain. We feel good when we hit some success point. And we want that success - sometimes no matter what the cost.

This often manifests as fake work or even negative work. The canonical example: a tech founder who is very good at tech builds a product with no users. What do they do? Rewrite it in some new better tech. Refactor it using some new pattern.

Why? Because learning to speak with users, discovering their problems, feeding that back into the development lifecycle - that’s going to be a long time until you get any success. But I can solve a technical problem right now and feel like I did something.

It’s like drug addict behavior, honestly. We prioritize that quick hit of success - our tests pass, we fix a minor bug - rather than the medium or long-term concerns.

I’m particularly careful with myself whenever I notice it’s been a while since I’ve had success. I get more self-critical: Am I doing this because it’s the real problem? Or am I doing this because I just want some success right away?

The Pit of Success

This leads to the concept of the “pit of despair” versus the “pit of success.” This originally comes from Eric Lippert, who worked on compilers at Microsoft. He talked about C++ as his personal pit of despair - so many ways to mess things up. You’re constantly walking on a precipice where just centimeters to either side is this pit you can fall into.

Pit of Success Concept

Rico Mariani and Brad Abrams (also at Microsoft) extended this, arguing we should search for the pit of success instead. To the extent that we allow people to get into trouble, that’s our failing as tool makers. What we want is for people to accidentally be successful without even thinking about it - they accidentally climb the mountain.

Restrictions in technology often lead to accidental success.

React is a good example. They really focus on the pit of success. By default, most React users - new or experienced - will do the right thing without thinking about it. It is possible to do the wrong thing, but it’s annoying. It becomes very clear when you’re reaching around the system. You’re entering dangerous territory.

Accidental success is important because if we can stay in that success state, we minimize the risk of decay and starting to do counterproductive work. We’re much more likely to ship product - which is the biggest success of all.

Redux and the Power of Restriction

Redux is another example. It’s a state management library that sees massive adoption, and it does so by encouraging a pattern that’s very, very restrictive compared to alternatives. It takes longer to set up. But in return, you get predictability and shared tooling.

Patterns are literally just a dampening coefficient in how quickly decay increases over time and code-base size.

The whole point of patterns is this: as our code scales over time, size, and people, inevitably the interval between successes grows. Our programs become more complicated, and our ability to reason about them becomes less. Patterns slow that growth.

Redux Pattern

Redux does this by being very explicit. It takes on one of the biggest challenges in UI programming - state-space size. From the root of your application, a user can do some number of things. Each represents a state change. From there, more options. It’s like a continuously expanding tree.

Redux requires you to explicitly state: from this state, here are all the transitions that are possible. Because of this enumeration, we can reason about how quickly our state space is scaling, and with the right tooling, make sure we never forget to handle a case.

Compare this to a powerful JavaScript approach using global variables - the most powerful, right? You can do anything! They can be updated from anywhere. Maybe via observers. Maybe via proxies.

(Proxies! You can wrap objects so that a read-only accessor like x.width actually goes and changes something. You’re reading it, it looks like straight-line code, but inside that proxy you can mutate global state. Fun times.)

With this power, state transitions are completely implicit. It’s very hard to understand all the transitions a user could actually do. And the state-space grows extremely quickly.

The Debugging Scenario

Let’s say a user is five minutes into your app, has transitioned states about a hundred times, and hits a bug. They’re kind enough to send you a report. Now what?

Debugging Scenario

In both cases, you start from the root and know the user is somewhere deep in this tree. Your job is to apply graph traversal algorithms in your head to figure out how they got there.

But in the Redux example, the tree is pre-pruned. The edges are explicit, clear. You have a much sparser graph to traverse. Whereas with that powerful JavaScript app, you may be in a desert of success for a long time before you can reason your way out.

And during that time? You’re going to feel unhappy. Burnt out. You may start doing dangerous things.

The Illusion of Success

One of the most dangerous things about powerful tools is they can lead to an illusion of success. It feels successful. As developers, we start from the root, have a goal, and trace the shortest line to it. We’re focused. We’re unaware that just because we’ve traced a path doesn’t mean there isn’t a pit of despair centimeters away that our users will hit.

Because of the sheer number of states possible and the lack of tooling, it’s possible our apps are more often broken than working. We’ve proven it’s possible to succeed - not that it’s likely.

These powerful tools allow the freedom to ignore cases, ignore state transitions - often accidentally. “I forgot that if someone logs in first, they have this thing happening over here, and I didn’t handle that edge case.”

Why Reason?

So how does all this relate to ReasonML?

Reason has an unusual focus on time to initial success. This is often overlooked or even belittled in more rigorous engineering disciplines.

We can all agree JavaScript is very, very quick to get to some level of success. NPM install anything, hot code reloading, REPL - within seconds you have a huge ecosystem at your fingertips.

As engineers, we often argue that doesn’t matter. The area to the left of initial success is so short compared to everything on the right. Who cares if it takes ten times as long to get started?

But this isn’t compelling to someone who literally just felt success. “What do you mean I have to go for days, weeks, or months feeling unsuccessful to reach this supposed promised land?”

JavaScript vs Elm vs Reason

We talk about these trade-offs as if they’re inherent - as if because a technology gives you benefits on the right side, it’s required to be hard to get started. I don’t think that’s true.

OCaml has spent 20+ years worrying about the right side of the graph - really good type system, powerful runtime, very fast. Reason can build on top of that without giving anything up. We can just drag the graph to the left and make it more accessible.

The Syntax Experiment

Reason is built entirely on OCaml - 100% compatible. Just different syntax and tooling with polish.

The hypothesis was: by making syntax closer to JavaScript, we enable people to jump into functional programming much more quickly. When someone tweeted their Reason code and someone asked “what version of JavaScript is that?” - the community loved it. People weren’t focusing on unfamiliar syntax. They were saying “that looks reasonable, I understand that. Now what are the actual differences?”

Try Reason Online

BuckleScript (the compiler from OCaml/Reason to JavaScript, now called ReScript) has an explicit goal: generate JavaScript that looks like what you would write by hand. This is important for onboarding - you can look at the generated JavaScript and work backwards. When we did a workshop with 80 people, they’d immediately focus on the compiled JavaScript output. “OK, I see what this is doing. Now let me see what the Reason thing is doing.”

The Trade-off Space

JavaScript: quick to get started, get some success, then potentially crumble under code-base weight.

Elm: takes a long time to learn, but gives you severe and amazing guarantees. NoRedInk has over 80,000 lines of Elm in production for two years with zero runtime exceptions. The language guarantees you cannot have them. But anytime you reach out to existing JavaScript, you have to stop and set up infrastructure.

Reason: straddles the middle. We don’t want to give up time to initial success unless it buys us something on the right side. Syntax doesn’t buy us anything there, so let’s make it familiar. Bad tooling doesn’t help anyone, so let’s have good tooling. Integration with NPM means we can pull in modules quickly. We have fewer guarantees than Elm - you can have runtime exceptions. There are trade-offs.

tl;dr

Happiness in programming comes from hitting success points. Those success points have three dimensions: time to initial success, intensity, and regularity.

We’re all chemically motivated to seek success, sometimes at the cost of doing fake work. The right restrictions can lead to accidental success - keeping us in the pit of success rather than the pit of despair.

Patterns and tools that are more restrictive aren’t just being annoying - they’re dampening the rate at which our ability to succeed decays over time.

Reason tries to give you the benefits of a mature type system and functional programming without sacrificing time to initial success. Because both sides of that equation matter.

If you want to dig deeper into Reason specifically, I’d recommend checking out Cheng Lou’s “Taming the Meta Language” talks and Jared Forsyth’s Strange Loop talk on Reason. But regardless of what tools you use - think about your success metrics. It might change how you evaluate your choices.


References