Hardscrabble šŸ«

By Maxwell Jacobson

See also: the archives and an RSS feed

Learning to love vim colorschemes

August 1, 2021

Up until a few months ago, I was living in the dark.

I’ve been using vim as my main editor for most of my career. It has a built-in feature for setting a colorscheme, and it comes with a handful of color schemes with intriguing names like elflord, delek, and darkblue.

All you have to do is a add a line like this to your ~/.vimrc to set a colorscheme on boot:

colorscheme darkblue

I never used it. I tried it out occasionally, but none of them looked that good.

When I wanted to customize the colors in my editor, I’d do it indirectly, by configuring the color schemes for my terminal app. Doing that affects the colors of all programs you run, not just vim, and it seemed good enough for me.

I kind of thought that was the best I could hope for. And, unfortunately, it was never great. Often it wasn’t even readable; Vim didn’t necessarily know which of the terminal’s 16 colors made sense for which contexts, and it might choose to render dark grey text on a black background, because it didn’t really know better.

It was a ā€œyou get what you get and you don’t get upsetā€ situation.

delta

Here’s what changed: I came across delta, a new-ish project which bills itself as ā€œA viewer for git and diff outputā€, and it just looks so good that it made me realize I need to raise my expectations a bit.

I configured git to use delta when displaying diffs (I use git show and git diff all the time), and I configured it to display them in a way that looks a lot like how the same diff would look on GitHub. That means a nice shade of green when highlighting new lines, a nice shade of red when highlighting removed lines, and actual syntax highlighting on all of the lines. That last bit is still quite cool to me, months later.

True color

But wait a second? How does delta look so good? I thought terminal programs were constrained to only have access to the 16 colors provided by your terminal theme?

Welp, turns out that’s not true. Modern terminal apps support True Color, which basically means they support whatever colors you want. For years, I used the Terminal app that comes with Macs because it seemed fine, and I didn’t understand why people preferred iTerm2. Now I get it. Per this indispensable reference, Terminal.app does not support True Color, whereas iTerm2 does.

bat

Before I heard of delta, I’d heard of bat, which bills itself as ā€œA cat(1) clone with wings.ā€ It’s a lot like cat (you can use it to look at the contents of a file), but it will syntax highlight the output.

Delta is actually building on top of bat, and it also looks great.

Go ahead and install both delta and bat if you want to follow along with me here as I talk you through all the color-related changes I’ve made in my dotfiles recently.

Getting a custom color scheme

Both bat and delta come with some number of syntax themes built-in, which might be good enough for you. But my heart yearns for smyck.

Smyck is not a standard color scheme, but it’s one that I happen to really like. It’s got a chill pastel vibe. The icy blue, mustardy yellow, and salmon pink all set me at ease. These steps should work for other themes too, but I’m going to take this opportunity to evangelize Smyck.

Here’s the one-time setup to get Smyck assets in place:

  1. Clone the smyck repo, which contains the color scheme in a handful of formats
  2. Double click Smyck.itermcolors to load the iTerm2 color scheme
  3. Copy the smyck.vim file into ~/.vim/colors, which is where vim will look later on when we run colorscheme smyck
  4. Copy the Smyck.tmTheme file into ~/.config/bat/themes and run bat cache --build – this will make the theme available to bat and delta

Configuring vim to use smyck

In my ~/.vimrc:

if $COLORTERM == 'truecolor'
  set termguicolors
  let &t_8f = "\<Esc>[38;2;%lu;%lu;%lum"
  let &t_8b = "\<Esc>[48;2;%lu;%lu;%lum"
  colorscheme smyck
endif

The conditional is there because this configuration only works in terminals that support true color. I’m definitely sold on iTerm2 now, but I don’t want everything to look wacky if I did try and use vim in Terminal.app.

Those funky &t_8f and &t_8b things are there for tmux compatibility. I have no idea what they mean. I copied them from the internet.

I also added this:

let g:fzf_colors =
\ { 'fg':      ['fg', 'Normal'],
  \ 'bg':      ['bg', 'Normal'],
  \ 'hl':      ['fg', 'Comment'],
  \ 'fg+':     ['fg', 'CursorLine', 'CursorColumn', 'Normal'],
  \ 'bg+':     ['bg', 'CursorLine', 'CursorColumn'],
  \ 'hl+':     ['fg', 'Statement'],
  \ 'info':    ['fg', 'PreProc'],
  \ 'border':  ['fg', 'Ignore'],
  \ 'prompt':  ['fg', 'Conditional'],
  \ 'pointer': ['fg', 'Exception'],
  \ 'marker':  ['fg', 'Keyword'],
  \ 'spinner': ['fg', 'Label'],
  \ 'header':  ['fg', 'Comment'] }

I use fzf as a fuzzy-file-finder in vim. This fzf_colors configuration tells it to infer its colorscheme from the active vim colorscheme.

Configuring git to use delta and smyck

In my ~/.gitconfig:

[core]
  pager = delta --syntax-theme Smyck
[interactive]
  diffFilter = delta --line-numbers --color-only --syntax-theme Smyck
[delta]
  features = unobtrusive-line-numbers
[delta "unobtrusive-line-numbers"]
  line-numbers = true

You’ll note that I abandoned the GitHub theme and went all-in on Smyck.

Configuring tmux to enable true color and smyck-friendly colors

In my ~/.tmux.conf:

set -g default-terminal 'screen-256color'
set -g status-bg '#96D9F1'
set -g status-fg '#282828'

Here I’m just manually specifying some of the Smyck colors so that tmux’s status bar at the bottom blends in.

Configuring bat to use smyck

In my ~/.config/bat/config:

--theme="Smyck"

This tells bat to use the Smyck theme by default, without me needing to specify it every time.

Configuring my muscle memory to use bat

In my ~/.zshrc:

alias 'cat'='bat'

The end result

As I tweeted recently, the end result feels like a pretty luxe experience:

Everything is Smyck.

I used to think the vim colorschemes command wasn’t very good, and that I was better off on my own. But I’ve matured. Come join me.

On conflicts

April 3, 2021

Usually when git tells me that my feature branch has a conflict, it makes sense to me. Occasionally though, I’ll have a conflict, and I’ll look at and think… ā€œHuh? Why is that even a conflict?ā€

I think it makes sense to me now though. Let’s talk about it.

An example conflict

Let’s look at a classic example of a git conflict.

Let’s say on the main branch, there’s a file that looks like this:

class Dog
  def speak
    puts "woof"
  end
end

Then let’s say I check out a feature branch my-feature, and I change that file to look like this:

class Dog
  def speak
    puts "ruff"
  end
end

So far, so good.

However, unbeknownst to me, my colleague also checked out a feature branch and changed the file like so:

class Dog
  def speak
    puts "bark"
  end
end

And then merged that in.

The commit history looks a bit like this:

* dog.rb is introduced     -- commit A, on branch main
| \
|  * I update dog.rb       -- commit B, on branch my-feature
* colleague updates dog.rb -- commit C, on branch main

I have a merge conflict on my branch now! But why exactly and what is a conflict?

Why we have conflicts, and what they are

Derrick Stolee recently published a very good blog post on the GitHub blog called Commits are snapshots, not diffs which I would encourage you to read. It seeks to clear up that common misconception by going into a lot of the weeds of how git organizes its information.

In our example, we have three commits, which means we have three snapshots of the repository at three points in time.

The problem, then, is: how do you combine two snapshots?

Commits are snapshots, not diffs. That just means that git is not storing diffs on your file system. However, each commit does know which commit came before it (its parent), and git does know how to generate a diff by comparing two snapshots.

In that example, commit B’s parent is commit A. Commit C’s parent is also commit A.

So what happens if, while on the my-feature branch, we run git rebase main, to keep our feature branch up-to-date with what our colleagues are doing?

  1. git figures out which are the new commits that aren’t yet on main (just commit B)
  2. git figures out what is the latest commit on main (commit C)
  3. git resets to how things are as of the commit C snapshot
  4. git goes through the new commits, one by one. For each one, git generates a diff between it and its parent, and then attempts to apply that diff to the current workspace. As soon as there’s a conflict, it halts.

In this simple example, there’s only one new commit, and its diff with its parent looks like this:

$ git diff A..B
diff --git a/dog.rb b/dog.rb
index 825e50b..079f1c1 100644
--- a/dog.rb
+++ b/dog.rb
@@ -1,5 +1,5 @@
 class Dog
   def speak
-    puts "woof"
+    puts "ruff"
   end
 end

When git attempts to apply this diff, it needs to do a handful of things:

  1. locate the file
  2. locate the old lines that need to change
  3. replace them with the new lines

