2008-08-13

Non-standard standard methods

Hello there again. Today we're discussing one useless and potentially distructive thing. But it's gonna be very educational.

The mad machine
There's a story about two engineers: Trurl and Klapaucius. They built once a machine that was absolutely sure that two plus two is seven. They had a lots of problems as the machine was chasing them to prove it was right, so be aware today!

When you open irb and write
2+2
, do you suppose the stupid machine could answer
7
? Well, let's imagine I'm Trurl and you're Klapaucius. Copy the following into your irb (you can copy all lines at a go):
class Fixnum
alias q +
def +(b)
if self==2 and b==2
7
else
q(b)
end
end
end
Try now:
2+2
Oh no, RUN, the machine is MAD! ... ahh no, sorry. We just changed the way that numbers get added to each other. Let's analyse what we just did here. First, we said
class Fixnum
. That would create a class, if only the class had not already existed. But if it does exist, this instruction does not do anything, it just signalises that we're going to add or change something inside this class. Note here the difference in behaviour between "redeclaring" classes and methods: when you declare again the same method, it destroys the old one (I explained this in the previous post), but if we "redeclare" a class, we just enter the inside of the class. Opening a class in this way is never destructive.

OK, what now? Let's skip for a moment the second line. We want to redeclare the method
+
. We do it here:
def +(b)
. As you see, we can define operators just like any other methods. This also means that writing
10.+(5)
in irb will work just like
10+5
- they both simply call a method
+
of the object
10
with the argument
5
.

So what we do inside this newly defined addition? We check, if both the object whose method it is (
self
) (that is: the first operand), and the second operand
b
are 2, we return 7. And if not, we're calling a method
q
(it's a one-argument method, wait a moment) (it could be written:
self.q(b)
, because it is method of the current object, that is the first operand).

So what's that method
q
? This method is defined in this line
alias q +
, and this line means: define method
q
that does exactly the same as the method
+
. Now note it well, that the
alias
line is above the
def
line, so the new method
q
is exactly the same as the old method
+
, not the new one that we just define below! So now we've covered the method
+
with the new one, so the old one is not accessible under its name
+
, but, what a lucky coincidence - we've made a "copy" of the old method
+
by giving it a new name
q
. So now, from within the new adding method, we can call the old one under its new name, and in this way we are able to add the two numbers if they are not two and two. That's how we maintain the old functionality while adding new one under the same name (the same method name, namely).

What are you doing, irb?
Let's try a variation now. (Close the console and open a new one to get rid of the mad machine.)
class Fixnum
alias q +
def +(b)
puts "#{self}+#{b}"
q(b)
end
end
Note that we used a very useful notation here: if you type
"a String #{with_something_inside} encosed like this,"
the thing in the braces gets evaluated and the result is inserted into the string.

Now let's type something trivial in the console now. I typed this:
"Al2O3::Cr"
:
irb(main):009:0> "Al2O3::Cr"
8+1
85+1
0+1
86+1
1+1
86+1
1+1
87+1
2+1
88+1
3+1
89+1
4+1
90+1
5+1
91+1
6+1
92+1
7+1
93+1
8+1
94+1
9+1
95+1
10+1
96+1
9+1
=> "Al2O3::Cr"
irb(main):010:0>
Wow, now we know what additions were performed by irb! Usually you cannot tell what these additions really do, but the last one is probably the line number incrementation in irb. Other of them are responsible for parsing the string and displaying it.

inspect
One more trick. You must have noticed that the objects can be displayed in two ways: as an internal representation, or as a displayable thing:
irb(main):001:0> obj="a \"string\"\nsecond line"
=> "a \"string\"\nsecond line"
irb(main):002:0> puts obj
a "string"
second line
As you see, when we
puts
a string, the result is something else than when we just use it, and irb shows us how it looks after the
=>
sign. The same is true for other types than String:
irb(main):003:0> obj=["a",5,:x]
=> ["a", 5, :x]
irb(main):004:0> puts obj
a
5
x
=> nil
irb(main):005:0> obj.to_s
=> "a5x"
Even one more representation here, obtained by calling
to_s
. So, how does it work?

The method
to_s
for a String returns itself, and for Array returns concatenation of elements'
to_s
's ( Note that this is going to change in Ruby 1.9!). But when irb wants to show us a result of an operation, it doesn't use
to_s
. It uses the method
inspect
. Try it:
[1,"a"].inspect
returns something that looks like
"[1, \"a\"]"
in irb, and what is really
[1, "a"]
, that is exactly how irb presents this value after the
=>
sign. You can emulate irb's behaviour like this:
puts obj.inspect
or easier
p obj
, but you are not very likely to use it in real programs, save for debugging purposes.

Let's do a little change now:
class String
alias inspect_old inspect
def inspect
inspect_old.gsub(/(^")|("$)/,"*")
end
end
We replace the method
inspect
with a new one, that calls the old one, and then replaces the quotation marks (
"
) at the beginning and the end of the result of the inspection with asterisks. Now just try it out:
irb(main):019:0> "Susan"
=> *Susan*
Do you like it?

No comments: