Hardscrabble šŸ«

By Max Jacobson

See also: the archives and an RSS feed

Sad Blocks

July 6, 2017

I wish Ruby knew when you wrote a sad block.

What is a sad block?

Itā€™s something I just made up.

Consider this code:

Candle.all.each do |candle|
  puts candle.inspect
end

Letā€™s say you run it, and you see no output. What do you conclude? Probably that there arenā€™t any candles.

Well, maybe. Or maybe Candle is implemented like this:

# candle.rb
class DatabaseResult
  def each; end
end

class Candle
  def self.all
    DatabaseResult.new
  end
end

Look, that would be weird, but itā€™s possible, and Ruby doesnā€™t do anything to help you out here, and I feel like it should.

Whatā€™s happening? Youā€™re calling the instance method #each of the DatabaseResult class. And itā€™s just not doing anything at all and doesnā€™t even know you gave it a block. Cool.

Brief digression time.

That method has an ā€œarityā€ of zero. How do I know that?

$ irb
>> require "./candle"
=> true
>> DatabaseResult.instance_method(:each).arity
=> 0

Also by looking at it.

What does it mean? It means that the method takes zero arguments.

But when we count the arity, weā€™re not considering blocks, because blocks are a special, weird kind of argument, where you can provide it or not and itā€™s kind of outside of the method signature. You can have methods that takes a block and uses it, and its arity will still be zero:

# candle2.rb
class DatabaseResult
  def initialize(values)
    @values = values
  end

  def each
    @values.each do |value|
      yield(value)
    end
  end
end

class Candle
  def self.all
    DatabaseResult.new([
      new("Geranium"),
      new("Lavender"),
    ])
  end

  def initialize(scent)
    @scent = scent
  end
end

Candle.all.each do |candle|
  puts candle.inspect
end
$ irb
>> require "./candle2"
=> true
>> DatabaseResult.instance_method(:each).arity
=> 0

Even though we use the block weā€™re given, the arity is still zero.

What about that other syntax where you explicitly put the block in the method signature, does that make it count toward the arity?

def each(&block)
  @values.each do |value|
    block.call(value)
  end
end

Iā€™ll tell you: it doesnā€™t. Even though it sort of feels like it should.

These versions of the method really require you to pass them a block, which you just have to know. If you forget to pass a block, you get a nasty error:

# candle4.rb
# ...
def each(&block)
  @values.each do |value|
    block.call(value)
  end
end
# ...

Candle.all.each
candle4.rb:9:in `block in each': undefined method `call' for nil:NilClass (NoMethodError)
        from candle4.rb:8:in `each'
        from candle4.rb:8:in `each'
        from candle4.rb:27:in `<main>'

Or in this version, an even better error:

# candle5.rb
# ...
def each
  @values.each do |value|
    yield value
  end
end
# ...

Candle.all.each
candle5.rb:9:in `block in each': no block given (yield) (LocalJumpError)
        from candle5.rb:8:in `each'
        from candle5.rb:8:in `each'
        from candle5.rb:27:in `<main>'

No block given. Local jump error. Sure. Thatā€™s Ruby trying to be helpful and I appreciate that.

Ruby helps you (by raising a helpful error) when you donā€™t provide a block, but you were supposed to. But it doesnā€™t help you when you do provide a block, and you werenā€™t supposed to.

Rubyā€™s like, yeah, sure, just provide a block wherever you want, this is a free country.

If you wanted to change this behavior in your code, and get helpful errors when your blocks are unexpectedly not invoked, you could do something like this:

class SadBlock
  def initialize(&block)
    @block = block
    @called = false
  end

  def verify
    raise 'hell' unless @called
  end

  def to_proc
    ->(*args) {
      @called = true
      @block.call(*args)
    }
  end
end

sad_block = SadBlock.new do |candle|
  puts candle.inspect
end

Candle.all.each(&sad_block)
sad_block.verify

I donā€™t think you should do this, but you could, and I kind of wish Ruby just did it automatically.

I know itā€™s an impractical request, because there are valid use-cases where you might pass a block to a method, and the method just assigns it to an instance variable without calling it, but it promises to call it later. But maybe Ruby could detect that somehow. Iā€™m just thinking out loud here.

Iā€™ve seen tests where assertions lived in blocks, and the blocks were never being called, so they werenā€™t actually asserting anything.

Iā€™ve seen configuration being done via a DSL in a block, except the block wasnā€™t being called, so the defaults were being used.

I guess what Iā€™m saying is itā€™s a little weird to me that blocks arenā€™t treated like ordinary arguments. If they were, youā€™d get an ArgumentError if you forgot to provide it or if you provided it and it wasnā€™t expected.

Thatā€™s what I want.

there are no rules in ruby

July 2, 2017

Note: Iā€™ve expanded on these ideas in a conference talk, which you can see here.


I recently learned about a feature of the Ruby programming language that has shaken me to my very core.

Consider this code:

# dog.rb
class Dog
  attr_reader :name

  def initialize(name)
    @name = name or raise ArgumentError
  end
end

def get_dog
  Dog.new("Milo")
end

thing = get_dog
if Dog === thing
  puts thing.name + " is a dog"
end

What happens when you run this code? Feel free to try.

But Iā€™ll tell you.

$ ruby dog.rb
Milo is a dog

This code seems pretty resilient to unexpected runtime errors.

Looking at the code, it seems pretty reasonable to believe:

when we have an instance of Dog, we will be able to send it the message name and get back a String

Up is up. The sky is blue. Weā€™re living in a society.

Well, ok, but we canā€™t actually assume that the value will be a String, because it doesnā€™t check that. If we change our definition of get_dog, things blow up:

def get_dog
  Dog.new(["Milo"])
end
$ ruby dog.rb
dog.rb:15:in `<main>': no implicit conversion of String into Array (TypeError)

But, OK, at least that error message is pretty good. This is user error. When we write thing.name + " is a dog", weā€™re expressing some amount of faith in ourselves that we expect a String, because values of other types donā€™t necessarily respond to a + method. This is a leap of faith that weā€™re all willing to make when we use Ruby. Other languages eliminate the need to make that leap of faith by checking types when you compile your code, but Ruby doesnā€™t do that.

And thatā€™s fine.

So maybe our expectation should be:

when we have an instance of Dog, we will be able to send it the message name and get back a truthy value

And weā€™ll just remember to provide Strings. Maybe weā€™ll write a comment indicating the expected type of the parameter.

Well, what if get_dog looked like this:

def get_dog
  dog = Dog.new("Milo")
  def dog.name
    nil
  end
  dog
end

Maybe it just casually redefined the name method for that instance. Then your program crashes like this:

$ ruby dog.rb
dog.rb:19:in `<main>': undefined method `+' for nil:NilClass (NoMethodError)

Whichā€¦ OK, whoā€™s going to write code like that? Not me and no one I work with, for sure!

But where does that leave our statement of beliefs?

when we have an instance of Dog, we will be able to send it the message name

We canā€™t even say ā€œand get back a valueā€ because what if the override raises an error?

Perhaps you see where this is goingā€¦

Well, what if get_dog looked like this?

def get_dog
  dog = Dog.new("Milo")
  dog.instance_eval('undef :name')
  dog
end
$ ruby dog.rb
dog.rb:17:in `<main>': undefined method `name' for #<Dog:0x007fecc104a870 @name="Milo"> (NoMethodError)

Which, again, lol. You can just remove methods if you want to? Sure. No one is going to write this. I know.

(By the way, hat tip to Agis on Stack Overflow for sharing this trick. I figured it was possible but didnā€™t know how.)

OK so what can we say for sure?

How about this:

when we have an instance of Dog, it will have an instance variable @name defined

Wow thatā€™s sad! How do we even check that? Maybe like this:

thing = get_dog
if Dog === thing
  puts thing.instance_variable_defined?("@name").inspect
  puts thing.instance_variable_get("@name").inspect
end
$ ruby dog.rb
true
"Milo"

OK great, have we reached the bottom?

No, because there are no rules in Ruby.

We can probably break this in many ways. Hereā€™s one:

def get_dog
  dog = Dog.new("Milo")
  dog.remove_instance_variable("@name")
  dog
end

IMO this one is a bit pedestrian. Yeah, fine, you can just remove instance variables on random objects if you want to. Of course. My spirit is already broken, this isnā€™t meaningfully worse.

So letā€™s just try to say something that we donā€™t have to take back right away:

when we have an instance of Dog, the code in the initialize method must have run

Right? That has to be true. Weā€™re living in a society, remember?

Nope:

def get_dog
  Dog.allocate
end

That results in this output:

$ ruby dog.rb
false
nil

What the hell is this?

This is the thing I mentioned at the beginning that I learned recently. When we create new objects in Ruby, we usually use the new class method. Notably, we donā€™t call the initialize instance method ourselves, although thatā€™s what we are responsible for defining. Ruby handles calling that method for us. But before Ruby can call an instance method, it needs an instance, and thatā€™s where allocate comes in. It just makes an instance of the class.

And youā€™re allowed to use it in your Ruby code, if you want to.

(Hat tip to John Crepezzi whose blog post explains this really well)

If you do, you get back a normal instance of your class in every way, except that the initialize method hasnā€™t run.

You can even call your own initialize method if you want to:

def get_dog
  dog = Dog.allocate
  dog.send(:initialize, "Milo")
  dog
end

We have to use send because initialize is private. Well, unless we change that:

class Dog
  attr_reader :name

  public def initialize(name)
    @name = name or raise ArgumentError
  end
end

def get_dog
  dog = Dog.allocate
  dog.initialize("Milo")
  dog
end

Sooooo where does that leave us?

when we have an instance of Dog, itā€™s a good dog

Basically: šŸ¤·ā€ā™‚ļø.

Thatā€™s the bottom. Thatā€™s as far as I know how to go. Maybe thereā€™s more. Please donā€™t tell me.


I want to emphasize: this is not a criticism of Ruby. Iā€™m only faux-alarmed. Ruby is a springy ball of dough. Itā€™s whatever you want it to be. All of these features are sharp knives you can use or abuse.

As Iā€™ve been learning another language which feels much less pliant, Iā€™ve started to notice things about Ruby that never occurred to me before. When I write Rust, I take some pleasure and comfort from the rigid rules. Itā€™s more possible to use words like ā€œguaranteeā€ and ā€œsafetyā€ in Rust-land.

But Ruby keeps you on your toes.

chainable shell functions

February 6, 2017

I learned a neat shell script refactoring strategy yesterday that Iā€™d like to share. First some background:

I used to use a rubygem to help me write this blog. When I wanted to create a new post called ā€œchainable shell functionsā€ I would have run:

bin/poole draft "chainable shell functions"

And it would create a file called _drafts/chainable-shell-functions.md with some metadata in the first few lines.

Yesterday I got the urge to try replacing that rubygem with a custom shell script which does exactly the same thing.

I am an enthusiastic novice shell scripter.

Iā€™m vaguely aware there are different dialects of shell scripting and that Iā€™m probably using the wrong one.

Really Iā€™m not expert in this stuff.

But while writing this one I learned something interesting that Iā€™m going to share with you now.

Here is the first draft (annotated with comments for your convenience):

#!/usr/bin/env bash

# fail fast if any expression fails
set -e

# read all of the arguments into a string
title="$*"

# OK don't worry about this gnarly line, I'm going to break it down
slug=$(
  echo "$title" | sed "s/ /-/g" | tr -dc '[:alnum:]-' | tr '[:upper:]' '[:lower:]'
)

# the file we're going to create
filename="./_drafts/$slug.md"

# create the folder if it doesn't already exist
mkdir -p _drafts

# stop if the file already exists -- I don't want to overwrite an in-progress draft
if [[ -e "$filename" ]]; then
  echo "$filename already exists"
  exit 1
fi

# create the draft by piping a string into a file
echo "---
title: $title
date: $(date '+%Y-%m-%d')
---

Alright, this is where your post goes." > $filename

# Print a successful message
echo "Created $filename"

OK did you read that? Great.

So you saw that line I promised I would break down? The idea with that line is that I want to take the input, which is the title of the post, and figure out what is an appropriate filename for the post. Iā€™m figuring that out by applying a series of transformations to the title:

  • echo "$title"
    • just repeats the title, directing the output into a ā€œpipeā€, which the next command will read
  • sed "s/ /-/g"
    • sed is a ā€œstream editorā€; it reads in a stream of data and prints out a stream of data
    • here weā€™re using regular expressions to ā€œsā€ or substitute all occurences of a space with - (hyphen)
    • we want hyphens because they make for nicer looking URLs than spaces, which get escaped to %20.
    • the g at the end means ā€œglobalā€; without it, we would only subsitute the first space
  • tr -dc '[:alnum:]-'
    • tr is short for ā€œtranslateā€
    • -d means ā€œdeleteā€
    • -c means ā€œcomplementaryā€
    • this command means ā€œdelete all the characters that complement this set of charactersā€
    • in other words, ā€œdelete all the characters that arenā€™t alphanumeric or a hyphenā€
  • tr '[:upper:]' '[:lower:]'
    • ā€œtranslateā€ again!
    • this time weā€™re translating all of the upper-case letters to lower-case letters
  • Finally, we stop piping the output to the next command, and weā€™re done, so the result is saved in that local variable.

OK so thatā€™s a lot going on in one line, and because of the compact nature of these commands, itā€™s not super readable.

In other languages, when I have a lot going on in one function, I want to split out smaller, well-named functions. Can I do the same thing here?

