[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.ruby

Subclassing Integer; solved!

Clifford Heath

2/12/2008 7:01:00 AM

Folk,

Refer to <http://www.ruby-forum.com/topic... for the context.

I'm doing some metaprogramming where I wanted folk to be able to
subclass Integer, for example: "class Year < Integer; ... end".
You can't do this with Integer in a sensible way, because Integers
are value types in Ruby, and don't support the new() method.

However, I came up with the following cunning bit of code, which
seems to work. The code is also here: <http://pastie.caboo.se/....
I use BasicObject from Facets, which isn't the only way to get that,
you might have 1.9 for example :-).

Please comment,

Clifford Heath.

require "facets/basicobject" unless Object.const_defined? :BasicObject

class Integer
class BoxedInteger < BasicObject
private
attr_accessor :__value

def self.new(*a)
super *a
end

def initialize(v)
@__value = v.to_i
end

def method_missing(meth, *args, &block)
__value.send meth, *args, &block
end
end

def self.inherited(subclass)
def subclass.new(i)
BoxedInteger.new(i)
end
end
end
9 Answers

Rick DeNatale

2/12/2008 1:16:00 PM

0

On 2/12/08, Clifford Heath <no@spam.please.net> wrote:
> Folk,
>
> Refer to <http://www.ruby-forum.com/topic... for the context.
>
> I'm doing some metaprogramming where I wanted folk to be able to
> subclass Integer, for example: "class Year < Integer; ... end".
> You can't do this with Integer in a sensible way, because Integers
> are value types in Ruby, and don't support the new() method.
>
> However, I came up with the following cunning bit of code, which
> seems to work. The code is also here: <http://pastie.caboo.se/....
> I use BasicObject from Facets, which isn't the only way to get that,
> you might have 1.9 for example :-).
>
> Please comment,

I don't think this does what you think:

require 'rubygems'
require "facets/basicobject" unless Object.const_defined? :BasicObject

class Integer
class BoxedInteger < BasicObject
private
attr_accessor :__value # !> private attribute?

def self.new(*a)
super *a # !> `*' interpreted as argument prefix
end

def initialize(v)
@__value = v.to_i
end

def method_missing(meth, *args, &block)
__value.send meth, *args, &block
end
end

def self.inherited(subclass)
def subclass.new(i)
BoxedInteger.new(i)
end
end
end

class Year < Integer
end

Year.new(2008).class # => Fixnum
Year.new(2008) == 2008 # => true

There's a lot of good advice in the referenced thread from experienced
Rubyists about why this is not such a good idea in general.

Ruby doesn't require forcing classes to inherit in order to conform to
a type system. Going through hoops to do so is a symptom of thinking
in Java or C++ rather than in Ruby.

--
Rick DeNatale

My blog on Ruby
http://talklikeaduck.denh...

Clifford Heath

2/12/2008 8:58:00 PM

0

Rick DeNatale wrote:
> I don't think this does what you think:
....
> class Year < Integer
> end
>
> Year.new(2008).class # => Fixnum
> Year.new(2008) == 2008 # => true

That's exactly what I want. I also want to be able to do
Year.include module, and Year.new.extend module, adding
instance variables and methods to both the Year class and
year instances, and I can't see any other way to do that,
while maintaining the *appearance* of an integer subclass.
Can you?

> There's a lot of good advice in the referenced thread from experienced
> Rubyists about why this is not such a good idea in general.

Heh... seven years and counting myself. It's not a good
idea in general. But this is not in general, it's in the
specific case of a data modeling API, where it is a good
idea to makes things *appear* consistent. It means that
my meta-programming doesn't have to include Integer as a
special case in hundreds of locations.

> Ruby doesn't require forcing classes to inherit in order to conform to
> a type system. Going through hoops to do so is a symptom of thinking
> in Java or C++ rather than in Ruby.

All true. I don't really care about the type system, I
care about the POLS which Ruby so freely violates with
Integers. All for good reasons of course, but my hack
doesn't reduce performance of normal integers, it just
makes Ruby work consistently.

Clifford Heath.

ThoML

2/12/2008 9:26:00 PM

0

> > Year.new(2008).class # => Fixnum
> > Year.new(2008) == 2008 # => true
>
> That's exactly what I want.

The latter gives false with ruby19 though. Also it seems impossible to
define a method in Year.

class Year < Integer
def foo
self * 2000
end
end
Year.new(2008).foo
# NoMethodError: undefined method `foo' for 2008:Fixnum

Also, I don't quite understand the hack with inherited() since it
doesn't do anything. The reason why the class() method returns
'Fixnum' is not because of the inheritance but because of
#method_missing. A IMHO slightly more accessible/"traditional" way to
achieve the same would be:

class Year < BasicObject
def initialize(value)
@value = value
end

def foo
@value * 2000
end

def ==(other)
@value == other
end

def respond_to?
@value.respond_to?
end

private
def method_missing(meth, *args, &block)
@value.send meth, *args, &block
end
end

