Hardscrabble 🍫

By Max Jacobson

order of operations

16 Jun 2015

Last month, we looked at Ruby operators, and I complained about how I wish I could define my own operators. Today I’m looking at them a little more, and thinking about how Ruby handles expressions made up of multiple operations.

Let’s say you have this Ruby code:

sum = a + b + c

What are a, b, and c? They could be

  1. variables containing numbers; that’s what they kind of look like they want to be
  2. invocations of methods which return numbers
  3. variables or methods that contain/return absolutely anything else; let’s not worry about this
  4. maybe they’re not even defined at all; let’s not worry about this possibility either

Let’s look at how option 1 plays out:

a = 1
b = 1
c = 1
puts a + b + c
# 3

So far, so good.

Let’s see how option 2 plays out:

def a; 1; end
def b; 1; end
def c; 1; end
puts a + b + c
# 3

Sort of funky-looking, but also sort of straight-forward. Here’s the question though: if Ruby is calling those 3 methods to get those 3 values, what order are they being called in? Let’s find out:

def a
  puts "a"
  1
end

def b
  puts "b"
  1
end

def c
  puts "c"
  1
end

puts a + b + c
# a
# b
# c
# 3

It kind of makes sense. It’s just going from left to right, like English.

One cool thing about Ruby is that (almost) everything is an object, and even core things like math operations are implemented as methods. This means the above could be written like this:

puts a.+(b).+(c)
# a
# b
# c
# 3

This rendition makes it clear that this is a chained sequence of method calls. Let’s make it even more clear, by refining the plus method and adding some logging:

module MathLogger
  refine Fixnum do
    alias_method :original_plus, :+

    def +(other)
      original_plus(other).tap do |sum|
        puts "#{self} + #{other} = #{sum}"
      end
    end
  end
end

using MathLogger

def a
  puts "a"
  1
end

def b
  puts "b"
  1
end

def c
  puts "c"
  1
end

puts a.+(b).+(c)
# a
# b
# 1 + 1 = 2
# c
# 2 + 1 = 3
# 3

Now it’s not as simple as “left to right”. We start at the left and call the a method. But the next method we call is b, not +. Before we can add two values, we need to know what the values are, and Ruby will evaluate the expression in parentheses (here it’s calling a method, but it could be calling multiple methods and they would all be evaluated before the + method is called).


A brief digression about defined?

This rule doesn’t apply to the defined? method, which ships with Ruby and behaves like this:

msg = "Hello"
defined?(msg) #=> "local-variable"
OMG           #=> NameError: uninitialized constant OMG
defined?(OMG) #=> nil
OMG = 4
defined?(OMG) #=> "constant"

The third line of this excerpt demonstrates that referencing an uninitialized constant normally raises a name error, so it would be normal to expect the same to happen on the fourth line, because we just saw that Ruby normally evaluates the arguments to a method. Here it just totally doesn’t, which feels kind of weird and inconsistent. It might be helpful to think of defined? as a language keyword and not a method. See also the alias method.


Back to math. Remember PEMDAS? When evaluating an arithmetic expression, we’re not supposed to just read from left to right, evaluating operations as we go; we’re supposed to prioritize some operations above others:

  • Parentheses
  • Exponents
  • Multiplication
  • Division
  • Addition
  • Subtraction

With this acronym memorized, children are able to evaluate complicated math expressions.

Can Ruby? Let’s see:

4 + 3 * 5   #=> 19

Well… yeah! Seems right! But let’s take a look into the order that methods are being called:

module MathLogger
  refine Fixnum do
    alias_method :original_plus, :+
    alias_method :original_times, :*

    def +(other)
      original_plus(other).tap do |sum|
        puts "#{self} + #{other} = #{sum}"
      end
    end

    def *(other)
      original_times(other).tap do |product|
        puts "#{self} * #{other} = #{product}"
      end
    end
  end
end


using MathLogger

def four
  puts 4
  4
end

def three
  puts 3
  3
end

def five
  puts 5
  5
end

puts four + three * five
# 4
# 3
# 5
# 3 * 5 = 15
# 4 + 15 = 19
# 19

Interesting! So, Ruby takes a look at four, takes a look at three, and then skips the addition, then takes a look at five, and performs the multiplication. Only then does it double back and perform the addition, inlining the product of three and five.

That’s great! And surely, if all of these operations are just methods, it will behave the same when I change it to this?

puts four.+(three).*(five)
# 4
# 3
# 4 + 3 = 7
# 5
# 7 * 5 = 35
# 35

