println in scala for-comprehension

2019-04-03 03:11发布

问题:

In a for-comprehension, I can't just put a print statement:

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
}

but I can circumvent it easily with a dummy assignment:

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
}

Being a side effect, and only used (so far) in code under development, is there a better ad hoc solution?

Is there a serious problem why I shouldn't use it, beside being a side effect?

update showing the real code, where adapting one solution is harder than expected:

From the discussion with Rex Kerr, the necessity has risen to show the original code, which is a bit more complicated, but did not seem to be relevant for the question (2x .filter, calling a method in the end), but when I tried to apply Rex' pattern to it I failed, so I post it here:

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

Here is my attempt -- (b * a).filter is wrong, because the result is an int, not a filterable collection of 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))
      }
    }
  }

Part II belongs to the comments, but can't be read, if written there - maybe I delete it in the end. Please excuse.

Ok - here is Rex last answer in code layout:

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

回答1:

This is how you need to write it:

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)


回答2:

I generally find that style of coding rather difficult to follow, since loops and intermediate results and such get all mixed in with each other. I would, instead of a for loop, write something like

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

This also makes adding print statements and such easier.



回答3:

It doesn't seem like good style to put a side-effecting statement within a for-comprehension (or indeed in the middle of any function), execept for debugging in which case it doesn't really matter what you call it ("debug" seems like a good name).

If you really need to, I think you'd be better separating your concerns somewhat by assigning an intermediate val, e.g. (your original laid out more nicely):

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

becomes

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


回答4:

Starting Scala 2.13, the chaining operation tap, has been included in the standard library, and can be used with minimum intrusiveness wherever we need to print some intermediate state of a pipeline:

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)

The tap chaining operation applies a side effect (in this case println) on a value (in this case a) while returning the value (a) untouched:

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


It's very convenient when debugging as you can use a bunch of taps without having to modify the code:

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