Search code examples
listscalafunctional-programmingmonads

List Monad: Consequences of the signature of List.flatMap(f: (A) ⇒ GenTraversableOnce[B])


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?


Solution

  • 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