I’ve noticed some confusion lately on the part of some newcomers to Ruby about how methods are found at runtime, and in particular, the relationship between instance and class methods.
A week or so ago, someone posted some questions on ruby-talk about this issue, and then today I ran across this blog review of David Black’s “Ruby for Rails” which contained this:
On every method call, Ruby will search its object space in the following order:
- Current instance, followed by class methods
- Mixed in methods
- Superclass instance (repeat 1)
- Object methods, followed by Kernel mixin
I often get confused with mixin, class, and instance method precedence, so this is a useful model to revisit and keep in mind.
I don’t know where that reference to class methods in item 1 came from. I don’t think David said that in his book.
Here’s how it really works
Here in the form of answers to the questions posed on ruby-talk last week is my explanation of how Ruby finds methods when a message is sent to an object
Q: Do all inheritance chains and all objects have a homologue
> metaclass at all times, which are behind the scenes?
In ruby metaclasses are singleton classes of classes. Just as a class
holds the table used to find the methods of its instances, the
metaclass holds the table used to find methods for its sole instance
i.e. the class.
The singleton class which serves as the metaclass of a class is
created when the class is created. This is done internally by the
Ruby interpreter.
While object’s don’t have metaclasses they can have singleton classes,
which are created when needed, e.g. when you do something like:
a = "abc" b = "def" def a.method end # singleton class for the object referenced by a is created now class <<b #singleton class for the object referenced by b is created now end
Q: Do a chain of metaclasses spontaneously appear at the moment when a class is used as a receiver : so to be used to call a method inside of a parentclass?
No, as I just said, metaclasses are created along with their
corresponding class. The metaclass’ superclass is set to the
metaclass of the classes superclass.
Since I first wrote this I’ve gotten some feedback that the last sentence is a little hard to digest, so here’s an example. Let’s say we create a class MyNiftyClass as a subclass of Object. The singleton class of the class object referenced by the MyNiftyClass global gets created at the same time as class object, no waiting for one of the things which trigger the creation of a singleton class for a non-class object. Thesuperclass of the MyNiftyClass is set to object, and the superclass of MyNiftyClass’ metaclass is set to Object’s metaclass.
Besides the fact that their creation occurs immediately, there is one other difference between
singleton classes of objects and singleton classes used as
metaclasses. The later can have subclasses, while the former cannot.
Normal object singleton classes have their internal superclass set to
the original class of the object.
Note that I’m using superclass and class here to describe the internal
relationship. Ruby’s class and superclass methods don’t always give a
true picture when singleton/metaclasses are involved. I personally
think of the class-like objects linked together with the superclass
field as a behavior chain since it’s used to implement the behaviour
of one or more objects.
Q: How can a metaclass contain more methods than its corresponding class has. Can you give an example of the code in the next addition(sic)?
The number of methods in the class and the number of methods in the
metaclass is completely independent, for example, look at the
following and compare it to what I said in reponse to question #1
class MyClass
def initialize # This defines the initialize method in MyClass which
# Like all such methods are available to INSTANCEs
# of the class, and its subclasses
end
def method1 # another instance method
end
def MyClass.class_method # This defines a class method in the
# singleton class of MyClass (i.e. the metaclass)
# which is available to instances of the
# metaclass and its subclasses
end
def self.class_method_2 # This adds another class method, it's
# Just a different syntax for the last form of
# definition since within the scpe of a class
# definition, self is bound to that class
end
class << self # or equivalently class << MyClass
# Now we are in an inner scope, that of MyClass' metaclass
def class_method_3 # So this also becomes a class method
# of MyClass
end
endSo MyClass has two instance methods (plus those it inherits from
Object) and three class methods (plus those the metaclass inherits from
Object’s metaclass.
As a related note, when a class includes a Module, another kind of
virtual class is inserted between the class and it’s superclass. This
virtual class (or I_Class) looks like a class but has a pointer to the
method table of the module instead of having its own table. This
lets modules appear in different behavior chains and lets those
including them see updates to the module when they are made. There
are some other fields of the module which are also copied, but I’m not
going to mention them here since they aren’t relevant and might
confuse.
Q: Which is searched first in the inheritance chain. methods in
the proper parents, or methods in the meta parents?
There’s no cross over at all. When finding a method for an object,
Ruby gets the head of what I called the behavior chain. For a normal
object with no singleton class, this will be the class, For an object
with a singleton class this will be the singleton class. If the
method is found, then it’s used, if not the next class-like thing in
the behavior chain is examined and so forth. The keyword super makes
the search start in the next link in the behavior chain after the one
in which the current method was found.
So if you’re invoking a method on a normal object, no metaclasses will
be on the chain. If you are invoking a method on a class, then the
behavior chain will consist of metaclasses, except that the superclass
of Object’s metaclass is Class. If you think about this hard enough it
makes sense, since the instance methods of Class are for use by
classes.
So if we have:
class B
...
end
b = B.new
b.to_sThe search goes:
B → Object → (I_Class wrapper for Kernel)
the last is because Object includes Kernel
and
B.newthe search goes
(singleton of B) → (singleton of Object) → Class → Module →Object → (I_Class wrapper for Kernel)
Q: If a method is found is the search stopped even though another method with the same name might exist in a parallel chain?
As I just described, the parallel chain is irrelevant, it isn’t
considered at all.
In a ruby-talk discussion of the pleasures and perils of adding to or changing core ruby classes. A practice denigrated as “monkey patching” by some, but embraced as a powerful technique, when practiced with care, by others. Someone just reported an experiment involving changing a basic core method:
Fixnum.class_eval do
def +(number)
self - number
end
endThe result: It blew up his irb.
Ruby certainly gives a lot of power. A little, and in some cases a lot, more than most popular OO languages. Read on..
Dave Ungar, who analysed and ‘optimized’ the Berkley implementation of Smalltalk-80 for his PhD dissertation, and then went on to invent the Self language used to go around and try to torment Smalltalk implementers by asking them to add an instance variable to the Object class, something which either was disallowed or would blow up. Keep in mind that Smalltalk is a dynamic language like Ruby and such changes happen while the code is running.
Ruby survives this test. Actually, it redefines it in a way similar to the way Self did. In Smalltalk, and most OO languages, instance variables are declared in classes and the classes define a template for the memory layout of the classes instances, a given instance variable is kept in a slot at a known distance from the beginning of the object. When a class is redefined all it’s instances, and the instances of all it’s subclasses need to be re-mapped.
In Ruby, classes in general don’t actually know about their instances instance variables. Instance variables are represented as an individual hash which maps the instance variable name to its value. The instance variables only come about when an instance method is executed which defines it by mention. This means that you might well have two instances of a class, one of which has a given instance variable and one which doesn’t.
This highly dynamic nature of ruby gives it a great deal of power which is put to use by advanced Rubyists and leads to nice results like Ruby on Rails.
With great power comes great responsibility!
Tricksy VM Implementers
It might be the case in some future implementations that patching FixNum#+ might be ineffective, or intermittently effective.
For example, some Smalltalk implementations, would handle a + b under the covers as an integer add of the two object pointers and a little adjustment, followed by a quick check to see if the result were a fixnum, and only send the + message if it wasn’t. This check can be done relatively inexpensively due to the way in which references to FixNums (or SmallIntegers as Smalltalk called them) are encoded.
If a number x is in FixNum range, i.e it’s twos-complement representation will fit into a bitstring of length one less than a pointer, then it’s encoded as a ‘pointer’ with the binary value x*2+1 This means that, if the low-order bit set it’s a Fixnum otherwise it isn’t. So if we have two object pointers xp and yp, referring to the objects x and y respectively we can implement x + y as something like this pseudo-C code:
result = xp + yp - 1;
if (arithmetic overflow || !(result && 1) ) {
result = send(xp,:+,yp)
}Note that, if xp and yp refer to Fixnums:
xp + yp - 1 = (x*2+1)+(y*2+1) - 1 = x*2 + y*2 + 1 = (x+y)*2 + 1
which is the correct representation for the FixNum x+y
If we don’t get an overflow, and the low-order bit is set when we compute result, we’ve got our FixNum result and we can move on. Otherwise we need to do it the old-fashoned way.
I don’t know whether YARV already does this or might in the future. This is one of the things which VM implementers tend to look for, they ‘cheat’ and try not to get caught in the interest of performance.
Now Matz and his team might reject some of these tricks since the dynamic nature of ruby might make it harder not to be caught, but in some of these edge cases, I think that case can be made that it’s probably OK since it’s unlikely that anyone will actually redefine basic arithmetic operations on core classes such as FixNum and live to tell about it without blowing up irb or worse.
The topic of defining a new ruby class which could have instances that, like false and nil were seen as a boolean which was not true, just came up on ruby-talk.
This has come up before, and it turns out that in Ruby being untrue is reserved to these two specific instances.
The boolean test is pretty deeply engrained into the implementation of ruby. The actual test seems to be defined in the RTEST macro in ruby.h
#define Qnil ((VALUE)4)
#define RTEST(v) (((VALUE)(v) & ~Qnil) != 0)Which means that any object whose reference VALUE has any bit set other than the 3rd LSB is true.
In ruby, the control flow statements like ‘if’ aren’t messages, but are ‘compiled’ into a direct test and conditional branch.
Even Smalltalk, which defined even if/then/else as a message, e.g.
booleanValue ifTrue:[Transcript show:'true'] ifFalse: [Transcript show:'false']Tended to cheat in the implementation…
In most Smalltalk implementations #ifTrue;ifFalse: and its ilk are never sent, but are, like in ruby, compiled into test and branch code. Some implementations might have had a fallback if booleanValue wasn’t actually a boolean, but IIRC most would trigger a MustBeBoolean exception.
By the way, those […] are the Smalltalk analog to ruby’s blocks. In Smalltalk, blocks can be used as the value of any argument to a method, and a method could take more than one block argument.
But blocks are also another area where Smalltalk implementations tend to cheat a bit.
Smalltalk maintains the fiction that ifTrue:ifFalse: is really a message, and the methods in True and False are there to see:
in the True class
ifTrue: trueBlock ifFalse; falseBlock
^trueBlock valueand in the False class
ifTrue: trueBlock ifFalse: falseBlock
^falseBlock valueThe sending the message value to a Smalltalk block is analogous to sending call to a Proc in ruby, although the actuall message varies with the arity of the block. In the case of ifTrue:ifFalse, the block arguments don’t really bedome block objects, they get compiled as in-line code.
A lot of Smalltalkers ran across a head-scratcher when they got to the point of looking at the implementation of the value method in block which looks something like thi value
"return the result of evaluating the receiver"
^self valueThis certainly looks like it should be an infinite loop, but it isn’t. The trick is that in almost all circumstances, the Smalltalk compiler compiles sending value to a block into either special bytecodes. The only reason that the value method in block needs to be there is to handle cases like:
aBlock perform:#valuewhere perform is the analog to ruby’s send.
Smalltalk VM implementors like to say that it’s okay to cheat as long as they don’t get caught. MustBeBoolean is one area where they do get caught.
Pushing the Envelope
It’s quite a daunting task to fully implement a computation model where everything is a message and get reasonable performance. Smalltalk pushed this model quite far, but bowed to practicality in a few cases.
Ruby, although it’s more dynamic than Smalltalk in a lot of ways, makes more concessions in it’s current implementation.
Dave Ungar’s self language, in the spirit of Nigel Tufnel, turned theknob up to eleven, and eschewed the pre-optimization of even if tests. Instead, the self implementation developed and used sophistacated run-time type inferencing and analysis to generate optimized code at runtime by detecting the common cases of a boolean receiver and converting the message send to tests and branches, while avoiding supporting the less common case.
In common with Ruby, self had a more dynamic object model than Smalltalk, based on delegation rather than inheritance. Something which engendered roaring debates in the early OOPSLA community over how delegation and inheritance are related. The status of that discussion ca. 1989 is captured in “The Treaty of Orlando.” which documented the observation that the differences were really a matter of perception and viewpoint.
The real contribution of self was setting the bar high in terms of the difficulty of implementing a simple and dynamic specification, and then jumping over it. The self team later applied the lessons they had learned to the implementation of Strongtalk, a Smalltalk VM which applied the techniques of the self implementation to both make Smalltalk more dynamic, and better performing.
Many of those sessons should be applicable to implementations of Ruby. I hope to see that unfold over time.
I've been meaning to write about Ruby performance for a while, and a recent blog post by an old friend and colleague, got me off my proverbial.
The old friend is John Duimovich, who wrote about the relative performance of C++ and Smalltalk and what that could mean for ruby.
John's message is important for those who bemoan the performance of Ruby, and I plan to expand on that message in this and later posts to this blog, but first a few words about Mr. Duimovich.
Consider the source
In his day job, paraphrasing his self description John "works for IBM on Java virtual machines and is the lead on the Eclipse tools project management commitee."
But some of my readers might be interested in John's background. John was for a very long time, the lead of the Smalltalk and Java virtual machine team at Object Technology International (OTI) dating from before the time it was acquired by IBM. Among other things John was responsible for the development of embedded Smalltalk virtual machines from OTI, which spawned the VM used in Smalltalk/V Mac, IBM Smalltalk (used in IBM/VisualAge), the 'Universal' Virtual machine which implemented Java on an extended Smalltalk VM, and which was used for the early releases of IBM/VisualAge for Java, and the J9 Java VM. A good deal of what I know about implementing VMs comes from working, lunching, and bar-hopping with John.
John had become OTI's Chief Technology Officer before OTI got assimilated into the IBMborg.
John is a brilliant guy, with a great sense of humor. Two characteristics which seem to have been requirements for a job at OTI. I'm still not sure how I ended up spending several years there.
Dynamically Typed Doesn't Need to Mean Slow
I encourage you to read John's blog post yourself, but to summarize; John ran across another blog item which gave a benchmark written in C++, Ruby and Python. The C++ version runs in under 1/10 of the time needed for either the Ruby or Python versions.
John duplicated the results on his machine, then decided to port the Ruby version of the benchmark to Smalltalk. He then ran it using VisualAge Smalltalk.
And the Smalltalk version runs in the same time as the optimized C++ version!
How can this be?
The Value of Pole Vaulting
Languages like Smalltalk and Self started from the position that a clean object-oriented language was more important than one which makes compromises to make efficient implementation obvious.
Early implementations of Smalltalk used obvious implementations of some features, which were 'fast enough' in many cases, but by no means fast. Two areas which cried for improvement were method dispatch and garbage colection. The obvious techniques were walking up the class hierarchy each time a method was needed, and relatively easy to implement GC techniques like reference counting, and mark-and-sweep. Reference counting has a fairly high cost for each change of an object reference, and also has the drawback of leaking memory because cyclical references lead to garbage which is uncollectable. Mark and sweep delays the overhead until storage is exhausted, but leads to more perceptible pauses when the application gets paused so that the housemaid cleans the room.
Encountering (or having set) this high bar, various implementors of these languages found very clever techniques for both problems. Dave Ungar made measurements of the lifespans of Smalltalk objects and observed that most objects died very shortly after being instantiated, with few living a long life. This led to the invention of generational GC techniques, which quickly dispatched young dead objects, which are the vast majority.
Method dispatching techniques of efficiently implemented dynamically typed languages tend to use clever caching algorithms which can get to what is probably the right method quickly, with a quick test to make sure that the right method was found.
These dispatching techniques turn out to be faster than the virtual function pointer dispatching made possible by strongly-typed languages like C++. In fact, I've heard that more modern implementations of these languages have actually used a more dynamic method dispatch mechanism internally in order to increase performance.
Anoher implementation choice is how to represent executable code. Most efficient implementations use a combination of byte-code representation, and some form of just-in-time translation of byte-codes to machine code. Just how to divide execution between byte-code and machine code is a complicated decision. Back when DIgitalk first produced a version of Smalltalk/V for OS/2, they decided to eschew byte-codes entirely and generate 80286 machine code. The reason was that they were tired of hearing complaints about Smalltalk being an 'interpreted' language.
The surprising result of this experiment was that the resulting implementation was slower. Machine code was bigger, so it took longer to load, and caused more swapping. These costs were paid whether the code in question was executed once or a million times.
Again caching was the basis for getting the best of both worlds. Peter Deutsch of Xerox, later ParcPlace, had introduced the notion of translating byte-codes to machine code into a cache during execution, David Ungar's implementation of Self introduced the notion of using light-weight profiling techniques to avoid the overhhead of translating byte-coded methods which were infrequently executed.
Another area which posed difficulties in implementation was control flow. Smalltalk-80 defines all control flow as methods. Even primitive control flow constructs such as if (ifTrue: in Smalltalk) were implemented as methods on Boolean classes. This is one area where Smalltalk implementations cheated compiling such methods in to testing and branching byte-codes, and requiring the receivers to be boolean instances.
Self eschewed this early optimization. Ungar's team instead relied on run-time type inference in order to dynamicaly generate code which achieved the same or better performance when such a message was sent to a boolean without restricting other cases.
The Current State of Ruby Implementation
Ruby performance today is surprisingly acceptable for a wide range of uses.
This is despite the fact that the implementation is relatively straightforward, almost to the point of being naive. In the current standard implementation of Ruby:
- Method dispatch is done by walking up the 'class' hierarchy looking for methods in a hash table in each class/module.
- Garbage Collection is done by a simple mark and sweep algorithm.
- Executable code is represented by a parse tree which is executed by traversal.
This is not meant to understate the achievements of Matz and the ruby developers. Ruby as it is definitely usable for many production uses.
The point is how much better Ruby performance can get as the implementation matures. A virtual machine, with byte-codes, and better GC is on the roadmap. Ruby virtual machines such as YARV, and JRuby are showing glimmers of the value of implementing Ruby as a virtual machine. If Ruby continues to grow in acceptance, I've no doubt that other clever implementers with experience in efficently implementing dynamically typed languages will provide more implementations.
My prediction is that the future will be so bright that we're going to have to wear (ruby colored) shades!
I’ve recently observed some posts to ruby-talk which evidence some confusion on the part of the posters about the relationship between variables and objects in Ruby. One currently active thread concerns several participants who are upset that instances of the Matrix class in the Ruby standard library can’t be changed once created.
In Ruby, like in other uniformly object-oriented languages, the relationship between variables and their values is subtly different than in other languages, and this is a crucial paradigm shift, which must be crossed in order to understand Ruby.
In many languages a variable names an area in memory which holds a “bag-of-bits” representing the value of the variable. The size of that area depends on the type of the variable. A variable holding an integer might be 4-bytes long, while one holding a particular structure might be 325 (or whatever) bytes.
In a uniformly object-oriented language, all variables reference objects, and any variable can reference any object, or different objects over time. My good friend at IBM, the late David N. Smith, used to say that in such a language, “all variables are the same size,” when he wrote about or taught Smalltalk.
This distinction can trip up the unwary. Let’s try to clear some of the stumbling blocks out of the way.
Ruby Variables Don’t Have (Permanent) Classes
In one recent thread, someone suggested adding a method to the “class of a variable.”
The problem with this idea, is that a variable doesn’t really have a class. For example, consider thiscode:
require 'matrix'
a = 5
a = a * 1.0
a = a * Matrix.identity(3)Here the variable a refers to a sequence of objects, each with a different class. First a Fixnum, then a Float, then a Matrix. While Fixnum, and Float inherit from Numeric, Matrix doesn’t but it can form a duck-type with Numeric in many uses.
This also illustrates that “all variables are the same size.” In a language like C, a variable holding an integer, will probably have a diffferent size as one holding a double (which is what Ruby defines as the representation of a float), and one which holds a 3×3 matrix of doubles certainly will be bigger than one holding a scalar double.
Ruby Variables Hold Object References, not Object State
The magic which allows this is the fact that in Ruby, as in Smalltalk, a variable doesn’t hold the “bag-of-bits” which represents a particular data-structure. Instead it contains a reference to an object which holds the “bag-of-bits.” The actual structure of that “bag-of-bits” is of no concern to, and is hidden from the variable and it’s users.
This encapsulation barrier is key to what makes a language truly “object-oriented,” at least the way I use the term.
The “bag-of-bits” which represents a particular object is visible to the methods of that object’s class, at least in an abstract way. Some details are even hidden to those methods at the Ruby source code level, and are tied up in the language implementation, hand written extensions to Ruby usually do need to deal with those details.
In Smalltalk, the pseudo-variable self is actually strongly typed in the C-sense since instances of Smalltalk classes have a fixed structure, and self in a method is guaranteed to refer to an instance of the class owning the method or one of it’s subclasses, and instance variables are found in known slots within the object.
In Ruby it’s a little different because Ruby instance variables are created dynamically and accessed via a hash table within the object, at least in the 1.8.x implementation.
As a first approximation, we can initially think of variables as holding pointers to objects, but I’ll expose this for the pedagogical lie that it is, a little later in this article.
Mutability, and Aliasing
Here’s one of those stumbling blocks for those who expect variables in a uniformly object-oriented language to work like they do in a language like C or Fortran:
1: a = [1, 2, 3]
2: b = [1, 2, 3]
3 1: a = [1, 2, 3]
2: b = [1, 2, 3]
3: c = a
4: a[1] = 0
5: p a #=> [1, 0, 3]
6: p b #=> [1, 2, 3]
7: p c #=> [1, 0, 3]Everything looks pretty normal up ot line 7. We never did anything with c after we assigned it a value, but it doesn’t seem to have held it’s value.
Or has it?
Well, it actually has, since it’s value is a reference to an object, and that object is the same one referenced by a.
Line 4 might look like an assignment to the variable a, but it’s really a method call to the array which a happens to be referencing at the time. And that method (called []=) changes, or mutates that array. That change will be visible through the variables a, c and any others that reference that particular array. Multiple references to the same object are called aliases to that object. They might be named variables, or referenced which are inside another object:
8: d = [[1, 2, 3], [4, 5, 6]]
9: e = d
10: d[1][1] = "Fred"
11: p e #=> [[1, 2, 3], [4, "Fred", 5]]]While such mutating methods are often very useful, this effect of aliasing is something which the Ruby programmer who uses them needs to keep in mind.
Many built-in Ruby classes have both mutating, and non-mutating versions of methods, for example Array#compact returns a new array which is a copy of the receiver with nil elements removed, leaving the original array intact, while Array#compact! mutates the receiver and removes the nil’s in-place.
As with compact and ocmpact!, it’s standard practice to give such mutating methods a name with a trailing ‘!’ which serves as a warning that they mutate the receiver. It’s a warning to the wise.
That Little White/Pedagogical Lie, or Variables and Object Identity
I said above that thinking that variables hold pointers to objects was a first approximation to reality. The fact is they really don’t
12: a1 = [1, 2, 3]
13: a2 = [1, 2, 3]
14: p a1.object_id #=> -605450882
15: p a2.object_id #=> -605225658The object_id method returns a number which is unique in the sense that no two active objects will ever have the same object_id, and a given object will always have the same object_id. In the Ruby 1.8.x implementation, object_id actually just gives the bits of the value used to reference the object in the guise of a Fixnum.
So we see here that a1 and a2 are diffferent objects. Let’s try a few more objects:
14: i1 = 1
15: i2 = 1
16: p i1.object_id #=> 3
17: p i2.object_id #=> 3
18: sym1 = :sym
19: sym2 = :sym
20: p sym1.object_id #=> 4090126
21: p sym2.object_id #=> 4090126 Here’s another surprise. Before when we created two different arrays, we ended up with two different objects. But even though we never assigned i2 to i1, or sym2 to sym1, the ‘two’ 1s have the same object_id, and the ‘two’ symbols named :sym share an object_id, what’s that about?
Certain classes ensure that if ‘two’ of their instances are equal they are the same object. We can see that Fixnum, and Symbol do this, others are NilClass, TrueClass, and FalseClass. For symbols, this conflation of value and identity is part of their definition, for the others, it’s probably not strictly necessary, but it makes some common operations like testing for equality, and in the case of Fixnums arithmetic operations, much faster.
What’s really held in variables is a value which can either be used to recognize that the object is one of these special objects, or that can be mapped efficiently to the real address of the object. The details are implementation-specific, and not really germane to the current topic, so I won’t go into them in this article.
Immutable Objects
It’s often advisable to design classes without mutating methods. For example, Fixnum has a method [] which returns the nth bit of the representation of the number where bit 0 is the least significant bit. Suppose that it also had a method []=, which set the value of the cooresponding bit:
#Hypothetical code - with a tribute to Jimi Hendrix
h1: a = 6
h2: b = 6
h3: a[0] = 1; a[1] = 0; a[2] = 0; a[3] = 1
h3: p a #=> 9
h4: p b #=> 9Since all 6’s are the same object, then if Fixnum had mutating methods, we could make all 6’s turn out to be 9. While Jimi might not mind, my program might not like it.
And it would cause head-scratching debugging sessions. Thirty-two years ago, I found myself in just such a situation. Fortran II had a bug in the language specification which allowed the value of integer literals to be changed when you passed a literal as an argument to a subroutine which assigned to that argument. I spent over a day trying to figure out why my program didn’t work.
For this reason, to quote Martha Stewart, it’s a good thing that the Ruby numeric classes don’t have mutating methods. Even though Bignums and Floats don’t conflate value with identity, it would probably be even more mystifying to find that some instance of 1.5 changed to 1.45 when other’s didn’t. To me it seems far better to head off that particular ‘feature’ at the pass.
Another reason for this choice of immutability is taken from functional programming. Although Ruby is not really an FP language, it takes many concepts from that paradigm. In FP, functions with side-effects are disallowed, and mutation is certainly a side-effect. Taking FP to the limit makes doing things which require side-effects, like I/O, take special tricks. Ruby allows side-effects, and doesn’t require an FP approach, but like other features like regular expressions, understanding functional approaches is a useful tool in the Ruby programmer’s bag.
Immutability at the Instance Level
Ruby Object’s have a freeze method which makes a particular instance immutable:
F1: a1 = [1, 2, 3]
F2: a2 = a1
F3: a1.freeze
F4: a1[1] = "Fred" #=> TypeError: can't modify frozen array
F5: a2[1] = "Fred" #=> TypeError: can't modify frozen arrayBeware, though, that this only freezes the object itself and not it’s sub-objects, or referenced objects:
F6: a3 = [[1, 2, 3], [4, 5, 6]]
F7: a3.freeze
F8: a3[0][1] = "Fred"
F9: p a3 #=> [[1, "Fred", 3], [4, 5, 6]]Matrix and Mutability
Following the pattern established by Numerics, the designer of the Matrix class, who, according to the comments in the source code, ported it from Smalltalk, made Matrix instances immutable.
This upsets some who prefer to think of a Matrix as a 2-dimensional array rather than a mathematical construct which acts much like a numeric in linear algebra.
If you want to view matrices that way you’ve got a few choices:
- Swim-against the current: Monkey-patch Matrix to add mutating methods.
- Swim with the current: Use the Matrix#to_a and Matrix[] to get a mutable array and then create a new Matrix from it:
M1: m1 = Matrix.identity(3) M2: p.m1 #=> Matrix[[1, 0, 0], [0, 1, 0], [0, 0, 1]] M3: a1 = m1.to_a M4 a1[1][1] = 3 M4 m1 = Matrix[a1] M5: p m1 #=> Matrix[[[1, 0, 0], [0, 3, 0], [0, 0, 1]]] - Find a new stream: look at other sources of a mutable 2-dimensional array, for example NArray
The choice, gentle reader, is yours. I hope that my efforts help you make it a more informed choice.




