Rubies in the Rough

This is where I try to teach how I think about programming.

1

JAN
2012

Perl's Golf Culture

I'm stealing some time to write this while on vacation. I am also under the weather. Given that, we'll make this article short and easier on me to think up. That's not always a bad thing though. There are plenty of simple concepts I would like to get across. For example, let's talk about how Perl programmers do what it is they do.

Ruby's Sister Language

I spent plenty of time in the Perl camps and I really learned a lot about programming there. That may shock you to hear, because Perl programmers often get a bad wrap from the rest of the programming community.

One reason they catch a lot flak is that their language is often terse to the point of obscurity. We joke that Perl is a "write only" language or too hard for other developers to read. That would be bad enough on its own, but Perl programmers seem to intentionally make this worse.

Perl programmers love to play the programmer's version of golf. That is writing a program with the fewest possible keystrokes. To shrink their program's size, they will resort to every dirty trick in the book, including:

  • Using one letter variable names
  • Eliminating all non-needed whitespace
  • Using and abusing global variables
  • Using magic numbers that only work on the desired subset of a problem
  • Intentionally avoiding the abstractions that keep us sane
  • Throwing as much code in every statement as the language allows

I could go on and on, but perhaps it's better to just show you what I mean. Here's a golfed program that prints the first 32 numbers in the Fibonacci sequence:

$ perl -e '$a=1;print$a-=$b+=$a*=-1,$/for 0..31'
0
1
1
2
3
5
8
13
21
34
55
89
144
233
377
610
987
1597
2584
4181
6765
10946
17711
28657
46368
75025
121393
196418
317811
514229
832040
1346269

If you know a little Perl, spend some time working out how that does its job. It took me about 20 minutes or so. I learned some great tricks too.

I'm not going to take you all the way there, but I'll give you some hints for how I broke that program down. This is your last chance to try it yourself, and learn more, before I start ruining it for you…

The biggest trick to understanding alien code is to modify it. Making changes allows us to observe what is happening. For example, I thought I remembered that $/ was a special Perl variable, but I couldn't remember what it contained. To find out, I just replaced it:

$ perl -e '$a=1;print$a-=$b+=$a*=-1,"-"for 0..31'
0-1-1-2-3-5-8-13-…1346269-

That reminded me that $/ holds the "record separator" or essentially a newline.

It didn't look like the for loop was doing much, so I tried to yank it out:

$ perl -e '$a=1;print$a-=$b+=$a*=-1,$/'
0

I was right. It was causing the code to repeat, but the value of the loop wasn't being used.

Finally, I started trying to figure out the math transformations, bit by bit:

$ perl -e '$a = 1; $a *= -1; print $a, "\n"'
-1
$ perl -e '$a = 1; $b += $a *= -1; print $b, "\n"'
-1
$ perl -e '$a = 1; $a -= $b += $a *= -1; print $a, "\n", $b, "\n"'
0
-1

You get the idea.

Our Way

I can translate that program into Ruby, but it looses something along the way, in my opinion.

Our version of …for 0..31 is a minimum of 32.times{…}. We also can't use Perl's autovivification of the $b variable, so we will need to initialize it manually. Finally, we need to break the math into two chunks, due to a difference in Ruby's assignment semantics. On the upside, we can switch from print to puts and drop some $'s.

Our code is the same size, but I feel it has lost some of its Perlish qualities:

$ ruby -e 'a,b=1,0;32.times{b+=a*=-1;puts a-=b}'
0
1
1
2
3
5
8
13
21
34
55
89
144
233
377
610
987
1597
2584
4181
6765
10946
17711
28657
46368
75025
121393
196418
317811
514229
832040
1346269

The most surprising difference in the Ruby code is how I had to break up the math. It doesn't work correctly if I don't do that. Here's why:

$ perl -e '$a = 1; $a -= $b += $a *= -1; print $a, "\n", $b, "\n"'
0
-1
$ ruby -e 'a, b = 1, 0; a -= b += a *= -1; puts a, b'
2
-1

As you can see, Perl and Ruby handle that last assignment differently. Both of them multiply a's 1 by -1, assign the result to back to a, add -1 to b's 0, and assign that back to b. For the last step, Perl subtracts -1 from the modified value of a: -1 from the rightmost assignment. Ruby, on the other hand, subtracts -1 from the original value of a: 1. It looks like Ruby handles all of the variable dereferencing before it ever starts the assignments, so all a's in the expression are their original value. Perl seems to lazily resolve the variables, so it picks up that a changed in the same expression.

This difference makes sense when you think about it. Ruby has multiple assignment and it allows us to swap variables with it. For example, this does what you expect in Ruby:

a, b = b, a

It makes sense that Ruby needs to dereference all of those variables before it handles that assignment.

Perl can do the same trick, but you have to use a different syntax to kick in its multiple assignment behavior. Without it, Perl seems to just lazily build up the references as needed.

Why We Do Crazy Things

This silly exercise brings me to a point: Rubyists hate golf. Sure, some of us play, but there's no comparing the two cultures. It's huge in Perl and an afterthought in Ruby.

There are several reasons for that. First, as we saw above, Ruby fights being bent that way a bit more than Perl does. Also, Rubyists tend to place a super high value readable code, so it's just against our nature to try and make it ugly.

However, you need to reconcile this in your mind: Perl's golfers–the good ones anyway–are generally great programmers. I know many of them and they are real experts. How can that be?

I've got one word for you: experimentation.

When Perl programmers are playing golf, they are experimenting with their language. It's hard to do that and not learn a ton. Heck, I learned quite a bit just translating 36 bytes of Perl above. I didn't expect to be bitten by how Ruby assigns variables, for example, and it took me a bit to work out what was different there.

I won't use that knowledge for evil. I'm not going to try collapsing my Rails applications down to one line. This exercise didn't make me think all variables should be one letter. I'm still the same programmer, but I'm now armed with more knowledge.

Someday I may find myself talking to a Rubyist who says something like, "I just don't understand what Ruby is doing with these assignments." I'll know and I'll be able to help them, all because I screwed around with a Perl one-liner today.

You don't have to play golf, but experimenting to learn more is important. Doing quizzes, katas, and the like can teach you a ton. Remember that programmers are always measured by how many ideas they can come up with. The more ways you can think of to attack a problem the better off you are. Experimentation gives you a head start on idea generation.

In the next article, I'll show you a very Rubyish form of experimentation and what we can learn from it…

Comments (0)
Leave a Comment (using GitHub Flavored Markdown)

Comments on this blog are moderated. Spam is removed, formatting is fixed, and there's a zero tolerance policy on intolerance.

Ajax loader