Cool: nil is a class that can have methods


#1

Excuse a newbee’s enthusiasm, but this is really cool. It makes some
code that would have had to treat nil specially.

class NilClass
def each
yield(self)
end
def length
0
end
end

a = nil
a.each {|a| puts a.class }

Doing it at the Object level is even more handy for the what I was
playing with.

class Object
def each
yield(self)
end
def length
if self == nil then 0 else 1 end
end
end

a = nil
a.each {|a| puts a.length }

So every Object has an each method, which yields itself and has a
length of 1, so everything can be treated like an array.
Really neat language.


#2

Hi –

On Sat, 20 May 2006, Rob B. wrote:

end
yield(self)
1, so everything can be treated like an array.
Really neat language.

Do be careful, though. Remember that the changes you make to Object
and NilClass will be in effect for all the library code that you
include in your application, including the standard library, so if
anyone else is relying on the fact that some objects respond to “each”
and others don’t, or that nil is dimensionless, something could go
wrong.

David


#3

Good point.

Looked sort of handy in the log filter I produced to generate gnuplot
files. Nil data has meaning in the plot, as distinct from a 0. It is
also handy to treat a single result in the same way as an array
result. The collation of the data, from the logs to the plot data
file, is being driven by an XML description, with embedded ruby
expressions as XML attributes and text. These XML expressions get
converted into methods to parse the logs. Having the targets of the
expressions work seamlessly, despite them being nil, single values or
arrays, was quite neat.

In the nil case, I wanted it to iterate and be printed as in the same
way as a non-nil value. In the plot data, a nil prints as ‘-’. I
hadn’t thought of other code expecting to fail when the value was nil
or not an array. They probably wont want nil to have a to_s()
returning ‘-’ either, not that anything has failed doing this, in
this instance.

I am modifying it to output multiple rows, for generating Histograms,
so I will look at doing this in a less dangerous way.


#4

removed_email_address@domain.invalid wrote:

    yield(self)

playing with.
a = nil
and others don’t, or that nil is dimensionless, something could go
wrong.

David

This is what namespace selectors would help fix right? Was there ever a
decision made about how that would work? Is it in 1.9 now?

-Jeff


#5

Quoting removed_email_address@domain.invalid, on Sat, May 20, 2006 at 09:50:49AM +0900:

In the nil case, I wanted it to iterate and be printed as in the same
way as a non-nil value. In the plot data, a nil prints as ‘-’. I
hadn’t thought of other code expecting to fail when the value was nil
or not an array. They probably wont want nil to have a to_s()
returning ‘-’ either, not that anything has failed doing this, in
this instance.

The temptation to change the core classes to work a little more like you
want is almost overwhelming… at least I have found it so. I guess in
this case you could do:

def gnuplot_each(v, &proc)
if v.respond_to? :each
v.each &proc
else
yield v
end
end

then instead of doing

data.each {…}

you could do

gnuplot_each(data) {…}

cheers,
Sam


#6

Quoting removed_email_address@domain.invalid, on Sat, May 20, 2006 at 09:20:20AM +0900:

Do be careful, though. Remember that the changes you make to Object
and NilClass will be in effect for all the library code that you
include in your application, including the standard library, so if
anyone else is relying on the fact that some objects respond to “each”
and others don’t, or that nil is dimensionless, something could go
wrong.

Whats the current wisdom on how to do this more nicely?

I’ve got a library that added #to_time to the Date builtin… and its
conflicting with another much more commonly used library, that has done
the same thing, but give time in local TZ, not UTC. Oops…

Easy way, is to move my code out into a utility method

module Util
def to_time(v)
case v
when Time
v
when Date
… cvt v to a Time …
else
… ?
end
end
end

but switches on class type are usally a bad thing… in OO code
generally, and in ruby in particular we are supposed to duck-type, and
its a real posibility that also DateTime, and perhaps even other date or
time classes might wander into my code. I’d really like not have this
kind of switch statement.

What to do?

