rubyruby-2.0

Can't modify frozen Fixnum on Ruby 2.0


I have the following code:

require 'prime'
class Numeric
  #... math helpers

  def divisors
    return [self] if self == 1
    @divisors ||= prime_division.map do |n,p|
      (0..p).map { |i| n**i }
    end.inject([1]) do |a,f|
      a.product(f)
    end.map { |f| f.flatten.reduce(:*) } - [self]
  end

  def divisors_sum
     @divisors_sum ||= divisors.reduce(:+)
  end

   #... more methods that evaluate code and 'caches' and assigns (||=) to instance variables
end

Which outputs an error with:

> 4.divisors
/home/cygnus/Projects/project-euler/stuff/numbers.rb:24:in `divisors_sum': can't modify frozen Fixnum (RuntimeError)

The error disappears when I remove caching into the instance variables @divisors, @divisors_sum... etc. And this only happens on ruby 2.0. Ran it on 1.9.3 without issues. What's happening?


Solution

  • @divisors is an instance variable on an instance of Fixnum, so you are trying to alter it. You probably shouldn't be doing this.

    What about this?

    module Divisors
      def self.for(number)
        @divisors ||= { }
        @divisors[number] ||= begin
          case (number)
          when 1
            [ number ]
          else
            prime_division.map do |n,p|
              (0..p).map { |i| n**i }
            end.inject([1]) do |a,f|
              a.product(f)
            end.map { |f| f.flatten.reduce(:*) } - [ number ]
          end
        end
      end
    
      def self.sum(number)
         @divisors_sum ||= { }
         @divisors_sum[number] ||= divisors(number).reduce(:+)
      end
    end
    
    class Numeric
      #... math helpers
    
      def divisors
        Divisors.for(self)
      end
    
      def divisors_sum
         Divisors.sum(self)
      end
    end
    

    This means that the methods in Numeric do not modify any instance, the cache is stored elsewhere.