Year.new(2008).class
# => Fixnum
Year.new(2008).foo
# => 4016000
Year.new(2008) == 2008
# => true
Year.new(2008).class.superclass
# => Integer

If you define #method_missing, you'll also have to define respond_to?.
It seems also necessary to define #== and a few other methods:

irb(main):001:0> BasicObject.instance_methods
=> [:==, :equal?, :"!", :"!=", :__send__]

Regards,
Thomas.

Rick DeNatale

2/12/2008 9:27:00 PM

0

On 2/12/08, Clifford Heath <no@spam.please.net> wrote:
> Rick DeNatale wrote:
> > I don't think this does what you think:
> ...
> > class Year < Integer
> > end
> >
> > Year.new(2008).class # => Fixnum
> > Year.new(2008) == 2008 # => true
>
> That's exactly what I want. I also want to be able to do
> Year.include module, and Year.new.extend module, adding
> instance variables and methods to both the Year class and
> year instances, and I can't see any other way to do that,
> while maintaining the *appearance* of an integer subclass.
> Can you?

Well this doesn't do it. Using the above code but adding a method to Year.

class Year < Integer
def foo
end
end

Year.new(2008).class # => Fixnum
Year.new(2008).foo # =>
# ~> -:18:in `send': undefined method `foo' for 2008:Fixnum (NoMethodError)
# ~> from -:18:in `method_missing'
# ~> from -:35

The point is that Year.new isn't returning a Year which looks like a
Fixnum, it IS returning a Fixnum.


--
Rick DeNatale

My blog on Ruby
http://talklikeaduck.denh...

Rick DeNatale

2/12/2008 9:42:00 PM

0

On 2/12/08, ThoML <micathom@gmail.com> wrote:
> > > Year.new(2008).class # => Fixnum
> > > Year.new(2008) == 2008 # => true
> >
> > That's exactly what I want.
>
> The latter gives false with ruby19 though. Also it seems impossible to
> define a method in Year.
>
> class Year < Integer
> def foo
> self * 2000
> end
> end
> Year.new(2008).foo
> # NoMethodError: undefined method `foo' for 2008:Fixnum
>
> Also, I don't quite understand the hack with inherited() since it
> doesn't do anything. The reason why the class() method returns
> 'Fixnum' is not because of the inheritance but because of
> #method_missing.

Well you really can define a method in Year, it's just that Year.new
doesn't return an instance of Year, it returns an instance of
BoxedInteger (not Fixnum as I had said earlier.)

> A IMHO slightly more accessible/"traditional" way to
> achieve the same would be:
>
> class Year < BasicObject
> def initialize(value)
> @value = value
> end
>
> def foo
> @value * 2000
> end
>
> def ==(other)
> @value == other
> end
>
> def respond_to?
> @value.respond_to?
> end
>
> private
> def method_missing(meth, *args, &block)
> @value.send meth, *args, &block
> end
> end
>
> Year.new(2008).class
> # => Fixnum
> Year.new(2008).foo
> # => 4016000
> Year.new(2008) == 2008
> # => true
> Year.new(2008).class.superclass
> # => Integer
>
> If you define #method_missing, you'll also have to define respond_to?.
> It seems also necessary to define #== and a few other methods:
>
> irb(main):001:0> BasicObject.instance_methods
> => [:==, :equal?, :"!", :"!=", :__send__]

Or just use Delegator.

--
Rick DeNatale

My blog on Ruby
http://talklikeaduck.denh...

ThoML

2/12/2008 9:50:00 PM

0

> If you define #method_missing, you'll also have to define respond_to?.

I just checked (or rather read my own mail). This isn't necessary,
since BasicObject doesn't respond to respond_to?.

Clifford Heath

2/12/2008 10:04:00 PM

0

Rick DeNatale wrote:
> The point is that Year.new isn't returning a Year which looks like a
> Fixnum, it IS returning a Fixnum.

No, that's not true, it's returning an Integer::BoxedInteger.
Year.new(2008).class is being forwarded by the method_missing,
simply fixed. However...

> Well this doesn't do it. Using the above code but adding a method to Year.
....
> Year.new(2008).foo # =>
> # ~> -:18:in `send': undefined method `foo' for 2008:Fixnum (NoMethodError)
> # ~> from -:18:in `method_missing'
> # ~> from -:35

Hmmm, that is a bigger problem :-). Probably showstopper in fact,
unless I make it into a really *big* ugly hack.

It'd be nice if it was possible, but it seems it would have to be
done inside Ruby.

Clifford Heath

2/12/2008 11:45:00 PM

0

Clifford Heath wrote:
> It'd be nice if it was possible...

This seems to work, requiring only a minimal adjustment
by callers to subclass Int instead of Integer:

class Int
def initialize(i)
@__value = i.to_i
end

private
attr_accessor :__value

def method_missing(meth, *args, &block)
@__value.send meth, *args, &block
end

public
alias __respond_to respond_to?
def respond_to?(m, p = false)
__respond_to(m, p) || @__value.respond_to?(m, p)
end
end

Clifford Heath.

Trans

2/13/2008 12:28:00 AM

0

What about subclassing Numeric?

T.