tags:

views:

120

answers:

3

For log tracing inside a for comprehension, I've used dummy assignment like this:

val ll = List(List(1,2),List(1))            

for {
  outer <- ll 
  a = Console.println(outer)   // dummy assignment makes it compile
  inner <- outer
} yield inner

The a = bit seems awkward. Is there a cleaner way?

+8  A: 

You could always define your own trace function:

def trace[T](x: T) = {
  println(x) // or your favourite logging framework :)
  x
}

Then the for comprehension would look like:

for { 
  outer <- ll
  inner <- trace(outer)
} yield inner

Alternatively, if you want more information printed, you can define trace as follows:

def trace[T](message: String, x: T) = {
  println(message)
  x
}

and the for comprehension would look like:

for { 
  outer <- ll
  inner <- trace("Value: " + outer, outer)
} yield inner

EDIT: In response to your comment, yes, you can write trace so that it acts to the right of the target! You just have to use a bit of implicit trickery. And actually, it does look much nicer than when applied to the left :).

To do this, you have to first define a class which is Traceable and then define an implicit conversion to that class:

class Traceable[A](x: A) { 
  def traced = {
    println(x)
    x
  }
}

implicit def any2Traceable[A](x: A) = new Traceable(x)

Then the only thing you have to modify in the code you have provided is to add traced to the end of the value you want to be traced. For example:

for { 
  outer <- ll
  inner <- outer traced
} yield inner

(this is translated by the Scala compiler into outer.traced)

Hope it helps :)

-- Flaviu Cipcigan

Flaviu Cipcigan
A good idea, thx. You got me thinking about a possible improvement -- It would be nice if trace() wouldn't interpose in the reading of the main logic. e.g. if the word 'trace' would be to the right of 'outer'
Lee Mighdoll
+1  A: 

For whatever it is worth, since the assignment is dummy, you can replace a with _:

for { 
  outer <- ll  // ; // semi-colon needed on Scala 2.7
  _ = Console.println(outer)   // dummy assignment makes it compile 
  inner <- outer 
} yield inner 
Daniel
Ah, I didn't realize you could do that.
Lee Mighdoll
I think the above needs a semi-colon after the `outer <- ll` to compile. (on 2.7.7)
Lee Mighdoll
+1  A: 

Flaviu's answer inspired me to try playing with implicits. The idea is to see if the trace looks better with the 'trace' further to the right on the line:

import Trace._

object Main {  
  def main(args:Array[String])  {
    val listList = List(List(1,2,3), List(3,4))    
    for {
      list <- trace1(listList, "lList is: %s", listList)  // trace() 
      item <- list traced("list is: %s", list)            // implicit         
    } yield item

I also wanted to try mixing in error logging in the same comprehension. Error logging seems to look best mixed with Daniel's approach:

    val optOpt:Option[Option[Int]] = Some(Some(1))
    for {
      opt <- optOpt;
      _ = trace2("opt found: %s", opt)   // trying Daniel's suggestion
      int <- opt orElse 
        err("num not found in: %s", opt)   // together with error logging
    } yield int
  }
}

Here's the supporting code for both experiments:

object Trace {
  def trace1[T](any:T, message:String, params:AnyRef*):T = {
    Console println String.format("TRA: " + message, params:_*)
    any
  }

  def trace2[T](message:String, params:AnyRef*) {
    Console println String.format("TRA: " + message, params:_*)
  }

  def err[T](message:String, params:AnyRef*):Option[T] = {
    Console println String.format("ERR: " + message, params:_*)
    None
  }

  implicit def anyRefToTraceable[T](anyRef:T):Traceable[T] = {
    new Traceable(anyRef)
  }

  class Traceable[T](val self:T) {
    def traced(message:String, params:AnyRef*):T = {
      Console println String.format("TRA: " + message, params:_*)
      self
    }  
  }  
}
Lee Mighdoll