At first I wasnā€™t sure. I knew it was possible to write functions that received arguments by checking $1, $2, etc in the function, but I wasnā€™t sure how to make them ā€œreturnā€ valuesā€¦

After a little googling I learned: you can just write a shell function that calls commands that read from a pipe, and pipe things to that function.

Let me show you what I mean.

Hereā€™s the second (and, frankly, final) draft:

#!/usr/bin/env bash

set -e

function dashify() {
  sed "s/ /-/g"
}

function removeSpecialChars() {
  tr -dc '[:alnum:]-'
}

function downcase() {
  tr '[:upper:]' '[:lower:]'
}

title="$*"
slug=$(
  echo "$title" | dashify | removeSpecialChars | downcase
)
filename="./_drafts/$slug.md"
mkdir -p _drafts

if [[ -e "$filename" ]]; then
  echo "$filename already exists"
  exit 1
fi

echo "---
title: $title
date: $(date '+%Y-%m-%d')
---

Alright, this is where your post goes." > $filename

echo "Created $filename"

Look at that!

When to use defined? to memoize in Ruby

February 5, 2017

Hereā€™s a quick Ruby thing.

traditional memoization in Ruby

Letā€™s say you have an object whose responsibility is to give a haircut to a dog.

(I may have recently been reading about this)

class DogStylist
  def initialize(dog_id)
    @dog_id = dog_id
  end

  def perform
    if dog
      dog.sedate
      dog.groom
      dog.instagram
    end
  end

  private

  def dog
    Dog.find(@dog_id)
  end
end

This is kind of fine, but it has one problem: each time you reference dog, youā€™re calling the dog method, which queries the database each time itā€™s called, so youā€™re querying the database over and over, when you really only need to do so once.

Better to write it like this:

def dog
  @dog ||= Dog.find(@dog_id)
end

Here youā€™re still calling the dog method over and over, but now itā€™s ā€œmemoizingā€ the result of the database query.

But what does that mean?

Hereā€™s a more verbose version of the dog method that does the same thing:

def dog
  @dog = @dog || Dog.find(@dog_id)
end

You can see that ||= is a syntactical shorthand similar to +=.

In case youā€™re unfamiliar with +=, hereā€™s an example. These two statements are equivalent:

count = count + 1
count += 1

Hereā€™s an even more verbose version of the dog method that does the same thing:

def dog
  if @dog
    @dog
  else
    @dog = Dog.find(@dog_id)
  end
end

The goal here is to avoid evaluating the database query more than once. The first time the method is called, the @dog instance variable is not defined. In Ruby, itā€™s safe to reference an instance variable that isnā€™t defined. It will return nil. And nil is falsey, so the database query will be evaluated, and its result assigned to the instance variable.

This is where things get interesting.

Ponder this question: does this memoization strategy guarantee that the database query will only be executed once, no matter how many times the dog method is called?

It doesnā€™t.

Why????

Iā€™ll tell you.

What if there is no dog with that ID? Dog.find(4000) returns either a dog, or nil. And, like we said earlier, nil is falsey. So hypothetically, if our perform method looked like this:

def perform
  dog
  dog
  dog
  dog
  dog
end

ā€¦ then we would execute the database query five times, even though we made an effort to prevent that.

This is actually totally fine, because our perform method isnā€™t written like that (again, that was just a hypothetical). Our perform method only calls the dog method more than once if itā€™s truthy, so thereā€™s no problem here.

memoization using defined?

Letā€™s consider another example, where things arenā€™t as hunky-dory. Hold please while I contrive one.

OK, Iā€™ve got it.

Letā€™s say we only want to groom a dog when he or she is unkempt. When she logs into our web site, we want to pepper some subtle calls to action throughout the page encouraging her to book an appointment. Weā€™ll need a method to check if she is unkempt, and weā€™re going to call it a few times. It looks like this:

class Dog
  HAIRS_THRESHOLD = 3_000_000

  def unkempt?
    Hair.count_for(self) > HAIRS_THRESHOLD
  end
end

Thatā€™s right: weā€™ve got a table in our database for all of the hairs on all of our dogs.

You can imagine this unkempt? method might be kind of ā€œexpensiveā€, which is to say ā€œslowā€.

Letā€™s try adding some memoization to this method:

def unkempt?
  @unkempt ||= Hair.count_for(self) > HAIRS_THRESHOLD
end

Here our goal is to prevent doing the expensive database query (Hair.count_for(self)) more than once.

Ponder this question: does our memoization strategy accomplish this goal?

Answer: it does not.

What?????

I know. Let me show you.

You can try running this Ruby script yourself:

$count = 0
class Hair
  def self.count_for(dog)
    $count += 1
    puts "called #{$count} times"
    2_000_000
  end
end

class Dog
  HAIRS_THRESHOLD = 3_000_000

  def unkempt?
    @unkempt ||= Hair.count_for(self) > HAIRS_THRESHOLD
  end
end

dog = Dog.new
puts "Is the dog unkempt? #{dog.unkempt?}"
puts "Is the dog unkempt? #{dog.unkempt?}"

It outputs the following:

called 1 times
Is the dog unkempt? false
called 2 times
Is the dog unkempt? false

In this script, I have a fake implementation of the Hair class. Itā€™s meant to demonstrate that the count_for method is being called more than once, even though we specifically tried for it not to.

So whatā€™s going on here?

Well, in a way, everything is working as itā€™s supposed to. The first time we call the unkempt? method, the @unkempt instance variable is not defined, which means it returns nil, which is falsey. When the instance variable is falsey, we evaluate the expression and assign its result, false, to the instance variable. The second time we call the unkempt? method, the @unkempt instance variable is defined, but its value is now false, which is also falsey (which you have to admit is only fair). So, again, because the instance variable is falsey, we evaluate the expression and assign its result, false, to the instance variable.

Shoot ā€“ that kind of makes sense.

So what to do? Hereā€™s another way to write this:

def unkempt?
  if defined?(@unkempt)
    @unkempt
  else
    @unkempt = Hair.count_for(self) > HAIRS_THRESHOLD
  end
end

This approach uses Rubyā€™s built-in defined? keyword to check whether the instance variable is defined at all, rather than if its value is truthy. This is more resilient to the possibility that your value may be falsey.

I wish there were a more succinct way to write this, because I think itā€™s generally how you actually want your code to behave when you use ||=.

To be fair, you can avoid defined? and instead write this method like this:

def unkempt?
  @hair_count ||= Hair.count_for(self)
  @hair_count > HAIRS_THRESHOLD
end

Itā€™s really just a matter of taste if you prefer one over the other.

Alright, take care.

Using git to track git

August 21, 2016

I made a screencast to share a fun idea I had while exploring a bit how git works.

You may know that when you use git to track a project, it creates a hidden .git directory with some files in it. But what actually goes on in there? And when do the contents of those files change?

Hereā€™s the idea: I know a tool for tracking the changes to a directory over time, and that tool is git itself!

So in this screencast you can see me try and do that ā€“ I initialized a git repository, which created a .git folder, and then I initialized another git repository within that .git directory.

I still donā€™t have a really great understanding of how git represents the data, although Iā€™ve read Mary Rose Cookā€™s very good essay about this topic Git From The Inside Out, which does contain those answers (I read it a while ago and forgot the details).

But I feel like I learned a few things thru this little experiment, specifically about when they change.

jfk

August 17, 2016

Note: this is a sort of personal story about a stressful experience. Iā€™m writing it because I want to remember it.

Last week I was on vacation in Berlin, and this week Iā€™m jumping back into my work life. Both things are great! In-between something wasnā€™t great.

My airbnb in Berlin
My airbnb in Berlin. 9:53

I had a few hours laid over in Copenhagen before my flight into JFK. I spent them doing laps around the terminal looking for something vegetarian to eat. While walking I listened to a few episodes of the Bike Shed podcast, which Iā€™m a few months behind on. Eventually I found and ate some nachos, which I happily ate while reading Squirrel Girl. All that, fine.

The flight was uneventful. Norwegian Air. I got around to watching Carol. I probably need to rewatch it on a bigger screen. Solid. Slept a bit. So far so good.

A husky American in a striped polo shirt sat on my right. A young Swedish woman slept on my left. None of us really talked much, except mild small talk about whether you need to declare spices at customs. (She asked me. I had no idea, but I said eh, you can probably get away with not mentioning it. I think she declared anyway.) She told me sheā€™s a student on her way to Arkansas to study abroad, and sheā€™s just transfering in New York.

When we landed, around 8pm, we couldnā€™t disembark right away. The pilot cited weather. They played music. People kept watching movies and TV. I think I didnā€™t disembark until around 9:40pm. I remember joking to the Swedish woman that if it wasnā€™t for the music, I might be kinda impatient, but with it Iā€™m pretty content. The playlist ran out of songs and looped back on itself. I only noticed because it was mostly upbeat pop songs, but then also Blue Bucket of Gold by Sufjan Stevens, a very slow and beautiful song about feeling lonely and alienated. And I didnā€™t realize it was Sufjan Stevens (who I really like) until it came on a second time and I laughed at how random that is. Then I fell asleep for a bit.

When we did start to disembark, I put on my big black backpack, but then we were asked to sit down again, and I took it off. A few minutes later, people started moving again and we flowed off the plane. I said ā€œthanksā€ to the flight attendants, thinking they were probably getting a lot of shit from impatient passengers and strolled down the jet bridge (a term I only learned this week) toward customs. I was kind of tired; Iā€™d just been napping with my forehead against a television. All I was thinking about was how to get home, weighing the choice of springing for a cab or just taking the train.

But first, customs. I got on the end of a long line, putting in my earbuds and selecting Hotline Operator, a song which bristles with an impatient energy. Because people were impatient, they pressed forward, as though becoming dense would make the line move faster. Some others got on the stationary moving sidewalk to move farther ahead in the line. I remember thinking it was like people driving on the shoulder of a highway to pass you. Without putting a lot of effort into hanging back, I was pretty much at the way back of the line. I couldnā€™t see all the way to the front. I started thinking about taking off my heavy backpack with the expectation that I would be standing for a while. When I expect to be bored for a while I kind of let my body go into autopilot and let my mind wander for a while, and thatā€™s what I was starting to do.

Iā€™m not sure what exactly snapped me back to reality, but the next thing I knew I was running, and so was everyone else. In only a few seconds, hundreds of mostly stationary people facing one way turned and ran the other way, toward me. You donā€™t really question that, you just go. You donā€™t really have another option. Not that youā€™re thinking at all.

The gush of humanity I was swept up in elected to run down a jet bridge. There were several abandoned rolling suitcases blocking the path. I knew people were charging behind me and were probably going to stumble on them, and I grab one by the handle and run with it in my hands. At the entrance to the plane, I set it to the side. This is the one good deed I did all night, I think, and I still left a bunch behind.

The Air Korea flight attendants were confused and kind of angry that these people were rushing onto their plane.

ā€œWhat is happening??ā€ one asked the streaming crowd.

I told her the crowd has panicked and I didnā€™t know why, but it seemed like maybe thereā€™s a shooter, but I didnā€™t know.

That seemed like the only explanation. People in the front must have seen something and bolted, understandably. What else could it be? No one needed to say it ā€“ we all knew, right away. And people did start saying it.

I felt incredibly alert. I would notice later my mouth was dry as a bone.

I paused in first class and moved into a seat to get my bearings. Some came panting, others crying onto the plane, everyone flowing into the back as though for a takeoff. I saw a woman calling for her son; they were separated in the chaos. ā€œWhat does he look like?ā€, someone asked. ā€œHeā€™s ten years oldā€, she said. I saw another mother holding her young, sobbing son to her stomach and telling him, ā€œItā€™s okay. Itā€™s okay.ā€ I wrote a text to my family:

Iā€™m safe. There was a a panic at the airport. Crowd ran. Iā€™m sitting on an airplane right now. Different one than I disembarked from. Followed running crowd. No signal. Not sure if this will send. Writing at 22:07 ET Will update. I am safe

I see now that it went through ten minutes later.

I wasnā€™t sure how safe I was. Even on the plane, I felt exposed and at risk. I was tempted to move to the right side of the plane to be just a little farther from the terminal. But I needed to send that.

I saw the Swedish woman, shocked, move into the back of the plane, and I joined her in a seat.

ā€œWhat the fuck is going on?ā€ I asked, and she didnā€™t reply.

Somehow, for some reason, the flight attendants communicated to us that it was time for us to get off the plane. I donā€™t recall if we were instructed to go back down the jet bridge. I donā€™t recall if the plane was becoming full of people. What happened next is that people started opening the emergency exits. Maybe thatā€™s why. They couldnā€™t manage to open the emergency exit in the rear of the plane. I recall seeing some kind of component hanging by a wire, and a flight attendant poking at it like, ā€œwell, this one isnā€™t openingā€. ā€œStay calmā€, an elderly woman urged everyone.

The mid-plane emergency exits were both open, and the one on the left (facing the terminal) had a slide going down to the tarmac. The one on the right had no slide, it was just open twenty something feet above the hard ground. One flight attendaant stood spread-eagle in front of it to make sure people knew not to go that way. Another stood by the slide, metering us out, telling us when to go. The two women shouted to each other in Korean over the din.

Ahead of me, people were sliding down to the tarmac and jogging around a corner of the terminal. I jumped, slid, and hit the ground running after them. I passed a man taking shelter in a corner behind what might have been gas tanks, and I was tempted to join him, but I ran on with the bigger group.

Around the corner, we reached a barbed-wire fence. I assume the idea is to make it harder to access the tarmac, but in the moment many people felt trapped. I saw some people pushing a bright yellow dumpster toward the fence, with the idea that they could use it to jump the fence.

I took several photos from this point on, nearly all of which came out blurry beyond interpretation.

The crowd and dumpster in the corner
The crowd and dumpster in the corner. 22:19

My sister texted me:

Max?

The rest of my family was in London, and sleeping, but Gaby was awake. I told her I was safe and to tell me if thereā€™s anything in the news about JFK. Then I fired off a tweet:

I sent tweets intermittently for the rest of the night, most of which have typos because I wrote them quickly while afraid.

People werenā€™t sure what to do. Personally, my plan was to stay put and wait for a hero. I think some others probably felt the same way. Opinions varied on where was safest to physically stand. There was a large, open garage (I think thatā€™s what youā€™d call it, although I donā€™t recall seeing any vehicles), which some stood within. This seemed like a good idea in the event that attackers came around the corner. Others seemed suspicious that attackers might come from inside the garage, and stood vigilant, eyes darting between the garage and the tarmac.

Around this point a policeman came and told us to follow him around the corner back toward the customs area, where there are more police. When he saw the people pushing the dumpster he shouted after them, something like, ā€œYou are NOT going to do that.ā€ A woman asked him what the plan was, said she wouldnā€™t go unless there was a plan. He made the point that he was going to walk us there, and what, is he going to willfully put himself in danger? This argument, an appeal to a relatable self-preservation, seemed to make sense to the crowd.

We started cautiously walking.

The crowd under an overpass
The crowd inching under an overpass toward customs. 22:28

We were all hugely reluctant to move back in the direction of airplane and customs. As we inched forward I saw silhouettes of figures in the windows of the terminal, some walking, some standing and watching us. I didnā€™t know what to make of that. A child let out a screaming cry, and everyone immediately turned to run back toward the corner, which had somehow started to feel safe, and not worth leaving.

I saw a woman was running barefoot.

ā€œIā€™m faster this wayā€, she said.

Gaby:

Something at terminal 1 and 8

They are bringing in bomb squad

What is your battery %

If you feel safe please save battery

They havenā€™t found shooter or anyone injured as of now

Me:

100%. I feel safe right now

Battery pack baby

Slowly the policeman and some other airport staffers coaxed us back toward customs, and two doors which went directly from the tarmac into the big open room. People were very reluctant to enter. I had the sense that the room must be safe if Iā€™m being told to enter it, and I went in.

Inside, I was shocked to find that there were already dozens of people queueing in an orderly fashion to go through customs. I couldnā€™t see if anyone was actually at the desks to process them. Beyond the desks, the airport seemed well-lit and deserted.

I didnā€™t feel safe getting on the line; Iā€™d seen this crowd turn into a stampede in seconds. I stood near the doors and tweeted this:

And a few minutes later, it happened. Something spooked the crowd and they bolted for the doors, feeling safer on the tarmac. I think they believed an active shooter must have still been in the terminal, even though some semblance of order was beginning to formā€¦ Outside I tweeted this (compare the timestamp to the previous one):

I meant ā€œpileā€, not pipe. Hereā€™s what I saw:

People struggling to leave customs
People struggling to leave customs, and one tarmac staffer helping a fallen person get up. 22:41

At this point I saw people were starting to move way in the opposite direction of customs, to the far edge of the tarmac. I saw why: there were 2 shuttle buses there, and people were getting on them. I walked under an airplane toward the buses.

View of airplane from below
I made my way under an airplane. 22:46
Buses in the distance
I made my way toward these buses. 22:47

A crowd formed here, hopeful. Weā€™re getting outta here. The two buses became full and left. I have no idea where they went. We waited, hopeful that more would come.

I took the opportunity to update Gaby, tweet a few updates, and search for any news. My twitter feed was full of people talking about the Olympics and or the election. I remember feeling slightly stunned that the whole world wasnā€™t talking about me. Every minute or two I got notifications of people sharing or replying to my tweet updates. Some folks mentioned or direct messaged me asking questions and providing updates from police scanners and news reports. No one knew anything. One BBC reporter mentioned me asking if I wanted to do an interview when I feel safe; a reporter from a talk radio show asked me if I could confirm that shots were fired.

I felt the need to offer this update:

I didnā€™t know if there was a shooting or not. I wanted to believe there hadnā€™t been. I knew there was a lot of confusion both among the passengers on the tarmac and among the people replying to my tweets. I couldnā€™t clear away the confusion but I wanted to emphasize to anyone who was following the story not to assume the worst.

After more than 20 minutes of standing restlessly and comparing confused notes with each other, three more buses arrived. I got on the last bus. The last person to board the bus before the door closed was a young guy who reminded me of my 20 year old cousin Eric from Georgia. He asked if I knew anything. Said his phone died. Said he got separated from his sister.

ā€œHow old is she?ā€ I asked

ā€œNineteenā€, he said.

Nearby a guy in a Panini Express shirt told us he was working when his boss suddenly turned and ran without saying anything. The next thing he saw was a traveler crawling behind the counter for shelter.

We waited on the bus for about ten minutes when an airport staffer boarded in the front and cheerfully, loudly gave us directions.

ā€œAlright, hereā€™s what weā€™re gonna doā€, he projected. ā€œWeā€™re going to exit the front of the bus and follow [muffled muffled]. OK? Do you understand? Who understands?ā€

He spoke with the cadence of a DJ trying to get the party started. I had missed some of what he said, but I still felt kind of compelled to echo back, ā€œI understand!ā€ This was the clearest instructions I heard all night. Give that guy a medal.

The bus was packed, and there was a second exit in the back. A woman shouted out to the bus driver, ā€œBack door!ā€ The kind of thing you might shout on a city bus on a normal day, when the door hasnā€™t opened. I couldnā€™t help but wonder if the front door was somehow safer than the back door, and that was why we were being told explicitly to exit through the front door.

Upon exiting I followed a stream of people back toward customs. The crowd was much bigger now than before, and much calmer. There were more police. At some point a fire truck showed up. People started to relax, breath, sit.

Gathering crowd on tarmac
Gathering crowd on tarmac. 23:28
Crowd including man with torn shirt
Man with torn shirt in crowd. 23:31
Woman in crowd prays
Woman in crowd was praying to herself. 23:39

I noticed my dad has replied to me:

Dad (23:19, 5:19 in London):

Just got up to pee. Any new updates? News at 15 minutes ago says unconfirmed reports of gun shots at terminal 8 & 1 (yours)

I told him where I was and that it was much calmer now.

Dad:

Be patient & be a calming influence. See if anyone is alone & nervous

Me:

Yep

I pictured my mom sleeping and my dad glued to his phone.

The Swedish woman approached me.

ā€œHeyā€, she said.

I was shocked and very relieved to see a familar face. I had been traveling alone on my vacation and kind of reveling in the freedom to do whatever I wanted at any time. But during this incident I wasnā€™t myself, wasnā€™t independent, I was just alone, an unindividuated animal in a suspicious herd. We shared a water bottle she got from a fireman. I started to feel like a human again.

She missed her transfer to Arkansas and was on the phone with her friend in New York to see if she could stay there.

Many people sat down. I realized I felt safe enough to sit down.

Soon we started queueing toward customs and I felt safe getting in line. A young woman stood with us for a while, seemingly wanting not to be alone.

We parted ways inside, and I went through customs. I think it was quiet. The kiosk was working, and I punched in that I wasnā€™t planning to declare any food, spices, or anything. I was glad I held onto my backpack as I dug my passport out.

The young southern man from the bus was ahead of me in line.

ā€œI found herā€, he said, indicating his nineteen year old sister.

It took me a moment to register who he was and figure out what to say.

ā€œIā€™m really glad to see thatā€, I finally said.

The police officer checked my passport and barely glanced at my customs slip.

ā€œYouā€™re okayā€, he said, and stamped my passport.

As I exited baggage claim, I was met by a huge group of people waiting for the long-overdue terminal 1 arrivals, and felt momentarily like they were all there just for me, just to tell me I had been brave and I could rest now.

Crowd at arrivals
Crowd at arrivals. 00:27

I felt destabilized by how normal operations were on this side of the terminal, when it had been such fright and chaos on the tarmac.

There was no question I was taking a taxi. I waited for a while and ended up sharing one with three other people. The dispatcher was long gone, so the drivers were free to negotiate whatever prices they wanted. No problem.

On the drive home, we compared notes. A guy my age sat in the front seat, and a mother and teenaged daughter sat with me in the back. The news was saying it was all a false alarm and that there was no evidence of a shooter or a bomb or anything. The daughter said something about how scary the night had been for her, and her mother made the point, ā€œWe were never actually in danger at any point.ā€

When I eventually found my way to my bed (around 2:30) and closed my eyes, I felt waves of cold energy coursing through my limbs.


If youā€™re curious for another experience of this night, read this article: Scenes From the Terrifying, Already Forgotten JFK Airport Shooting That Wasnā€™t which captured a lot of what I felt and am feeling and also filled in some details for me.

This passage jumped out like a lightning bolt:

The fact that there had been, actually, nothing to panic about was an enormous relief, of course. But it made things all the more eerie the next morning, when we woke up feeling like survivors of a ghost trauma, a minor local-news story. For several hours, we were in the flood of panic and chaos of an ongoing act of terror. Thereā€™s no other way to describe it. That it was an overreaction almost doesnā€™t matter; in fact, that is how terrorism works.

Reading it made me feel some amount of relieved and want to write something myself.

integrating vim with the mac clipboard

July 30, 2016

Using terminal text editors has a lot of advantages, but for a while the biggest disadvantage Iā€™ve felt as a vim user is that itā€™s kind of hard to interact with the system clipboard. Iā€™m aware that thereā€™s a concept called ā€œregistersā€ which are something like multiple clipboards that you can copy and paste from, and one of them is the system clipboard, and the others are all virtual, or something like this, but I havenā€™t taken the time to really learn how those work yet.

If I want to copy a helpful code snippet from Stack Overflow into vim and I copy it to the mac clipboard, and then press ā€œcommand + vā€ to paste it into vim, the indentation gets totally screwed up. This is becuse vim is trying to help. It doesnā€™t know that I just pasted, it thinks that I was suddenly just typing super super fast and each newline character I ā€œtypedā€ caused it to helpfully auto-indent the appropriate amount. When I actually am typing, this is helpful. But when Iā€™m pasting, itā€™s kind of annoying.

Pasting into vim doesn't work well

(You can see in this example that not only is the indentation screwed up, but also there is an extra end which vim-endwise helpfully tried to auto-insert)

The workaround Iā€™ve used for a while is to always run :set paste before I paste, and then :set nopaste afterward. This mode doesnā€™t auto-indent. It also breaks a number of my other vim configurations, such as jk being an alias for the escape key.

Pretty annoying.

Copying text out of vim is even more difficult. I can use my mouse to highlight the text I want to copy and then press ā€œcommand + cā€ to copy it, but this is pretty awful, too, because itā€™s very easy to accidentally copy things like line numbers (which are just text in the terminal, and your mouse doesnā€™t know to avoid it) or to even copy text from multiple files which you happen to have open side by side in split buffers, such that the code is totally broken when you paste it out again.

Copying from vim split buffer doesn't work well

My workaround for this is even worse! I generally close my splits, turn off line numbers (:set nonumber) and sometimes make my font smaller so I can fit the whole lines on my screen and select the text and copy it. When I do this, I generally pick up a bunch of trailing whitespace that wasnā€™t there in the source code. It totally stinks.

Sometimes I will just open the file in Atom so I can copy text in a sane way.

Other times I will run :! cat % | pbcopy to ā€œshell outā€ to a bash command and copy the entire contents of the file to the clipboard.1

OK. So obviously that sucks, right? Thatā€™s just some context for how Iā€™ve been doing things. I meant to look into a better technique and never got to it.

The other day at work I saw my coworker Will very seamlessly copy some text out of vim and paste it into Slack.

Scandalized, I asked him how he had done that. He told me heā€™s using neovim and itā€™s probably something neovim does.

I made a note to look into it. Iā€™m open to the idea of using neovim instead of regular vim ā€“ I think itā€™s cool that you can run a terminal inside vim, which makes me wonder if I even need tmuxā€¦

One of the first things I found in my research was a neovim issue from April 2014 about how some vim configuration was working in vim but not neovim

ā€¦ the follwing works perfectly fine with mainline vim, ā€œyā€ and ā€œpā€ work with X clipboard:

set clipboard=unnamedplus

but not for neovim.

Iā€™ve tried setting it to:

set clipboard=unnamed

still works in vim, but not neovim.

Hm. Wait. Does this mean vim already supports clipboard integration this whole time and no one told me!?

Indeed, yes, and this is why Iā€™m writing this blog post to tell you. I feel like thereā€™s a good chance you already knew.

So yep, I added that second config option to my .vimrc and now it works great:

  • I can yank text from vim and then ā€œcommand + vā€ it into other apps.
  • I can copy text from Stack Overflow and then ā€œpā€ it into vim ā€“ no weird indentation behavior or anything

I may yet switch to neovim2 or learn about registers, but for now I donā€™t yet need to, and for that I celebrate.

  1. The ! means to run a bash command; the % will expand to refer to the file name; pbcopy is a mac thing for piping data to your clipboard.Ā 

  2. Note that neovim did fix that issue and it does work now.Ā 

the first useful thing I wrote in Rust

June 9, 2016

Iā€™ve been interested in the Rust programming language for a while, but it wasnā€™t until this week that I wrote something in it which I found useful.

Letā€™s rewind. I like to have a random, nice emoji in my shell prompt. Itā€™s just to add a little flair to the proceedings, some color. The emoji donā€™t mean anything, theyā€™re just for fun.

