MSA-A Vector Class with + and --Ruby

__NOTITLE__

Table of Contents = A Vector Class with + and - (Ruby Version) =

 Vector Subtraction
Erica: Well, Carol, you've pulled a really neat trick. What a difference, being able to add vectors by writing

v = v1 + v2

rather than

v = [] v1.each_index{|k| v[k] = v1[k] + v2[k]}

Dan: Thanks, Carol! You've just made our life a whole lot easier.

Carol: Not quite yet; I'll have to do the same thing for subtraction, multiplication and addition as well. And I'm sure there are a few more things to consider, before we can claim to have a complete  Vector class. But I, too, am encouraged with the good start we've made!

I'll open a new file vector_try_add_sub.rb. First thing to add, after addition, is subtraction. That part is easy:

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

Erica: So now we can write v = v1 + v2 and v = v1 - v2

But what about v = -v1? Or even just v = v1? Would that work too?

Carol: Good question! Probably not. But before getting into the why not, let's play with  irb and see what happens:

|gravity> irb require "vector_try_add_sub.rb" true v1 = Vector[1, 2, 3] [1, 2, 3] v2 = Vector[5, 6, 7] [5, 6, 7] v = v1 + v2 [6, 8, 10] v = v1 - v2 [-4, -4, -4] v = v1 [1, 2, 3] v = -v1 NoMethodError: undefined method `-@' for [1, 2, 3]:Vector from (irb):7 from :0 quit

Dan: Huh? A method by the name of a minus sign followed by an @</tt> symbol? That's the strangest method name I've ever seen. And what can it mean that it is undefined? Should it be defined?

Erica: At least writing v = v1</tt> worked. So we've come halfway!

Dan: Ah, but would it also work if we would write v = +v1</tt>? Let me try:

|gravity> irb require "vector_try_add_sub.rb" true v1 = Vector[1, 2, 3] [1, 2, 3] v2 = Vector[5, 6, 7] [5, 6, 7] v = +v1 NoMethodError: undefined method `+@' for [1, 2, 3]:Vector from (irb):4 from :0 quit

Aha! You see, we're not even half-way yet. Neither of the two work. But it's intriguing that we get a similar error message, this time with a plus sign in front of the mysterious @</tt> symbol.

Unary +</tt>
Carol: Let me consult the Ruby manual. Just a moment. . . aha, I see! Well, this is perhaps an exception to Ruby's principle of least surprise. The manual tells me that -@</tt> is the Ruby notation for a unary minus, and similarly, +@</tt> is the Ruby notation for a unary plus.

Dan: A unary minus?

Carol: Yes, and the word `un' in unary here means `one,' like in `unit.' A unary minus is an operation that has only one operand.

Erica: As opposed to what?

Carol: As opposed to a binary minus. Most normal operations, such as addition and subtraction, as well as multiplication and division, are binary operation. Binary here means two operands. When you use a single plus sign, you add two numbers. Similarly, a minus allows you to subtract two numbers. So when you write 5 - 3 = 2</tt> you are using a binary minus. However, when you first write x = 5</tt> and then y = -x</tt>, to give y</tt> the value -5</tt>, you are using not a binary minus, but a unary minus. The construction -x</tt> returns a value that has the value of the variable x</tt>, but with an additional minus sign.

Dan: So you are effectively multiplying x</tt> with the number -1</tt>.

Erica: Or you could say that you are subtracting <tt>x</tt> from <tt>0</tt>.

Carol: Yes, both statements are correct. But rather than introducing multiplication, it is simpler to think only about subtraction. So writing <tt>-x</tt> uses a unary minus, while writing <tt>0-x</tt> uses a binary minus, while both are denoting the same result.

Dan: But why does Ruby use such a complicated symbol, <tt>-@</tt>, for unary minus?

Carol: That symbol is only used when you redefine the symbol.

Here, let me try it out, in a new file <tt>vector_try_unary.rb</tt>. And I may as well start with the unary plus, since that seems the simplest of the two unary operations.

We have just redefined the binary plus as follows:

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

We can now use the <tt>+@</tt> symbol to redefine also the unary plus for the same <tt> Vector</tt> class:

def +@ self end

When we use it, we don't have to add the <tt>@</tt> symbol, which is only used in the definition, to make the necessary distinction between the unary and binary plus.

Dan: That's it? You just return the vector itself? I guess it makes sense, but it seems almost too simple. Let's try it out:

