Just now I finished the barely-running version of this little project, seriously using Ruby(actually MacRuby, I will mention it later) for the first time.

Althrough I fell in love with Ruby the first time I saw it, I didn’t realize that writting with Ruby is such a pleasure. A GUI program using multithread tech and carefully handling exceptions is written in only 200+ lines, which seems impossible to Objective-C(I should have used it if I didn’t see MacRuby). Before talking about the details, I remind you that the project is avaliable in my github repo. Feel free to give me some feedbacks.

MacRuby is a awesome hack

As we all know the most common language used to develop on Mac is Objective-C, besieds C and C++ also have a seat. MacRuby is a interface allowing you to use Ruby instead, which is a boon to Ruby lovers like me. If you don’t know the difference, I’m gonna tell you now.

In my project I must handle six checkboxs. And now, for example, I need to hide them. What should I do?

In Objective-C, the code may look like this:

1 [checkboxs0 setHidden:true];
2 [checkboxs1 setHidden:true];
3 [checkboxs2 setHidden:true];
4 [checkboxs3 setHidden:true];
5 [checkboxs4 setHidden:true];
6 [checkboxs5 setHidden:true];

Well, a little bit stupid. Why not using the loop statement. Actually I can’t if the six checkboxs named like this, I mean six variables with similar name. To do this, I must use array.

1 for(i = 0; i < 6; ++i)
2   [checkboxs[i] setHidden:true];

Looks better now, but here comes anthoer problem. The variables and corresponding GUI components are bundled together, which can be done easily by some dragging tricks, but only when the variable is individually and explicitly defined. Variables within an array can not be recognized by the IDE in this specific case. If you really want to archieve this, you can, by editing the tedious XML files by hand and you won’t like that.

Now Let’s see how Ruby solves this problem.

1 instance_variables.each do |checkboxs|
2   next if nil == (/checkbox/ =~ checkboxs)
3   instance_eval %{
4     #{checkboxs}.setHidden true
5   }
6 end

It needs six lines, the same as objective-c, but you can see that there is no repeated statement. I’m gonna give a detailed explaination of it.

instance_variables are actually a function which returns an array of instance variables of this class. you may think that it is just a method, but actually you are facing the most flexible part of Ruby – metaprogramming. The method returns the inner content of a class in runtime, which is impossible in static programming languages like C. Now with this method, I have an array of all the variables including the checkboxs

Statements between do and end, called code block, are a little trick in Ruby acting like iteration. With each method we know that for each variables, it will be assigned to checkboxs, which is a local variable, and run the following code. Actually code block is a different kind of function, called repeatedly and with specific arugments, which can make code cleaner and more readable.

next if nil == (/checkbox/ =~ checkboxs) is nothing but a if statement with if behind. Content within / are regular expression, with the samplest form of it, matches any string containing “checkbox”. next just like continue, means start the next step of iteration. So this line choose all the variable with “checkbox” in its name. In our example, they are checkbox0 et al.

So, you may think that we will call the setHidden method with each checkbox. Yes, but more complicated.

1 instance_eval %{
2   #{checkboxs}.setHidden true
3 }

instance_eval is such a method that tells ruby interpreter to run the argument string as ruby source code. It is a powerful but dangerous method. Running the code generated in the runtime cann’t guarantee security, but brings a lot of flexibility. Security is a huge issue, so I will not go any deeper here.

%{} is nothing but " " expression, but it is able to use “ freely in the block. #{} let the inner content “pop out”, means that in the string, the variable in the #{} block will be shown as its value in the string form (implicit call the to_s function which returns a descriptive string of an object). So the string will be like checkbox0.setHidden true et al.

Now you can see the difference. If we have 100 checkboxs, we need 100 verbose lines in objective-c but still 6 lines in ruby, because the six lines will select all the variables and call the function with it.

More about metaprogramming

I showed some ruby tricks of metaprogramming but it is just a drop in the ocean. Ruby allows you to do everything about the programme itself, like querying for the name of variables before, and more over, define a new method if you like.

In the project, I still have to write about six functions, or methods, to be the handler of the checkboxs. The functions must be explicitly defined for the same reason as before. Since the content of the functions are so similar and I have zero tolerance in repeating, I’ve come up with a way using metaprogramming again.

 1 def checkbox0_handler(sender); end
 2 def checkbox1_handler(sender); end
 3 def checkbox2_handler(sender); end
 4 def checkbox3_handler(sender); end
 5 def checkbox4_handler(sender); end
 6 def checkbox5_handler(sender); end
 7 
 8 GROUP_NUM.times do |i|
 9     class_eval %{
10       def checkbox#{i}_handler(sender)
11         if sender.state == NSOnState
12           @category_list << sender.title
13           refresh_textfield
14           @label.setStringValue "Successfully added \#{sender.title}"
15         elsif sender.state == NSOffState
16           @category_list.delete sender.title
17           refresh_textfield
18           @label.setStringValue "Successfully deleted \#{sender.title}"
19         end
20       end
21     }
22   end

What I’ve done is very clear. Define six empty functions to let the IDE know. Then do a loop statement to open each function and give them the corresponding content.

Open the function? I’ve never heard of it when I’m using C, but it is such an easy thing in ruby. Just define the function again, the interpreter will notice there has been a synonym function, so it open the funcion instead of giving a definition. Everything you do within the definition statements will be add to the function. So easy~

The code is similar to the former one so I skip the description. If you really like it, how about starting a ruby project now?

Conclusion

It is just a exercise project, but I did feel the power of ruby and I really love it. It brought me a lot of pleasures and surprises when coding and gave out the decent result as well. People who have never touched interpreted language should try to use it. And you will never regret.

Keyboard Stroke Frequency Analysis

Count and show keyboard strokes.

Koans Games

Published on May 12, 2014

About VIM and It’s Philosophy

Published on May 01, 2014