mty
mty

Reputation: 11

A "Simple' Scala question but took me long time to debug

Please check the two pieces of script as above.

genComb4(lst) works since I put z <- genComb4(xs) before i <- 0 to x._2 in the for-comprehension; genComb(lst) does not work since I change the order of these two lines in for-comprehension.

It took me almost half day to find this bug, but I cannot explain it by myself. Could you tell me why this happened?

Thank you very much in advance.

// generate combinations
val nums = Vector(1, 2, 3)
val strs = Vector('a', 'b', 'c')
val lst: List[(Char, Int)] = strs.zip(nums).toList

def genComb4(lst: List[(Char, Int)]): List[List[(Char, Int)]] = lst match {
  case Nil => List(List())
  case x :: xs =>
    for {
      z <- genComb4(xs)  // correct
      i <- 0 to x._2     // correct
    } yield ( (x._1, i) :: z)
}
genComb4(lst)

def genComb(lst: List[(Char, Int)]): List[List[(Char, Int)]] = lst match {
  case Nil => List(List())
  case x :: xs =>
    for {
      i <- (0 to x._2)  // wrong
      z <- genComb(xs)  // wrong
    } yield ( (x._1, i) :: z)
}
genComb(lst)

Upvotes: 1

Views: 160

Answers (1)

Boris Azanov
Boris Azanov

Reputation: 4481

It's because of different types of container in for comprehension. When you start for-comprehension from line: i <- (0 to x._2) it's set type of result container as IndexedSeq but in case where first line is z <- genComb4(xs) the type of result container is List, take a look:

val x = 'a' -> 2
val indices: Seq[Int] = 0 to x._2
val combs: List[List[(Char, Int)]] = genComb4(List(x))

// indexed sequence
val indicesInFor: IndexedSeq[(Char, Int)] = for {
  i <- 0 to x._2
} yield (x._1, i)

// list 
val combsInFor: List[List[(Char, Int)]] = for {
  z <- genComb4(List(x))
} yield z

so for make your second case is working, you should cast (0 to x._2).toList:

val indicesListInFor: List[(Char, Int)] = for {
  i <- (0 to x._2).toList
} yield (x._1, i)

result code should be:

def genComb(lst: List[(Char, Int)]): List[List[(Char, Int)]] = lst match {
  case Nil => List(List())
  case x :: xs =>
    for {
      i <- (0 to x._2).toList
      z <- genComb(xs)
    } yield ( (x._1, i) :: z)
}
genComb(lst)

You should remember about type of starting line in for-comprehension and inheritance of scala collections. If next types in for-comprehension can't be converted by inheritance rules to the first expression line type you should take care about it by yourself.

Good practise is unwrap for-expression into flatMap, map and withFilter functions, then you will find miss-typing or something else faster.

useful links:

Upvotes: 2

Related Questions