Genetically Modified Origin

George's Blog - Digital Adventures in The Big Apple

You Can Learn a Lot From Shaving a Yak

| Comments

On the third day of class, we learned a term called “yak shaving”. The idea is that you want to do something, let’s say change a lightbulb, but that ends up requiring you to do something else, which in turn requires you to do something else, which requires you to do something again… After diving into subtask after subtask, eventually you find yourself shaving a yak when all you wanted to do was change your lightbulb!

Etymology: the term is thought to have been coined at the MIT AI Lab circa 2000, as inspired by an episode of Ren & Stimpy. Here are some definitions from various sources: MIT, Urbandictionary, Seth Godin (who helped popularize the term)

I didn’t think much about the term until pretty recently. In the last 2 weeks or so, as I’ve been diving heavily into code, I’ve found myself shaving yaks constantly – I can see why it’s a “thing” in programming now!

Case in point – the jukebox. As my teammates can attest, I’ve been building a command-line interface jukebox which when given a list of songs, will retrieve the songs’ artists and genres, and allow the user to browse or choose by category and select a song to “play” (it only displays the song & doesn’t actually play it yet, haha). Well technically, this was an assignment everyone had to do, but since I liked the idea I decided to take it a lot further. (Also, to order to generate a song list, we parsed a directory for mp3 files and built a library of artists, songs, and genres)

My current version of the CLI jukebox can not only browse by “artist”, “song”, or “genre”, but it can also search by artist, song, or genre, and whatever the results are of the current search or browsing results, you can keep entering input to further filter results, as well as choose a result by number (thanks Ning for some of the inspiration!)

Here are some examples of yak shaving while making the CLI jukebox:

  • In order to get the program to understand so many different kinds of user inputs, I ended up having to learn regular expressions (Rubular is awesome!). Specifically, valid commands that the program understands are stored in class constant:
Valid Inputs
1
VALID_COMMANDS = [/^(artist|song|genre)s?$/, /^(artist|song|genre)\s+\S+/, "stop", "help", "exit"]

This allows for the program to understand the input “artist(s)”, “song(s)”, “genre(s)”, as well as the input “artist [name or number]”, “song [name or number]”, “genre [name or number]”.

Here is what my code for seeing whether or not a valid command is recognized looks like:

See if input is valid
1
@valid_command_entered = !VALID_COMMANDS.grep(@command).empty?

This doesn’t work though. After some banging my head on the wall, I ended up reading the “grep” documentation in the Ruby Enumerables module, and discovered the problem:

Problem
1
2
3
4
5
6
7
8
 # [element1, element2, etc].grep(pattern) only matches when pattern === element
 # However, if element is a regular expression grep will fail to find the match
  #    /abc/ === "abc" returns true
  #    However, "abc" === /abc/ returns false

   # We want to enhance the functionality so that it also matches when element =~ pattern
  #    /abc/ =~ "abc"  &  "abc" =~ /abc/ will both find a match
  #    if element and pattern have the same class, "=~" won't work and we will have to use ==

Simple enough. We open the class and give it a new method grep2:

The Fix
1
2
3
4
5
6
7
8
9
10
11
class ::Array
  def grep2(pattern)
    self.select do |element|
      if pattern.class == element.class
        element == pattern
      else
        element =~ pattern
      end
    end
  end
end
  • After creating this program on my own computer, I wanted to import this to the web so that it could run as a CLI “web app” over the internet. I was inspired by a few command-line interface websites: xkcd, goosh, After many futile attempts at finding an answer which made me question my google-fu, I finally found something reasonable which had been staring at me in the first page of google results: a Ruby gem called Rack Webconsole! After installing, more banging my head on the wall and finagling, I finally got it to work:

Now I need to learn how to get “puts” and “gets” to work in the webconsole interface instead of on the server-side console:

To be fixed…
1
2
3
4
5
6
7
8
9
10
11
12
13
  def output(string)
    puts string #=> doesn't work right
    # Ripl.shell.print_result(string)
    # Rack::Webconsole::Shell::eval_query(string)[:result]
    # Ripl.shell.loop_eval(string)
    #   ==> none of the above work...
  end

  def input
    # gets  => no longer works
    # Ripl.shell.loop_once
    #   ==> fix this too...
  end
  • I also learned about a little bit about XRSF, a way for bad people to hack into your website, as I was learning about Webconsole. In retrospect, using this gem probably isn’t the best idea to use in production, though it’s pretty awesome in development. Maybe I’ll learn enough javascript by looking at the source code of sites like xkcd and goosh to make this work someday.

Enjoying the view and striving for the destination

There’s always more to learn, and to do something I thought would be so simple ended up requiring hours and hours of banging my head on the way. However, in retrospect it was all a great learning experience and helped me become a better coder, as I suspect yak shaving can oftentimes be.

It’s the journey that matters, not the destination. However, a program that doesn’t work right isn’t useful either. So I guess both are important – enjoy the view along the way, but to learn the most, don’t stay satisfied until you reach the destination.

Comments