5

In Ruby 2.4 and for Integer Ranges, Range(Enumerable)#sum is optimized to return a result directly, without iterating over all elements.

I don't understand why the corresponding code is defined in enum.c for the Enumerable module and not in range.c for Range class.

Why should Enumerable know about classes that include it (e.g. Range, Hash, ...) and check for their type instead of letting those classes overwrite Enumerable#sum?

Seen in enum.c :

 return int_range_sum(beg, end, excl, memo.v);
 # or
 hash_sum(obj, &memo);
Community
  • 1
  • 1
Eric Duminil
  • 52,989
  • 9
  • 71
  • 124

1 Answers1

5

Because rb_range_values might be true for arbitrary class instances (not only explicit Ranges) and we all want them to be optimized too.

Basically, it means that as soon as an instance responds to both begin and end (and exclude_end? btw,) we are to enter this optimization.

Aleksei Matiushkin
  • 119,336
  • 10
  • 100
  • 160
  • Excellent, thanks. I just tried it with `class MyRange; include Enumerable;...` and `#sum` works fine, as long as `begin`, `end` and `exclude_end?` are defined. Couldn't those arbitrary classes inherit from Range when they're so Range-like? – Eric Duminil Jan 13 '17 at 14:51
  • “Couldn't those arbitrary class instances inherit from Range when they're so Range-like?”—I am definitely not a proper person to address this question :) – Aleksei Matiushkin Jan 13 '17 at 14:52
  • @EricDuminil generally speaking one would not want to inherit from a core class. That being said I agree that `Enumerable#sum` should not type check and including classes that need specific functionality should redefine `#sum` or accept the default thus resulting in `Enumerable#sum`, `Range#sum` and `Hash#sum`. This implementation of redefinition per object is perpetuated currently for classes like `Array` and `Hash` where they implement their own versions of `#select` so I cannot see why `#sum` should be any different – engineersmnky Jan 13 '17 at 15:33
  • "one would not want to inherit from a core class"? Why? As far as I can tell, every Class inherits from a core class in Ruby. – Eric Duminil Jan 13 '17 at 15:48
  • @mudasobwa: I didn't notice that `rb_range_values` is defined in `range.c`. That confuses me even more! – Eric Duminil Jan 13 '17 at 15:57
  • @EricDuminil say I inherit from `Array` (e.g. `class MyArray < Array` so that I can implement a new method specifically for my purposes in my subclass but calling any standard `Array` method gets me back an `Array` not `MyArray` so now my custom method is useless because `Array` has no knowledge of this method. – engineersmnky Jan 13 '17 at 16:04
  • @engineersmnky this is not true. `Array` re-implements most of `Enumerable` methods, but not all of them: `class MyArray < Array; def each(&cb); yield 42; end; end; MyArray.new.all? #⇒ true ; MyArray.new.all? { |e| puts e } #⇒ 42` – Aleksei Matiushkin Jan 13 '17 at 16:13
  • @engineersmnky: Thanks for the explanation. See how Pathname is implemented. It calls `self.class.new(result)` all the time. – Eric Duminil Jan 13 '17 at 16:15
  • @mudasobwa I was not saying that `Array` redefines all `Enumerable` methods I was saying when it makes sense to have an object specific version of the method it is re implemented there in congruence with the post as to why `Range#sum` would not make this modification in place of `Enumerable#sum` – engineersmnky Jan 13 '17 at 16:28
  • @EricDuminil yes but I would not consider `Pathname` a core class either, while it is part of the Standard Library it is not part of the core. Would you like to inherit from `Range` and wrap all the methods to ensure the return of your inheriting class? Seems contrived to me – engineersmnky Jan 13 '17 at 16:35