Hardscrabble 🍫

By Max Jacobson

operators as self expression

26 May 2015

EDIT June: I’ve followed-up this post: order of operations

NOTE: this post seems to have a ton of code samples, but it’s pretty much just the same one over and over with some small variations to play with an idea.

Suppose you were writing a program to help you write a novel, and you start out with something like this:

class Novel
  def initialize(title)
    @title = title
    @chapters = []
  end
end

class Chapter
  def initialize(text)
    @text = text
  end
end

moby_dick = Novel.new("Moby Dick")
first_chapter = Chapter.new("Call me Ishmael...")

After you write this, you pause, because how are you going to define the interface for adding a new chapter to a novel?

Something like this works fine:

class Novel
  def initialize(title)
    @title = title
    @chapters = []
  end

  def add_chapter(chapter)
    chapters << chapter
  end

  private

  attr_reader :chapters
end

class Chapter
  def initialize(text)
    @text = text
  end
end

moby_dick = Novel.new("Moby Dick")
moby_dick.add_chapter Chapter.new("Call me Ishmael...")
moby_dick #=> #<Novel:0x007fdabc81c1f0 @title="Moby Dick", @chapters=[#<Chapter:0x007fdabc81c178 @text="Call me Ishmael...">]>

Writing methods is good, and that method has a perfectly adequate name. But some clever ducks will be dissatisfied by it, because they know there’s a more fun, or maybe a more expressive way:

class Novel
  def initialize(title)
    @title = title
    @chapters = []
  end

  def <<(chapter)
    chapters << chapter
  end

  private

  attr_reader :chapters
end

class Chapter
  def initialize(text)
    @text = text
  end
end

moby_dick = Novel.new("Moby Dick")
moby_dick << Chapter.new("Call me Ishmael...")
moby_dick #=> #<Novel:0x007fdabc81c1f0 @title="Moby Dick", @chapters=[#<Chapter:0x007fdabc81c178 @text="Call me Ishmael...">]>

The << “shovel” operator is familiar to most Ruby programmers, and chapters are a natural thing to shovel into a novel, so it feels kind of natural to use it here.

<< is the idiomatic operator to use, but sometimes I don’t want to be idiomatic, I want to be weird. Maybe I feel like this version suits me better:

class Novel
  def initialize(title)
    @title = title
    @chapters = []
  end

  def <=(chapter)
    chapters << chapter
  end

  private

  attr_reader :chapters
end

class Chapter
  def initialize(text)
    @text = text
  end
end

moby_dick = Novel.new("Moby Dick")
moby_dick <= Chapter.new("Call me Ishmael...")
moby_dick #=> #<Novel:0x007fdabc81c1f0 @title="Moby Dick", @chapters=[#<Chapter:0x007fdabc81c178 @text="Call me Ishmael...">]>

For me, <= is a more visually stimulating, writerly operator.

I probably shouldn’t do this. It’s a totally weird interface! <= means “less than or equal to”, not append..!

In fact, I’m only allowed to even do that because <= is on a list of acceptable operators1. You can’t just name your operator whatever. Let’s try:

class Novel
  def initialize(title)
    @title = title
    @chapters = []
  end

  def ✏️(chapter)
    chapters << chapter
  end

  private

  attr_reader :chapters
end

class Chapter
  def initialize(text)
    @text = text
  end
end

moby_dick = Novel.new("Moby Dick")
moby_dick ✏️ Chapter.new("Call me Ishmael...") #=> undefined method `✏️' for main:Object (NoMethodError)

It totally blows up. This works, though:

moby_dick = Novel.new("Moby Dick")
moby_dick.✏️ Chapter.new("Call me Ishmael...")
moby_dick #=> #<Novel:0x007fdabc81c1f0 @title="Moby Dick", @chapters=[#<Chapter:0x007fdabc81c178 @text="Call me Ishmael...">]>

Surprisingly, this does too:

moby_dick = Novel.new("Moby Dick")
moby_dick . ✏️ Chapter.new("Call me Ishmael...")
moby_dick #=> #<Novel:0x007fdabc81c1f0 @title="Moby Dick", @chapters=[#<Chapter:0x007fdabc81c178 @text="Call me Ishmael...">]>

There are so many spaces on that second line, but it totally works.

It sucks that the period is necessary for this to be syntactically valid. I think. I don’t know what the consequences would be of allowing programmers to define arbitrary operators. Maybe they’re vast?

  1. here’s a stack overflow post about which operators are overloadable. It may not be up-to-date with Ruby 2, though, so overload at your own risk. 

time zones are still the worst

25 Apr 2015

After publishing my last post, a few minutes ago, I of course triggered refreshes until the reminder ticked down to “0 days and 0 hours”, but… it didn’t work.

Instead, I saw: “0 days and 4 hours”. What the hell?

My first instinct was to blame the script and feel a little embarrassed for sharing a faulty script.

After a little digging, I realized it was worse than that: it was actually my RSS feed that was faulty! This commit fixed the problem: 642cd54.

Because I wasn’t supplying a time zone in the metadata for the post, Jekyll (my blog generator) had to make an assumption, and it assumed UTC, which is currently 4 hours ahead of New York, from where this blog emanates, and from where I anxiously remind myself to update it.

By stating that I was publishing a post at 15:53 UTC as opposed to 15:53 EDT, I was effectively backdating it, which makes it sort earlier in RSS feeds. One of my favorite blogs, The Setup, doesn’t supply times at all, only dates, so whenever a new interview arrives, it arrives buried below all of the day’s posts, which I find mildly inconvenient. It pains me to know I’ve caused something of the same, and I apologize.

But at least the script is fine!

my new menu bar guilt trip

25 Apr 2015

I just discovered TextBar via this article and it’s a pretty sweet Mac app.

You provide it a script and an interval, and it runs the script over and over, and prints the result to your Mac menu bar.

It comes with a few neat starter scripts which tell you things like how full your disk is and which wifi network you’re attached to.

For some reason, my instinct was to make a script that told me how long it’s been since I updated this blog.

I tried writing it as a shell script but gave up halfway through and switched to Ruby because, as much as I enjoy a challenge, parsing dates with the Mac date utility was making me sad.

#!/bin/sh

function latestPost {
  local xml=$(curl http://www.hardscrabble.net/feed.xml 2>/dev/null)
  echo $xml > /tmp/feed.xml
  local lastPost=$(xmllint /tmp/feed.xml --xpath "//item[1]/pubDate/text()")
  echo $lastPost | ruby -rdate -e "
    def pluralize(number, word)
      %{#{number} #{word}#{'s' unless number == 1}}
    end
    diff = (DateTime.now - DateTime.parse(STDIN.read)).to_f
    days = diff.to_i
    hours = ((diff * 24) % 24).round
    STDOUT.write %{#{pluralize days, 'day'} and #{pluralize hours, 'hour'}}"
}

latestPost

This is kind of a Frankenstein script (it even has a little Rails in it) but it works so :bowtie:.

To use it with TextBar, I put this in a file and made it executable, and then just referenced the path to the file as the “Script”:

TextBar UI screenshot

I’m having it refresh hourly, because the script is only specific to the nearest hour, so it’ll always be more-or-less right.

Here’s what my menu bar currently looks like:

menubar screenshot

(Tweetbot, this script via textbar, postgres, 1password, dropbox with some notification?, google drive, alfred, caffeine, and then some native Mac stuff)