Reputation: 41749
I'm sure there's a good reason for this, but I'm not seeing it.
Fold
on (say) List
returns
the result of applying fold operator
op
between all the elements andz
It has an obvious relationship with foldLeft
and foldRight
that do the same thing but with defined order (and so don't need associative operators)
Fold
on Option
returns
Returns the result of applying
f
to thisscala.Option
's value if thescala.Option
is nonempty. Otherwise, evaluates expressionifEmpty
.
ifEmpty
is (in the position of) the z
for a List. f
is (in the position of) the op
For None
(which, using my mental model of Option
as a "container" that may or may not contain a value, is an "empty" container), things are OK, Option.fold
returns the zero (value of ifEmpty
).
For Some(x)
, though, shouldn't f
take two params, z
and x
so it's consistent with the fold
on sequences (including having a similar relationship to foldLeft
and foldRight
).
There's definitely a utility argument against this - having f
just take x
as a parameter in practice probably more convenient. In most cases, if it did also take z
that would be ignored. But consistency matters too...
So can someone explain to me why fold
on Option is still a "proper" fold
?
Upvotes: 5
Views: 329
Reputation: 53
scala.Option.fold is a mistake; poor API design.
Option {
// This is equivalent to: map f getOrElse ifEmpty.
def fold[B](ifEmpty: => B)(f: A => B): B = if (isEmpty) ifEmpty else f(this.get)
}
The Option.fold
method may be handy, but it's no fold
(likewise for Either.fold
).
TraversableOnce {
// List, et alia
def fold[A1 >: A](z: A1)(op: (A1, A1) => A1): A1 = foldLeft(z)(op)
}
The benefit of a standard method is that the concept and type signature are consistent (no need to review the docs again).
Also, Option.foldLeft
is consistent, and does take a binary operator.
Upvotes: 1
Reputation: 4662
Why should it be?
The reason fold "normally" takes a binary operator is because, "normally" is used over a list that can be processed one by one with binary operators (and the seed value z).
Fold on an option is, de facto, a map function with a default case. If you look at its definition, it literally is:
if (isEmpty) ifEmpty else f(this.get)
Since you only have one possible argument, f has to be an unary operator. One could argue to have an option fold that takes a binary operator and used the ifEmpty value as the seed in case the option is defined, but your sensible seed value and your sensible value for when the option is empty may be greatly different.
As someone pointed out, for different structures you need different arities (such as for trees), because the "sensible" application of a reduction has different structures.
Upvotes: 2