Moving Quickly with Compositional Chains
When you find a more powerful tool, it’s hard to avoid using it. We like to think of this as the ‘if all you have is a hammer, everything looks like a nail’ syndrome, but the fact of the matter is - I have plenty of tools. It’s just that some are more powerful and it's hard not to use them.
Last night, I was playing noise music at an art space with a friend, and I met a couple of other guys who were doing interesting work. One of them had done a bit of programming and was excited about the the possibilities of algorithmic composition. We had a chat and spoke about collaborating. Naturally programming came up. He mentioned that he’d like to have a program that creates guitar tablature notation from simple text files. I love little toy programs so I started thinking about it.
Tablature is a notation that often easier for guitarists than standard musical notation. In standard notation, you see the notes that you need to play but the guitar gives you many options. The same note can be played in many different places on the guitar neck. It’s not uncommon to sight-read a piece and discover that it’s easy to play a note where your hand is currently, but the next notes will either have you stretching your hand unnaturally or moving around too much to be comfortable.
Tablature solves this by notating the strings of the guitar as lines and placing numbers on the lines that indicate the exact fret you have to press. With this notation, the only variable that flummoxes people is hand size.
Here’s an example of tablature. The top line is the highest pitched string. This tab tells us that
we have to press down at frets 5, 3, 1, and 3 successively on that string and then move down to the next string to press at fret 3, and so on.
5--3--1--3--------------5--3--1--0-----
------------3--1--0--------------------
---------------------2-----------------
---------------------------------------
---------------------------------------
------------------------------------1--
Writing this out is tedious. What would it be like if we had a program that takes lines of string/fret pairs as input and produces the tab for us:
1 5
1 3
1 1
1 3
2 3
2 1
2 0
3 2
1 5
1 3
1 1
1 0
6 1
How would we write it?
This is what I ended up with:
STRING_COUNT = 6
def tab_column string, fret
["---" ] * (string - 1) +
[fret.ljust(3,'-')] +
["---" ] * (STRING_COUNT - string)
end
puts ARGF.each_line
.map(&:split)
.map {|string,fret| tab_column(string.to_i, fret) }
.transpose
.map(&:join)
.join($/)
I pieced the code together in about ten minutes and then spent some time refactoring and testing.
Essentially, this code takes all of the lines within a list of text files at the command line and produces a “column” from each of them.” This is the first column produced for the input above:
5--
---
---
---
---
---
Note that the fret number is padded in a field of three hyphens. Each line of the column is an element in an array. After the map operation we have an array of arrays. Now, our job is to print it. First we transpose so that we have an array of the first element of each column, followed by an array of the second element of each column, etc. We can join the strings in those arrays to make a textual string that represents a string in tablature.
Here’s the first string:
"5--3--1--3--------------5--3--1--0-----"
And, here’s the second:
"------------3--1--0--------------------"
The final join in the code above joins all of the strings using a newline separator. At that point we just print, and we have tablature for our input.
Why is this worth writing about? Well, aside from it being fun, I think its a good example of how having tools like transpose and map can help you arrive at solutions quickly - if you know how to use them. Once you have them as part of your tool set you can dramatically reduce the amount of time it takes to arrive at a solution.
A few years ago, I did a presentation that I called ‘Ninja Secrets of the Enumerable Masters’ which was all about doing this sort of programming in Ruby. Ruby has a module called Enumerable that contains a vast number of methods that can be used for compositional programming. It is, however, hard to imagine what to do with many of them if you haven’t been doing this style of programming for a while.
The thing that tipped it for me was to become familiar with each them and then try to solve every problem I could by chaining functions together. It’s amazing how much you can do. The only disadvantage is that readability can suffer. When I write a toy like this, I spend quite a bit of time fussing with the code after I get it working to make it clearer.
Hearing this, you might think that it’s not worth it - that it’s better to just code in an old fashioned imperative way. I don’t think so. I think there is tangible benefit to being able to get to a solution faster. Power tools like ‘transpose’ help us validate our understanding of a problem quickly by seeing actual answers. If we choose to take a different approach to implementation later, at least we know that our understanding is solid.