Sketchup::Selection#sum doesn't work properly

Using the Ruby API, select a couple edges, then run the following to see the bug:

selection = Sketchup.active_model.selection
selection.sum(&:length) # => Incorrect, number is too small
selection.to_a.sum(&:length) # => Correct
selection.map(&:length).sum # => Correct

So it seems that Array#sum works properly, but Sketchup::Selection#sum does not.

The Sketchup::Selection class does not define a #sum method.

Sketchup.active_model.selection.method(:sum).owner
#=> Enumerable

So refer to the core Ruby documentation …
Enumerable#sum
… which says …

If a block is given, the block is applied to each element before addition.

Your snippet would then be calling #length on each member of the selection set in turn.
But not all selectable objects respond to a #length method call, so this snippet is dangerous.

Also, in Ruby #+ does not always equate to addition. The #+ method of the first object is called using the second operand as an argument. In the case of nested arrays this performs array concatenation.

Often, the methods in the Enumerable module vary somewhat from those of the same name in the Array class. This is the case here. You can see the difference in the C code by clicking the “click to toggle source” link when hovering over the method docstring. (You’ll need to scroll down a bit to see the C code at the bottom of the method doc.)

Regardless, Trimble cannot do anything about Ruby Core libraries and I think would likely not attempt to override the #sum method mixed in from the Enumerable module.
Any bugs would need to be reported to the Core Ruby project.

The way this module’s block form methods are meant to work, is that the user defines a block for custom behavior.

The bottom line is, Ruby is multi-paradigm … so use what works.

1 Like

From a quick test, Enumerable#sum does not know how to handle the SketchUp API’s Length class objects, which result from the 1st #length call on the first edge in the collection. So the method’s iteration poops out and returns the length of only 1 edge.

This probably happens because of a quirk in the API as Length no longer is a subclass of Float (even though the documentation still indicates this.)

Further proof. The 4 10" sides of a rectangle are selected:

Sketchup.active_model.selection.sum {|edge| edge.length.to_f }
#=> 40"

So Enumerable#sum will work correctly if the edge value’s are converted to Float.

2 Likes

That’s interesting. I think Dan is right, this probably relates to Length attempting to be a subclass of Float which isn’t really possible.

Ok, so looking a little deeper into the docs:

Both sum (Enumerable) - APIdock and sum (Array) - APIdock say that they

may not respect method redefinition of “+” methods such as Integer

Which is presumably what the Sketchup Length object does, however I’m not actually sure where this definition is, since Class: Length — SketchUp Ruby API Documentation doesn’t show any definition.

Thankfully, length summing does work with normal arrays of entities, but this behavior with Enumerable#sum definitely was surprising. Anyway, it’s obviously up to y’all whether you want to patch it, I was just trying to report the issue.

Thanks for your responses.

I was looking at this as well and am not sure myself. Class introspection seems to indicate that the class defines a #+ method, but this may be an artifact of the “shoehorned” nature of how Class: Length was redefined for Ruby 2.x and higher.

Patching a Core Ruby library is probably “out of scope” for the API.

I just did a test and modifying Length#+ has no effect on Enumerable#sum.

FYI. The official API Issue tracker is here …
Issues · SketchUp/api-issue-tracker · GitHub

There are several open issues where this quirk could be mentioned: