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?
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
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.
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.