Issue #12092 has been updated by Jeremy Evans.


Nobuyoshi Nakada wrote:
> Why does it need to be a singleton method but can't a method from an included module?

I think this should work with arbitrary objects, and all objects in ruby that can have singleton classes support singleton methods. If you just want to deal with modules, you can currently do:

~~~
a1 = a.dup
(a.singleton_class.ancestors[1..-1] - a.class.ancestors).each do |m|
  a1.extend m
end
a1.opts = a.opts.merge(hash).freeze
a1.freeze
~~~

However, there is no way to handle singleton methods AFAIK:

~~~
a.singleton_methods.each do |meth|
  um = a.method(meth).unbind
  # Raises TypeError
  um.bind(a1)
end
~~~

In addition, doing dup/freeze instead of clone performs worse even if you are just copying modules.  Here's a comparison using alternative approach #2 listed above. Code:

~~~
A = Struct.new(:opts) do
  def initialize_clone(orig, opts={})
    self.opts = orig.opts.merge(opts).freeze
    super(orig)
  end

  def clone2(opts={})
    clone = dup
    (singleton_class.ancestors[1..-1] - self.class.ancestors).each do |m|
      clone.extend m
    end
    clone.opts = self.opts.merge(opts).freeze
    clone.freeze
  end
end
module B; def b; 2 end end
module C; def c; 3 end end
a = A.new({})
a.extend B
a.extend C
def a.a; 1; end
a.freeze
h = {:a=>1}

require 'benchmark'

Benchmark.bm(15) do |x|
  x.report('clone'){100000.times{a.clone(h)}}
  x.report('dup/freeze'){100000.times{a.clone2(h)}}
end
~~~

Results:

~~~
                      user     system      total        real
clone             2.210000   0.000000   2.210000 (  2.209889)
dup/freeze        5.490000   0.000000   5.490000 (  5.488063)
~~~


----------------------------------------
Feature #12092: Allow Object#clone to yield cloned object before freezing
https://bugs.ruby-lang.org/issues/12092#change-57506

* Author: Jeremy Evans
* Status: Feedback
* Priority: Normal
* Assignee: ruby-core
----------------------------------------
This allows creating modified clones of frozen objects that have
singleton classes:

~~~ruby
a = [1,2,3]
def a.fl; first + last; end
a.freeze
a.fl # => 4

clone = a.clone{|c| c << 10}
clone.last # => 10
clone.fl # => 11
clone.frozen? # => true
~~~

Previously, this was not possible at all.  If an object was
frozen, the clone was frozen before the cloned object could
be modified.  It was possible to modify the clone using
`initialize_clone` or `initialize_copy`, but you couldn't change how
to modify the clone on a per-call basis.  You couldn't use `dup`
to return an unfrozen copy, modify it, and then freeze it, because
`dup` doesn't copy singleton classes.

This allows ruby to be used in a functional style with immutable
data structures, while still keeping the advantages of singleton
classes.

---Files--------------------------------
0001-Allow-clone-to-yield-cloned-object-before-freezing.patch (2.51 KB)
0001-Allow-clone-to-take-a-second-argument-passed-to-init.patch (2.37 KB)


-- 
https://bugs.ruby-lang.org/

Unsubscribe: <mailto:ruby-core-request / ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-core>