I could change my method name from Date#to_time, to
Date#vpim_to_time_utc, basically namespacing my extension, and adding
one to Time as well.

I was also considering having some kind of hash:

module Util
@@cvt_to_time_utc = {
Time => lambda{|t| t },
Date => lambda(|d| Time.utc(d.year, d.mon, d.day)
}
def self.cvt_to_time_utc(v)
@@cvt_to_time_utc[v.class].call(v)
end
end

Then 3rd party classes could register some handlers to convert to Time
in UTC, and I wouldn’t have to modify my case statements.

But… what if they have

class MyTime < Time
end

That won’t work… so I could maybe be more clever, search their
ancestry looking for something that is in my hash… This is starting
to be a bit of work, and unfotunately similar to what ruby does for
method dispatch…

Maybe just using name mangling, using my library prefix, is the way to
go!

Thoughts?

Cheers,
Sam


#7

Probably total sacrilege, but I was using this to have “nil” as a
valid value in arithmetic and to treat numbers as if they were
arrays (in a limited sense). The latter is easy to fix, by storing
all numbers as 1 element arrays. This solves the iterator on “nil”
too. All nil values will be in an array, so I just need to iterate on
the array.

Using “nil” in arithmetic is to differentiate the no data state
(nil), from having seen a data point, even a 0 value. Probably best
to subclass Fixnum and build my own objects for arithmetic. Altering
the numeric classes is definitely a tempting thing though.

I have XML definitions of the source log file and the resulting
summary I want to get.
e.g.
a



string


string








int

%d


This gets turned into ruby code that parses the log. It uses modified
column name as variables. The test expression determines if the
output column is modified, and the value expression is used to set or
alter the value. If the values start as nil, then adding a value,
will initialise them. Meeting further values, will result in them
being added on.

If multiple results are required (e.g. for a histogram), then a value
expression like

results in the to and from traffic being added to an array result
called @rslt_link1, rather than a simple numeric result.

I did this by I adding “each” and “each_with_index” to every Object,
thus allowing numbers and nil values to have an iterator.
I replaced the Fixnum + with one that would add arrays to numbers,
giving an array result or Fixnum result, depending on the value being
added.
Also the Arrays class was modified to add two arrays, by adding the
elements at the same array index to each other, not concatenating the
arrays.
I then committed the ultimate sin, and added a plus method to nil.

After all these hacks, I can
add a number to an array, and the first element will get altered.
add an array to a number, and get an Array with the first element
altered
add a number or Array to nil, and get that number or the Array

i.e.

class Object
def each
if self != nil then yield(self) end
end
def each_with_index
if self != nil then yield(self,0) end
end
def length
if self == nil then 0 else 1 end
end
end

class Array
def +(value)
value.each_with_index { |x,i| self[i] += x }
self
end
end

class Fixnum
alias oldPlus +
def +(value)
if(value.length == 1)
self.oldPlus(value)
else
value.+(self)
end
end
end

class NilClass
def +(value)
value
end
end

a = [1,3]
a += [5,2]
a.each {|b| puts b }
puts

a = 1
a += 2
a.each {|b| puts b }
puts

a += [5,2]
a.each {|b| puts b }
puts

a = nil
a += 10
a.each {|b| puts b }
puts

a = nil
a += [3,8]
a.each {|b| puts b }
puts

a = [“hello”,“I”]
a += [" world"," repent"]
a.each {|b| print b, " " }
puts


#8

On Sat, May 20, 2006 at 09:42:11AM +0900, Jeff R. wrote:

This is what namespace selectors would help fix right? Was there ever a
decision made about how that would work? Is it in 1.9 now?

It’s not in 1.9 yet. AFAIK this is the last thing we heard about
selector
namespaces coming from matz (albeit indirectly):

“He has fixed the SelectorNamespace specification, however, which is
only in
his mind now.”

See http://redhanded.hobix.com/cult/rubyspameeting2006.html

As for the specification, [ruby-dev:27417] might give some clues.