This is a deceptively difficult task. Let’s review the information that is available in the diff:

  1. the old filename (--- a/dog.rb means it used to be named dog.rb)
  2. the new filename (+++ b/dog.rb means it’s still named dog.rb)
  3. the line numbers represented in the diff. We can interpret @@ -1,5 +1,5 @@ as: ā€œin the old version of the file, this diff starts at line 1 and covers 5 lines and in the new version of the file, … same!ā€
  4. the lines that were removed (the ones starting with -)
  5. the lines that were added (the ones starting with +)
  6. some contextual lines before and after

That certainly sounds like a lot of information, but consider a scenario. Let’s say someone added a one line comment on the main branch. Our diff thinks it applies to lines 1-5, but now it applies to lines 2-6.

The line numbers, then, are not enough to locate the lines that need updating.

Interesting, right?

Ok then, so we can imagine that it scans through the file looking for the line to be removed (puts "woof"), and replaces it with the line to be added (puts "ruff"), whatever line it is on now.

This gives us our first hint of what a merge conflict actually is. If we want to change puts "woof" to puts "ruff", but it no longer even says puts "woof", how exactly are we supposed to do that? The diff cannot be applied.

Fair enough, right?

It isn’t that simple, though. We can imagine a scenario where that same line shows up several times in the same file, but only one of them should be updated. So how does git figure out which ones to update?

Look: perhaps it’s too late in the blog post to disclose this, but I’m just guessing and speculating about how any of this works. But here’s what I think. I think it looks at those unchanged contextual lines before and after the changed lines.

By default, git-diff produces diffs with three lines of context on either side of a change. With that, git can locate which is the relevant change. Don’t just look for the line to change, but also make sure it appears in the expected context.

This points to another kind of conflict, which I hinted at the beginning of this post. This is a conflict which can feel unnecessary, but when I think about how git applies diffs in this way, it makes more sense.

A more interesting example

Let’s say on the main branch, there’s a file that looks like this:

def calculate(a, b, c)
  a + b + c
end

Then let’s say I check out a feature branch my-feature, and I change that file to look like this:

def calculate(a, b, c)
  a + b * c
end

So far, so good.

However, unbeknownst to me, my colleague also checked out a feature branch and changed the file like so:

def calculate(a, b, c)
  # Tells us how many lollipops there are
  a + b + c
end

And then merged that in.

The commit history looks a bit like this:

* calculate.rb is introduced     -- commit A, on branch main
| \
|  * I update calculate.rb       -- commit B, on branch my-feature
* colleague updates calculate.rb -- commit C, on branch main

We updated different lines, so there shouldn’t be a conflict, right?

Wrong! There is a conflict.

And it kind of makes sense now, doesn’t it? When git is applying the diff, it relies on those context lines to figure out where in the file to make the change. If the context lines in the diff no longer exactly line up, git cannot be 100% sure it’s doing the right thing, and so it raises a flag.

And I suppose all I can say is… fair enough!

How to watch tennis

March 27, 2021

I like to watch tennis, but I had to learn what to pay attention to. Here are some of those things. This is all pretty basic stuff, and I’m not at all an expert, etc, etc. I’ll assume you know the basic rules.

Actually watch

While the ball is in motion, tennis audiences are silent. They will erupt in uncontained screams and cheers, but only at the appropriate times: when a point has been won. This means you actually need to watch. If you’re on your phone and you’re waiting for some audience cues to tell you when something exciting is happening and you should pay attention, you’re going to miss everything.

What do you see?

It’s two people hitting a ball back and forth, adversarially?

Yes.

Gladiators

It’s also very high drama. Two players enter the tennis court alone, carrying their own gear, and enter into a battle of wills, endurance, resolve, and skill, and push each other to their limits, while thousands of people cheer and boo. At the end, one slinks off, carrying their own bag into the locker room, defeated.

The other survived.

Grand slams

The tennis season is super long, it’s basically the whole year, and there are events pretty much all the time. As a tennis fan, what should you actually watch?

Watch the Grand Slams. They’re the four biggest tournaments each year. There’s the most prize money, so everybody shows up. And because they’re in this special class of their own, people pay attention to how many Grand Slams a player has won, and that’s the measure of who are the very best players.

The grand slams are:

  1. The Australian Open. It happens in January. Players are well-rested after their holiday break. It’s summer there, which is fun. They call it ā€œthe happy slamā€. It’s played on hard courts.
  2. The French Open (aka Roland-Garros). It happens in May. It’s played on clay. On the men’s side, Rafael Nadal always wins, because he is ā€œthe king of clayā€.
  3. Wimbledon. It happens in July, in London. It’s played on grass. Everyone dresses in all white. It’s a bit retro.
  4. The US Open. It happens in August in Queens, New York. It’s played on hard courts. I’ve gone to this one a few times.

If you aren’t sure where to start, just wait until the next one of these and dive in.

Where do I watch?

I mostly watch on Tennis Channel Everywhere a streaming site that I pay for. During Grand Slams, some of the matches are only streaming on ESPN, so I might temporarily subscribe to cable for a month to get those matches, too.

How do I enjoy a tournament?

At the start of a tennis tournament, there may be something like 128 players in the draw. You’re not gonna know who most of them are, but if you stick around you’ll start learning some names. In the early rounds, just pick some matches at random and watch them. Because of the way brackets get seeded, all of the top players will be paired up against bottom players and are fairly likely to advance, and it’s not that fun to watch somebody get clobbered. It’s probably going to be more fun to watch some of the middle players who got paired up against each other, who are more evenly matched. Pick someone and root for them. If they’re both boring, just switch to another match. The men’s and women’s events run at the same time, which means that there’s really quite a lot of matches on at the beginning of an event. Try to find someone you like rooting for and stick with them for the whole match.

Each round, half of the players are knocked out. It’s vicious, thrilling, efficient.

Storylines start to emerge. Someone seems unstoppable. Someone no one has heard of has made it to round three. Someone sustained an injury and we wonder how it’ll affect them in their next match. Last year’s finalists are on a collision course for a rematch in round four!

Etc.

Pick the story that resonates with you. Root for someone to go all the way. Believe that they will. See what happens for you emotionally, if anything.

Get comfortable

Sometimes tennis matches are really long. Like it’s not that weird for a match to be five or six hours long. You can sometimes take a break from a match, watch a movie, and come back to catch the end.

Women’s matches are basically always three sets, while men’s matches are sometimes three and other times five, depending on the particular tournament.

A typical set takes anywhere from 30-90 minutes depending on factors like how many games there are (a 7-6 set takes a lot longer than a 6-0 one, of course), how long the players are taking between serves, how long the rallies are, how many deuces there are (you have to win by two, so individual games can technically go on forever), and whether there are any medical timeouts.

Some tournaments use a tie breaker in the final set, while others let the final set go on indefinitely, until someone wins by two games.

There are perennial debates about whether they should downsize all five set match events to three sets, which are less grueling for the player and audience. No one can agree. Part of the sport is stamina, and some are loath to decrease that element.

You’ll know you’re a lost cause when you start wishing matches were longer.

Enjoy the crescendos

The most exciting moments in a tennis match are at the end of sets, especially sets which could decide the match. I’m reminded of that Seinfeld bit about muffin tops. Why don’t they make the whole match out of the exciting ending?

Well, in tennis, they kind of do. In a normal sport, there’s only one exciting ending. In tennis there’s as many as five, if it’s a five set match.

True, after each set they reset and start over, and that can be a little bit boring. But it can be fun to pay attention at the start of the new set. The person who just lost the last set, do they seem defeated or do they seem pissed off and full of resolve? The person who just won, can they keep up the momentum, or are they acting like they’re just happy they were able to win one and they can go home now?

Are we on serve?

One thing I didn’t realize when I first started watching tennis is that for each game, one of the players is ā€œsupposedā€ to win, and that’s the player who’s serving. In each game, only one player is serving, and they alternate. If the serving player wins each game, you will hear the commentator say ā€œthey are on serveā€. When the set is on serve, the score will move like this:

  • 0-0
  • 1-0
  • 1-1
  • 2-1
  • 2-2
  • 3-2
  • 3-3
  • 4-3
  • 4-4
  • 5-4
  • 5-5
  • 6-6
  • And then they’ll play a special tiebreaker game to determine who wins the set

If that’s what’s happening, it means that the players are well-matched, and you’re building to that dizzying crescendo of the set tiebreaker.

If the serving player loses a game, that’s called ā€œgetting brokenā€, and it puts them on a course toward losing the set. We can imagine a sequence like this:

  • 0-0
  • 1-0
  • 2-0 (a break of service!)
  • 3-0
  • 3-1
  • 4-1
  • 4-2
  • 5-2
  • 5-3
  • 6-3

In that sequence, there is only one break, but the first player will take the set 6-3. In a set like this, it’s pretty clear after the second game which player is on course to win the set. That can still be very exciting, though, because it puts a lot of pressure on the losing player to right the ship. Every single game they serve, they must win and they need to ā€œbreak backā€ to ā€œget back on serveā€. And even if they do that, which is not easy, then they still need to break again to get ahead, or try their luck in a tiebreaker.

