What is the purpose of the constraint `T : S` in reduce?

Why is the reduce function defined like this

public inline fun <S, T : S> Iterable<T>.reduce(operation: (acc: S, T) -> S): S

and not like this

public inline fun <S, T> Iterable<T>.reduce(operation: (acc: S, T) -> S): S

which would essentially turn it into fold without an initial value? What are the advantages?

How would you define it? What would it return for an empty iterable? What would it provide as an acc for the first item?

1 Like

I’m not sure about your first two questions, but the main point is this:

In fold the caller provides the initial accumulator, so it can be anything, it doesn’t have to be related to the iterable type. In reduce the first item becomes an initial accumulator, so T and S can’t be entirely unrelated, because otherwise how would it pass the first item as an accumulator?

2 Likes

By “how would you define it” I meant “try to implement it yourself and you’ll see that it fails”
By “what would it return for an empty iterable” I meant what would it return for emptyList<Int>().reduce { _, i -> i.toString() } for example. If reduce gets an empty iterable, it has no Ts, so forget about using operation, so then you have to magically produce an S to return

Alright, got it - thanks!

The key for me was understanding that in reduce the first item becomes the initial accumulator and thus must be compatible. It also helped to see how reduce is defined in TypeScript (version 5.1.6). There - when not providing an initial value - it is defined so strict that the accumulator must be of the same type as the item. So, one may think of the allowance of a supertype in Kotlin’s reduce as a bonus. TypeScript’s reduce - given an initial value - also fulfills the purpose of Kotlin’s fold.

1 Like

The original reduce function is defined with the constraint T : S to ensure type safety, allowing the first element of the iterable to act as the initial accumulator. This guarantees that all elements can be treated as instances of S, preventing type mismatches and aligning with the typical semantics of a reduction operation. Removing this constraint would introduce potential type safety risks and ambiguities in how the initial value is derived.