Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can't modify frozen Fixnum on Ruby 2.0

Tags:

ruby

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?

like image 234
ichigolas Avatar asked Dec 16 '22 12:12

ichigolas


2 Answers

@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.

like image 66
tadman Avatar answered Jan 01 '23 22:01

tadman


In addition to @tadman's answer, the reason why works in 1.9.3 and not in 2.0.0 is because 2 years ago the decision was made to freeze the Fixnums (and Bignums) as evidenced by this and this.

like image 40
fmendez Avatar answered Jan 01 '23 22:01

fmendez