Hm, nope. When we call the methods directly, the order of operations breaks.

I always thought it was just “syntactic sugar” that I could omit the dot when calling the + method (and its siblings) but it’s doing slightly more than just inlining the dots: it’s also, more or less, inlining the parentheses, so it looks something like this:

puts four.+(three.*(five))

How does it choose where to put the parentheses? It has a precedence table which Ruby references when deciding which operations to evaluate before others. This means that if I were able to define my own operators, I would need to be able to insert them somewhere in this hierarchy, and this hierarchy would also be cluttered with all the operators added by the gems included in my project.

Naturally, my operator would be at the top of the list.

designing your own extendible command line utility

10 Jun 2015

I like that you or I can write command line utilities in any language, name it git-butterfly, or whatever you want, and then run it as though it were an official part of the git tool, as git butterfly.

It feels a lot like magic, but it’s actually not that complicated a thing to do. If you don’t believe me, watch this video or skip after it to the code, and make your own extendible utility, pig.

designing your own extendible command line utility from Max Jacobson on Vimeo.

If you want your own pig command line utility, copy this code to an executable file on your $PATH called pig. Then, try running pig. Try running pig oink.

#!/usr/bin/env sh

subcommand=$1
bin="pig-$subcommand"
paths="${PATH//:/$'\n'}"

if [[ -z $subcommand ]]; then
  echo "Gimme a subcommand"
  exit 1
fi

for path in $paths; do
  pathToBin="$path/$bin"
  if [ -f $pathToBin ]; then
    $pathToBin
    exit 0
  fi
done

echo "$bin is not a pig command"
exit 1

gemfiles are ruby files

02 Jun 2015

A while back I saw this cool blog post: Stay up to date with the latest github pages gem by Parker Moore, who maintains Jekyll. Jekyll is the static site tool that powers github pages, but github pages doesn’t necessarily use the latest version of Jekyll. If you’re deploying your website to github pages, you probably want to make sure your local Jekyll server behaves in the same way as the Jekyll that runs on github pages.

A Jekyll site is a Ruby project, and Ruby projects should have a Gemfile listing their dependencies. Jekyll is a gem, so you might think that you should add this to your Gemfile:

gem 'jekyll'

But actually, maybe not!

Consider this intsead:

gem 'github-pages'

This includes the exact version of Jekyll that github pages is using (and a few other things).

That’s great, because you know you can preview your website locally and know that it will look the same when you deploy it to GitHub pages.

You may be asking: what about when they upgrade the version of Jekyll they’re using to compile your site?? Well, they release a new version of the github-pages gem, which bumps its Jekyll dependency. So, ideally, before pushing to your Jekyll site on github pages, you should know that you’re using the latest version of the github pages gem.

Here’s how you can do that (so far I’m just summarizing that Parker Moore blog post):

require 'json'
require 'open-uri'
versions = JSON.parse(open('https://pages.github.com/versions.json').read)
gem 'github-pages', versions['github-pages']

Isn’t that cool? Now, when you start your local server with bundle exec jekyll serve, it will confirm that you have the appropriate version of github pages.

Happy ending? Sort of.

I did that a few months ago, and was happy for those months. Then, last weekend, I found myself somewhere without internet access and a blog post idea, but was sad to see that I was unable to run my local blog server because my Gemfile was unable to connect to the internet to confirm it had the latest dependencies.

When you’re without internet access, you can’t push changes to your blog anyway, so there’s not really any danger of seeing an offline preview of what the site looks like without confirming you have the latest versions of everything.

Now my Gemfile looks like this:

begin
  require 'json'
  require 'open-uri'
  versions = JSON.parse(open('https://pages.github.com/versions.json').read)
  gem 'github-pages', versions['github-pages']
rescue SocketError
  gem 'github-pages'
end

Nice! When the network request fails, a SocketError is raised, so we’re able to rescue that error and fallback to any old version of the gem. This might still fail; it assumes that we’ve previously successfully installed a version of the gem to fall back to, and that version is cached locally by bundler.

It’s neat that we’re able to use whatever Ruby we want to in our Gemfile, but kind of non-obvious because it doesn’t end with .rb. Well, what if it was just called something else? Turns out you can rename your Gemfile to gems.rb, and your Gemfile.lock to gems.locked since Bundler 1.8.0, and this will be the new normal in Bundler 2.0.

Neat! Now my Gemfile looks like the above, but it’s now my gems.rb file.