Skip to content

Return type of Enumerable#sum and #product for union elements #15269

Open
@straight-shoota

Description

Enumerable(T)#sum and #product usually use the element type (T) as the type of the sum/product unless specified.

This is usually a good default but raises the question what should happen when the element type is not a single type, but a union?
Turns out, the implementations picks the first type in the union (note: order of types in a union is lexicographic by the type name):

typeof([1_i8, 2_i16].sum) # => Int8

I think this is quite unexpected and can lead to surprising behaviour: For example [1_i16, 12345678_i32].sum raises an overflow error when the result would fit perfectly into Int32.

If the implementation picks one type from the union, I figure it should rather be the biggest type of the union. For number types this might be relatively easy, but not without issues (e.g. when combinging signed and unsigned types of the same width). And there would be no way this could work for custom types. I don't have a real use cases for math operations on unions outside of Number, but there is probably some with related values types.

In my opinion, it would probably be best to not automatically pick a type if there are multiple options. It's better to make this a compile time error instead of running into weird overflows at runtime because the sum type is smaller than expected.
It's trivial to specify the desired target type by passing an initial value in that type.
For example:

[1_i16, 12345678_i32].sum(0_i32) # => 12345679

Of course this would be a breaking change. But IMO that's acceptable because it corrects a confusing behaviour. This is a rarely used feature anyway, so impact should be minimal.

This repo doesn't seem to use this and test-ecosystem doesn't show anything breaking either: https://github.com/crystal-lang/test-ecosystem/actions/runs/12285549227/job/34286237286

This issue was originally reported at https://fosstodon.org/@[email protected]/113626739326812835

Metadata

Assignees

No one assigned

    Labels

    kind:bugA bug in the code. Does not apply to documentation, specs, etc.topic:stdlib:numeric

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions