在Scala的for推导式中使用println

33

在for循环中,我不能仅仅放置一个打印语句:

def prod (m: Int) = {
  for (a <- 2 to m/(2*3);
    print (a + "  ");
    b <- (a+1) to m/a;
    c = (a*b) 
    if (c < m)) yield c
}

但我可以很容易地通过一个虚拟赋值来规避这个问题:

def prod (m: Int) = {
  for (a <- 2 to m/(2*3);
    dummy = print (a + "  ");
    b <- (a+1) to m/a;
    c = (a*b) 
    if (c < m)) yield c
}

这只是一个副作用,并且目前只用于正在开发中的代码,是否有更好的临时解决方案?

除了是副作用之外,我是否不应该使用它的严重问题?

更新显示实际代码,其中适应一种解决方案比预期更困难:

根据与 Rex Kerr 的讨论,有必要展示原始代码,它有点更加复杂,但似乎与问题无关 (2x .filter,在最后调用一个方法),但当我尝试将 Rex 的模式应用于它时,我失败了,所以我在这里发布它:

  def prod (p: Array[Boolean], max: Int) = {
    for (a <- (2 to max/(2*3)).
        filter (p);
      dummy = print (a + "  ");
      b <- (((a+1) to max/a).
         filter (p));
      if (a*b <= max)) 
        yield (em (a, b, max)) }

这是我的尝试-- (b * a).filter 是错误的,因为结果是一个 int,而不是可过滤的 int 集合:

  // wrong: 
  def prod (p: Array[Boolean], max: Int) = {
    (2 to max/(2*3)).filter (p).flatMap { a =>
      print (a + " ")
      ((a+1) to max/a).filter (p). map { b => 
        (b * a).filter (_ <= max).map (em (a, b, max))
      }
    }
  }

第二部分属于注释,但如果写在那里就无法阅读 - 也许最后我会将其删除。请谅解。

好的 - 这是Rex在代码布局中的最后一个答案:

  def prod (p: Array[Boolean], max: Int) = {
    (2 to max/(2*3)).filter (p).flatMap { a =>
      print (a + " ")
      ((a+1) to max/a).filter (b => p (b) 
        && b * a < max).map { b => (m (a, b, max))
      }
    }
  }
 

代码包含“dummy”在我的REPL(scala 2.9.0.1)中运行。例如,使用prod(20)调用它。 - user unknown
1
通过实际的代码示例,((a+1) to max/a).filter(b => p(b) && b*a < max).map{ b => em(a,b,max) } 就可以解决问题。此外,第一个 map 应该是 flatMap。 - Rex Kerr
非常感谢。部分地,我的错误现在对我来说是显而易见的——过滤器中的布尔数组 p 使得表达式中的 b 消失了,而后面还需要它,所以 filter (b => p(b)) 是正确的方式。将过滤器与 && b*a < max 结合也很清楚。然后重复 b => 是我如果再搜索 4 个小时也不会找到的东西,我想明天也不会,在这里看一下才行。 - user unknown
1
如果你真的想的话,第二次可以称之为 x => 而不是 b =>。这只是需要一个名称的东西;在经过过滤器后它恰好是相同的东西,所以我使用了同一个变量。 - Rex Kerr
4个回答

50

你需要这样写:

scala> def prod(m: Int) = {
     |   for {
     |     a <- 2 to m / (2 * 3)
     |     _ = print(a + " ")
     |     b <- (a + 1) to (m / a)
     |     c = a * b
     |     if c < m
     |   } yield c
     | }
prod: (m: Int)scala.collection.immutable.IndexedSeq[Int]

scala> prod(20)
2 3 res159: scala.collection.immutable.IndexedSeq[Int] = Vector(6, 8, 10, 12, 14
, 16, 18, 12, 15, 18)

所以我可以省略花括号中的分号,并使用下划线作为比将事物命名为dummy更规范的表达方式,但我可以独立地使用这两种方法。 - user unknown
4
说实话,如果这是唯一的更改,我认为 dummy = print(a + " ") 更清晰明了。其他人看到这行代码时,立即就会知道它是一个用于打印的虚拟变量赋值。而在使用下划线时,他们可能会想知道是否这是一种新的、他们之前没见过的广泛使用的下划线。 - huynhjl
2
我觉得“_”版本更清晰。可能是个人口味问题吧。 - missingfaktor

5

Scala 2.13 开始,链式操作 tap 已被包含在标准库中,并且可以在管道的任何需要打印中间状态的地方以最小的干扰使用:

import util.chaining._

def prod(m: Int) =
  for {
    a <- 2 to m / (2 * 3)
    b <- (a + 1) to (m / a.tap(println)) // <- a.tap(println)
    c =  a * b
    if c < m
 } yield c

prod(20)
// 2
// 3
// res0: IndexedSeq[Int] = Vector(6, 8, 10, 12, 14, 16, 18, 12, 15, 18)

tap 这个链式操作可以在不改变值(a)的情况下,在值(a)上应用副作用(在这种情况下是println):

def tap[U](f: (A) => U): A


当调试时,使用一堆 tap 可以非常方便,而无需修改代码:

def prod(m: Int) =
  for {
    a <- (2 to m.tap(println) / (2 * 3)).tap(println)
    b <- (a + 1) to (m / a.tap(println))
    c = (a * b).tap(println)
    if c < m
 } yield c

1
tap 也可作为 Scala 2.12 的后移版本使用:https://github.com/bigwheel/util-backports - seanf

2

通常我发现这种编码风格很难跟踪,因为循环、中间结果等都混在一起。相比使用for循环,我会写类似下面的代码:

def prod(m: Int) = {
  (2 to m/(2*3)).flatMap { a =>
    print(a + " ")
    ((a+1) to m/a).map(_ * a).filter(_ < m)
  }
}

这也使添加打印语句等操作变得更加容易。

实际代码在a和b上有一个额外的过滤器,并且不仅产生c,而且还取决于(a、b、m)的方法结果,这意味着我需要一个额外的花括号来捕获b,但是在正确包装它的过程中我会迷失方向。相反,我可以在我的外部for中添加花括号,然后使用没有虚拟变量的print并展平结果,但是我认为这也更难以理解。但对于类似的情况,我必须记住map/flatMap方法中的花括号。 - user unknown
@用户未知 - (2到m/(2*3)).filter(f).flatMap { a =>map(b => g(a,b,m)) 就可以了,其中 f 是应用在 a 上的筛选器, g 是你的函数,接受 a, b, m 三个参数(除非你的意思是不根据函数结果筛选,否则需要在内部循环中交换 filter 和 map 的顺序)。 - Rex Kerr
谢谢。我把我的代码放到问题中,只是出于好奇,因为我观察到我的“b”的生成已经过了(a*b > m)的净化,所以最后一个测试是不需要的,然后我能够毫无问题地执行您的模式。但是更冗长的结构在某种程度上阻碍了我的思维。也许你还是想解决它。 :) - user unknown

2

在 for-comprehension 中放置一个具有副作用的语句(或者在任何函数中间),似乎不是好的编程风格,除非进行调试,在这种情况下无论你如何命名它都无关紧要("debug" 似乎是一个好的名称)。

如果您真的需要,我认为通过分配一个中间变量来分离您的关注点会更好,例如(您原来的代码排列得更好):

  def prod (p: Array[Boolean], max: Int) = {
    for {
      a <- (2 to max / (2 * 3)) filter p
      debug = print (a + "  ")
      b <- ((a + 1) to max / a) filter p
      if a * b <= max
    } yield em(a, b, max) 
  }

变成

  def prod2 (p: Array[Boolean], max: Int) = {
    val as = (2 to max / (2 * 3)) filter p

    for(a <- as) print(a + "  ")

    as flatMap {a => 
      for {
        b <- ((a + 1) to max / a) filter p
        if a * b <= max
      } yield em(a, b, max)
    }
  }

虽然我已经了解了如何将for循环转换为map/flatMap,但我对它的工作方式有错误的印象,只有通过使用上述方法才发现我的印象是错误的。所有的a都被打印出来了,但是执行em(a, b, max)花费了几分钟时间 - 我曾经认为这些方法调用是逐个执行的。是的 - 这只是一个一次性代码中的调试目的。 - user unknown

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接