scala中的println用于理解

Posted

技术标签:

【中文标题】scala中的println用于理解【英文标题】:println in scala for-comprehension 【发布时间】:2011-11-16 19:27:31 【问题描述】:

为了理解,我不能只写一个打印语句:

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,而不是一个可过滤的 ints 集合:

  // 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))
      
    
  

第二部分属于 cmets,但无法阅读,如果写在那里 - 也许我最终将其删除。请见谅。

好的 - 这是 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))
      
    
  
 

【问题讨论】:

包含“虚拟”的代码在我的 REPL (scala 2.9.0.1) 中运行。例如,用prod (20) 调用它。 在真实的代码示例中,((a+1) to max/a).filter(b =&gt; p(b) &amp;&amp; b*a &lt; max).map b =&gt; em(a,b,max) 可以解决问题。此外,第一张地图应该是 flatMap。 非常感谢。部分地,我的错误现在对我来说很明显 - 过滤器 ...filter (p) 中的布尔数组 p 使表达式中的 b 消失,而稍后需要它,所以 filter (b =&gt; p(b)) 是要走的路。将过滤器与&amp;&amp; b*a &lt; max 结合起来也很清楚。然后重复 b =&gt; 是我如果再搜索 4 个小时就不会找到的东西,而且我想我明天也找不到它,不看这里。 如果你真的愿意,你可以第二次调用它x =&gt; 而不是b =&gt;。它只是需要一个名字的东西;它通过过滤器后恰好是同一件事,所以我使用了相同的变量。 【参考方案1】:

你需要这样写:

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)

【讨论】:

所以我可以省略带花括号的分号,并使用下划线作为表达unused dummy 的更规范的方式,而不是命名事物虚拟,但我可以独立使用这两种方法。 老实说,我认为dummy = print(a + " ") 更清晰 - 如果这是唯一的变化。其他人看到这一行,他们会立即知道这是一个用于打印的虚拟变量分配。使用下划线时,他们可能想知道这是否是他们以前从未见过的普遍下划线的新用法。 我发现_ 版本已清除。个人喜好问题,我猜。【参考方案2】:

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)应用副作用(在本例中为 println),同时返回未触及的值 (a):

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


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

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

【讨论】:

tap 也可用作 Scala 2.12 的后向端口:github.com/bigwheel/util-backports【参考方案3】:

我通常发现这种编码风格很难遵循,因为循环和中间结果等都会相互混合。我会写一些类似

的东西,而不是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 to m/(2*3)).filter(f).flatMap a =&gt;map(b =&gt; g(a,b,m)) 可以解决问题,其中 f 是 a 上的过滤器,g 是你的 a,b,m 函数(除非你表示您不对函数的结果进行过滤,这种情况下您需要在内部循环中交换过滤器和映射的顺序)。 谢谢。出于好奇,我将代码放入问题中,因为我观察到我的 b 的生成已经针对 (a*b > m) 进行了清理,因此不需要最后一个测试,而且我能够毫无问题地执行您的模式。但更冗长的结构不知何故阻塞了我的大脑。也许你喜欢解决它。 :)【参考方案4】:

将副作用语句放在 for-comprehension 中(或者实际上放在任何函数的中间)似乎不是一种好的风格,除了调试,在这种情况下,你怎么称呼它并不重要(“调试”似乎是个好名字)。

如果你真的需要,我认为你最好通过分配一个中间值来更好地分离你的关注点,例如(你原来的布局更好):

  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-loop是如何被翻译成map/flatMap的,但是我对它的工作原理有一个错误的印象,并且只发现我的印象是错误的,通过使用upper方法。打印了所有as,但是执行em (a, b, max) 需要几分钟——我原以为方法调用是一个一个执行的。是的 - 它仅用于一次性代码中的调试目的。

以上是关于scala中的println用于理解的主要内容,如果未能解决你的问题,请参考以下文章

scala中Stream理解

关于scala的简单理解,对象,属性

带有值列表的 Scala Futures 用于理解

Scala中的隐式转换|理解

Scala基础:变量字符串数据类型常量和标识符

深入理解Scala的隐式转换系统