From: shugo@... Date: 2015-11-26T08:20:02+00:00 Subject: [ruby-core:71696] [Ruby trunk - Bug #11704] [Assigned] Refinements only get "used" once in loop Issue #11704 has been updated by Shugo Maeda. Status changed from Open to Assigned Assignee set to Yukihiro Matsumoto Daniel P. Clark wrote: > I wrote a benchmark for testing different ways of implementing a `uniq` method and I chose to do it using refinements. I looped over the results in the benchmark and refined the method with 6 different refinements which each worked. But the next iteration when `using` is called it doesn't re-refine with previously used refinements. Once refinements are activated by `using` in a specific order, the precedence of the refinements cannot be changed, because Refinements are not designed for such dynamic use. What do you think, Matz? ---------------------------------------- Bug #11704: Refinements only get "used" once in loop https://bugs.ruby-lang.org/issues/11704#change-55103 * Author: Daniel P. Clark * Status: Assigned * Priority: Normal * Assignee: Yukihiro Matsumoto * ruby -v: * Backport: 2.0.0: UNKNOWN, 2.1: UNKNOWN, 2.2: UNKNOWN ---------------------------------------- Same results on Ruby 2.2.2 through Ruby 2.3.0dev (2015-11-18 trunk 52625) [x86_64-linux] I wrote a benchmark for testing different ways of implementing a `uniq` method and I chose to do it using refinements. I looped over the results in the benchmark and refined the method with 6 different refinements which each worked. But the next iteration when `using` is called it doesn't re-refine with previously used refinements. Example benchmark output on first iteration: ~~~ Array.new(200) !self.dup.uniq! 1.770000 0.010000 1.780000 ( 1.778248) Array.new(200) == uniq.length 1.860000 0.000000 1.860000 ( 1.866862) Array.new(200) == uniq.sort 2.580000 0.010000 2.590000 ( 2.584515) Array.new(200) each index 41.450000 0.080000 41.530000 ( 41.626149) Array.new(200) combination(2) 23.460000 0.060000 23.520000 ( 23.568865) Array.new(200) == self.uniq 1.900000 0.010000 1.910000 ( 1.909466) ~~~ After that the same methods did not get refined. ~~~ Array.new(210) !self.dup.uniq! 1.990000 0.000000 1.990000 ( 2.004269) Array.new(210) == uniq.length 2.030000 0.010000 2.040000 ( 2.032602) Array.new(210) == uniq.sort 1.990000 0.000000 1.990000 ( 1.999509) Array.new(210) each index 1.990000 0.010000 2.000000 ( 2.000181) Array.new(210) combination(2) 2.000000 0.000000 2.000000 ( 2.010159) Array.new(210) == self.uniq 2.000000 0.010000 2.010000 ( 2.009117) Array.new(220) !self.dup.uniq! 2.100000 0.010000 2.110000 ( 2.113701) Array.new(220) == uniq.length 2.070000 0.000000 2.070000 ( 2.075249) Array.new(220) == uniq.sort 2.090000 0.010000 2.100000 ( 2.102771) Array.new(220) each index 2.070000 0.000000 2.070000 ( 2.077393) Array.new(220) combination(2) 2.070000 0.010000 2.080000 ( 2.079561) Array.new(220) == self.uniq 2.100000 0.010000 2.110000 ( 2.110839) Array.new(230) !self.dup.uniq! 2.210000 0.000000 2.210000 ( 2.236008) Array.new(230) == uniq.length 2.160000 0.010000 2.170000 ( 2.166484) Array.new(230) == uniq.sort 2.140000 0.000000 2.140000 ( 2.150384) Array.new(230) each index 2.130000 0.010000 2.140000 ( 2.134572) Array.new(230) combination(2) 2.120000 0.000000 2.120000 ( 2.129683) Array.new(230) == self.uniq 2.130000 0.010000 2.140000 ( 2.137515) ~~~ I found no way to inspect what code was being used as refinements currently don't allow introspection (I have read the Refinement Specs https://bugs.ruby-lang.org/projects/ruby-trunk/wiki/RefinementsSpec ). But I figure if I wanted to see what the refinement was I could write an additional method to document the current refinement in use. ~~~ruby module BooleanUniqWithEqualLength refine Array do def uniq? self.length == self.uniq.length end def which_refinement_uniq? "self.length == self.uniq.length" end end end ~~~ But introspection is not the issue I'm raising here. The issue is that you can only use the refinement once within the scope of the loop. Look at the benchmark results and see the first 6 are correct, and the following 3 times the output is lying about what refinement is being used. Here's the full benchmark code: ~~~ruby require 'securerandom' require 'benchmark' # written to allow 65,536 unique array items array_of_x = lambda {|x| SecureRandom.hex(x*2).scan(/..../) } module Refinements module BooleanUniqWithDupUniqBang refine Array do def uniq? !self.dup.uniq! end end end module BooleanUniqWithEqualLength refine Array do def uniq? self.length == self.uniq.length end end end module BooleanUniqWithEqualSort refine Array do def uniq? self.sort == self.uniq.sort end end end module BooleanUniqWithEachIndex refine Array do def uniq? self.each_with_index { |a,b| self.each_with_index { |c,d| next if b == d return false if a == c } } true end end end module BooleanUniqWithCombination refine Array do def uniq? self.combination(2).each {|a,b| return false if a == b} true end end end module BooleanUniqWithUniq refine Array do def uniq? self == self.uniq end end end end bench_reps = 10_000 bench = Benchmark.benchmark("\nTesting various ways of implementing :uniq? on Array\n(smaller numbers are better)\n\n",34) do |x| # Note doing anymore than one test per test type seems to wash the results into all things being equal. # Only the first test gives realistic numbers. [500].each do |qty| x.report("Array.new(#{qty}) !self.dup.uniq!") { using Refinements::BooleanUniqWithDupUniqBang bench_reps.times do array_of_x.(qty).uniq? end } x.report("Array.new(#{qty}) == uniq.length") { using Refinements::BooleanUniqWithEqualLength bench_reps.times do array_of_x.(qty).uniq? end } x.report("Array.new(#{qty}) == uniq.sort") { using Refinements::BooleanUniqWithEqualSort bench_reps.times do array_of_x.(qty).uniq? end } x.report("Array.new(#{qty}) each index") { using Refinements::BooleanUniqWithEachIndex bench_reps.times do array_of_x.(qty).uniq? end } x.report("Array.new(#{qty}) combination(2)") { using Refinements::BooleanUniqWithCombination bench_reps.times do array_of_x.(qty).uniq? end } x.report("Array.new(#{qty}) == self.uniq") { using Refinements::BooleanUniqWithUniq bench_reps.times do array_of_x.(qty).uniq? end } end end ~~~ -- https://bugs.ruby-lang.org/