Scala:表达式表现出意想不到的行为

时间:2017-08-23 11:05:10

标签: scala for-loop

我们正在使用Scala for表达式并迭代具有多个循环的元素,例如 First Expression 代码:

for {
        a <- 1 to 10
        _ = print(a)
        b <- 11 to 20
        _ = print(b)
    } yield 1

根据scala,for表达式包含a <- 1 to 10之类的表达式,如果存在多个表达式,则将它们视为内部循环。我们假设像上面的代码一样是第二个表达式

for(a <- 1 to 10) {
        print(a + " --- ")
        for(b <- 11 to 20) {
            print(b + " === ")
        }
    } 

但两个代码的输出都不同。我们对表达式代码的预期输出如下:

1 --- 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 === 2 --- 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 === 3 --- 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 === 4 --- 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 === 5 --- 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 === 6 --- 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 === 7 --- 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 === 8 --- 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 === 9 --- 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 === 10 --- 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 ===

但此输出仅由第二 for表达式生成。第一个表达式给了我们意想不到的输出。

1 --- 2 --- 3 --- 4 --- 5 --- 6 --- 7 --- 8 --- 9 --- 10 --- 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 === 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 === 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 === 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 === 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 === 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 === 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 === 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 === 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 === 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 ===

此输出打印第一个1 2 .. 10元素,然后打印11 12 ... 20个元素10次。为什么这两个输出不同?

第二次,同时使用其他for表达式:

for {
    a <- 1 to 10
    _ = print(a + " --- ")
    b <- 11 to 20
    _ = print(b + " === " + a + "  ")
} yield 1

输出结果为:

1 --- 2 --- 3 --- 4 --- 5 --- 6 --- 7 --- 8 --- 9 --- 10 --- 11 === 1  12 === 1  13 === 1  14 === 1  15 === 1  16 === 1  17 === 1  18 === 1  19 === 1  20 === 1  11 === 2  12 === 2  13 === 2  14 === 2  15 === 2  16 === 2  17 === 2  18 === 2  19 === 2  20 === 2  11 === 3  12 === 3  13 === 3  14 === 3  15 === 3  16 === 3  17 === 3  18 === 3  19 === 3  20 === 3  11 === 4  12 === 4  13 === 4  14 === 4  15 === 4  16 === 4  17 === 4  18 === 4  19 === 4  20 === 4  11 === 5  12 === 5  13 === 5  14 === 5  15 === 5  16 === 5  17 === 5  18 === 5  19 === 5  20 === 5  11 === 6  12 === 6  13 === 6  14 === 6  15 === 6  16 === 6  17 === 6  18 === 6  19 === 6  20 === 6  11 === 7  12 === 7  13 === 7  14 === 7  15 === 7  16 === 7  17 === 7  18 === 7  19 === 7  20 === 7  11 === 8  12 === 8  13 === 8  14 === 8  15 === 8  16 === 8  17 === 8  18 === 8  19 === 8  20 === 8  11 === 9  12 === 9  13 === 9  14 === 9  15 === 9  16 === 9  17 === 9  18 === 9  19 === 9  20 === 9  11 === 10  12 === 10  13 === 10  14 === 10  15 === 10  16 === 10  17 === 10  18 === 10  19 === 10  20 === 10

内部循环中存在a的预期值。关于这种行为,我们仍然感到困惑。这种行为有什么好处?

3 个答案:

答案 0 :(得分:1)

原因是:

for {
    a <- 1 to 10
    _ = print(a)
    b <- 11 to 20
    _ = print(b)
} yield 1

相当于:

(1 to 10).
 map{a => print(a); a}.
 flatMap{a => 
   (11 to 20).
   map{b => print(b); 1}
}

您看,由于1 to 10是一个集合,因此映射它将立即构建新集合并执行所有print语句。 如果您希望仅在需要时完成print(a),则可以将1 to 10更改为(1 to 10).view

你的第二个循环相当于:

(1 to 10).foreach { a =>
    print(a + " --- ")
    (11 to 20).foreach { b =>
        print(b + " === ")
    }
}

自我解释了为什么它会在a

的所有值之前打印b

你的第二个问题相当于:

(1 to 10).map{a => print(a + " --- "); a}.
flatMap{ a =>
  (11 to 20).map{ b =>
    print(b + " === " + a + "  ")
    1
  }
}

希望输出现在有意义。

答案 1 :(得分:0)

您应该阅读:http://docs.scala-lang.org/tutorials/FAQ/yield.html,并且:http://www.scala-lang.org/files/archive/spec/2.11/06-expressions.html#for-comprehensions-and-for-loops

简而言之,您的第一个和第二个表达式实际上是非常不同的,因为一个使用yield,而另一个则不使用yield。具有map的那个被称为“for-comprehension”,另一个被称为“for-loop”。

