use ruby Enumerable#sum if available #25202

Open
wants to merge 2 commits into
from

Projects

None yet

7 participants

@vipulnsward
Member
vipulnsward commented May 30, 2016 edited

Enumerable#sum was added in ruby/ruby@41ef7ec

  • use Enumerable#sum if its available, to replicate how we enhance on usage of native Aray sum.
  • rm extra check that passed identity is numeric for Array#sum

r? @jeremy

@jeremy jeremy was assigned by rails-bot May 30, 2016
@jeremy jeremy and 1 other commented on an outdated diff May 30, 2016
activesupport/lib/active_support/core_ext/enumerable.rb
@@ -119,7 +134,7 @@ class Array
alias :orig_sum :sum
def sum(init = nil, &block) #:nodoc:
- if init.is_a?(Numeric) || first.is_a?(Numeric)
+ if init || first.is_a?(Numeric)
@jeremy
jeremy May 30, 2016 Member

Why? Removing the Numeric check because other objects are supported now?

@vipulnsward
vipulnsward May 30, 2016 Member

Actually my mistake, its not interchangeable for all objects. probably Enumerable isn't too, let me see.

@jeremy
jeremy May 30, 2016 Member

Ah right, supports other objects if an init arg is provided. It'd be clearer to split up the conditional, then:

if init
  orig_sum(init, &block)
elsif first.is_a?(Numeric)
  orig_sum(0, &block)
else
  super
end

Since Ruby supports other objects now, we could go even further:

if init
  orig_sum(init, &block)
elsif init = _identity_for_sum
  orig_sum(init, &block)
else
  super
end

private def _identity_for_sum #:nodoc:
  case first
  when Numeric; 0
  when String; ''
  when Array; []
  …
  end
end
@jeremy
jeremy May 30, 2016 Member

We could also take the opportunity to remove AS #sum entirely and rely on Ruby 2.4+ behavior for the future. It's so close to AS sum now; the only difference is requiring the initial value.

We could deprecate calling #sum on a non-Numeric Enumerable without an initial arg, then break compat in 5.1, and remove our #sum entirely in Rails 6.

@jeremy jeremy commented on an outdated diff May 30, 2016
activesupport/lib/active_support/core_ext/enumerable.rb
@@ -26,6 +26,21 @@ def sum(identity = nil, &block)
end
end
+ if Enumerable.instance_methods(false).include?(:sum) && !(%w[a].sum rescue false)
+ alias :orig_enum_sum :sum
+
+ def sum(identity = nil, &block) #:nodoc:
+ if identity || first.is_a?(Numeric)
+ identity ||= 0
+ orig_enum_sum(identity, &block)
+ else
+ _enum_sum(identity, &block)
+ end
+ end
@jeremy
jeremy May 30, 2016 Member

extra indentation

@jeremy jeremy added the backport label May 30, 2016
@jeremy jeremy added this to the 5.0.1 milestone May 30, 2016
@yui-knk yui-knk commented on an outdated diff May 31, 2016
activesupport/lib/active_support/core_ext/enumerable.rb
@@ -17,7 +17,7 @@ module Enumerable
# The default sum of an empty list is zero. You can override this default:
#
# [].sum(Payment.new(0)) { |i| i.amount } # => Payment.new(0)
- def sum(identity = nil, &block)
+ def _enum_sum(identity = nil, &block)
@yui-knk
yui-knk May 31, 2016 Contributor

I think it is not good to define public method even if method name starting with _.
And I feel inlining this method is better than a private method.

@vipulnsward vipulnsward commented on the diff May 31, 2016
activesupport/lib/active_support/core_ext/enumerable.rb
+ Support for calling #sum on a non-numeric enumerable without an identity argument,
+ is deprecated and will soon be removed.
+ MSG
+ case first
+ when String; ''
+ when Array; []
+ end
+ end
+ else
+ def sum(identity = nil, &block)
+ if block_given?
+ map(&block).sum(identity)
+ else
+ sum = identity ? inject(identity, :+) : inject(:+)
+ sum || identity || 0
+ end
@vipulnsward
vipulnsward May 31, 2016 Member

hmm, the inlining makes this pretty much grow a lot.

@jeremy
jeremy May 31, 2016 Member

Probably worth it in this case :)

