2

Recently I stumbled upon some code that shouldn't have worked but did. This is a simplified illustration:

val l=List(1,2,3,4,5)
def isEven(i:Int):Option[Int] = if (i%2==0) Some(i) else None

for {elem <- l
     even <- isEven(elem)
    } yield even

That will produce List(2,4) and might be no surprise to Scala developers.

But it shouldn't work given that List is monadic and should be of this form:

class M[A] {
    def flatMap[B](f:A => M[B]):M[B]
    def map[B](f:A => B):M[B]
}

Following this definition, every element of a chain of monadic operations must belong to the same monad. Map transforms elements within the context of the monad and flatMap must 'trap' free elements into the monad. In the example above, we have this desugarized representation:

l.flatMap(elem => isEven(elem))

which is of type: List[Int].flatMap[Int](f:Int => Option[Int]) and does not conform to the monadic definition.

The correct form of isEven should have been:

def isEven(i:Int):List[Int] = if (i%2==0) List(i) else Nil

Looking into the scala-docs, it turns out that the reason that List and Option can be combined in a for comprehension in Scala, is that the flatMap on List is defined as flatMap[B](f: (A) ⇒ GenTraversableOnce[B]): List[B] and that allows for any traversable instance to be 'flatmapped' over. Option is traversable, Set, Seq, and many others are too.

I'm left wondering: What are the consequences of this broader definition of flatMap.

Are there cases that one should be aware/beware of?

Daniel Widdis
  • 8,424
  • 13
  • 41
  • 63
maasg
  • 37,100
  • 11
  • 88
  • 115
  • 2
    A small point, but `Option` is not a `GenTraversableOnce`. There is an implicit conversion from `Option` to `Iterable` that allows the `flatMap` to work. – wingedsubmariner May 30 '14 at 01:29
  • 1
    This flaw is part of motivation to create a more principled Maybe in Scalaz. See https://github.com/scalaz/scalaz/pull/728 – drstevens Jun 16 '14 at 15:59

1 Answers1

2

The signature you have listed is the notorious "use case", not the method signature, aka the "full signature".

def flatMap[B, That](f: (A) ⇒ GenTraversableOnce[B])(implicit bf: CanBuildFrom[List[A], B, That]): That

You can build any target for which there is a CanBuildFrom.

The question is quite broad; I'm not sure the list of consequences has been catalogued.

Is the Scala 2.8 collections library a case of "the longest suicide note in history"?

The attempt to do so in the space of one talk.

Or for example:

https://stackoverflow.com/a/17377859/1296806

Community
  • 1
  • 1
som-snytt
  • 39,429
  • 2
  • 47
  • 129
  • I had seen the two first links. I'm more interest by the last: Practical implications of this broader signatures have in day-to-day programming. One I figured out: E.g. for {e <- l; i <- s} yield e+i could be affected given `l` is a list, `s` is a set and we flip their order in the for comprehension. – maasg May 30 '14 at 21:53