For-comprehension表示为flatMapforeach的组合(加上一些与当前讨论无关的其他结构),for-loop表示为scala.Predef .intWrapper(1) .to(10) .map[(Int, Unit), scala.collection.immutable.IndexedSeq[(Int, Unit)]]((a: Int) => { val x$1 = scala.Predef.print(a.+(" --- ")); scala.Tuple2.apply[Int, Unit](a, x$1) })(scala.collection.immutable.IndexedSeq.canBuildFrom[(Int, Unit)]) .flatMap[Int, Any]((x$4: (Int, Unit)) => (x$4: @scala.unchecked) match { case scala.Tuple2((a @ _), _) => scala.Predef .intWrapper(11) .to(20) .map[(Int, Unit), scala.collection.immutable.IndexedSeq[(Int, Unit)]]((b: Int) => { val x$2 = scala.Predef.print(b.+(" === ")); scala.Tuple2.apply[Int, Unit](b, x$2) })(scala.collection.immutable.IndexedSeq.canBuildFrom[(Int, Unit)]) .map[Int, scala.collection.immutable.IndexedSeq[Int]]((x$3: (Int, Unit)) => (x$3: @scala.unchecked) match { case scala.Tuple2((b @ _), _) => 1 })(scala.collection.immutable.IndexedSeq.canBuildFrom[Int]) })(scala.collection.immutable.IndexedSeq.canBuildFrom[Int])

也就是说,您的第一个表达式由编译器转换为:

(1 to 10).map { a =>
  val x = print(a + " --- ")
  (a, x)
}.flatMap { case (_, _) =>
  (11 to 20).map { b =>
    val x = print(b + " === ")
    (b, x)
  }.map { _ =>
    1
  }
}

可以简化为:

(1 to 10).foreach(a => {
  print(a + " --- ")
  (11 to 20).foreach(b => print(b + " === "))
})

虽然第二个表达成:

map

这里很容易看出表达式之间的区别。无论如何,我会仔细阅读解释,特别是对于第一个表达,因为它是最复杂的。

在第一个表达式中,首先执行此(1 to 10).map { a => val x = print(a + " --- ") (a, x) }

1 --- 2 --- 3 --- 4 --- 5 --- 6 --- 7 --- 8 --- 9 --- 10 ---

按预期打印Tuple2[Int, Unit]。现在,该表达式的结果是具有以下元素的集合:(1, ()), (2, ()), ... (10, ()),结果集合中包含10个:flatMap

flatMap在该中间集合上执行。因此11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 ===下的函数执行10次,并打印10次以下:1 --- 2 --- 3 --- 4 --- 5 --- 6 --- 7 --- 8 --- 9 --- 10 --- 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 === 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 === 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 === 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 === 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 === 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 === 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 === 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 === 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 === 11 === 12 === 13 === 14 === 15 === 16 === 17 === 18 === 19 === 20 ===

最终结果与问题相同,即第一个表达式打印:

mpldatacursor

我不会通过第二个表达式,因为它更简单,并且表示OP所期望的行为。

答案 2 :(得分:-1)

所以,

 for { 
     a <- 1 to 10
     _ = print("something") 
     b <- 11 to 20
     _ = print("something else") 
 } yield 1

相当于:

 (1 to 10).flatMap { a =>  
      print("something") 
      (11 to 20).map { b => 
         print("something else") 
         1
      }
 }

现在,此循环的行为将取决于您用于第一个flatMap的集合类型。 scala中的一些集合是&#34;懒惰&#34;其他集合不是。 &#34;非懒惰&#34;那些是那些,当你做foo.flatMap { x => ... }时会立即评估整个集合的函数,并返回一个包含结果的新集合。惰性集合是一个,它将在访问元素时逐个评估转换。

   Seq(1,2,3,4)
    .map { n => println("foo" + n); n + 1 }
    .map { n => println("bar" + n); n - 1 }

打印:

foo1   
foo2
foo3
foo4
bar2
bar3
bar4
bar5

所有元素首先经过第一张地图,然后经过下一张地图。

另一方面:

Iterator(1,2,3,4)
  .map { n => println("foo" + n); n + 1 }
  .map { n => println("bar" + n); n - 1 }

根本不打印任何东西。 如果您在结果.next上致电Iterator,则会看到

foo1
bar2

因此,在转到第二个元素之前,它会通过两个映射发送第一个元素。

您的循环也会发生同样的事情:(1 to 10)Range,这是一个渴望&#34;采集。这意味着,在继续for之前,1理解的整个主体都会针对2进行评估。

您可以将行为切换到其他代码段,方法是使收集&#34;懒惰&#34;:a <- (1 to 10).iteratora <- (1 to 10).toStream 但是现在,for理解的结果也将是懒惰的 - 它与列表中的第一个集合的类型相同,并且,为了看到整个事物被打印出来,你&#39必须&#34;强迫&#34; al元素的评估,例如,通过在结果上调用.toList