Sum with Block

I think @anon9791410 is proposing that we add a new version of reduce that doesn't have an "initial value" parameter, and returns an optional if the sequence is empty. However, that's quite different from what Tim and I are proposing here and I think would best suited to another thread. Even with this new version of reduce, the summing code is still not as clean as using .sum(_:).

These are more statistically oriented, where summing has a wider variety of uses ā€” for example summing up the heights in a bunch of stacked views. These other functions also have issues with integers, as mentioned in the previous thread.

1 Like

I would prefer if the solution kept the reduce(0, +) spelling, which makes the semantics clear. sum() makes you wonder what numerical summation algorithm is being used (just like average() and standardDeviation() does).

1 Like

The same could be said in the opposite direction for using the ternary operator and .isEmpty, so Iā€™m still firmly on the side of representing the sum operation in a mathematically faithful way.

2 Likes

and allSatisfy. :wink:

Edit:

Wait, I thought you mean that those two are examples of mathematically faithful functions :grimacing:

1 Like

This makes no sense at all to me, aren't we talking about AdditiveArithmetic here? What it does is precisely that, it defines what zero means and what addition means.

I'm aware that the documentation for AdditiveArithmetic even has the following example:

extension Sequence where Element: AdditiveArithmetic {
  func sum() -> Element {
    return reduce(.zero, +)
  }
}

But again, I think a sum() method, if provided by the Standard Library, should do better than that.

For example, using the above implementation:

let array1: [Float] = [ 3_000_000, -3_000_000,      0.125 ]
let array2: [Float] = [ 3_000_000,      0.125, -3_000_000 ]
let set = Set(array1)
print(array1.sum()) // 0.125
print(array2.sum()) // 0.0
print(set.sum()) // Either 0.0 or 0.125, it's different each time the program runs.

Why shouldn't sum() return 0.125 for all three of those cases?


It's easy to envision many more similar examples of where a reduce(0, +) hiding behind a sum() may result in surprising results, like trapping or not randomly between runs, etc.

reduce(0, +) says on the tin what it does, and unless you are unfamiliar with the specifics of the numeric type or operator, you will not be surprised.

sum() on the other hand, is IMO something that probably belongs in swift numerics or some other module.

From the other thread:

5 Likes

Part of my argument (basically echoed by others, in the other thread) is against going the way of C# and Kotlin and having specialized functions for too much. This sum overload is too specific, but the less specific version of it is not, and we don't have it: a combination of map and reduce.

The alternative is too messy, and might not be optimally performant. You've argued very well for the right level of what we need, but you've taken it further than it needs to go. I think now that you know this, you'll go play around with the idea and see the other cases where sum is two important abstractions away from what we have, not just one.

It's truth elsewhere, but it's a bogus argument for Swift's domain. A number is a loss of information over an optional number. Go render a SwiftUI view conditionally for a sum, if there are numbers to sum, and tell someone to their face that it's the right choice for the job.

A sane person will then bite your nose.

I was only replying to the idea that you'd need to tell it which 0 to use as the starting point, I thought that was part of your point.

As to the sum being dependent on order, that's unfortunate but it doesn't seem very different from all the other ways that Set conforms to Collection. When you iterate through it or ask for the first element, you also don't know what to expect, for basically the same reason.

These small errors are just part of life when working with floating point numbers, and if you need more control you simply have to avoid using Set. I don't think this is an argument against .sum.

1 Like

You could say that you are losing information, but that is the whole idea of sum. You are starting with an array of numbers and end up with one number. Even you expect to lose information about the specific number of elements, so why would you retain the knowledge that there are 0 elements or not?

The fact is that the sum of no elements IS 0, just like raising anything to the zeroth power gives you 1. It might be slightly surprising to some people, but basic mathematical functions in Swift should behave like in real life, else they will be extremely surprising and actually incorrect.

Finding the sum of an array is a separate operation from counting the elements, so you shouldn't mourn the loss of count information.

4 Likes

I'm simply saying that I would expect a method that is called sum() to take care of all the nitty gritty and gotchas involved in computing the sum of a sequence, that's what a Standard Library is for, it provides proper implementations of things, freeing users from taking care of implementation details like making sure the order of elements doesn't produce different results or make my program crash or not randomly between runs despite the exact same input, etc.

6 Likes

I can't see how .sum could crash, but how could the standard library ensure a "correct" sum for a set of Floats? Since the order isn't specified, how could the addition error be defined?

let array1: [Int8] = [127, 8, -128]
let array2: [Int8] = [127, -128, 8]
let set = Set(array1)
// print(array1.sum()) // traps if uncommented
print(array2.sum()) // 7
// print(set.sum()) // prints 7 or traps randomly between runs if uncommented

Maybe I shouldn't have used the word "correct", I meant something like this.

I think most people would expect the result of a method called sum() to produce the same result no matter the order of the elements, so I agree with what you said here:

(Although it's of course not possible. But we could perhaps say that mathematically named functions in Swift should make an effort to not surprise people.)

Yeah but it seems that this is really an insoluble dilemma. On the one hand float addition will always give order dependent errors, and on the other hand the order of elements in a set is not defined. Since these are basic features of these respective types, and not related to summing per se, is it really an argument against sum? The same problem applies if you sum a set yourself with .reduce(0, +).

It's a dilemma that can be more or less dealt with, and I guess it all boils down to:

  • Does a sum() method belong in the Standard Library?
  • If so, what should its exact semantics be and why?
  • Also, why should or shouldn't related methods like mean() and median() be in the Standard Library?

(Order is related to summing in that summation algorithms reorder the elements in specific ways, I guess.)

Sure it is related, that's the point. On computers the order of summation will lead to different results. But since Set doesn't have a guaranteed order you either have to accept that the result might differ between calls (just like a calling .first or iterating through a Set are not stable). Or you need to come up with some kind of canonical way to sum a Set, but that seems strange when you haven't done that for any other collection operations on Set.

Again, it would be reasonable to expect that method called sum would, for example, return the same result no matter the order of the elements, a trivial implementation of such a sum method would be to replace
reduce(0, +)
with
sorted().reduce(0, +)
(Yes, it would require AdditiveArithmetic & Comparable and be less efficient, and no I'm not proposing that as an alternative implementation.)

I think various different (non-trivial and usable) sum methods belong in Swift Numerics, and I'm repeating myself so I will back out of this thread :slight_smile: .

I agree with @Jens here. It's plausible that sum would be perceived as (and might actually be) a numerically sophisticated numeric calculation, in contrast to the sequential reduction over operator +.

It may be that there's never a good-enough sum function of this kind. Either way, I think it's much better to leave it clear that the sequential reduction is a reduction.

For that, I'd support adding a third parameter specifying a keypath to address the original concerns of this thread.

1 Like

I think that there's definitely room in a package like Swift Numerics for a sum function that lets you specify what you want:

data.sum(strategy: .reproducible)
data.sum(strategy: .correctlyRounded)
data.sum(strategy: .yolo)

I don't think that an API like this is a good fit for the standard library, and as others have suggested, I'm not sure that the simplest algorithm actually carries its weight.

20 Likes

That doesn't sound very sane to me.

3 Likes