MSA-Overloading the + Operator-Ruby

__NOTITLE__

Table of Contents = Overloading the + Operator (Ruby Version) =

A DRY Version of Modified Euler
Dan: Now how did we get into all this array stuff?

Erica: I wanted to move on to the leapfrog algorithm, but Carol brought up the DRY principle, Don't Repeat Yourself, insisting on first cleaning up the modified Euler code. ..

Carol:. . . which we haven't done yet, but now we're all set to do so! It is just a matter of translating the old file euler_modified_1000_steps.rb, introducing our array notation, just as we did for the code in euler_array_each_def.rb.

Here it is, in euler_modified_array.rb

include Math def print_pos_vel(r,v) r.each{|x| print(x, " ")} v.each{|x| print(x, " ")} print "\n" end r = [1, 0, 0] v = [0, 0.5, 0] dt = 0.01 print_pos_vel(r,v) 1000.times{ r2 = 0 r.each{|x| r2 += x*x} r3 = r2 * sqrt(r2) a = r.map{|x| -x/r3} r1 = [] r.each_index{|k| r1[k] = r[k] + v[k]*dt} v1 = [] v.each_index{|k| v1[k] = v[k] + a[k]*dt} r12 = 0 r1.each{|x| r12 += x*x} r13 = r12 * sqrt(r12) a1 = r1.map{|x| -x/r13} r2 = [] r1.each_index{|k| r2[k] = r1[k] + v1[k]*dt} v2 = [] v1.each_index{|k| v2[k] = v1[k] + a1[k]*dt} r.each_index{|k| r[k] = 0.5 * ( r[k] + r2[k] )} v.each_index{|k| v[k] = 0.5 * ( v[k] + v2[k] )} print_pos_vel(r,v) }

And before Dan can ask me to do so, let me run it:

|gravity> ruby euler_modified_array.rb | tail -1 0.400020239524913 0.343214474344616  0.0  -1.48390077762002  -0.0155803976141248  0.0

I will also compare it to the previous result:

|gravity> ruby euler_modified_1000_steps.rb | tail -1 0.400020239524913 0.343214474344616  0.0  -1.48390077762002  -0.0155803976141248  0.0

Not quite DRY yet
Dan: Bravo! Same answers. And yes, the code has become shorter. I like that.

Erica: Hmmm, just making a code shorter does not necessarily make it better. You can use semicolons to put two or three lines on one line, but that doesn't make the code any prettier. Most likely, it will make the code more opaque.

In fact, I'm sorry to say, I don't find the new code easier to read. While the old code was longer, it was very easy to see what happened. But in the new code, there are all those [k] clumps floating around. . . I thought the whole point of using arrays was that we could hide the elements of the array!

Carol: To some extent, we have hidden things. The methods  map,  each and each_index can be attached directly to the arrays themselves, without showing elements. And our use of  each in the print statements shows an example where there is no ugly [k]</tt> showing up at all. But I agree with you, we should be able to do better.

Erica: Can we really do much better? An array does seem to be the natural way to represent a physical vector quantity in a computer program. I've never seen any other way. . . ah, no, I take that back. I once saw a C++ program, where the writer had introduced a special vector class.

Carol: Can you remember what the reason was for doing so?

Erica: I'm not exactly sure now, but it may have had to do with making it easier to add vectors, by overloading the +</tt> operator, and that sort of thing.

Carol: That sounds exactly like what we need. If we take a look at the first line in our code that contains one of these ugly [k]</tt> notations that you so disliked:

r1 = [] r.each_index{|k| r1[k] = r[k] + v[k]*dt}

you really would like to write this as

r1 = r + v*dt

right?

Erica: Yes, that would be great! I would love to get rid of all those [k]</tt> blocks. In fact, I think that we should get rid of them if we want to follow the DRY principle. Look, we have been repeating this [k]</tt> thingy three times in one line, in our latest code!

13.3. Array Addition
Carol: Fair enough. Well, it's always a good idea to start simply. The simplest case I can think of is to add two vectors. If we continue to represent them as arrays, we can add arrays a1</tt> and a2</tt> to obtain their sum  a</tt> as follows:

a = [] a1.each_index{|k| a[k] = a1[k] + a2[k]}

which includes the declaration, which is necessary if  a</tt> has not yet been introduced as an array, earlier in the program.

Now what you would like to write is

a = a1 + a2

without any further complications that include references to elements [k]</tt> or to methods such as each_index</tt> or to a declaration of  a</tt>, since after all the addition of two vectors should obviously produce a new vector. Right?

Erica: It sounds too good to be true, to be able to leave out all that crap, and to tell the computer only the minimal information needed, as if you were writing a note for yourself on a scratch pad. Do you really think you can implement all that, and even do away with the need for declarations?

Carol: I think so. First, let's see what happens if we don't make any modification. I must admit, I'm not sure what Ruby will do if we ask it to add two arrays. Well, let's find out:

|gravity> irb a1 = [1, 2, 3] [1, 2, 3] a2 = [5, 6, 7] [5, 6, 7] a = a1 + a2 [1, 2, 3, 5, 6, 7] quit

Dan: I guess that is one way to add two arrays, to just put them end to end, and string all the elements together. And for many applications that might just be the right thing to do, for example, if you have an array of the names of countries in a nation, and you want to add a few more names. But in our case, this is not what we want. We'd better get:

a = [1+5, 2+6, 3+7] = [6, 8, 10]

Carol: Of course, we can change the definition of "+" for array.

Dan: How can you change the definition of a built-in function?

Carol: In Ruby you can change anything, or at least almost anything! But let's take only one step at a time. The simplest and safest way to get the correct addition behavior, is to introduce a new array method. We can call it Array#plus</tt>, and use it to add two arrays in the way Dan just specified, according to the vector rules that we have in mind for the physical addition of two vectors.

Erica: But how can you add a new method to the  Array</tt> class? Somebody else already has defined the  Array</tt> class for us. I guess we'll have to dig into wherever Ruby is defined, and change the  Array</tt> class definition?

Carol: No digging needed! The neat thing about Ruby, one of the neat things, is that it allows you to augment a class. Even if someone else had defined a class, we can always add a few lines to the class definition, for example, when we want to add a method. The different bits and pieces of the class definition can live in different places. Nothing to worry about!

It should be simple. From what I've seen so far, I guess that Ruby wants us to write something like this, in file <tt>array_try_addition1.rb</tt>:

class Array def plus(a) sum = [] self.each_index{|k| sum[k] = self[k]+a[k]} return sum end end

This should add the method <tt>plus</tt>, that is doing the addition, to the other existing methods in the <tt> Array</tt> class. In this way, we don't disturb anything in the <tt> Array</tt> class, so everything should work as it did before. The only difference is that we can now add arrays in the way we intend for vectors.

Who is Adding What
Erica: That's a lot shorter and simpler than I had expected. It seems to deliver all the three things you promised: it hides the all <tt>[k]</tt> occurrences, it hides the <tt>each_index</tt> and it creates a new vector, so that you don't have to declare anything. And all that in just a few lines!

Dan: Not so fast. I'm not there yet. In fact, I don't understand this at all. To begin with, shouldn't addition have two arguments? You're going to add two vectors, no?

Carol: Yes, but here we're describing addition from the point of view of an array. Given one array, all we have to do is to specify one second array, which I have called <tt> a</tt>, which can then be added to the given array. The given array itself simply goes by the name of <tt> self</tt>, a reserved word that points to the current instance of the class.

Dan: You mean that the <tt> Array</tt> class definition describes arrays in general,but if I deal with a particular array, <tt>a1</tt>, then from the point of view of <tt>a1</tt> itself, <tt>a1</tt> is called <tt> self</tt>?

Carol: Right.

Dan: But we want to get the result <tt>a = a1 + a2</tt>. So from the point of view of <tt> a</tt> there really are two other arrays involved.

Carol: Yes, but at first we only have <tt>a1</tt> and <tt>a2</tt>. That's all we've got, and that's what we have to use to construct <tt> a</tt>. The way I'm trying to define addition is by starting with one of the two arrays <tt>a1</tt>, and to define a method that allows <tt>a1</tt> to accept a second array <tt>a2</tt>. So the whole operation can then be written as

a1.plus(a2)

where <tt>a2</tt> is the argument for the method <tt> plus</tt> that is associated with the class <tt> Array</tt> of which <tt>a1</tt> is an instance. Now this expression will result a new instance of the <tt> Array</tt> class, and we can assign that new instance to the new variable <tt> a</tt> by writing:

a = a1.plus(a2)

Dan: You can't write

a = a1 plus a2 ?

Carol: Sorry, no, you can't; that wouldn't make any sense. What you can do in Ruby is leave out the parentheses around the argument of a method. So instead of writing

a = a1.plus(a2) you can indeed write

a = a1.plus a2

The <tt> plus</tt> Method
Dan: I'll take your word for it. So if we write this, <tt>a1</tt> uses its own <tt> plus</tt> method, and according to the definition you wrote in the <tt> Array</tt> class, <tt>a1</tt> first creates a new array called <tt> sum</tt>, which is an empty array, specified by writing <tt>[]</tt>. Next it assigns the the value <tt>sum[k] = a1[k]+a2[k]</tt> to each component <tt>[k]</tt> of the array <tt> sum</tt>. That makes sense!