|gravity> irb require "vector_try_unary.rb" true v1 = Vector[1, 2, 3] [1, 2, 3] v = +v1 [1, 2, 3] quit

Good! Now what about unary minus?

Unary <tt>-</tt>
Carol: What about it?

Dan: My first guess would be to let the method return <tt>-self</tt> but that's too simple, I'm sure. ..

Carol: Yes, that would beg the question! By writing <tt>-self</tt> you are trying to invoke the very method you are trying to define. That certainly won't work. But, hey, remember our friend <tt> map</tt>, which maps an operation on all elements of an array? Well, because the <tt> Vector</tt> class inherits the <tt> Array</tt> class, any method working for an array will work for a vector as well, so here we go:

def -@ self.map{|x| -x} end

And here is the reality check:

|gravity> irb require "vector_try_unary.rb" true v1 = Vector[1, 2, 3] [1, 2, 3] v = -v1 [-1, -2, -3] quit

Dan: It's real. Congratulations!

Erica: Can you compose these operations arbitrarily?

Carol: Of course you can. The syntax is the same, we have only overloaded the <tt>+</tt> and <tt>-</tt> operators; the way you can combine them is the same as in normal arithmetic.

Erica: Let me try:

|gravity> irb require "vector_try_unary.rb" true v1 = Vector[1, 2, 3] [1, 2, 3] v2 = Vector[5, 6, 7] [5, 6, 7] v = -((-v1) + (+v2)) NoMethodError: undefined method `-@' for [-1, -2, -3, 5, 6, 7]:Array from (irb):4 from :0 quit

An Unexpected Result
Dan: So much for normal arithmetic.

Carol: That is very unexpected, I must say. What does the error message say? It talks about a very long array, with six components. Wait a minute, it should only talk about vectors. It seems that not all of our vectors are really members of the <tt> Vector</tt> class. Could it be that some of them are still <tt> Array</tt> members?

Erica: Easy to test:

|gravity> irb require "vector_try_unary.rb" true v1 = Vector[1, 2, 3] [1, 2, 3] v2 = Vector[5, 6, 7] [5, 6, 7] v1.class Vector v2.class Vector (+v2).class Vector (-v1).class Array quit

Carol: Aha! Unexpected, yes, but it all makes sense. For the unary plus method, we just returned <tt> self</tt>, the object itself, which already is a member of the <tt> Vector</tt> class. But the way I wrote the unary minus, things are more tricky:

def -@ self.map{|x| -x} end

You see, <tt> self</tt> is a instance of the <tt> Vector</tt> class, which inherits the <tt> Array</tt> class, and thereby inherits all the methods of the <tt> Array</tt> class. But now the question is: what does a method such as <tt> map</tt> do? It is a member of the <tt> Array</tt> class, something that Ruby folks write as <tt>Array#map</tt> in a notation that I find somewhat confusing, but we'll have to get used to it. So, what <tt>Array#map</tt> does, and the only thing it can do, is to return an <tt>Array</tt> object.

Erica: And not a <tt> Vector</tt> object. Got it! So all we have to do is to tell the result of the <tt>Array#map</tt> method to become a <tt> Vector</tt>.

But wait a minute, we can't do that. We want to pull off a little alchemy here, turning an <tt> Array</tt> into a <tt> Vector</tt>. But doesn't this mean that the <tt> Array</tt> class has to learn about vectors?

Converting
Carol: Well, I may have a lead here. In Ruby, you will often see something like <tt>to_s</tt> as a method to write something as a string. Or more precisely, to convert it into a string.

Dan: What does it mean to convert something? Can you give a really simple example?

Carol: The simplest example I can think of is the way that integers are being converted into floating point numbers. I'm sure you're familiar with it. If you specify a multiplication like <tt>3.14 * 2</tt> to get an approximate result for 2π, the floating point number <tt>3.14</tt> will try to make the fixed point number 2, in integer, into a floating point number first.

In other words, <tt>3.14</tt>, an object that is an instance of the class <tt> Float</tt>, will try to convert the number 2, an object that is an instance of the class <tt> Fixnum</tt>, into an instance of the class <tt> Float</tt>. To say it in simple terms: <tt>3.14</tt> will convert <tt>2</tt> into <tt>2.0</tt> and since it knows how to multiply two floating point numbers, it can then happily go ahead and apply its multiplication method.

Dan: I see. I guess I never worried about what happened when I write something like <tt>3.14 * 2</tt>; I just expect <tt>5.28</tt> to come out.

Carol: <tt>6.28</tt>.

Dan: Oops, yes, of course. But you see what I mean.

Carol: I agree, normally there is no reason to think about those things, as long as we are using predefined features that hopefully have been tested extensively. But now we are going to define our own objects, vectors, and we'd better make sure that we're doing the right thing.

Erica: You mentioned the <tt>to_s</tt> method.

Carol: Yes, for each class <tt> XXX</tt> that has a method <tt>to_s</tt> defined, as <tt>XXX#to_s</tt>, we can use <tt>to_s</tt> to convert an object of class <tt> XXX</tt> into an object of class <tt> String</tt>.

Here, let me show you:

|gravity> irb 3 3 3.class Fixnum 3.to_s "3" 3.to_s.class String 3.14 3.14 3.14.class Float 3.14.to_s "3.14" 3.14.to_s.class String quit

Erica: Ah, so the <tt>"..."</tt> notation already shows that we are dealing with a character string, or string for short, and indeed, the class of <tt>"..."</tt> is <tt> String</tt>. That makes sense.

So you want to do something similar with vectors, starting with an array and then converting it into a vector, with a method like <tt>to_v</tt>.

Carol: Exactly! And I like the name you just suggested. So we have to define a method <tt>Array.to_v</tt>. Then, once we have such a method, we can use it to create a vector by writing

v = [1, 2, 3].to_v

Augmenting the <tt> Array</tt> Class
Erica: But how can we define <tt>to_v</tt>? Somebody else already has defined the <tt> Array</tt> class for us. I guess we'll have to dig into wherever Ruby is defined, and change the <tt> 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. Let me copy our previous <tt>vector_try_unary.rb</tt> into a new file <tt>vector_try.rb</tt>. Hopefully we're getting closer to the real thing!

Here is my first attempt to augment the <tt> Array</tt> class:

class Array def to_v Vector[*self] end end

And now, keeping my fingers crossed:

|gravity> irb require "vector_try.rb" true [1, 2, 3].class Array [1, 2, 3].to_v.class Vector v1 = Vector[1, 1, 1] [1, 1, 1] v2 = [1, 2, 3].to_v [1, 2, 3] v = v1 + v2 [2, 3, 4] v.class Vector quit

Erica: Your hope was justified: <tt>to_v</tt> does indeed seem to produce genuine vectors. How nice, that we have the power to add to the prescribed behavior of the <tt> Array</tt> class!

Dan: It may be nice, but I'm afraid I don't understand yet how <tt>to_v</tt> works. You are returning a new vector, and that new vector should have the same numerical components as the original array, <tt> self</tt>, right? Now what is that little star doing there, in front of <tt> self</tt>?

Carol: Ah, that's a way to liberate the components. We have seen that we can create an array by writing

[1, 2, 3]

which you can view as a shorthand for

Array[1, 2, 3]

where the <tt>Array[]</tt> method receives a list of components and returns an array that contains those components.

Now for our vector class we can similarly write:

Vector[1, 2, 3]

in order to create vector <tt>[1, 2, 3]</tt>.

Now, let me come back to your question. If I start with an <tt> Array</tt> object <tt>[1, 2, 3]</tt>, which internally is addressed by <tt> self</tt>, and if I then were to write:

Vector[self]

that would be translated into

Vector1, 2, 3

Dan: I see: that would be a vector with one component, where the one component would be the array <tt>[1, 2, 3]</tt>. Got it. So we have to dissolve one layer of square brackets, effectively.

Carol: Indeed. And here is where the <tt>*</tt> notation comes in. Let me show you:

|gravity> irb a = [1, 2, 3] [1, 2, 3] b = [a] 1, 2, 3 c = [*a] [1, 2, 3] quit

Fixing the Bug
Erica: So now we can go back and fix the bug in our unary minus.

Carol: Ah, yes, that's how we got started. Okay, we had in file <tt>vector_try_unary.rb</tt>:

def -@ self.map{|x| -x} end

In our new file, <tt>vector_try.rb</tt>, I can now make this:

def -@ self.map{|x| -x}.to_v end

Dan: Shall we repeat our old trial run? Here we go:

|gravity> irb require "vector_try.rb" true v1 = Vector[1, 2, 3] [1, 2, 3] v2 = Vector[5, 6, 7] [5, 6, 7] v = -((-v1) + (+v2)) [-4, -4, -4] quit

Great! Okay, now we have really covered a complete usage of <tt>+</tt> and <tt>-</tt> for vectors, the unary and binary forms for each of them.