If a player goes up by two breaks, there’s not a ton of suspense. It’s so hard to come back from being down two breaks. So… when someone does come back from two breaks, you would not believe how exciting it is. When you, as the spectator, have given up on the player, but the player has not given up, it teaches you something.

Who’s your favorite chair umpire?

The chair umpire sits in a big chair and directs the flow of the match. You’ll hear their voice throughout the match, doing things like:

  • impassively declaring the score of the current game
  • stating that a serve hit the net, and the serving player will need to serve again
  • acknowledging when a player has challenged a call (e.g. the ball was called out but they really think it was in)
  • issuing warnings for taking too long to serve or cursing
  • acknowledging that a player is taking a medical time out

The tours have a stable of chair umpires who they re-use all the time. The die-hard fans know their names and have favorites. Some of them have legendary voices. There’s one guy who says ā€œdeuceā€ with such abundant gravitas that you almost wonder if he’s joking.

I don’t know any of their names, but I think I’ll get there.

Watch out for the fist pumps

The universal tennis celebration is the fist pump. If someone wins a long rally they might let out a furtive little fist pump If someone wins a set, you know that fist is pumping above their head. Absolutely nobody pumps their fist better than Rafael Nadal. There are YouTube compilations. It’s a thrill. I encourage you to adopt this habit in your day-to-day life.

Another common celebration is to scream something like:

  1. ā€œCome on!ā€
  2. ā€œVamos!ā€
  3. ā€œLet’s go!ā€

These are all also great, the more guttural the better.

ā€œNew balls, pleaseā€

During a match, there’s a finite set of balls that they’re playing with. You’ll notice that there are a number of ā€œball kidsā€ on court. After each point, a ball kid will scurry after the ball and grab it, then get back into position. At the next opportunity, when the next point ends, they’ll roll that ball down the court in the direction of the player who’s serving. At that end, there are two dedicated ball kids who are accumulating all of the balls, so that they’re ready to feed them to the serving player.

The players really wallop these balls, and they pretty quickly lose their bounciness. After every X games, the chair umpire will say ā€œnew balls, pleaseā€, at which point they replace the dead ones with some fresh ones. The commentators will often act like this has a big impact on the vibe of the gameplay but I honestly have never noticed this.

Geography

It really matters where on the court the player is standing. Some players will stand way behind the baseline. This can be great: if their opponent hits the ball all the way to the left or all the way to the right, they have more time to get to it. This can be bad: their opponent can hit a ā€œdrop shotā€; if they pop the ball just over the net, there’s no way to make it in time.

Some players like to work their way toward the net. This can be great: from the net, you can smash the ball into the ground, sending it flying out of your opponent’s reach. This can be bad: your opponent can make a ā€œpassing shotā€ where they send the ball flying past you, and because you’re so close to them, you need to have lightning reflexes to actually return it; or, they can make a ā€œlobā€ where they hit the ball in a high arc over your head, forcing you to try and chase it down, staggering backwards, eyes peering into the sun.

Nothing makes me happier than watching a beautiful lob float over someone’s head and drop right on the edge of the baseline.

Running ragged

It can be instructive to pay attention to which player is running more. You’ll often see one player standing stock still in the middle of the court around the baseline, hitting the ball left, then right, then left, then right, forcing their opponent to run back and forth. Which one would you rather be?

If you’re just looking to get a quick sense of which player is ā€œin controlā€, pay attention to who is moving more.

Second serves

There are very specific rules for what makes a valid serve. If your serve isn’t valid, you get to try again. If your second serve isn’t valid, you just lose the point, which is called ā€œdouble faultingā€.

That’s all clear enough, but what I didn’t realize until I started watching tennis is that this system incentivizes players to serve in a particular way. Basically: on your first serve, players take more risk and on second serves they take less risk. A riskier serve is one where you’re hitting it harder, or aiming for the edges of the service box, making it more likely that you wham the ball right into the net, or out of bounds. Those riskier serves are going to be harder to return, so you’re more likely to win that point if you get that sweet serve in. And if you don’t, who cares, you just get to try again. The second serve has higher stakes, because you can actually lose the point if you mess up twice. So a typical ā€œsecond serveā€ is much slower, and it’s aiming for right square in the middle of the service box. These are going to be much easier to return, but at least you didn’t double fault.

Some players make the calculated choice to always go all out and do ā€œtwo first servesā€. With this strategy, they will double fault sometimes, but they’ll never give their opponent something easy to work with. If they’re consistent enough, that can be a smart calculation. Players with very consistent, aggressive serves are sometimes called ā€œservebotsā€.

If one player is making more of their first serves in than the other player, that’s a big advantage, and something worth paying attention to.

Tennis twitter

One of the best parts of following tennis is tennis twitter. I have a list with ~75 people on it that I will check in on during tournaments to know if there’s an exciting match I should be watching or a storyline brewing I should be aware of.

Some of the active/informative/fun ones I follow, if you want to bootstrap your own tennis twitter:

Other media

If you want more tennis media to stay up-to-date, I recommend these:

Wrap up

In conclusion, tennis is great. It’s also fun to play it. But that’s maybe another post.

Making FactoryBot.lint verbose

February 10, 2021

Here’s a quick blog post about a specific thing (making FactoryBot.lint more verbose) but actually, secretly, about a more general thing (taking advantage of Ruby’s flexibility to bend the universe to your will). Let’s start with the specific thing and then come back around to the general thing.

If you use Rails, there’s a good chance you use FactoryBot to help you write your tests. The library enables you to define ā€œfactoriesā€ for the models in your system with sensible default values.

FactoryBot has a built-in linter, which you can run as part of your CI build. It will try to identify any factory definitions which are faulty.

At work, we run this as a Circle CI job. It almost always passes, but every now and then it catches something, so we keep it around.

Recently, it started failing occasionally, but not because of anything wrong with our factories. Instead, it was failing because it was just… slow. Circle CI is happy to run things like this for you, but it gets antsy when something is running for a while and printing no output. Is it stuck? Is it going to run forever? If something is running for 10 minutes with no output, Circle CI just kills it.

Our factory linter apparently takes ten minutes now, we learned.

So, what to do about that?

Per that support article, one option is to just bump up the timeout. That’s easy enough. We could tell Circle CI to wait 15 minutes. In a year or two, maybe we’ll need to bump it up again, assuming such pedestrian concerns continue to dog us then, while we drive around in our flying cars.

Another option would be to just stop running it. It’s useful-ish but not essential. That’s easy enough.

Another option would be to configure the linter to print verbose output while it’s running. If we could do that, then we’d get multiple benefits: first of all, Circle CI would be satisfied that it is not stuck, and that it is making progress, and that it might eventually finish, even if it takes more than ten minutes; but also, having some output might be interesting and useful, no? Hmm. I pulled up the FactoryBot docs and saw an option verbose: true, but it didn’t seem to be what I wanted:

Verbose linting will include full backtraces for each error, which can be helpful for debugging

I want it to print output even when there are no errors. I didn’t see anything like that.

Imagine a ruby file with this stuff in it:

require 'factory_bot'

class Dog
  attr_accessor :name

  def save!
    sleep 4
    true
  end
end

class Band
  attr_accessor :name

  def save!
    sleep 4
    true
  end
end

FactoryBot.define do
  factory :dog, class: Dog do
    name { 'Oiva' }
  end

  factory :band, class: Band do
    name { 'Bear Vs. Shark' }
    albums { 2 }
  end
end

factories = FactoryBot.factories
FactoryBot.lint(factories)

There’s actually a bug in our Band factory: it references an attribute called albums which does not exist in our model code. The linter will catch this.

Looking at that last line, it looks like we just pass in the list of factories, and then presumably it will loop over that list and check them one-by-one.

Looping over things is a really common thing in Ruby. Anything that you can loop over is considered ā€œenumerableā€. Arrays are enumerable. Hashes are enumerable. When you query a database and get back some number of rows, those are enumerable.

A list of factories is enumerable. Hmm.

Let’s try writing our own enumerable class, to wrap our list of factories. We’ll call it ChattyList. It’ll be a list, but when you loop over it, it’ll chatter away about each item as they go by.

In general, if you’re calling a method and passing in one enumerable thing, it would also be fine to pass in some other enumerable thing. It’s just going to call each on it, or reduce, or something like that from the Enumerable module.

class ChattyList
  include Enumerable

  def initialize(items, before_message:, after_message:, logger:)
    @items = items
    @before_message = before_message
    @after_message = after_message
    @logger = logger
  end

  def each
    @items.each do |item|
      @logger.info @before_message.call(item)
      yield item
      @logger.info @after_message.call(item)
    end
  end
end

factories = ChattyList.new(
  FactoryBot.factories,
  logger: Logger.new($stdout),
  before_message: -> (factory) { "Linting #{factory.name}" },
  after_message: -> (factory) { "Linted #{factory.name}" },
)

FactoryBot.lint(factories)

When I run the script, the output looks like this:

I, [2021-02-10T22:43:03.676359 #57462]  INFO -- : Linting dog
I, [2021-02-10T22:43:07.678616 #57462]  INFO -- : Linted dog
I, [2021-02-10T22:43:07.678712 #57462]  INFO -- : Linting band
I, [2021-02-10T22:43:07.679373 #57462]  INFO -- : Linted band
Traceback (most recent call last):
        2: from app.rb:59:in `<main>'
        1: from /Users/max.jacobson/.gem/ruby/2.6.6/gems/factory_bot-6.1.0/lib/factory_bot.rb:70:in `lint'
/Users/max.jacobson/.gem/ruby/2.6.6/gems/factory_bot-6.1.0/lib/factory_bot/linter.rb:13:in `lint!': The following factories are invalid: (FactoryBot::InvalidFactoryError)

* band - undefined method `albums=' for #<Band:0x00007ff7b9022890 @name="Bear Vs. Shark"> (NoMethodError)

Nice! As the linter chugs thru the factories, it prints out its progress. With this, Circle CI will see that progress is happening and won’t decide to kill the job. This option wasn’t offered by the library, but that doesn’t have to stop us. Isn’t that fun?

By the way: that might be a good option to add to FactoryBot! Feel free, if you’re reading this, to take that idea.

Hardscrabble #2

February 8, 2021

Note: this was originally posted on a Substack newsletter that I abandoned after two issues. Here’s the second one, converted to a regular blog post.

Hey! Thanks to everyone who read Hardscrabble #1 in July. I have to acknowledge: I said it would be a weekly thing and then let it sit for like seven months without sending a second email. I think I’ve learned some lessons about expectations-setting, because this introduction is about to end.

Today, as usual, I have a story from my life, an observation from work, a cultural recommendation, and some hyperlinks. Hope you’re taking care of yourself.

A story from my life

Will at a body building competition

In July last year, I called up a bodybuilder named Will, who I follow on Instagram, and chatted for about an hour. He was at his day job, where he does graphic design for a newspaper. A few days earlier, he had competed in his first event, The Missouri State Championships, and won the novice division. What was it like training and competing for that during a pandemic, I wondered.

I also had a bit of an ulterior motive. Will and I crossed paths on the internet almost ten years ago, and became Internet Friends. We shared a common interest: Apple gadgets, and the loose network of bloggers and podcasters who talked about them. Both of us had set up personal blogs modeled after Daring Fireball, which meant that they were mostly short posts linking to other blogs with our commentary tacked on. This was the thing to do at the time, for a particular kind of nerd, resulting in a shaggy, growing constellation of blogs which kept my Google Reader account full of takes each day.

Will and I gradually fell out of touch as we outgrew our shared interest, but we still followed each other on social media. I’m sure you have friendships like this, where you don’t talk to each other, but you sporadically see each other’s updates, when the algorithms happen to allow it. People’s lives move in fast forward when you aren’t paying attention to them.

Will physically transformed in a pretty dramatic way. I wondered whether I had transformed, too, from his perspective. I spent those years focusing on establishing a career in tech. Like his thing, it required a lot of dedication. Is that the same?

I’ll share a few excerpts from our conversation, lightly edited for clarity.


Will

Most of the people who follow me [on Instagram], up until recently, weren’t bodybuilding or fitness people, they were just like, normal people, so they get a peek into this weird, crazy lifestyle.

Hardscrabble

Yeah, that’s sort of where my fascination came from. There’s this feeling of like, could I do that? If I actually dedicated, you know, five-plus years —

Will

Literally, anybody can.I feel like with this sport, you can super nerd out in it. There’s science behind the training, science behind all the nutrition. You can really nerd out on every single detail. You just get lost in YouTube videos about proper training, proper diets, why you eat certain foods, what is effective. Like, bro science versus real science. Like, bro science is kind of what works and what people have done. Real science says this, but bro science says ā€œYeah, but if you do this, it works.ā€

Hardscrabble

Wait, what are you saying?

Will

You can’t always listen to the science, sometimes you’ve got to listen to your body. ā€œFor my body, we tried it the science way, and it didn’t really work. I’m gonna try it the way this huge bodybuilder dude told me he did it.ā€


To be clear, I don’t personally want to become a bodybuilder. But I like believing that I could, if I wanted to, according to someone who’s done it. Transformative change usually feels impossible, but it happens all the time. It just requires commitment:

Will

It’s such a weird lifestyle. No joke. You’ve got to be partially insane, obsessed to want to do this. It’s fitness, but to such an extreme that, to a normal person, it’s hard to understand. My mom is like, ā€œWhy are you doing this?ā€ Because I’m, like, suffering while they’re all enjoying this great food. They came to visit when I was two weeks away from the competition. They’re having a birthday party, celebrating, pizzas, I’m eating these boring chicken and rice and broccoli meals. And it’s like ā€œAh, it’ll be worth itā€¦ā€œ

And it takes time:

Will

I set out to do this two years ago. The goal was to compete in a competition, and then after after a year it was, I want to win one. I have no problem waiting. That’s the thing about bodybuilding, is it takes a long time. When I first started, I knew that in ten years I would probably be where I want to be. That’s how long this thing takes. And I’m six years into it. I’m like halfway from my original ten year idea. I have no problem, if I want to do this, but it might take two years. And then a year later, it’s ā€œin a year, I think I’ll be readyā€. And even now, I probably could’ve waited another year. But I was just itching to do it.

Hardscrabble

I think that’s what a lot of people struggle with, with fitness, is the long term nature of it. This is something that I can speak to from experience, the fact that it takes time is partly what gives you that sense of accomplishment, but it’s also what can be very frustrating about it if you don’t have that perspective that you expect it to take that long.

Will

Yeah. I always thought, five years is going to pass. I’m going to be forty eventually. So I can either do this, and then at forty I’ll be really happy with where I’m at or I can not do it and I’ll be at the same exact place that I’m at.


The big question, of course, is: why?

Will

At the end of the day, if you did everything perfect, it makes you feel superhuman. It’s like, wow, I was able to mentally make my body do things it didn’t want to do, turn down things it wanted, only eat the foods I was supposed to eat, and do everything you’re supposed to do. And you do that for weeks on end, and it makes you feel like you have incredible control over yourself. It makes you feel powerful.


For Will, the milestone he was working toward was his first competition. Perhaps you already know what happens at a body building competition, but I didn’t. I asked if it’s like, who can lift the most?

Will

No, that would be a powerlifting competition. There’s nothing physical. You’re judged on your muscular size, symmetry and proportion, and then ā€œconditioningā€ which is how lean you are. Your hamstrings and your glute muscles only start to show when you’re really, really, really lean. They judge everybody standing next to each other, and then you have a couple hour break, and we all come back, and then we did our little routine to music.

The routine is about a minute long. In it, Will cycles through almost twenty poses, flexing various muscles, wearing nothing but his glasses and some bikini bottoms (he graciously credits Kitty’s Bikinis in the caption). I think I’d feel a bit exposed, but he looks very comfortable, like it’s the most natural thing in the world to be doing. Like dance, it has both movement and stillness. Will tells me his posing role model is Terrence Ruffin whose work is high art. I have to agree.

Here’s how Will felt onstage:

Will

My heart was racing onstage. I look like a deer in headlights in some of my pictures because I was just so nervous. You want to be able to flex every muscle in your body, but have a completely relaxed face, smiling, looking great, and I just didn’t have that stage confidence for my first time. I was just, heart pounding, trying to listen to my coach who’s yelling at me, telling me what to do.

Hardscrabble

And you’re wearing glasses.

Will

I hate contacts. And I feel like that’s just part of my look. I wasn’t even sure if you were allowed to wear glasses onstage. I asked my coach, _are they going to make me take these off?

Will was competing in the novice division, which is anyone who has never competed before. He won. He also competed in the open, and came in second. Why not first?

Will

He just had more muscles, so he won. I just have another year or two and then I’ll be able to compete with people. Progress is measured in years.

It’s heartening to me that bodybuilders are also self-conscious about how they look. We talked a bit about Instagram:

Will

I had quite a few unfollow. Which I don’t mind. I’m posting pics half naked, so if you don’t want to randomly see that on your feed, like people from high school, they’re not into it.

Hardscrabble

One of the reasons I can imagine a guy from high school might unfollow is maybe a bit of homophobia, like ā€œUh oh, not for me! I can’t look at that!ā€

Will

Maybe? Maybe. I think it’s more like, they think I’m way too into myself. If I post a crazy picture I try to have a funny caption, or poke fun at myself. But I think people can just be like, ā€œOh, he’s so into himself.ā€ When really, I post a picture, and I’m like, ā€œEhh, I wish this was like this, or this is no good, this picture is terrible.ā€

He tells me about the process of taking a bodybuilding photo for Instagram, most of which are taken by his girlfriend.

Will

It takes hundreds of tries to get the right one. I’m making a dumb face or, to get the portrait mode popping just right, all the things. We’ll take like ten different things that are the same exact pose and it’s just, ā€œAll right, let’s try it again.ā€ And if you watch that, it’s like, ā€œWho are these losers?ā€


It was a good conversation. We covered a lot more, but honestly it’s kind of hard to transcribe and summarize interviews, and I feel like you get the gist. I’ll leave you with this.

Finally, I asked Will about the tech blogs. Do you remember all those characters from that scene? Do you remember that one anonymous blogger who just razzed everybody? Who do you think that was?

Will

I still read Daring Fireball, and that is the only one. I don’t listen to any of the podcasts.

An observation from work

I wrote a few blog posts about coding in the last few weeks:

  1. A little trick that makes using ripgrep in visual studio code nicer
  2. sindresorhus/pure is such a good zsh prompt
  3. heredocs in ruby

A cultural recommendation

In December, I wrote a blog post all about Zoey’s Extraordinary Playlist, the NBC musical dramedy that just started up its second season. I really, really love it, to a corny extent.

Some hyperlinks

  • Thanks Will for doing the interview. Make sure to follow his very entertaining instagram: @kujawawa.
  • My friend Alex Liu, a product manager at Verily Health, launched a newsletter called Refactored Health about ā€œdigital health strategyā€. Seeing his announcement also inspired me to try writing this, and I regret not mentioning that in the list last week. I’m already learning a lot about our modern healthcare industry from his first few posts.
  • Tom Scocca argues against using brackets to clean up quotations when quoting somebody. Too late, Tom! But I’ll keep it in mind next time.
  • Giri Nathan’s writing about tennis, such as this coverage of Denis Shapovalov’s match from this morning, which mostly focuses on a tantrum the Canadian threw between games, is so fun to read.

heredocs in ruby

February 3, 2021

I’ve recently been writing a lot of heredocs in Ruby. We have to talk about it.

what is the deal with heredocs?

It’s one of the ways to make a string. It looks like this:

def help
  <<TEXT
Help.
I need somebody
TEXT
end

help # => "Help.\nI need somebody\n"

The idea is that you have some all caps label on the first line (TEXT in that example), and then Ruby will look at the next line as the start of the string, and keep going until it sees that label again, and then the string is over.

It’s pretty similar to just using quotation marks like usual:

def help
  "Help.
I need somebody
"
end

help # => "Help.\nI need somebody\n"

One nice thing about the heredoc syntax is that you can use quotation marks in the middle of the string, and you don’t need to worry that you’re accidentally going to close the string.

That’s a pretty standard one, but there are a bunch of variations on the theme.

For example, this one is more common in my experience:

def help
  <<-TEXT
Help.
I need somebody
  TEXT
end

help # => "Help.\nI need somebody\n"

It looks a little nicer to indent the closing TEXT at the same level as the starting one, but that’s not allowed with standard heredocs. If you want to do that, you need to start the heredoc with <<- instead of <<.

It would look even nicer if you could indent the text of the string itself. Unfortunately, if you do that, it affects the actual value of the string:

def help
  <<-TEXT
    Help.
    I need somebody
  TEXT
end

help # => "    Help.\n    I need somebody\n"

No worries – they thought of that. You can use the ā€œsquiggly heredocā€ syntax, which lets you write it like that without actually affecting the value of the string:

def help
  <<~TEXT
    Help.
    I need somebody
  TEXT
end

help # => "Help.\nI need somebody\n"

Most of the time, you should use a squiggly heredoc.

There is one last variation, which I’ve never seen in production code, but which I’ll share for completeness’s sake. This is the single-quote heredoc:

def help
  <<~'TEXT'
    Help.
    I need #{somebody}
  TEXT
end

help # => "Help.\nI need \#{somebody}\n"

When you put single quotes around TEXT – our heredoc delimiter in these examples – Ruby will treat the string like a single-quoted string rather than a double-quoted string. You know how in Ruby, if you want to use interpolation, you need to use double quotes?

"#{1 + 1}" # => "2"
'#{1 + 1}' # => "\#{1 + 1}"

Well, someday you’ll want to create a heredoc which behaves like a single quoted string (I don’t know why, to be honest) and you’ll be glad that you can.

why have I been writing so many heredocs recently?

At work, we use Rubocop to format our code. One of its rules, Layout/LineLength, checks that your lines aren’t longer than 120 characters. I think it’s a pretty good rule, and I’m gradually updating the existing code to follow it.

For the most part, it’s pretty straight-forward. Maybe you have a line that looks like:

foo(:a, :b, :c)

And you change it to

foo(
  :a,
  :b,
  :c,
)

Great, now it’s growing vertically instead of horizontally.

But what about lines that look like:

Rails.logger.info "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."

What do you do with that?

Let’s throw a heredoc on it:

Rails.logger.info <<~MSG.chomp
  Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
MSG

It’s still quite long, but the Rubocop rule has a loophole: heredocs are fine. You can disable this loophole via configuration, but I don’t want to, I like it. As a reader, I know that when I’m looking at a heredoc, the whole thing is a string; even if part of it’s off screen, I’m not missing much, it’s just more string.

If we wanted to disable the loophole, we might write that as:

Rails.logger.info <<~MSG.squish
  Lorem ipsum dolor sit amet,
  consectetur adipiscing elit,
  sed do eiusmod tempor incididunt
  ut labore et dolore magna aliqua.
  Ut enim ad minim veniam, quis
  nostrud exercitation ullamco
  laboris nisi ut aliquip ex ea
  commodo consequat. Duis aute
  irure dolor in reprehenderit in
  voluptate velit esse cillum dolore
  eu fugiat nulla pariatur. Excepteur
  sint occaecat cupidatat non proident,
  sunt in culpa qui officia deserunt
  mollit anim id est laborum.
MSG

That uses the String#squish method in Rails, which squishes a multi-line string onto one line. Is that better? That’s between you and your God. I can go either way.

One unexpected benefit of using heredocs

Imagine this code:

def my_great_query
  "select count(*) from users"
end

my_great_query # => "select count(*) from users"

When you’re editing that code in your text editor, your editor is using syntax highlighting to help you. Maybe it’s turning all of the keywords orange, or all of the method names blue. This can help your eyes to scan thru the code, and can help alert you to syntax errors. But that string on the second line is just a string, and it looks like all of the other string literals. Your editor does not know that it is a fragment of SQL, and that it can apply its SQL syntax highlighting to the contents of that string. How could it know that?

Well, imagine if you wrote it like this instead:

def my_great_query
  <<~SQL.chomp
    select count(*) from users
  SQL
end

my_great_query # => "select count(*) from users"

Now your editor has a context clue it can use, and perhaps it will elect to apply SQL highlighting to that string. VS Code, for one, does. I only use VS Code sometimes, but it’s things like this that give me a little pop of delight and make me want to make it more of a habit.

some of the quirks of using heredocs

One thing that really must be said before putting a bow on this blog post is that heredocs are kind of … weird. Like, what if you want to call a method on a heredoc, like to reverse it? It kind of feels like you should put that .reverse all the way at the end, like you would for a normal string:

# this is invalid
def help
  <<-TEXT
    Help.
    I need somebody
  TEXT.reverse
end

help

Why is this invalid? Well, remember what I said at the beginning of this blog post

Ruby will look at the next line as the start of the string, and keep going until it sees that label again, and then the string is over

(Yes I’m quoting this blog post in this blog post. I’m pretty sure that’s allowed.)

I could have been more clear there but I didn’t want to be so clear that it was confusing: the string ends when Ruby sees a line that has that label and nothing else. If it sees TEXT.reverse, that does not satisfy that rule.

So you need to write it like:

def help
  <<-TEXT.reverse
    Help.
    I need somebody
  TEXT
end

help # => "\nydobemos deen I    \n.pleH    "

One last quirk, via my colleague Ian. What if you want to start two heredocs on the same line? You probably shouldn’t, but it is possible:

def help
  [<<~TEXT, <<~SQL]
    Help.
    I need somebody.
  TEXT
    select * from lyrics
  SQL
end

help # => ["Help.\nI need somebody.\n", "select * from lyrics\n"]

Whoa.

sindresorhus/pure is such a good zsh prompt

February 2, 2021

Given how much time I spend in a terminal, typing things and hitting enter, I think it’s a good idea to keep the vibe in my shell nice. For the first like seven years of my coding career, I felt it was important that I design and maintain my own prompt. There’s evidence of this on my blog and in my dotfiles repo:

  • In December 2012, when I used this character Ļ” (???)
  • In June 2013, when I used a little Ruby script to make it print a random emoji each time
  • In June 2015, when I wrote a shell script to display some git information about the current repository
  • In June 2016, when I kept the random emoji, but rewrote the script in Rust
  • In December 2017, when I finally got rid of the emoji, because I was using Linux at work and couldn’t figure out how to display emoji in my terminal emulator
  • In January 2019, when I rewrote the git shell script in Rust, which I thought would make it faster, but actually made it slower, but by then I was too stubborn and just kept it

On some level, I felt like my shell prompt was an avenue for self expression. I took some pride in that. No offense if you don’t care about yours, it’s not a judgment thing. I’m just trying to establish some stakes here.

I found myself annoyed by how long my prompt took to render, especially when working in large git repositories. I spent some time optimizing it. I disabled some of the functionality; for example, I updated the prompt to display my current git branch, but ripped out the ā€œdirty checkingā€ which changes the color of the branch when there are uncommitted changes. I missed that functionality and brought it back.

Eventually, just as an idea, I decided to see what was out there. I googled around a little bit, and found sindresorhus/pure, which bills itself as a ā€œPretty, minimal and fast ZSH promptā€. That’s basically what I want.

I installed it a few months ago, and… well, shit. It’s very good.

Here are a few things that are great about it.

When your last command failed, the prompt turns red.

When your last command took a while, the prompt automatically displays how long it took. You don’t need to have the thought ā€œhm, that felt slow, was that slow? Should I re-run it with time?ā€

The prompt tells you when your branch is behind the remote, and you should might want to pull. That means it automatically fetches, so that it can know that. At first that seemed kind of crazy, that rendering my prompt would have side effects on my repository (ā€œHow dare you, prompt?ā€ was my gut reaction), but I’ve come to really appreciate it. It’s the best kind of automation, in that it becomes just one less thing I need to worry about. That requires building some trust, but it did.

It renders super fast because it does a lot of its work asynchronously, like checking the git status. It uses this script called mafredri/zsh-async to do that. The effect is pretty novel: the prompt renders right away, and then (sometimes) it changes a half second later. It’s a lot like an asynchronous request in a website, which fetches data and displays it when it’s ready. I skimmed the readme of that repo and I have no idea how the hell it works, but I basically don’t care, I’m happy to let it be some magic.

There’s only one thing that I miss from my days of customization. Before I used pure, my prompt looked like:

hardscrabble.net main*

All on one line. The first bit is the name of the folder. Then the name of the branch – printed in red, and with an asterisk, to indicate that there are uncommitted changes.

Imagine that you were at that prompt, and then you ran cd _posts. What should it display then? It would be pretty intuitive if it displayed this:

_posts main*

In my days of customization, though, my prompt would have displayed:

hardscrabble.net/_posts main*

The idea being that I wanted to know two pieces of information:

  1. what repo am I in?
  2. what path am I at, in that repo?

To achieve that, I needed to write some clumsy, but workable Rust.

By contrast, pure displays the absolute path to the working directory:

~/src/gh/maxjacobson/hardscrabble.net/_posts main*
āÆ

Which is…fine.

In summary, be like me: get over yourself and use pure.

A little trick that makes using ripgrep in visual studio code nicer

January 24, 2021

Hello friends, happy new year. I’m writing now to share a quick tech tip.

One of my most-used command line utilities is rg aka ripgrep, which I use to search thru a codebase and print out results. Using it looks like this:

$ rg 'class User\b' activemodel
activemodel/lib/active_model/secure_password.rb
42:      #   class User < ActiveRecord::Base
111:        #   class User < ActiveRecord::Base

activemodel/lib/active_model/serialization.rb
97:    #   class User

activemodel/lib/active_model/attribute_methods.rb
8:  #   class User < ActiveRecord::Base

activemodel/test/models/user.rb
3:class User

activemodel/test/cases/serialization_test.rb
7:  class User

(Thank you to rails/rails for being my model repo for this blog post).

This output is nice:

  • Very readable
  • When it prints in your terminal, it uses color to show you which part of the line matched your search
  • It’s clear which matches go with which files, even when some files have multiple matches

We can contrast with the similar grep invocation:

$ grep --line-number --color=always --recursive 'class User\b' activemodel
activemodel/test/cases/serialization_test.rb:7:  class User
activemodel/test/models/user.rb:3:class User
activemodel/lib/active_model/serialization.rb:97:    #   class User
activemodel/lib/active_model/attribute_methods.rb:8:  #   class User < ActiveRecord::Base
activemodel/lib/active_model/secure_password.rb:42:      #   class User < ActiveRecord::Base
activemodel/lib/active_model/secure_password.rb:111:        #   class User < ActiveRecord::Base

A few UX things of note:

  1. you need to tell it to search recursively in the directory
  2. you need to opt in to color
  3. you need to opt in to line numbers
  4. the output is very compact which makes it well-suited for scripting but less pleasant for a human to scan through

Additionally: grep is not git-aware, so it will look at every file, even if it is listed in your .gitignore.

So, anyway, I tend to prefer using ripgrep.

My workflow is generally confined to a terminal, with vim and tmux being the key players. But not always. Sometimes I pop open Visual Studio Code, if I’m doing something which will benefit from using the mouse a lot.

VS Code has a nice feature where you can run a terminal right inside the app, under your editor. Of course, VS Code has a nice code search feature built right in, but my muscle memory has me always opening a terminal and using ripgrep to search for something.

This is where things get interesting. VS Code also has a nice feature where you can ⌘-click in the terminal on a file path, and it will open that file path in a VS Code tab. That pairs really well with ripgrep: often I’m searching the repo because I want to open up those files and make some tweaks. If that file path is formatted with the line number, like path/to/file.rb:45, then ⌘-clicking on it will open the file and jump to that line. If it looks like path/to/file.rb:45:17, it jumps to the line and column.

That’s very nice. But, sadly, does not work well with the default ripgrep output format, which has the line number on a separate line from the file path.

Now, ripgrep has a whole bunch of options for customizing its behavior and output. By using these options, I can make it print output in a format that works well with VS Code’s ⌘-click feature:

$ rg --no-heading --column 'class User\b' activemodel
activemodel/lib/active_model/secure_password.rb:42:11:      #   class User < ActiveRecord::Base
activemodel/lib/active_model/secure_password.rb:111:13:        #   class User < ActiveRecord::Base
activemodel/lib/active_model/attribute_methods.rb:8:7:  #   class User < ActiveRecord::Base
activemodel/lib/active_model/serialization.rb:97:9:    #   class User
activemodel/test/models/user.rb:3:1:class User
activemodel/test/cases/serialization_test.rb:7:3:  class User

I decided that I would like rg to behave like this when I invoke it inside of a VS Code terminal, but otherwise print output in its normal way. I did not want to have to remember to use those flags. I’ve learned that when a solution depends on me remembering to do something, it’s not going to be a successful solution.

I added this to my shell intialization:

if [[ "$TERM_PROGRAM" == 'vscode' ]]; then
  alias 'rg'='rg --smart-case --hidden --no-heading --column'
else
  alias 'rg'='rg --smart-case --hidden'
fi

Now I can use rg anywhere I want, and it behaves how I want. Nice.

It’s still pretty compact and not that human-scannable. I think I’d like it even better if the output looked like this:

activemodel/lib/active_model/secure_password.rb:42:11
#   class User < ActiveRecord::Base

activemodel/lib/active_model/secure_password.rb:111:13
#   class User < ActiveRecord::Base

activemodel/lib/active_model/attribute_methods.rb:8:7
#   class User < ActiveRecord::Base

activemodel/lib/active_model/serialization.rb:97:9
#   class User

activemodel/test/models/user.rb:3:1
class User

activemodel/test/cases/serialization_test.rb:7:3
class User

But I couldn’t figure out how to make it do that. Life is full of compromises.

Zoey's Extraordinary Playlist

December 19, 2020

I was indoors a lot this year, and I watched a lot of TV, and I think my favorite thing I watched was Zoey’s Extraordinary Playlist, the NBC musical dramedy that aired from January to May this year. It has a kind of silly high concept that I imagine will immediately turn off a lot of people: Zoey, an introverted software engineer1 in San Francisco who’s not particularly in touch with her own emotions or the ones of those around her, suddenly starts having visions of the people around her breaking out into elaborate song and dance numbers, and in the process she gains access to exactly the emotion they’re experiencing in that moment.

Hm, okay. And it’s good? Yes, thank you for asking, let me tell you why.

When we meet Zoey (Jane Levy), she’s bottled up. In the first episode, she’s angling to transition from an individual contributor to a manager at her company. Her boss, Joan (Lauren Graham, very fun as an impatient, callous tech executive), asks her ā€œWhat makes you think you could be a good manager? Are you an effective communicator? Do you think you can get others to follow your lead? Are you comfortable being the bad guy?ā€

She answers with a joke: ā€œI’m not particularly comfortable with anything, that’s why I became a coderā€. The joke doesn’t land with Joan.

Her father Mitch (Peter Gallagher) is slowly dying. He has some neurological thing that leaves him unable to move or speak. It came on quickly within the last year. He’s still with them, but kind of not. She’s got this reservoir of feelings about it, a mix of grief about losing him, anxiety about whether she’ll get it too, fear about the condition progressing, pressure to be present while she can even while continuing to live her own life, worrying about her mother (Mary Steenburgen), resenting herself and her brother for not being helpful enough, etc, etc etc. So she mostly buries it and just does her best, like a lot of us do.

She actually gets the powers while visiting the hospital for an MRI, to check if her brain shows signs of the same neurological condition. The MRI technician puts a playlist on shuffle for her called ā€œAwesome MRI Mixā€. The first song is ā€œIt’s The End of the World As We Know It (And I feel Fine)ā€, which begins:

That’s great, it starts with an earthquake

Birds and snakes and aeroplanes

And Lenny Bruce is not afraid

Then there’s an earthquake and, the show gently suggests, somehow the playlist and the MRI and the earthquake combine to modify her brain and grant her a music-related super power. Just go with it.

For Zoey, it does start with an earthquake, and it is the end of the world as she knows it, because now people sing to her. There’s no original songs. For the most part, it’s very mainstream pop and rock songs you’d hear on the radio. First it’s a lone middle-aged woman walking past her, singing ā€œall by myself… don’t wanna be all by myselfā€.

ā€œThat’s… sad?ā€ Zoey says, confused by the plain expression of a feeling. The woman, who wasn’t actually singing, brushes her off and keeps walking.

Then, as Zoey keeps walking, the entire city of San Francisco breaks out in an ensemble performance of ā€œHelp!ā€ by The Beatles. A forlorn man with loosened tie stares her right in the eyes and says, ā€œHelpā€. As others join in – perfectly ordinary people in ordinary clothes with nice but unremarkable singing voices – Zoey runs from their outstretched hands like she’s fleeing zombies. On the cable car, a young woman holding a baby carries on the tune, ā€œAnd now my life has changed in, oh, so many ways.ā€ A man picks up the next line, ā€œMy independence seems to vanish in the hazeā€ as his partner clings to his shoulder. Three strangers come together to tell her, ā€œEvery now and then I feel so insecureā€.

She has adult onset empathy, and it’s kind of a nightmare.

This show suggests, again and again, generously, that every single person you walk past has a rich interior life, a tender heart, whether they know how to express it or not.

I think it’s notable that the show suggests some scientific source of what is plainly a magical ability. It could have gone in a more religious direction, is what I’m saying. Did God give her this power, so that she could learn to connect with others’ emotions and learn to express her own? Well, that is what eventually happens, so sure, but the show really, to its credit, doesn’t care about investigating or explaining the origins of the power, it’s much more interested in staying grounded in the plane of human emotion and experience, and it’s struck on this device to examine those. So, it was the MRI machine with the earthquake and the playlist, what more do you need?

Later, Zoey will tell her neighbor Mo about what’s happening: ā€œSome [are singing] to me, some to themselves, almost as if they were singing what they were thinking out loud, collectively, as a people. Does that make sense?ā€

Mo (in what is probably my favorite line of the first episode) replies, ā€œNo, but I’m an open-minded person. I’m willing to roll with this.ā€

Mo (Alex Newell) plays an important role in the series. On one level, he’s important because Zoey needs to confide in someone about what’s going on so that we can have scenes where Zoey talks about what she thinks is happening and how she feels about it. But more importantly, Mo is someone farther along in the journey of self-knowledge and self-expression than Zoey. He’s gender fluid, Black, dates men, sings beautifully (even outside of fantasy sequences), likes drugs, and has a hilariously extravagant home decor style. You would be forgiven for finding him much more interesting than Zoey and wondering why he’s not the main character. He’s not just a symbol of the beauty of unbottled expression, as he does get a few turns in the spotlight that develop and round out who this character is, but he’s also that.

Zoey hasn’t just bottled up her own emotions, she’s also basically oblivious about others. She takes them at face value. She’s so out of touch with her own interior life that she doesn’t understand that other people have an interior life, either. She has a crush on a guy at work, Simon (John Clarence Stewart), who’s good looking and energetic, and she’s shocked and confused when she overhears him singing a powerfully sad song. She later tells Mo, ā€œI almost felt embarrassed listening to it.ā€

I think that gets at it really well. The reason we live in an ironic, disconnected time is that we find emotion and sincerity embarrassing. Accepting that has done us great harm. When you boil it down to the simple moral ā€œemotions are good, actuallyā€, maybe it sounds like obvious stuff. Personally, I needed to be reminded.

It’s embarrassing to look someone in the eyes and plaintively sing, ā€œI want you to want meā€. People don’t talk like that in real life, as they do in pop music, but why don’t we?

By gaining access to the pure, sincere, unfiltered emotional inner lives of others, Zoey’s walls start to erode. She starts to feel their feelings, and her own start to come out in the pull of the tide.

There’s a scene in the first episode where Maggie, Zoey’s mother, talks about the drug regimen that Mitch is on. She confesses that she’s tried his drugs: ā€œ[I’ve tried] some of them. I just – I wanted to feel what your dad was feeling. That’s all.ā€

That line stands out to me now, as I reflect on this theme of empathy. Medically, it’s probably irresponsible, but it’s emotionally valiant.

Peter Gallagher’s performance is magnetic. He’s a great actor, and he spends the vast majority of his time on screen sitting stock still on a couch, with a blank expression. He’s amazing. At all times, you feel the seriousness of his condition, but you also feel that there’s someone in there. It’s a serious feat.

There’s some obvious symbolism here: Zoey is walled off psychologically, and he’s walled off physically. She wants so badly for him to be able to express something, anything. She also needs, badly to learn how to express herself. Both seem impossible at the start of the show, but both eventually happen. This show has deep streaks of despair running through it, but it’s ultimately hopeful. It suggests that there are ways forward. Allow your heart to be nourished by Zoey’s Extraordinary Playlist.

It’s not all emotions and morals, it’s also teeming with the exuberance of pop. It’s fun when people sing and dance. The cast is dotted with a few ringers from Broadway like Skylar Astin and Andrew Leeds, and others swing by in guest roles like Bernadette Peters and RenĆ©e Elise Goldsberry. The choreography (from Mandy Moore, who worked on La La Land) is fantastic, always entertaining and often moving.

Jane Levy anchors the whole thing with a lot of emotion and neurotic energy. Perhaps because the protoganist is a software engineer, she spends a lot of time trying to figure out how her new ability ā€œworksā€, and the show has a lot of fun probing its boundaries, stretching the premise out like taffy, and hanging a lantern on the conceptual silliness while always respecting the emotional performances that are made possible by the silly concept.

This was obviously a weird, hard year. This show aired from January through May, a period of time when the world was coming to terms with a new reality, while feeling disconnected. Watching Zoey come to terms with her own situation and learning to connect gave me a lot of comfort. The first season felt like a complete story to me, but I’m nevertheless very excited to see where they go with it when it comes back in January.

But again, most of all, it’s fun when people sing and dance!!

  1. Side note: a weird number of shows that I watched this year were about tech companies, and the software engineers that work at them. Was this me missing working in an office?? I watched this, DEVS (Hulu – hypnotic), Halt and Catch Fire (AMC – series rewatch of an all-time fav), Mythic Quest: Raven’s Banquet (Apple TV+ – very funny), and Start-Up (Netflix – very charming).Ā 

Hardscrabble #1

July 12, 2020

Note: this was originally posted on a Substack newsletter that I abandoned after two issues. Here’s the first one, converted to a regular blog post.

Hey! Welcome to the first issue of the Hardscrabble newsletter. I’m Max Jacobson, a software engineer and writer from New York, filing this correspondence from Cape Cod, where I’m visiting with my parents for a few weeks. A small amount of context: ā€œHardscrabbleā€ is the name of my blog that I’ve kept over the last seven years as I’ve gotten my feet under me in tech. Before that, it was the name of a science fiction screenplay I wrote in college. Before that it was a road sign I drove past.

Each week, the newsletter will come in 3-4 segments: a story from my life, a workplace observation, a cultural recommendation, and some hyperlinks.

A story from my life

July 3, 2am. If you’d asked me, I would have told you I was trying to fall asleep, but if you’d observed me, you’d’ve seen that I was browsing TikTok. I had lowered the screen brightness to an ember’s glow, which I felt made it Good Enough Sleep Hygiene. I had a slight fever and chills and I was watching young people dancing to the same twenty second snippets of the same three songs.

(I’m 31 and I haven’t fully come to terms with not being plugged into What’s Going On Culturally, so I downloaded the young people app. I only follow people I read NYT profiles about, which I assume is also how the youths use the application. So that’s Charli D’Amelio (charming), Melissa Ong (hysterical), and Sarah Cooper (brilliant) and I’m now an expert on TikTok).

When you open the app, you land on the For You Page, which is a feed of algorithmically recommended posts. There’s no dates on them. They could be from whenever. It’s meant to be uncanny. When I open it right now, the first post is from a software engineer making jokes about what it’s like working as a software engineer, and it’s funny, and I just tapped through and watched all of her videos. OK, so it’s uncanny.

That night, the second or third video I scrolled past was of a young woman named Claira Janover introducing herself ā€œfor the non-existent number of people who want to get to know meā€. In the video, among other things, she shares that her worst quality is that she’s insecure.

I paused. Why is the algorithm showing me this?

I think it’s important to do the work of self-discovery, and the healthy version of that probably involves things like therapy, journaling, and meditation. Which, sure, I do those things. But, I can’t help but wonder: Can TikTok’s algorithm tell me something new about myself?

I tapped through to her profile. It turned out the post I’d seen was from a few weeks earlier, but she’d posted several times more recently. To my alarm, her most recent posts were all close up videos of her, tears streaking down her face, explaining that she was, in that moment, being harassed by an online, right wing mob, who had singled her out for a video she posted critiquing the phrase ā€œall lives matterā€. It’s a funny video that makes a good point, but it was being willfully misunderstood as a threat of violence, and that was enough to make her name into a trending hashtag, get her doxxed, and get her job offer rescinded.

As I soaked up this context, I got madder, and curiouser, and farther away from slumber. I switched over from TikTok to Twitter and pulled up the hashtag, which, indeed, was chock full of jackasses and creeps harassing this well-meaning, righteous 22 year old. I saw some valiant youth rallying to her defense, lobbing counterarguments back at the sock puppet accounts, patiently explaining the satirical point Janover’s video was making.

Before my interest could wane, I noticed that one of the accounts tweeting with the #ClairaJanover hashtag had a profile photo that looked familiar. It was a man with a mustache sitting at the wheel of a boat, clad in plaid, looking over his shoulder to the camera with a goony smile that seemed to say, ā€œOh hey, I didn’t see you there. I’m just driving my boat.ā€ It looked familiar because it was a photo of me, from the one time my dad let me drive his boat, when I felt super cool and posed for several photos.

At first I thought it was a glitch, and Twitter was just showing my own profile photo in place of other people’s. I can imagine how that kind of bug would slip in. And, I thought, that’s my current profile photo, so that’s what it is. I flipped over to my profile to double check. Yeah, there I am, on the boat, behind the wheel, looking straight ahead, my tongue sticking out a little as I concentrate on driving the boat. Wait no, that’s a totally different photo from that day!

I tapped thru to his profile. His name was ā€œArtie Jā€. His bio was ā€œAdventurer, cook, dad.ā€ His photo was my photo. (Do I look like an adventuresome dad? I’ll have to sit with that at some point…) Every one of his recent tweets was a reply to someone talking about Janover. If they were defending her, he would argue with them. If they were critical of her, he would agree with them. He applauded Deloitte’s decision to rescind her job offer. He crowed, ā€œall lives matterā€. He spoke about her personal biography in a way that struck me as invasive, sharing details and photos of her family. He had my face.

Screenshot of the Artie tweet

I decided to report the account to Twitter. I reported that he was impersonating me. I had to provide a photo of my driver’s license, so I got out of bed to snap a photo. ā€œImpersonationā€ wasn’t exactly right, but it was close enough. It seemed like a clear-cut example of a troll account, possibly operated by a professional in an agency like the one described in Adrian Chen’s brilliant 2015 story about Russia’s Internet Research Agency. I was aware of this phenomenon, and its potential to increase conflict and division online in ways that can spill out past the four corners of our screens, but I had always just ignored it: as a policy, I don’t argue with people online, I just post photos of trees and oddball thoughts that cross my mind; it doesn’t affect me.

But… that’s… my… face. I eagerly awaited Twitter’s reply. I slept for 11 hours two nights in a row. My fever went away. My sore throat went away. Phew. I kept pulling up Artie’s account, every few hours, expecting to see the account suspended, and instead watched him willfully antagonizing people. With my face.

Screenshot of another Artie tweet

What are the odds that I would organically stumble across a troll account with my own face? It felt like, if my face is on one troll account, it’s on two. I felt sick, but, like, because of the unsettling situation now.

Twitter got back to me saying the account didn’t violate any of their rules:

After a review, we didn’t find a clear violation that would make it necessary to remove the image you reported.

If you see content in a Tweet that might be considered sensitive, you can flag it for review directly from the Tweet. After a review, sensitive content will require people to click through a warning message before the media is displayed if it’s considered sensitive.

We appreciate you reporting this to us, and hope if you see potential violations in the future, you’ll let us know.

Can you take a minute to rate your experience and complete a short survey?

Sigh. I filled out the survey, saying I kind of felt like they hadn’t even understood the issue I had reported?

I tried to forget all about it. We watched Hamilton, which draws a contrast between one character who acts boldly, and one character who waits and waits and waits and never acts or commits to a point of view. Shamefully, I found myself relating to the latter character. I decided, during Wait For It, that I wouldn’t wait for Twitter to save me or for the episode to slip my mind. For the first time in a very, very long time, I decided to get in an argument on the internet.

I found a thread where Artie was arguing with Janover defenders, and I replied all:

ā€œArtieā€ if you insist on sliding into the mentions of well-meaning young people to spout ignorant, racist takes, please use your own face. Mine is taken.

And then I went back to watching the musical, and then I went to sleep.

Truthfully, I was a bit afraid. Afraid to speak bluntly. Afraid I was being a dork. Afraid trolls would descend on me next. Afraid to make something about me that really wasn’t about me.

But in the morning, everything was fine. Artie had sputtered a few protestations about how he wasn’t racist, or a troll, but he had changed his photo to a cartoon, and he had fessed up:

Your photo was a happy one, and I used it, but geez, if I had known… Have a good day Max.

I felt more relieved than I expected to.

So, anyway, in conclusion: the TikTok algorithm, which provided me with a days-long emotional rollercoaster, is truly uncanny. And Gen Z, which seems to be a bunch of ultra-genuine try-hards, is going to save us all.

A workplace observation

When you merge a pull request on GitHub, you can choose from three options: ā€œMerge pull requestā€; ā€œRebase and mergeā€; and ā€œSquash and mergeā€. I don’t like any of these. The missing choice I would prefer: ā€œFast forward mergeā€, which would make it possible to do a git merge --ff-only via the web. With a fast forward merge, your new commits would merge from the feature branch into the main branch much like ā€œrebase and mergeā€, but they’d have the exact same commit SHAs that they had before. And unlike ā€œMerge pull requestā€, there would be no need for a gross merge commit. There would be no need to run the tests again, because the commits would retain their commit statuses. If the commits were GPG-signed, they would retain their signatures. Doesn’t that sound nice?

A cultural recommendation

Last year’s comedy special John Mulaney & The Sack Lunch Bunch (on Netflix) was so funny and I laughed so much that it made me realize that most comedies I watch aren’t actually that funny. This week, following the happy news that they’re making two more Sack Lunch Bunch specials, I rewatched a handful of the musical numbers, such as the pitch perfect Grandma’s Boyfriend Paul:

  • My friend Vaidehi Joshi is launching a newsletter imminently, which I can’t wait to read. Vaidehi’s one of the most thoughtful software engineers and prolific writers I know, on topics like distributed systems and green tech, and her announcement is what inspired me to spend some time putting this together this weekend.
  • My friend Thomas Countz, a software engineer and advocate for privacy-focused tech, launched a tech newsletter called The Pseudocode a few months ago. I contributed a piece for the first issue, in which I reflected on what ā€œSenior Engineerā€ means to me. I hope more issues follow.
  • The idea to include a few recurring segments is inspired directly by Hmm Weekly, a newsletter that I’m perennially six months behind on, and will eventually catch up with only if they stop making them. For a few months, they published ā€œNineteen Folktales: A Seriesā€, one per week, and I gobbled them up, and have returned to one in particular, ā€œThe Discontented Butterflyā€, every few months since reading it. I don’t know if there’s any way for you, now, to go read it, but it’s in my email archives (not to gloat).
  • Another inspiration: The Studio Neat Gazette, a brief weekly update from a neat little company in Texas that makes things like Mark One, a very nice pen that I love to write with. Each week the two cofounders just share something they like, and I always enjoy reading it.
  • I got a major kick out of learning about the comedian Ziwe Fumodoh’s interview show, which she publishes on her Instagram Live. I imagine a lot of comedians are struggling with how to make jokes during the George Floyd protests, and she’s found a way in by mercilessly exploiting the tension that arises from the fear of saying the wrong thing. Definitely turn on notifications for when she goes live.
  • The best thing I read this week was this twitter thread from Ayesha Siddiqi, a writer I’ve learned so much from in the last decade, on the phrase ā€œcancel cultureā€, and what it obscures about the real power dynamics that really exist in the real world.