5

I want to fold a collection or Y's and return an Option[X]. I want to start with None. Like this...

def f(optX: Option[X], y: Y): Option[X]
val optX = collectionOfY.fold(None) { case (prev, y) => f(prev,y) }

adding unneeded types to make it clearer

val optX: Option[X] = collectionOfY.fold(None) { case (prev: Option[X], y: Y) => f(prev,y) }

However, the compiler can not figure out the type properly and I have to write it like this

val xx: Option[X] = None
val optX = collectionOfY.fold(xx) { case (prev, y) => f(prev,y) }

What is the magic Scala syntax to write this?

Thanks Peter

dbc
  • 104,963
  • 20
  • 228
  • 340
opus111
  • 2,744
  • 4
  • 25
  • 41

3 Answers3

7

Just use foldLeft and any of the following

... foldLeft(Option.empty[X]) ... or ... foldLeft(None: Option[X]) ... or ... foldLeft[Option[X]](None) ...

After all, fold just calls foldLeft. You only really want to use fold when your A1 really is a super-type of A, if that really is the case then you can use fold as above and the compiler will know the type correctly.

For example, Option[List[Int]] <: Option[Seq[Int]] by covariance so we don't get an Any here:

List(Some(List(1,2,3))).fold[Option[Seq[Int]]](None)((_, _) => Some(Seq(1)))

> res2: Option[Seq[Int]] = Some(List(1))

Finally, if you do indeed know Option[X] will be a super-type of Y then say this explicitly in the type declaration of Y - i.e. Y <: Option[X], then you can use fold with the solutions given above.

See When should .empty be used versus the singleton empty instance? for a related discussion.

Community
  • 1
  • 1
samthebest
  • 30,803
  • 25
  • 102
  • 142
  • Thank you. That's interesting, I didn't realize the rules would be different for foldLeft and fold. However, for my problem, Option.empty[T] does the trick nicely – opus111 Aug 01 '14 at 11:59
2

As was pointed out in the comments above, the preferred solution is change the first parameter passed to Option.fold to avoid the use of None and use Option.empty[X] instead.

val optX = collectionOfY.fold(Option.empty[X]) { case (prev, y) => f(prev,y) }

The Scala compiler accepts this without complaint.

Daryl Odnert
  • 522
  • 3
  • 15
1

This behavior is logical, because fold is defined as

def fold[A1 >: A](z: A1)(op: (A1, A1) ⇒ A1): A1

that means the starting parameter is a supertype of A, but None is's a supertype of Option[T]. If you specify the type directly (like in your second example) then the compiler has enough information to figure out the return type of fold.

One possible workaround is to specify the result of interaction of head of your collection with None as a starting point, and fold it with the rest of the collection (adding a check for Nil):

val optX = collectionOfY.match {
  case Nil => None
  case x:xs => xs.fold(f(None,x)) {case (prev,y) => f(prev,y) }
}
Ende Neu
  • 15,581
  • 5
  • 57
  • 68
Ashalynd
  • 12,363
  • 2
  • 34
  • 37