mon
mon

Reputation: 22234

Explanation on the error with for comprehension and co-variance

Question

Would like to get assistance to understand the cause of the error. The original is from Coursera Scala Design Functional Random Generators.

Task

With the factories for random int and random boolean, trying to implement a random tree factory.

trait Factory[+T] {
self => // alias of 'this'
    def generate: T
    def map[S](f: T => S): Factory[S] = new Factory[S] { 
        def generate = f(self.generate) 
    }
    def flatMap[S](f: T => Factory[S]): Factory[S] = new Factory[S] { 
        def generate = f(self.generate).generate 
    }
}
val intFactory = new Factory[Int] {
    val rand = new java.util.Random
    def generate = rand.nextInt()
}
val boolFactory = intFactory.map(i => i > 0)

Problem

The implementation in the 1st block causes the error but if it changed into the 2nd block, it does not. I believe Factory[+T] meant that Factory[Inner] and Factory[Leaf] could be both treated as Factory[Tree].

I have no idea why the same if expression in for block is OK but it is not OK in yield block. I appreciate explanations.

trait Tree
case class Inner(left: Tree, right: Tree) extends Tree
case class Leaf(x: Int) extends Tree

def leafFactory: Factory[Leaf] = intFactory.map(i => new Leaf(i))
def innerFactory: Factory[Inner] = new Factory[Inner] {
  def generate = new Inner(treeFactory.generate, treeFactory.generate)
}

def treeFactory: Factory[Tree] = for {
  isLeaf <- boolFactory
} yield if (isLeaf) leafFactory else innerFactory
                    ^^^^^^^^^^^      ^^^^^^^^^^^^
                    type mismatch; found : Factory[Inner] required: Tree
                    type mismatch; found : Factory[Leaf]  required: Tree    

However, below works.

def treeFactory: Factory[Tree] = for {
  isLeaf <- boolFactory
  tree   <- if (isLeaf) leafFactory else innerFactory
} yield tree

Upvotes: 1

Views: 73

Answers (1)

Yuval Itzchakov
Yuval Itzchakov

Reputation: 149518

I have no idea why the same if expression in for block is OK but it is not OK in yield block

Because they are translated differently by the compiler. The former example is translated into:

boolFactory.flatMap((isLeaf: Boolean) => if (isLeaf) leafFactory else innerFactor)

Which yields the expected Factory[Tree], while the latter is being translated to:

boolFactory.map((isLeaf: Boolean) => if (isLeaf) leafFactory else innerFactory)

Which yields a Factory[Factory[Tree]], not a Factory[Tree], thus not conforming to your method signature. This isn't about covariance, but rather how for comprehension translates these statements differently.

Upvotes: 2

Related Questions