And finally, in the next line you return the value <tt> sum</tt>, before you reach the end of the method. In that way <tt> a</tt> receives the value that <tt>a1.plus a2</tt> returns, which is the sum of <tt>a1</tt> and <tt>a2</tt>. Okay, I got it now!

Carol: Let's hope it works, after everything I've told you!

|gravity> irb require "array_try_addition1.rb" true a1 = [1, 2, 3] [1, 2, 3] a2 = [5, 6, 7] [5, 6, 7] a = a1.plus a2 [6, 8, 10] quit

Dan: Wonderful! That's just what we ordered.

Erica: A great improvement over the old array addition!

The <tt>+</tt> Method
Dan: Still, I can't say I like your new notation. I'm still not happy about the asymmetry. Writing

a = a1.plus a2

gives the impression that <tt>a1</tt> is charging forward, gobbling up <tt>a2</tt> and then spitting out the result. You told me that we cannot write

a = a1 plus a2

and I understand that such a statement would have no clear meaning inRuby. But is there really no way to make the expression more symmetric, rather than making <tt>a1</tt> the predator and <tt>a2</tt> the prey?

Carol: Actually, there is a way to make it at least look more symmetric. It is just a form of syntactic sugar, as they call it: a way to let the syntax look more tasty, without really changing the underlying code.

The idea is what is called `overloading operators'. We can use the <tt>+</tt> symbol, instead of the word <tt> plus</tt>, and we can redefine the meaning of <tt>+</tt> for the case of arrays. This is what I meant when I said earlier that in Ruby you can change almost anything. I have read about that; let me see whether it works. I believe the idea is to write something like this, in file <tt>array_try_addition2.rb</tt>:

class Array def +(a) sum = [] self.each_index{|k| sum[k] = self[k]+a[k]} sum end end

A Small Matter: the Role of the Period
Dan: All you've done is to change <tt> plus</tt> into <tt>+</tt> in the second line. Can that really work?

Erica: There is one more change: you've also left out the word <tt> return</tt> in the third line of the definition.

Carol: Ah yes, most people writing Ruby seem to leave out <tt> return</tt>; it is really not necessary to add that. You just have to remember to let the last line of a definition echo the result you want to return. The result of invoking a method is to return whatever the last line of the definition evaluates to.

And yes, other than that, I've just replaced <tt> plus</tt> by <tt>+</tt>. In fact, in all cases where Ruby uses <tt>+</tt>, it is only syntactic sugar for invoking a method that is associated with the left-hand side of the <tt>+</tt> symbol. So even though it  looks symmetric, it never really has been a symmetric operation.

Dan: But how can it work? I thought that you always needed to write a dot between an object and its method.

Carol: Generally, that is true, and in fact, if you want to, you still can write the <tt>+</tt> operator using a period like normal Ruby methods.

Dan: Let me try:

|gravity> irb 2.+ 3 5 8.* 4 32 quit

Erica: Wow, surprise. They work just like ordinary Ruby methods.

Dan: Are you sure? Isn't <tt>2.</tt> just translated into <tt>2.0</tt>

so that we are only evaluating <tt>2.0 + 3</tt>? Let's check, by adding a space after the periods:

|gravity> irb 2. + 3 5 8. * 4 32 quit

Ah, you see, the zero is just added, like in Fortran.

Carol: I don't think so. Let me try a simpler case:

|gravity> irb 2. quit NoMethodError: undefined method `quit' for 2:Fixnum from (irb):1 from :0 quit

You see: <tt>2.</tt> is not translated into <tt>2.0</tt>, but is in fact illegal. Or more accurately, it is okay in Ruby to leave space after the period between an object and its method. Here <tt> irb</tt> is asking for the method name, and doesn't like the fact that I just wanted to quit; <tt> irb</tt> interpreted the <tt> quit</tt> as the method name it was waiting for.

Dan: I agree, we can now be sure that <tt>2.+</tt> really invokes the addition operator of the number <tt>2</tt>. Okay, now we know.

Carol: But of course, it is much more intuitively obvious to write <tt>2 + 3</tt> than to write <tt>2.+ 3</tt>. I have mentioned earlier the principle of least surprise, introduced by Matsumoto, the designer of Ruby, as a guide line for the Ruby syntax. Even though in fact Ruby has much in common with Lisp, Matsumoto decided not to use a lisp like notation, in which <tt>2 + 3</tt> would have looked something like <tt>(+ (2, 3))</tt>, a beautifully clear notation once you get used to it, but unlike <tt>2 + 3</tt> not immediately obvious when somebody comes across it for the first time.

Dan: I'd say!

Testing the <tt>+</tt> Method
Carol: Well, enough talk: let's test my second version of array addition:

