美文网首页
Scala 的 for 背后的故事

Scala 的 for 背后的故事

作者: 陈半仙儿 | 来源:发表于2018-06-21 14:48 被阅读13次

    一个简单的 for

    一个非常简单的 for 表达式,看起来像这样,

    val ns = List(1, 2)  
    val qs = for (n <- ns) yield n * 2  
    assert (qs == List(2, 4)) 
    

    它看起来像一个循环,但它不是。这是 map 的伪装,

    val qs = ns map {n => n * 2}  
    

    规则很简单,

    for (x <- expr) yield resultExpr  
    
    //展开为
    expr map {x => resultExpr}  
    
    //它等同于
    expr flatMap {x => unit(resultExpr)}  
    

    多一点的 For

    前面的例子中,for 的括号中只有一条表达式,现在增加一条,

    val ns = List(1, 2)  
    val os = List (4, 5)  
    val qs = for (n <- ns; o <- os) yield n * o  
    assert (qs == List (1*4, 1*5, 2*4, 2*5))  
    

    这个形式的 for 看起来想一个嵌套循环,但它不过也是 map 和 flatMap,

    val qs = ns flatMap {n => os map {o => n * o }}  
    

    这里需要注意一下,两层 map 它是怎么计算的呢,

    // 第一步
    val qs = ns flatMap {n => 
                os map { o => n * o } // 1
              }
    
    // 第二步
    val qs = ns flatMap {n =>        // 2
                List(n * 4, n * 5)          // 上面的1
             }
    
    // 第三步
    val qs = List(1 * 4, 1 * 5, 2 * 4, 2 * 5) // 上面的2
    

    这说明,命令式语言的多层循环之类,可以通过 map/flatMap 来替代。

    更多的表达式

    val qs = for (n <- ns; o <- os; p <- ps) yield n * o * p  
    
    // 展开为
    val qs = ns flatMap {n => 
                os flatMap {o =>   
                    {ps map {p => n * o * p}}}}  
    

    可以看到,转换是递归的,重复的 flatMap,直到只剩下一条表达式,将最后一条转换为map。

    写成更详细的代码就是这样,

    // 第一步
    val qs = for (n <- ns;  // 第一个表达式
                    o <- os;    // 第二个
                    p <- ps)    // 第三个
                yield n * o * p
    
    // 第二步
    val qs = ns flatMap {n => // 第一个表达式,用 flatMap 翻译
                    for(o <- os; 
                    p <- ps) 
                    yield n * o * p}
    
    // 第三步
    val qs = ns flatMap { n => 
                    os flatMap {o => // 第二个也用 flatMap 翻译
                    for(p <- ps) 
                    yield n * o * p}}
    
    // 第四步
    val qs = ns flatMap {n => os flatMap {o =>
                    {ps map {p => n * o * p}}} // 第三个(最后一个)用 map 翻译
    

    命令式的 for

    for 也有一个命令式的版本,用于那些只调用一个函数,并不在意副作用的情况。这种版本只用去掉 yield 声明,例如,

    val ns = List(1, 2)  
    val os = List (4, 5)  
    for (n <- ns; o <- os)  println(n * o) 
    

    这个展开规则和 yield 版本很像,但用 foreach 替代了 flatMap 或 map,展开为,

    ns foreach {n => os foreach {o => println(n * o) }}  
    

    如果不想使用命令式的 for,就不需要实现 foreach。不过既然已经实现了 map,foreach 的实现是很简单的,

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

    换句话说,foreach 可以通过调用 map 并丢掉结果来实现。

    不过这么做运行时效率可能不好,所以 scala 允许用自己的方式自定义 foreach。

    带过滤的 for

    Scala 的 for 声明还有一个特征:if 守护(guard),

    val names = List("Abe", "Beth", "Bob", "Mary")  
    val bNames = for (bName <- names;  // 注意这里分号不能少
                    if bName(0) == 'B'  
                ) yield bName + " is a name starting with B"  
    
    assert(bNames == List(
        "Beth is a name starting with B",   
        "Bob is a name starting with B"))  
    

    if 守护会映射为 filter 方法,上面的 for 语句会翻译成下面这样,

    val bNames = (names filter { bName => bName(0) == 'B' }) 
                 .map { bName =>   
                    bName + " is a name starting with B"   
                 }  
    
    Haskell Scala
    do var1<- expn1 var2 <- expn2 expn3 for {var1 <- expn1; var2 <- expn2; result <- expn3 } yield result
    do var1 <- expn1 var2 <- expn2 return expn3 for {var1 <- expn1; var2 <- expn2; } yield expn3
    do var1 <- expn1 >> expn2 return expn3 for {_ <- expn1; var1 <- expn2 } yield expn3

    相关文章

      网友评论

          本文标题:Scala 的 for 背后的故事

          本文链接:https://www.haomeiwen.com/subject/urnqyftx.html