My shell prompt is set like this:

PROMPT="%F{grey}%C%f \$(random_nice_emoji) \$(git_prompt) "

random_nice_emoji is a command line program on my PATH. git_prompt is a shell function. The \$(...) syntax means that the program or function should be called each time the prompt is drawn, not just once when you first open your terminal.

I could have written random_nice_emoji as a shell function if I could figure out how to use shell arays, but I could not.

Instead I wrote it as a simple ruby script:

#!/usr/bin/env ruby

print %w(
  šŸ–
  šŸ˜…
  šŸŒø
  šŸ™
  šŸŽ‘
  šŸ–Œ
  ā˜•
  šŸ“Š
  šŸ‹
  šŸŒˆ
  āœØ
).sample

And my prompt looks like this:

my prompt, where each line includes a random fun emoji

But over time I noticed that it was kind ofā€¦.. slow. And I started to wonder if maybe my fun affectation was worth it. Some benchmarking suggests that this program takes about a tenth of a second to run. Thatā€™s not a lot, really. But we can do better.

Maybe the shell function would be much faster, but yea, still donā€™t know how to use shell arrays.

So letā€™s try writing this little script as a Rust program ā€“ Rust is supposed to be fast!

To make a new command line program in Rust, you can use Cargo to scaffold the project:

cargo new random_nice_emoji --bin

The --bin part means that it will be a command line program. Without it, I think the idea is that youā€™re making a package which will be used in an application.

That command crates a directory called random_nice_emoji, and within that there is a file src/main.rs which is where you put your code which should run when the command line program is invoked.

Hereā€™s what I came up with (Iā€™m really new to Rust so this isnā€™t necessarily good code):

extern crate rand;
use rand::distributions::{IndependentSample, Range};

fn main() {
    // cool, friendly emoji that look fine against a black terminal background
    let list = vec!["šŸ–", "šŸ˜…", "šŸŒø", "šŸ™", "šŸŽ‘", "šŸ–Œ", "ā˜•", "šŸ“Š", "šŸ‹", "šŸŒˆ",
                    "āœØ"];
    let between = Range::new(0, list.len());
    let mut rng = rand::thread_rng();
    let index = between.ind_sample(&mut rng);
    let emoji = list[index];
    print!("{}", emoji);
}

I couldnā€™t find a super-simple sample method, so I did my best to adapt the example from the docs for the rand crate to achieve that behavior.

You can install it yourself with cargo install random_nice_emoji. Maybe I shouldnā€™t have released it because itā€™s not generally useful ā€“ but itā€™s very convenient for me so I can install it on multiple computers, for example.

And this one usually finishes in 0.006 seconds ā€“ 16 times faster. And it was maybe 5 times harder to write? Iā€™m hopeful that if I get better at Rust, that will go down.

If youā€™re into Ruby and intrigured by Rust, I recommend checking out this Helix project which makes it easy to embed Rust code in Ruby projects to ease performance hot spots. I havenā€™t used Helix yet, but that talk does a really great job of explaining the idea and was really inspiring to me.

Fun method names in Ruby

June 8, 2016

One thing I like about Ruby is that you can use a little punctuation in your method names, which can help you write expressions that read like nice sentences:

delete_user user unless user.special?

Kind of fun.

I knew a guy who liked to use these question mark methods in conjunction with the ternary operator to write code that reads like a panicked friend:

user.special?? protect(user) : delete(user)

The double question mark always makes me smile, which makes me wonderā€¦ Can I just define a method with double question marks right in the method signature? Like this:

class User
  def special??
    name == 'Max'
  end
end

Turns out: nope. Thatā€™s a syntax error. Not valid Ruby code.

Wellā€¦ OK. But this is Ruby, so thereā€™s not just one way to do a thing. Thereā€™s another way to define a methodā€¦ Letā€™s try this:

