Sub-Classing Fixnum in Ruby

Sub-classing Fixnum in ruby

You can pretty easily set up a quick forwarding implementation yourself:

class MyNum
def initialize(number)
@number = number
end

def method_missing(name, *args, &blk)
ret = @number.send(name, *args, &blk)
ret.is_a?(Numeric) ? MyNum.new(ret) : ret
end
end

Then you can add whatever methods you want on MyNum, but you'll need to operate on @number in those methods, rather than being able to call super directly.

12345.class returning 'Integer' not 'Fixnum' in Ruby

It depends on the Ruby version. From Ruby 2.4.0 we have just Integers, no more Fixnums and Bignums distinction

https://www.ruby-lang.org/en/news/2016/12/25/ruby-2-4-0-released/

Mimic another Ruby class so the object passes the === type check

This is a hackish solution that I warned against in my question:

Fixnum === page  #=> false

Numeric.extend Module.new {
def ===(obj)
obj.instance_of?(PageNumber) or super
end
}

Fixnum === page #=> true

It solves the problem but raises a question is it safe to do? I can't think of any drawbacks of this method from the top of my mind but since we're messing with a very important method here it might not be something we'd want to do.

Why can't I extend the Fixnum class in a module and use it?

There are three problems with your code:

  1. You are creating a new class EanControl::Fixnum, but you actually want to change the existing builtin ::Fixnum. Solution: explicitly start the constant lookup from the top-level, or, more idiomatically, just drop the module.

    module EanControl
    class ::Fixnum
    # …
    end
    end

    # although it would be much simpler to just do this:

    class Fixnum
    # …
    end
  2. You define roundup as a singleton method of the object Fixnum, but you call it as an instance method of instances of Fixnum. Solution: make roundup an instance method:

    class Fixnum
    def roundup
    return self if (self % 10).zero? # already a factor of 10
    self + 10 - (self % 10) # go to nearest factor 10
    end
    end
  3. The Ruby Language Specification does not actually guarantee that there even is a Fixnum class. It only guarantees that there is an Integer class, and it allows that different implementations may provide implementation-specific subclasses. (E.g. YARV has Fixnum and Bignum subclasses of Integer.) Since you only add the method to Fixnum, it won't work for other Integers, which aren't Fixnums. And since the range of Fixnums is different for different implementations of architectures (e.g. on YARV on 32 bit systems, Fixnums are 31 bit, on 64 bit systems, they are 63 bit, on JRuby, they are always 64 bit), you don't even know for sure what numbers your method will work on and when it will fail. (E.g.: 9223372036854775808.roundup # NoMethodError: undefined method 'roundup' for 9223372036854775808:Bignum.) Solution: make the method an instance method of Integer:

    class Integer
    def roundup
    return self if (self % 10).zero? # already a factor of 10
    self + 10 - (self % 10) # go to nearest factor 10
    end
    end

Lastly, I want to suggest at least using a mixin here:

module IntegerWithRoundup
def roundup
return self if (self % 10).zero? # already a factor of 10
self + 10 - (self % 10) # go to nearest factor 10
end
end

class Integer
include IntegerWithRoundup
end

Now, if someone else debugs your code, and wonders where this roundup method comes from, there is a clear trace in the ancestry chain:

12.method(:roundup).owner
# => IntegerWithRoundup

Even better would be to use a Refinement, that way your monkeypatch doesn't pollute the global namespace:

module IntegerWithRoundup
module Roundup
def roundup
return self if (self % 10).zero? # already a factor of 10
self + 10 - (self % 10) # go to nearest factor 10
end
end

refine Integer do
include Roundup
end
end

12.roundup
# NoMethodError: undefined method `roundup' for 12:Fixnum

using IntegerWithRoundup

12.roundup
# => 20

What is the difference between Integer and Fixnum?

You never "use" Integer. It is an abstract class whose job is to endow its children (Fixnum and Bignum) with methods. Under effectively no circumstances will you ever ask for an object's class and be told that it is an Integer.

How to override Ruby + method?

It seems you use ruby < 2.4. If so you want to patch Fixnum and not Integer. Be careful as the system itself uses numbers as well.

class Fixnum
alias_method :add, :+

def +(other)
puts 'plus method overridden'
add(other)
end
end

puts 5 + 9

Case statement for class objects

Something more functional?

is_descendant = lambda { |sample, main| main <= sample }
not_a_class = lambda { |x| !x.kind_of?(Class) }

mine = Fixnum

case mine
when not_a_class then raise 'Not a class' # Credits to @brymck
when is_descendant.curry[Float] then puts 'Float'
when is_descendant.curry[Integer] then puts 'Integer'
else raise 'Shit happens!'
end

# ⇒ Integer

Besides Float and Fixnum, are there any other objects that can't be created with *.new?

I guess immediate values are like that: true, false, nil.



Related Topics



Leave a reply



Submit