println in scala for-comprehension

15,220

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

Solution 2

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

Solution 3

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.

Solution 4

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)
    }
  }
Share:
15,220
user unknown
Author by

user unknown

scala, java, linux currently not searching a job in Berlin. :)

Updated on June 06, 2022

Comments

  • user unknown
    user unknown almost 2 years

    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))
          }
        }
      }
     
    
  • user unknown
    user unknown over 12 years
    Well, the real code has an additional filter on a, on b and yields not just c, but a methods result, depending on (a, b, m) which means, that I need an additional set of curly brackets to capture the b, but somehow I get lost in the process of wrapping it correctly up. In the opposite direction, I can add curly brackets inside my outer for, and then use print without an dummy, and flatten the result, but that too gets harder to follow, imho. But for similar situations, I have to keep the curly brackets with the map/flatMap approach in mind.
  • user unknown
    user unknown over 12 years
    So I can omit the semicolons with the curly brace, and use the underline as a more canonical way to express unused dummy than naming the thing dummy, but I can use both approaches independently from each other.
  • huynhjl
    huynhjl over 12 years
    Honestly, I think dummy = print(a + " ") is clearer - if that's the only change. Somebody else looks at this line and they'll know immediately that it's a dummy variable assignment for printing. When using the underscore, they may wonder if it's a new usage of the pervasive underscore they haven't seen before.
  • missingfaktor
    missingfaktor over 12 years
    I find the _ version cleared. Matter of personal taste, I guess.
  • Rex Kerr
    Rex Kerr over 12 years
    @user unknown - (2 to m/(2*3)).filter(f).flatMap { a => and map(b => g(a,b,m)) will do the trick, where f is the filter on a, and g is your function of a,b,m (unless you mean that you do not filter on the result of the function, in which case you need to swap the order of filter and map in the inner loop).
  • user unknown
    user unknown over 12 years
    Thanks. I put my code into the question, just for curiosity, because I observed that the generation of my b already is sanitized against (a*b > m), so the last test is not needed, and than I'm able to perform your pattern without problem. But the more lengthy structure is somehow blocking my brain. Maybe you like to solve it although. :)
  • user unknown
    user unknown over 12 years
    Although I have read about how a for-loop is to be translated into map/flatMap, I had a wrong impression about how it works, and only observed that my impression was wrong, by using the upper approach. All the as where printed, but then it took minutes, to perform the em (a, b, max) - I had thought that the method calls were performed one by one. And yes - it was only for debug purpose in a throw-away code.
  • seanf
    seanf about 5 years
    tap is also available as a backport for Scala 2.12: github.com/bigwheel/util-backports