|gravity> irb require "array_try_addition2.rb" true a1 = [1, 2, 3] [1, 2, 3] a2 = [5, 6, 7] [5, 6, 7] a = a1 + a2 [6, 8, 10] quit

Dan: I like this a whole lot better! And I'm glad Matsumoto did not introduce four parentheses to add two things.

Carol: I like it too, but I'm afraid we can't leave things like this.

Dan: Why not?

Carol: Because we've now been tinkering with the <tt> Array</tt> class, we can no longer use arrays in the standard way. That's why not.

Erica: Ah, you mean that we can no longer concatenate arrays, the way we saw before, using the <tt>+</tt> method. What did we do again? It was something like this:

|gravity> irb a1 = [1, 2, 3] [1, 2, 3] a2 = [4, 5, 6] [4, 5, 6] a = a1 + a2 [1, 2, 3, 4, 5, 6] quit

Dan: Of course, that no longer will work, when we add our modification:

|gravity> irb require "array_try_addition2.rb" true a1 = [1, 2, 3] [1, 2, 3] a2 = [4, 5, 6] [4, 5, 6] a = a1 + a2 [5, 7, 9] quit

But who cares? I don't expect us to have much use for array concatenation anyway.

Carol: Ah, not so quick. I think you should care a lot! If you use any Ruby program, written by someone else, you don't know what that program relies on. Most likely some Ruby programs do rely on the default way of array addition, in the form of concatenation. If you're going to change the default rules, you're likely to invite disaster.

When we introduced the new <tt> plus</tt> method, there was no danger, since we left the existing methods, such as <tt>+</tt>, alone. That's fine. But tinkering with existing methods is simply a bad idea.

A <tt> Vector</tt> Class
Dan: Is there no way out? I like what we've done, and it would be a pity to give it up, now that we've just figured out how to do it.

Carol: Yes, there is a way. What we want to do is to introduce a new class, similar to the <tt> Array</tt> class, but with a different name and with somewhat different rules. To be precise, we want to define a vector class. Ruby has the convention that class names start with a capital, so a natural name for our new class would be <tt> Vector</tt>.

In principle, we could define our new class from scratch, but it would be a lot easier to use the features that the <tt> Array</tt> class already has. All we really want to do is to tame the <tt> Array</tt> class to behave like proper physical vectors, and we can do this by redefining only some of the array operations, such as adding two arrays, as we have just done, and multiplying an array with a scalar, and probably a few more such operations.

In Ruby, just like in C++ and many other object-oriented languages, we can do this by an approach called inheritance. Instead of just defining a new class

class Vector

we can write

class Vector &lt; Array

which means that the new <tt> Vector</tt> class inherits all the features of the existing class <tt> Array</tt>. The <tt> Vector</tt> class is then called a subclass of the <tt> Array</tt> class, and the <tt> Array</tt> class is called a superclass of the <tt> Vector</tt> class.

Okay, let me see whether I can redefine the array addition operator, so that vectors can be added in the right way. From what I've seen so far, I guess that Ruby wants us to write something like this, in file <tt>vector_try_addition2.rb</tt>, to replace <tt>array_try_addition2.rb</tt>:

class Vector &lt; Array def +(a) sum = Vector.new self.each_index{|k| sum[k] = self[k]+a[k]} sum end end

This new class definition so far contains only one new method, by the name of <tt>+</tt>, that is doing the addition. Note that I create an empty new vector by writing <tt>Vector.new</tt> instead of <tt>[]</tt>, the notation we used to create a new array. In fact, <tt>[]</tt> is simply syntactic sugar for <tt>Array.new</tt>. So therefore it is a straightforward change to replace it by <tt>Vector.new</tt> as I've done above.

We can use this new class in the same way as we did before, but there is one difference: we have to declare all objects we will play with to be vectors. Before, we declared objects as arrays by using the <tt>[]</tt> notation, which is really a shorthand for <tt>Array[]</tt>. Now we have to specify that they are vectors by writing <tt>Vector[]</tt>. At least I think that's how it works. Let's try:

|gravity> irb require "vector_try_addition2.rb" true v1 = Vector[1, 2, 3] [1, 2, 3] v2 = Vector[5, 6, 7] [5, 6, 7] v = v1 + v2 [6, 8, 10] quit

Dan: That seems to do the right thing. And I guess we have now left the <tt> Array</tt> class alone, without changing it in any way, right?

Carol: Yes, but let us test that as well:

|gravity> irb require "vector_try_addition2.rb" true a1 = [1, 2, 3] [1, 2, 3] a2 = [5, 6, 7] [5, 6, 7] a = a1 + a2 [1, 2, 3, 5, 6, 7] quit

Dan: Good. So we're now playing it safe.