32

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))
      }
    }
  }
 
Community
  • 1
  • 1
user unknown
  • 35,537
  • 11
  • 75
  • 121
  • The code containing the 'dummy' runs in my REPL (scala 2.9.0.1). Call it with `prod (20)` for instance. – user unknown Sep 14 '11 at 08:31
  • 1
    With the real code example, `((a+1) to max/a).filter(b => p(b) && b*a < max).map{ b => em(a,b,max) }` will do the trick. Also, the first map should be a flatMap. – Rex Kerr Sep 14 '11 at 15:06
  • Thanks much. Partly, my error is now obvious to me - the boolean array p in the filter `...filter (p)` made the `b` vanish in the expression, while it is needed later, so `filter (b => p(b))` is the way to go. Combining the filter with `&& b*a < max` is clear too. Then repeating the `b =>` is something which I wouldn't have found if I had searched 4 more hours, and I guess I wouldn't find it tomorrow, without looking here too. – user unknown Sep 14 '11 at 15:30
  • 1
    You could call it `x =>` instead of `b =>` the second time if you really wanted to. It's just something that needs a name; it happens to be the same thing after it goes through the filter, so I used the same variable. – Rex Kerr Sep 14 '11 at 20:19

4 Answers4

50

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)
missingfaktor
  • 90,905
  • 62
  • 285
  • 365
  • 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. – user unknown Sep 14 '11 at 09:16
  • 4
    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. – huynhjl Sep 14 '11 at 14:00
  • 2
    I find the `_` version cleared. Matter of personal taste, I guess. – missingfaktor Sep 14 '11 at 14:10
5

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
Xavier Guihot
  • 54,987
  • 21
  • 291
  • 190
  • 1
    `tap` is also available as a backport for Scala 2.12: https://github.com/bigwheel/util-backports – seanf Mar 26 '19 at 23:56
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.

Rex Kerr
  • 166,841
  • 26
  • 322
  • 407
  • 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 Sep 14 '11 at 09:02
  • @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). – Rex Kerr Sep 14 '11 at 14:13
  • 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 Sep 14 '11 at 14:57
2

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)
    }
  }
Luigi Plinge
  • 50,650
  • 20
  • 113
  • 180
  • 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 `a`s 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. – user unknown Sep 14 '11 at 19:49