@vipulnsward vipulnsward and 1 other commented on an outdated diff May 31, 2016
activesupport/lib/active_support/core_ext/enumerable.rb
+ orig_enum_sum(identity, &block)
+ else
+ if block_given?
+ map(&block).sum(identity)
+ else
+ sum = identity ? inject(identity, :+) : inject(:+)
+ sum || identity || 0
+ end
+ end
+ end
+
+ private def _identity_for_sum #:nodoc:
+ return 0 if first.is_a?(Numeric)
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Support for calling #sum on a non-numeric enumerable without an identity argument,
+ is deprecated and will soon be removed.
@vipulnsward
vipulnsward May 31, 2016 Member

@jeremy something like this?

@jeremy
jeremy May 31, 2016 Member

Yeah, though the warning belongs in #sum itself. Since we don't want to warn if the elements are numeric, we could split that conditional out:

if identity
  …
elsif first.is_a?(Numeric)
  …
elsif identity = _identity_for_sum
  …
elsif block_given?elseend
@vipulnsward
vipulnsward May 31, 2016 Member

Yeah, that is how this is being handled right now, see the return in case of Numeric.

@jeremy jeremy commented on the diff May 31, 2016
activesupport/lib/active_support/core_ext/enumerable.rb
@@ -1,3 +1,4 @@
+require 'active_support/deprecation'
@jeremy
jeremy May 31, 2016 Member

Newline between require and module declaration

@jeremy jeremy commented on an outdated diff May 31, 2016
activesupport/lib/active_support/core_ext/enumerable.rb
@@ -17,28 +18,44 @@ module Enumerable
# The default sum of an empty list is zero. You can override this default:
#
# [].sum(Payment.new(0)) { |i| i.amount } # => Payment.new(0)
- def _enum_sum(identity = nil, &block)
- if block_given?
- map(&block).sum(identity)
- else
- sum = identity ? inject(identity, :+) : inject(:+)
- sum || identity || 0
- end
- end
-
if Enumerable.instance_methods(false).include?(:sum) && !(%w[a].sum rescue false)
alias :orig_enum_sum :sum
@jeremy
jeremy May 31, 2016 Member

Still have four spaces indentation here instead of two :)

@jeremy jeremy commented on an outdated diff May 31, 2016
activesupport/lib/active_support/core_ext/enumerable.rb
@@ -118,13 +150,8 @@ def sum(identity = nil)
class Array
alias :orig_sum :sum
- def sum(init = nil, &block) #:nodoc:
- if init.is_a?(Numeric) || first.is_a?(Numeric)
- init ||= 0
- orig_sum(init, &block)
- else
- super
- end
+ def sum(identity = nil, &block) #:nodoc:
+ super
@jeremy
jeremy May 31, 2016 Member

Ruby has separate Array#sum and Enumerable#sum implementations, so we'll want to continue using orig_sum for the optimized Array implementation but calling super to get our compatible #sum behavior.

@vipulnsward
Member

Ok, update. How does this look.

vipulnsward added some commits May 30, 2016
@vipulnsward vipulnsward Enumerable#sum was added in ruby/ruby@41ef7ec
- use Enumerable#sum if its available.
- rm extra check that passed identity is numeric
6469293
@vipulnsward vipulnsward Depracate calling #sum on enumerables without identity argument f92bb78
@vipulnsward
Member

Did another rebase, test failure is unrelated.

- else
- sum = identity ? inject(identity, :+) : inject(:+)
- sum || identity || 0
+ if Enumerable.instance_methods(false).include?(:sum) && !(%w[a].sum rescue false)
@rwz
rwz Nov 3, 2016 edited Contributor

what about method_defined?(:sum). Seems more straightforward and concise

+ return 0 if first.is_a?(Numeric)
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Support for calling #sum on a non-numeric enumerable without an identity argument,
+ is deprecated and will soon be removed.
@matthewd
matthewd Nov 3, 2016 Member

Do we actually agree with this? It's nice that upstream has partially adopted the API and optimized it, but we shouldn't lose sight of the fact we added it to make code look better... it's possible we still prefer our original, more generous, API. I certainly do.

OTOH, if we have in fact changed our mind about the API, this deprecation should apply on earlier Ruby versions too.

+ end
+ else
+ def sum(identity = nil, &block)
+ if block_given?
@jmolina91
jmolina91 Nov 4, 2016

You are repeating this code twice, maybe you can abstract it in a private common method

@kaspth
Member
kaspth commented Dec 31, 2016

@vipulnsward you still interested in picking this back up again? 😊

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment