Previous | ToC | Up | Next |
Bob: Now that we can integrate, let me come back to these lines in
the code that you were not so happy with, containing a k for the
components, remember?
Alice: Yes, do you have a way of avoiding any component notation,
even on the code level?
Bob: I think I do. In a way, it is pure syntactic sugar, but then
again, you may say that a compiler or interpreter is one giant heap of
syntactic sugar on top of the machine language. If it makes the code
much easier to read, I'm happy with it.
The trick must be to make sure that you can add vectors the way you
expect to add them in physics.
Alice: But according to the principle of least surprise, I would have
guessed that Ruby would be able to add two vectors, in array notation,
at least if they contain numerical entrees, no?
Bob: One person's expectation is another person's surprise! Not
everyone approaches arrays with your physicist's view of the world.
Here, this is a good question for which to go back to irb.
Bob: Ah, but see, if you were a computer scientist, or anyone not
used to working frequently with vectors, what could it possibly mean
to add two arrays? You have an ordered collections of components, and
another such collection. The components can be apples and oranges.
Alice: Or cats.
Bob: Yes. Or numbers. And the most sensible way to add those
collections is to make one large ordered collection, in the order
in which they were added. Note that there is something interesting
here for a mathematician: addition suddenly has become noncommutative.
Alice: I see what you mean. Least surprise for most people. How was
that again, about striving for the largest happiness for the largest
amount of people? But still, I sure would like to have my vector addition.
Bob: Now the good news, in Ruby, is: you can, and rather easily! All
you need to do is to introduce a new class, let us call it Vector,
and implement it as an array for which the addition operator +
does what you want it to do. Let's try it right away. I will put the
definition of the Vector class in the file vector1.rb:
Alice: Ah, I see that Ruby uses the same == construct as C
does, to test for equality. Many students are going to trip on that,
when they write if a = b, not realizing that they are now
assigning the value of the variable b to the variable a.
Bob: Yes, in Ruby, like in C, this can easily give rise to confusion.
Here is the correct usage:
Bob: And very friendly!
Alice: Before further analyzing what it all means, what you have put
in the Vector class definition in vector1.rb,
let us first see whether it works.
Bob: Here we are:
Bob: That is puzzling. Hmmm. Cannot convert Vector into Integer.
And all that somewhere in the definition of *. There are
only three vectors involved, within the definition of *(a).
There is the instance of the vector class itself, called self,
there is the other vector a with which it can be multiplied in a
inner product, and there is the temporary vector product[].
Neither of the first two are threatened anywhere with conversion
into an integer. Nor is the third one. Hmmm.
But wait, I have tried to use the same name product[] for a
vector, in the else clause, and for a scalar, in the if clause.
I thought that Ruby was so clever that you could do that. Because a
type is determined only at run time, I thought that you could wait to
see which branch of the if statement would be actually traversed, to
see which type Ruby is giving to product array or scalar value.
In either case, that is what should be returned.
I guess I was wrong. After all, Ruby is doing a lot of clever thinking
behind the scenes, in order to give us this nice behavior of minimum
surprise. So I guess that Ruby noticed that product is being defined
as an array in the else clause, while the value 0 is assigned to it
in the if clause just above. Could that be the reason that irb is
complaining that a Vector cannot be converted to an Integer?
If my theory is right, I should be able to resolve it by giving two
different names to the product, iproduct for inner product, and
sproduct for scalar product. Let me try that, and name the file
vector2.rb:
We have seen that addition and scalar multiplication all went fine.
Let me see whether we can get the inner product to work now.
Alice: I heard you talking to yourself, but I must admit, I couldn't
follow all that. We'll have to step through the class definition a lot
slower, so that I can catch up. But before doing that, are you sure
that it now works, and more importantly, that your understanding why it
went wrong is correct, so that you can avoid that error, whatever it was,
in the future.
Bob: Well, I must be right! I reasoned my way through, starting from
the error message, and voila, the correct result appeared.
Alice: Well, you may be right, but just to make sure, why don't you
repeat the exact same sequence of commands that you give before, all six
of them.
Bob: What good would that do? They worked, and I didn't change anything
about the way they worked!
Alice: Just to make me feel better.
Bob: Oh, well, it's only six lines typing. Here you are:
Alice: No comment.
Bob: How can that possibly be??? The same error message as before,
with the same operation. And all the previous five tests were still
okay. And yet, I just showed you that a*b worked, by itself,
with the same vectors no less! This is spooky.
Alice: The same vectors? But didn't you add b to a in line 3?
And wait, you changed a again in line 5.
Bob: Oh yes, true, but what difference can that make? Hmm. Forget
I just said that. All bets are off now, for me. I will multiply those
last two vector values. I won't go home before I've traced this bug,
somehow. This is perplexing. Okay:
Alice: At least now we know what works and what doesn't. And all
we have to do is to find out why. Just to put it back to back, let me
repeat all six commands again, and print out the values of a and b
before taking their inner product:
Alice: And yet it is. Something must be different. Something must
have happened. I wonder, do we know what a and b really are?
They are the only variables in town, in this small session. And the
session is so short, we should be able to look at them under a
microscope at every step. And let us try to make our steps as small
as we can, doing only one thing at a time.
Bob: Okay, divide and conquer. That's generally a good method.
Let me start a new irb session. Above, we started by creating a
vector with three components, to which we assigned values. One
way to split this command into smaller steps is to separate the
creating and assigning parts. First, I'll create a vector and then
I'll assign its values. I'll do the same for the second vector,
replacing the second command above by a creation and assignment
statement.
Alice: And then I suggest you replace the third command by a simple
addition, rather than the += combination.
Bob: Good, more divide and conquer strategy! Let's see what
sort of surprise we'll run into next.
Alice: Somehow Ruby has fallen back onto its old habit of adding by
concatenation. But I thought you had taught Ruby to not do that any
longer?
Bob: I taught Ruby not to concatenate when adding vectors. Normal
arrays will still be concatenated by addition. But a and b are
not normal arrays. You are my witness that we have created both
a and b as vectors, brand new vectors, right out of the oven!
And yet they don't behave like vectors. I'm beginning to despair.
There seems to be no room left here for introducing a bug.
Hmmmm. Maybe we should go over the vector class definition in more
detail. There just has to be something wrong there.
Alice: Before doing that, let us enlarge the magnification of our
microscope just a bit more. We have taken smaller steps now, but we
haven't yet looked into the results of each step further. In other
words, we have divided, but not yet conquered.
How about asking a and b themselves what they think is going
on? For starters, we can ask them their type, what class they think
they are, with the class method we have seen before.
Bob: That's not a bad idea. Actually a great idea. Let's try it.
Alice: It seems to have forgotten its vectorness. Growing up in a bad
environment, it seems.
Bob: But how bad can this environment be? Let me think, and stare
at the third line for a while. Hmmppfff . . . On the left hand side
we have a variable called a that has just told us that it considers
itself to be a decent citizen of class Vector. Good. On the right
hand side we have an array with three components. Good. We give those
values to the vector and . . . hey, that must be it!
Alice: what must be what?
Bob: That's the answer!
Alice: You sure look happier now, but can you enlighten me?
Bob: Dynamic typing!!
Alice: Yes, Ruby has dynamic types, how does that solve our problem?
7. Debugging
7.1. A Vector Class
|gravity> irb --prompt short_prompt
001:0> a = [1, 2]
[1, 2]
002:0> b = [3, 3]
[3, 3]
003:0> a + b
[1, 2, 3, 3]
Alice: Hey, that's not fair! Making a four-dimensional vector out
of two two-dimensional ones, how did that happen??
|gravity> irb --prompt short_prompt
001:0> a = 3
3
002:0> if a == 4
003:1> p "amazing!"
004:1> else
005:1* p "of course not"
006:1> end
"of course not"
nil
And here is what happens often during the first few weeks that you are
programming in C, C++ or Ruby:
007:0> if a = 4
008:1> p "amazing!"
009:1> else
010:1* p "of course not"
011:1> end
(irb):7: warning: found = in conditional, should be ==
"amazing!"
nil
Alice: Hey, that is nice! irb tells us that we are probably making
a mistake. Very clever!
7.2. Debugging
|gravity> irb --prompt short_prompt -r vector1.rb
001:0> a = Vector[1, 2, 3]
[1, 2, 3]
002:0> b = Vector[0.1, 0.01, 0.001]
[0.1, 0.01, 0.001]
003:0> a += b
[1.1, 2.01, 3.001]
004:0> p a
[1.1, 2.01, 3.001]
nil
005:0> a = b*2
[0.2, 0.02, 0.002]
006:0> a*b
TypeError: cannot convert Vector into Integer
from (irb):6:in `*'
from (irb):6
Alice: Ah, what a pity. We had vector addition, and also scalar
multiplication of a vector, but the inner product of two vectors
didn't work.
|gravity> irb --prompt short_prompt -r vector2.rb
001:0> a = Vector[1, 2, 3]
[1, 2, 3]
002:0> b = Vector[0.1, 0.01, 0.001]
[0.1, 0.01, 0.001]
003:0> a*b
0.123
Ha! That was it. Interesting. But at least it now works.
7.3. An Extra Safety Check
|gravity> irb --prompt short_prompt -r vector2.rb
001:0> a = Vector[1, 2, 3]
[1, 2, 3]
002:0> b = Vector[0.1, 0.01, 0.001]
[0.1, 0.01, 0.001]
003:0> a += b
[1.1, 2.01, 3.001]
004:0> p a
[1.1, 2.01, 3.001]
nil
005:0> a = b*2
[0.2, 0.02, 0.002]
006:0> a*b
TypeError: cannot convert Vector into Integer
from (irb):6:in `*'
from (irb):6
I'll be darned!!
|gravity> irb --prompt short_prompt -r vector2.rb
001:0> a = Vector[0.2, 0.02, 0.002]
[0.2, 0.02, 0.002]
002:0> b = Vector[0.1, 0.01, 0.001]
[0.1, 0.01, 0.001]
003:0> a*b
0.020202
WHAT?!? I now typed in the exact values, and it still works. But the
original sequence of six commands, above, didn't work.
7.4. And Yet It Is
|gravity> irb --prompt short_prompt -r vector2.rb
001:0> a = Vector[1, 2, 3]
[1, 2, 3]
002:0> b = Vector[0.1, 0.01, 0.001]
[0.1, 0.01, 0.001]
003:0> a += b
[1.1, 2.01, 3.001]
004:0> p a
[1.1, 2.01, 3.001]
nil
005:0> a = b*2
[0.2, 0.02, 0.002]
006:0> p a
[0.2, 0.02, 0.002]
nil
007:0> p b
[0.1, 0.01, 0.001]
nil
008:0> a*b
TypeError: cannot convert Vector into Integer
from (irb):8:in `*'
from (irb):8
Bob: This can't be.
|gravity> irb --prompt short_prompt -r vector2.rb
001:0> a = Vector.new
[]
002:0> a = [1, 2, 3]
[1, 2, 3]
003:0> b = Vector.new
[]
004:0> b = [0.1, 0.01, 0.001]
[0.1, 0.01, 0.001]
005:0> a + b
[1, 2, 3, 0.1, 0.01, 0.001]
AAHHHAAAA!! There we have a clear smoke signal. A new trail to follow!
But the trail seems to hang in mid air . . .
7.5. Using a Microscope
|gravity> irb --prompt short_prompt -r vector2.rb
001:0> a = Vector.new
[]
002:0> a.class
Vector
003:0> a = [1, 2, 3]
[1, 2, 3]
004:0> a.class
Array
What the heck . . . !! How can that possibly be? We created a as a
vector, it told us that it was a vector, we gave it some values, and
now it thinks it is an ordinary array.
Previous | ToC | Up | Next |