class User
  def initialize(name)
    @name = name
  end

  define_method("special??") do
    @name == 'Max'
  end

  define_method("multi
                line
                method
                name??") do
    puts "sure, why not?"
  end

  define_method("!?") do
    "ā€¼"
  end
end

user = User.new("Max")
user.public_send("special??") #=> true
user.public_send("!?") #=> "ā€¼"
user.public_methods(false) #=> [:"special??", :"multi\n                line\n                method\n                name??", :"!?"]

Haha that works!

OK itā€™s not as satisfying calling the methods with public_send, but as far as I know, itā€™s the only syntactically-correct way to call these methods.

Articulating my Vegetarianism

April 5, 2016

note: this is going to be sort of personal and self-indulgent and Iā€™m mostly writing it for myself to work out some ideas and make some decisions

Iā€™m writing this from vacation, in Mumbai. Last year, I googled ā€œbest vegetarian citiesā€ and Mumbai was near the top. New York City, where I live, was also near the top. Itā€™s not a shock ā€“ New York has many specialty restaurants that focus on vegetarians and vegans, and almost any restaurant has something for me. But after spending a few days in Mumbai, I think itā€™s truer here.

In software, we sometimes talk about ā€œsensible defaultsā€. If youā€™re going to provide options to your user to change the behavior of your app, the default behavior should probably make sense for the majority of users. I donā€™t think the majority of Mumbaikers are vegetarian, actually, but the nomenclature suggests it. It manifests like this:

  • FourSquare tips like ā€œbest place for non-veg in the cityā€.
  • Restaurant servers asking ā€œveg or non-veg?ā€
  • Restaurant signs indicating that a place is ā€œpure vegā€ (which I think means itā€™s suited for Jain vegetarians)

I like that the ā€œnonā€ is attached to ā€œvegā€ and not ā€œmeatā€.


If you become a vegetarian, people will want to know why. Iā€™ve been a vegetarian for a little over three years, and I often struggle to come up with a good answer. This dissatisfies people, myself included.

The truthful answer is that Iā€™m a vegetarian today because I was a vegetarian yesterday and for no other conscious reason. This of course raises a few questions:

  1. why did I start eating vegetarian?
  2. what do I like about eating vegetarian?
  3. what do I do next?

1. Why did I start eating vegetarian?

In February 2013 my toe hurt. I thought maybe it was broken. I had a vague memory of stubbing it badly while walking in the snow. There were drinks involved.

The doctor x-rayed me and said it looked fine. No broken toe or anything.

ā€œYou probably have goutā€ (I paraphrase).

Gout, as I understood it, is a form of arthritis. If your diet is too purine-rich, uric acid causes crystallization in your joints, which can be painful or uncomfortable. (This is probably not a fully accurate definition of gout ā€“ Iā€™m writing without WiFi currently and going from memory from a conversation 3 years ago) He took some blood to assess the levels of uric acid and gave me a cheat sheet of foods to avoid.

He didnā€™t tell me to become vegetarian. He told me to moderate my levels of purine by limiting my consumption of specific foods to lower levels. The list was kind of surprising to me: I recall spinach and oatmeal being on the no-fly list. There were specific amounts of red meat I was recommended to eat in a week, limited to a few ounces. Similarly for other meat, maybe a little more generous.

Iā€™ve never been a very conscientious eater. I still donā€™t feel like I understand the basics of nutrition. I wasnā€™t excited to memorize a list of foods to eat or to buy a kitchen scale. I decided to try eating vegetarian for a few weeks and see if the toe pain went away.

It did.

Curiously, the blood test showed normal levels of uric acid. I mightā€™ve had it, anyway although I may never know.

I think in the first few weeks I was just superstitious and didnā€™t want the toe pain to return. It was very inconvenient! I could barely walk.

And then it became a habit and I forgot about stopping.


In the interim, Iā€™ve bought a few books about veg* eating, but I think Iā€™ve only finished one: Eat & Run. Itā€™s a very inspiring memoir about a vegan ultra-marathoner. This dude runs 100+ miles at a time powered by smoothies, salads, and black bean burritos. He makes a very persuasive case for veganism as an ideal diet for some kinds of athletic pursuits. Iā€™m not an athlete, but I do encounter people who insist that they ā€œcould never be vegetarianā€ because their gym regimen requires amounts of protein which would be impossible, inconvenient, impractical, or unaffordable to attain through a plant-based diet. Theyā€™re probably right, I donā€™t know their life. One takeaway from the book I took away is that most Americans are consuming too much protein. I like to share that when people worry about my protein.

People worry about my protein all the time. ā€œBut how do you get your protein?ā€ People need to know. Iā€™ll tell ya: I donā€™t worry about it. Iā€™m still not a very conscientious eater, and I feel pretty much fine. I think I feel a little better than before, but I donā€™t really remember.

(This post continues not to be medical advice.)

2. What do I like about eating vegetarian?

There are a few benefits Iā€™ve picked up on, which Iā€™ll enumerate now, as they occur to me.

Easier decision making

Some people talk about clothing this way: I wear the same thing every day so I have one fewer decision to make each day. I havenā€™t heard people talk about food in the same way, but I think itā€™s similar. I have the personality where I feel compelled to read the whole menu before I make a decision, lest I make the mistake of overlooking something I might like. Have you ever been to a diner? These menus are an astonishment. I can get thru a menu much quicker when I can skip all of the non-veg items. No more ā€œplease, come to me lastā€; I know what Iā€™m ordering.

No more storing and cooking meat

I used to try to cook meat. I was real bad at it. I couldnā€™t make a burger without setting off the smoke alarm or undercooking it. I couldnā€™t defrost a chicken breast without accidentally cooking it a bit, which I found kind of gross at the time but pushed down. I didnā€™t really enjoy handling raw meat ā€“ itā€™s kind of slimy and sticky, right? I donā€™t really remember, and maybe itā€™s grown more ghoulish with distance.

No more gristle, no more veins, no more bones

Itā€™s been a long time since Iā€™ve taken a bite and felt a surprising popping resistance in my mouth. I used to secret unpleasant, gristly bites into napkins. I used to worry I wasnā€™t effective at extracting as much meat as I could from a bone. I used to avoid eating veins. No more!

Physical comfort

Meat sits like a lump in your stomach in a way that vegetables donā€™t. Restaurants sometimes serve these giant sandwiches, burgers, steaksā€¦ And I would eat the whole thing, and it would just sit there. Look, I donā€™t want to go into too much detail about gastro-intestinal scenarios. But letā€™s just say itā€™s harder to cause problems when you skip all that stuff.

Moral correctness

This is a touchy one. When a meat eater asks me why Iā€™m vegetarian, what they really seem to mean is: do you think Iā€™m an asshole? I donā€™t think youā€™re an asshole. But I do think itā€™s morally correct to avoid eating meat.

I donā€™t think itā€™s majorly evil. But I think itā€™s kind of wrong.

I donā€™t feel strongly enough to try and convince others to give up meat. But I do take a small amount of pleasure in feeling like Iā€™m doing the right thing, the same feeling I get when I recycle. Thatā€™s the moral level I put it at. Itā€™s a nice thing to do.

I ate meat for 24 years and didnā€™t eat meat for 3 years. I think of it like Iā€™m offsetting some damage, paying off some debt. If I take that thought to its conclusion, I should remain vegetarian for another 21 years, after which point I would be in the black and could start eating meat again guilt free.

Thatā€™s not necessarily the plan, by the way.

Having a label for my diet

There are many diets. Iā€™ll list a few (summarized crudely):

  1. Omnivore ā€“ Iā€™ll eat anything
  2. Kosher/Halal ā€“ Iā€™ll eat anything except a few specific things
  3. Vegetarian ā€“ Iā€™ll eat anything except an animal
  4. Pescaterian ā€“ Vegetarian, except Iā€™ll eat seafood
  5. [?????] ā€“ Iā€™ll eat anything except red meat
  6. Vegan ā€“ Iā€™ll eat anything unless an animal was involved
  7. Jain vegetarianism ā€“ Iā€™ll eat anything except animals, eggs, and vegetables which grow in the ground

Before I stopped eating meat I didnā€™t belong in any of these categories and I was kind of embarrassed about it. Not a deep burning shame or anything, but I had a hard time explaining it, and I was put into some uncomfortable situations. My diet was something like: ā€œInverse Pescatarian ā€“ Iā€™ll eat anything except seafoodā€¦ or mushroomsā€¦ or green olivesā€¦ or solid tomatoesā€¦ or a few other thingsā€. Not great! Iā€™m very proud and happy that I fit into a group now, called vegetarian. People understand it and people have been mostly happy to accommodate what they can understand.

And, happily, Iā€™ve expanded to fill the space I entered and have come to appreciate many or most of the vegetables I used to discriminate against.

If I were to start eating meat again, I think I would need to eat seafood as well, or I wouldnā€™t belong to a group anymore.

3. What do I do next?

Iā€™m writing this out because recently Iā€™ve been tempted to eat meat again and I wanted to explore the idea.

Here are my temptations, as they occur to me:

  • Shake Shack burger
  • McDonaldā€™s burger
  • Wendyā€™s burger
  • Most other burgers
  • sliced pastrami ā€“ specifically in a pastrami reuben from a specific deli in Vermont near where I lived senior year of college
  • boneless spare ribs at literally any takeout Chinese restaurant
  • pork belly bao at Baohaus
  • Chicken Parmesan at an Italian restaurant near where I grew up
  • Lamb ā€¦ in some context? I forget when I ate lamb. Maybe it was goat? Something kind of tough and rich.

My mouth is watering as I think about these. But it passes pretty quickly.

I fear if I started eating meat again, even slowly and carefully, I would shortly be subsumed by it.

And I really have no desire to start eating seafood.

And no real reason.

Well, it ainā€™t broke.