verbose logger usage

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
19 messages Options
Reply | Threaded
Open this post in threaded view
|

verbose logger usage

Ittay Dror
Hi All,


Something that has bothered me since starting to use Scala is how to use
logging.


In Java the standard way is of course:


     class Foo {

        private static final Logger log =
Logger.getLogger(com.example.package.Foo.class);


        public void example {log.info("this is an example");}

     }


In Scala, the straight forward way is:

     class Foo {

       def example = Foo.log.info("this is an example")

     }

     object Foo {

        val log = Logger.getLogger(classOf[Foo])

     }


Without counting character-by-character, the Scala version looks more
verbose.


Alternatively, I can create a Logging trait

    trait Logging {

        lazy val log = Logger.getLogger(this.getClass)

     }


But then I'd have a Logger instance in every object


What are the alternatives to get both short code and small memory footprint?


Regards,

Ittay

Reply | Threaded
Open this post in threaded view
|

Re: verbose logger usage

Kevin Wright-3

Alternatively, I can create a Logging trait

  trait Logging {
    lazy val log = Logger.getLogger(this.getClass)
  }

But then I'd have a Logger instance in every object


This is the correct approach, you still only end up with one logger per class thanks to caching in the logger framework.



--
Kevin Wright

mail / gtalk / msn : [hidden email]
pulse / skype: kev.lee.wright
twitter: @thecoda

Reply | Threaded
Open this post in threaded view
|

Re: verbose logger usage

Ittay Dror


Kevin Wright wrote:

Alternatively, I can create a Logging trait

  trait Logging {
    lazy val log = Logger.getLogger(this.getClass)
  }

But then I'd have a Logger instance in every object


This is the correct approach, you still only end up with one logger per class thanks to caching in the logger framework.

and the cost of one reference per instance.

--
Kevin Wright

mail / gtalk / msn : [hidden email]
pulse / skype: kev.lee.wright
twitter: @thecoda

Reply | Threaded
Open this post in threaded view
|

Re: verbose logger usage

Kevin Wright-3


On 12 December 2010 09:51, Ittay Dror <[hidden email]> wrote:


Kevin Wright wrote:

Alternatively, I can create a Logging trait

  trait Logging {
    lazy val log = Logger.getLogger(this.getClass)
  }

But then I'd have a Logger instance in every object


This is the correct approach, you still only end up with one logger per class thanks to caching in the logger framework.

and the cost of one reference per instance.


So a little less expensive that making a single val lazy, cheap at twice the price...
You pay a much higher cost with autoboxing, structural types, etc.

With a decent implementation of a logger trait, the "payload" will be lazy and not even evaluated unless the appropriate logging level is enabled, so you're *still* making a net saving.

Don't worry about this, seriously!  It's the worst sort of premature optimisation.

--
Kevin Wright

mail / gtalk / msn : [hidden email]
pulse / skype: kev.lee.wright
twitter: @thecoda

Reply | Threaded
Open this post in threaded view
|

Re: verbose logger usage

Tony Morris
In reply to this post by Ittay Dror

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

On 12/12/10 19:24, Ittay Dror wrote:
>
>
> What are the alternatives to get both short code and small memory
> footprint?
>

Hello, below is a compilable program and example run of the program
(see comment) that should hopefully answer your question.




trait Monoid[A] {
  def append(a1: A, a2: A): A
  def empty: A
}

object Monoid {
  implicit def ListMonoid[A]: Monoid[List[A]] = new Monoid[List[A]] {
    def append(a1: List[A], a2: List[A]) = a1 ::: a2
    def empty = Nil
  }
}

case class Logger[LOG, A](log: LOG, value: A) {
  def map[B](f: A => B) =
    Logger(log, f(value))

  def flatMap[B](f: A => Logger[LOG, B])(implicit m: Monoid[LOG]) = {
    val x = f(value)
    Logger(m.append(log, x.log), x.value)
  }

  // insert much more
}

object Logger {
  def unital[LOG, A](value: A)(implicit m: Monoid[LOG]) =
    Logger(m.empty, value)

  // insert much more
}


//// Begin example

/*
$ scala Main 456
RESULT: 1000

LOG
- ---
adding one to 456
converting int to string 457
Checking length of 457 for evenness
Converting to 100 or 1000 using false
*/
object Main {
  def main(args: Array[String]) {
    val x = args(0).toInt // parse int from command line
 
    val r =
      for(a <- addOne(x);
          b <- intString(a);
          c <- lengthIsEven(b);
          d <- hundredOrThousand(c)
         ) yield d

    println("RESULT: " + r.value)
    println
    println("LOG")
    println("---")
    r.log foreach println
  }

  implicit def ListLogUtil[A](a: A) = new {
    def ~>[B](b: B) = Logger(List(a), b)
  }

  def addOne(n: Int) =
    ("adding one to " + n) ~> (n + 1)

  def intString(n: Int) =
    ("converting int to string " + n) ~> n.toString

  def lengthIsEven(s: String) =
    ("Checking length of " + s + " for evenness") ~> (s.length % 2 == 0)

  def hundredOrThousand(b: Boolean) =
    ("Converting to 100 or 1000 using " + b) ~> (if(b) 100 else 1000)
}

- --
Tony Morris
http://tmorris.net/

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.10 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iEYEARECAAYFAk0EqPwACgkQmnpgrYe6r62mGACgtj3uPVjEisiAjF1sKHDrKBc6
HvYAn1XvLbtiS1Q8P4vhdZKgJWisCvdT
=NYzX
-----END PGP SIGNATURE-----

Reply | Threaded
Open this post in threaded view
|

Re: verbose logger usage

Ittay Dror
Doesn't this mean that every method that logs needs to return a Logger
and so is every method that uses it, transitively? And the log will be
actually created at the end of the application? What if the application
is a service?

Ittay

Tony Morris wrote:

> -----BEGIN PGP SIGNED MESSAGE-----
> Hash: SHA1
>
> On 12/12/10 19:24, Ittay Dror wrote:
>>
>> What are the alternatives to get both short code and small memory
>> footprint?
>>
> Hello, below is a compilable program and example run of the program
> (see comment) that should hopefully answer your question.
>
>
>
>
> trait Monoid[A] {
>    def append(a1: A, a2: A): A
>    def empty: A
> }
>
> object Monoid {
>    implicit def ListMonoid[A]: Monoid[List[A]] = new Monoid[List[A]] {
>      def append(a1: List[A], a2: List[A]) = a1 ::: a2
>      def empty = Nil
>    }
> }
>
> case class Logger[LOG, A](log: LOG, value: A) {
>    def map[B](f: A =>  B) =
>      Logger(log, f(value))
>
>    def flatMap[B](f: A =>  Logger[LOG, B])(implicit m: Monoid[LOG]) = {
>      val x = f(value)
>      Logger(m.append(log, x.log), x.value)
>    }
>
>    // insert much more
> }
>
> object Logger {
>    def unital[LOG, A](value: A)(implicit m: Monoid[LOG]) =
>      Logger(m.empty, value)
>
>    // insert much more
> }
>
>
> //// Begin example
>
> /*
> $ scala Main 456
> RESULT: 1000
>
> LOG
> - ---
> adding one to 456
> converting int to string 457
> Checking length of 457 for evenness
> Converting to 100 or 1000 using false
> */
> object Main {
>    def main(args: Array[String]) {
>      val x = args(0).toInt // parse int from command line
>
>      val r =
>        for(a<- addOne(x);
>            b<- intString(a);
>            c<- lengthIsEven(b);
>            d<- hundredOrThousand(c)
>           ) yield d
>
>      println("RESULT: " + r.value)
>      println
>      println("LOG")
>      println("---")
>      r.log foreach println
>    }
>
>    implicit def ListLogUtil[A](a: A) = new {
>      def ~>[B](b: B) = Logger(List(a), b)
>    }
>
>    def addOne(n: Int) =
>      ("adding one to " + n) ~>  (n + 1)
>
>    def intString(n: Int) =
>      ("converting int to string " + n) ~>  n.toString
>
>    def lengthIsEven(s: String) =
>      ("Checking length of " + s + " for evenness") ~>  (s.length % 2 == 0)
>
>    def hundredOrThousand(b: Boolean) =
>      ("Converting to 100 or 1000 using " + b) ~>  (if(b) 100 else 1000)
> }
>
> - --
> Tony Morris
> http://tmorris.net/
>
> -----BEGIN PGP SIGNATURE-----
> Version: GnuPG v1.4.10 (GNU/Linux)
> Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/
>
> iEYEARECAAYFAk0EqPwACgkQmnpgrYe6r62mGACgtj3uPVjEisiAjF1sKHDrKBc6
> HvYAn1XvLbtiS1Q8P4vhdZKgJWisCvdT
> =NYzX
> -----END PGP SIGNATURE-----
>
Reply | Threaded
Open this post in threaded view
|

Re: verbose logger usage

Josh Suereth
In reply to this post by Kevin Wright-3
I wouldn't say anything revolving around logging is premature optimisation.  One application I worked on saw significant boost when removing all logging lines from the codebase, even with the early-exit code turned on.  I agree that a good scala logger implementation can mitigate these issues.

I also think clever use of @inline and by-name variables can help.   However when it comes to the actual logger objects, I agree that the backend implementation and logging use cases are more important than any sort of global "minimize # of references" optimisations.   It's the flow of data to log that can cause issues, not necessarily the amount of memory used by extra references for logging.   That is, assuming that you aren't embedding a logger into data types (of which there are possible lots of live objects at runtime) but keeping logging in the 'service layer' (of which there should be far less live objects at any given time).

As an aside, if you wanted to remove the extra reference you could do the more verbose:

trait Foo {
  def logger = Foo.logger
}
object Foo {
  lazy val logger  = Logger.getLogger(classOf[Foo])
}

Which turns into something similar to the java version.   Have you looks at any of the logging libraries for scala like Configgy?   I haven't used on in production to know what the ideal scenarios are, but it does use java.util.logging.

- Josh

On Sun, Dec 12, 2010 at 5:01 AM, Kevin Wright <[hidden email]> wrote:


On 12 December 2010 09:51, Ittay Dror <[hidden email]> wrote:


Kevin Wright wrote:

Alternatively, I can create a Logging trait

  trait Logging {
    lazy val log = Logger.getLogger(this.getClass)
  }

But then I'd have a Logger instance in every object


This is the correct approach, you still only end up with one logger per class thanks to caching in the logger framework.

and the cost of one reference per instance.


So a little less expensive that making a single val lazy, cheap at twice the price...
You pay a much higher cost with autoboxing, structural types, etc.

With a decent implementation of a logger trait, the "payload" will be lazy and not even evaluated unless the appropriate logging level is enabled, so you're *still* making a net saving.

Don't worry about this, seriously!  It's the worst sort of premature optimisation.

--
Kevin Wright

mail / gtalk / msn : [hidden email]
pulse / skype: kev.lee.wright
twitter: @thecoda


Reply | Threaded
Open this post in threaded view
|

Re: verbose logger usage

Tony Morris
In reply to this post by Ittay Dror

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

On 12/12/10 22:29, Ittay Dror wrote:
> Doesn't this mean that every method that logs needs to return a
> Logger and so is every method that uses it, transitively?


No. Since Logger is a pointed functor, you have a function A =>
Logger[Log, A]. You can use this function on values for which there
"is no log."

> And the log will be actually created at the end of the
> application?


No, see the flatMap method. Notice the appending of log values.

> What if the application is a service?

I'm not sure what this means. I shall assume that by "service" you
mean married to side-effects, in which case, all bets are off. I was
hoping to answer your question regarding "short code" (assuming you
mean more practical code) and relatively small memory footprint.

>
> Ittay
>
> Tony Morris wrote: On 12/12/10 19:24, Ittay Dror wrote:
>>>>
>>>> What are the alternatives to get both short code and small
>>>> memory footprint?
>>>>
> Hello, below is a compilable program and example run of the
> program (see comment) that should hopefully answer your question.
>
>
>
>
> trait Monoid[A] { def append(a1: A, a2: A): A def empty: A }
>
> object Monoid { implicit def ListMonoid[A]: Monoid[List[A]] = new
> Monoid[List[A]] { def append(a1: List[A], a2: List[A]) = a1 ::: a2
> def empty = Nil } }
>
> case class Logger[LOG, A](log: LOG, value: A) { def map[B](f: A =>
> B) = Logger(log, f(value))
>
> def flatMap[B](f: A => Logger[LOG, B])(implicit m: Monoid[LOG]) =
> { val x = f(value) Logger(m.append(log, x.log), x.value) }
>
> // insert much more }
>
> object Logger { def unital[LOG, A](value: A)(implicit m:
> Monoid[LOG]) = Logger(m.empty, value)
>
> // insert much more }
>
>
> //// Begin example
>
> /* $ scala Main 456 RESULT: 1000
>
> LOG --- adding one to 456 converting int to string 457 Checking
> length of 457 for evenness Converting to 100 or 1000 using false
> */ object Main { def main(args: Array[String]) { val x =
> args(0).toInt // parse int from command line
>
> val r = for(a<- addOne(x); b<- intString(a); c<- lengthIsEven(b);
> d<- hundredOrThousand(c) ) yield d
>
> println("RESULT: " + r.value) println println("LOG")
> println("---") r.log foreach println }
>
> implicit def ListLogUtil[A](a: A) = new { def ~>[B](b: B) =
> Logger(List(a), b) }
>
> def addOne(n: Int) = ("adding one to " + n) ~> (n + 1)
>
> def intString(n: Int) = ("converting int to string " + n) ~>
> n.toString
>
> def lengthIsEven(s: String) = ("Checking length of " + s + " for
> evenness") ~> (s.length % 2 == 0)
>
> def hundredOrThousand(b: Boolean) = ("Converting to 100 or 1000
> using " + b) ~> (if(b) 100 else 1000) }
>
> -- Tony Morris http://tmorris.net/
>
>>


- --
Tony Morris
http://tmorris.net/

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.10 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iEYEARECAAYFAk0FNA4ACgkQmnpgrYe6r60RCwCfYESjHRbwhdO6ajB4hzmHJRWX
KZwAn3DlzTxT4bI0GCzlg4kQVc+ZcDo3
=gow4
-----END PGP SIGNATURE-----

Reply | Threaded
Open this post in threaded view
|

Re: verbose logger usage

Ittay Dror
Thank you for your replies, I have some followup questions below.

Tony Morris wrote:

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

On 12/12/10 22:29, Ittay Dror wrote:
> Doesn't this mean that every
method that logs needs to return a

> Logger and so is every method that uses it, transitively?


No. Since Logger is a pointed functor, you have a function A =>
Logger[Log, A]. You can use this function on values for which there
"is no log."

How do I convert a function such as the one below?:

def foo(a: A, b: B) = {
   var v = bar(a)
   if(someCondition(b)) {
     log.debug(b + " has some condition")
     v = car(a, b)
   }
   if(someOtherCondition(v)) {
     log.warn(v + " met some other condition")
     v = zoo(42)
   }
   v
}
 


> And the log will be actually
created at the end of the

> application?


No, see the flatMap method. Notice the appending of log values.
Sorry for the confusion. I meant the log file (side effect).



> What if the application is a
service?


I'm not sure what this means. I shall assume that by "service" you
mean married to side-effects, in which case, all bets are off. I was
hoping to answer your question regarding "short code" (assuming you
mean more practical code) and relatively small memory footprint.

Yeah, I was talking nonsense here. Please ignore.

Ittay


>

> Ittay

>

> Tony Morris wrote: On 12/12/10 19:24, Ittay Dror wrote:

>>>>

>>>> What are the alternatives to get both short
code and small

>>>> memory footprint?

>>>>

> Hello, below is a compilable program and example run of the

> program (see comment) that should hopefully answer your
question.

>

>

>

>

> trait Monoid[A] { def append(a1: A, a2: A): A def empty: A
}

>

> object Monoid { implicit def ListMonoid[A]: Monoid[List[A]]
= new

> Monoid[List[A]] { def append(a1: List[A], a2: List[A]) = a1
::: a2

> def empty = Nil } }

>

> case class Logger[LOG, A](log: LOG, value: A) { def
map[B](f: A =>

> B) = Logger(log, f(value))

>

> def flatMap[B](f: A => Logger[LOG, B])(implicit m:
Monoid[LOG]) =

> { val x = f(value) Logger(m.append(log, x.log), x.value) }

>

> // insert much more }

>

> object Logger { def unital[LOG, A](value: A)(implicit m:

> Monoid[LOG]) = Logger(m.empty, value)

>

> // insert much more }

>

>

> //// Begin example

>

> /* $ scala Main 456 RESULT: 1000

>

> LOG --- adding one to 456 converting int to string 457
Checking

> length of 457 for evenness Converting to 100 or 1000 using
false

> */ object Main { def main(args: Array[String]) { val x =

> args(0).toInt // parse int from command line

>

> val r = for(a<- addOne(x); b<- intString(a); c<-
lengthIsEven(b);

> d<- hundredOrThousand(c) ) yield d

>

> println("RESULT: " + r.value) println println("LOG")

> println("---") r.log foreach println }

>

> implicit def ListLogUtil[A](a: A) = new { def ~>[B](b:
B) =

> Logger(List(a), b) }

>

> def addOne(n: Int) = ("adding one to " + n) ~> (n + 1)

>

> def intString(n: Int) = ("converting int to string " + n)
~>

> n.toString

>

> def lengthIsEven(s: String) = ("Checking length of " + s +
" for

> evenness") ~> (s.length % 2 == 0)

>

> def hundredOrThousand(b: Boolean) = ("Converting to 100 or
1000

> using " + b) ~> (if(b) 100 else 1000) }

>

> -- Tony Morris http://tmorris.net/

>

>>


- --
Tony Morris
http://tmorris.net/

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.10 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iEYEARECAAYFAk0FNA4ACgkQmnpgrYe6r60RCwCfYESjHRbwhdO6ajB4hzmHJRWX
KZwAn3DlzTxT4bI0GCzlg4kQVc+ZcDo3
=gow4
-----END PGP SIGNATURE-----

Reply | Threaded
Open this post in threaded view
|

Re: verbose logger usage

Tony Morris
No worries mate.

The answer to your question is:
1) First, tidy up the code. Logging and side-effecting are very different concepts. We should delineate them. A bit like "Aspect-Oriented Programming" and other such attempts at delineation, except we need something practical, right?

So here is the code tidied up without logging:

class FooTidied[A, B, V] {
  def foo(a: A, b: B): V = {
    val v = bar(a)
    if(someCondition(b))
      car(a, b)
    else if(someOtherCondition(v))
      zoo(42)
    else
      v
  }

  def someCondition(b: B) =
    b.toString.length % 2 == 0

  def someOtherCondition(v: V) =
    v.toString.length % 2 == 1

  def bar(a: A): V = {
    error("todo")
  }

  def car(a: A, b: B): V = {
    error("todo")
  }
 
  def zoo(n: Int): V = {
    error("todo")
  }
}

2) Now we need to "add logging." If we have appropriate delineation, we really should just be able to "add it." Now the code looks like this:

sealed trait LogLevel {
  def log[A](a: A) = new {
    def ~>[B](b: B) = Logger(List((this, a)), b)
  }
}
case object Debug extends LogLevel
case object Warn extends LogLevel

class Foo[A, B, V] {
  def foo(a: A, b: B) = {
    val v = bar(a)
    if(someCondition(b))
      (Debug log (b + " has some condition")) ~> car(a, b)
    else if(someOtherCondition(v))
      (Warn log (v + " met some other condition")) ~> zoo(42)
    else
      Logger.unital(v)
  }

  def someCondition(b: B) =
    b.toString.length % 2 == 0

  def someOtherCondition(v: V) =
    v.toString.length % 2 == 1

  def bar(a: A): V = {
    error("todo")
  }

  def car(a: A, b: B): V = {
    error("todo")
  }
 
  def zoo(n: Int): V = {
    error("todo")
  }
}

3) Profit!

On 13/12/10 14:10, Ittay Dror wrote:
> Thank you for your replies, I have some followup questions below.
>
> Tony Morris wrote:
>>

On 12/12/10 22:29, Ittay Dror wrote:
> Doesn't this mean that every

method that logs needs to return a



> Logger and so is every method that uses it, transitively?

No. Since Logger is a pointed functor, you have a function A =>
Logger[Log, A]. You can use this function on values for which there
"is no log."

> How do I convert a function such as the one below?:

> def foo(a: A, b: B) = {
>    var v = bar(a)
>    if(someCondition(b)) {
>      log.debug(b + " has some condition")
>      v = car(a, b)
>    }
>    if(someOtherCondition(v)) {
>      log.warn(v + " met some other condition")
>      v = zoo(42)
>    }
>    v
> }



> And the log will be actually

created at the end of the



> application?

No, see the flatMap method. Notice the appending of log values.
> Sorry for the confusion. I meant the log file (side effect).



> What if the application is a

service?

I'm not sure what this means. I shall assume that by "service" you
mean married to side-effects, in which case, all bets are off. I was
hoping to answer your question regarding "short code" (assuming you
mean more practical code) and relatively small memory footprint.

> Yeah, I was talking nonsense here. Please ignore.

> Ittay






> Ittay







> Tony Morris wrote: On 12/12/10 19:24, Ittay Dror wrote:



>>>>



>>>> What are the alternatives to get both short

code and small



>>>> memory footprint?



>>>>



> Hello, below is a compilable program and example run of the



> program (see comment) that should hopefully answer your

question.



















> trait Monoid[A] { def append(a1: A, a2: A): A def empty: A

}







> object Monoid { implicit def ListMonoid[A]: Monoid[List[A]]

= new



> Monoid[List[A]] { def append(a1: List[A], a2: List[A]) = a1

::: a2



> def empty = Nil } }







> case class Logger[LOG, A](log: LOG, value: A) { def

map[B](f: A =>



> B) = Logger(log, f(value))







> def flatMap[B](f: A => Logger[LOG, B])(implicit m:

Monoid[LOG]) =



> { val x = f(value) Logger(m.append(log, x.log), x.value) }







> // insert much more }







> object Logger { def unital[LOG, A](value: A)(implicit m:



> Monoid[LOG]) = Logger(m.empty, value)







> // insert much more }











> //// Begin example







> /* $ scala Main 456 RESULT: 1000







> LOG --- adding one to 456 converting int to string 457

Checking



> length of 457 for evenness Converting to 100 or 1000 using

false



> */ object Main { def main(args: Array[String]) { val x =



> args(0).toInt // parse int from command line







> val r = for(a<- addOne(x); b<- intString(a); c<-

lengthIsEven(b);



> d<- hundredOrThousand(c) ) yield d







> println("RESULT: " + r.value) println println("LOG")



> println("---") r.log foreach println }







> implicit def ListLogUtil[A](a: A) = new { def ~>[B](b:

B) =



> Logger(List(a), b) }







> def addOne(n: Int) = ("adding one to " + n) ~> (n + 1)







> def intString(n: Int) = ("converting int to string " + n)

~>



> n.toString







> def lengthIsEven(s: String) = ("Checking length of " + s +

" for



> evenness") ~> (s.length % 2 == 0)







> def hundredOrThousand(b: Boolean) = ("Converting to 100 or

1000



> using " + b) ~> (if(b) 100 else 1000) }







> -- Tony Morris http://tmorris.net/









>>

--
Tony Morris
http://tmorris.net/


Reply | Threaded
Open this post in threaded view
|

Re: verbose logger usage

Ittay Dror
So now foo has the signature Logger[List[(LogLevel, String)], V] (right?). Then using it becomes awkward, doesn't it? How do I do this:
  bar(tidied.foo(a, b))

Furthermore, say foo started as a non-logging function, now I want to add logging (e.g., to trace a bug). Now its signature changed and so it means the module and all dependent modules need to be compiled, right? Maybe even changing their code to at least import some function that makes the use of foo seem as before. Right?

Ittay

Tony Morris wrote:
No worries mate.

The answer to your question is:
1) First, tidy up the code. Logging and side-effecting are very different concepts. We should delineate them. A bit like "Aspect-Oriented Programming" and other such attempts at delineation, except we need something practical, right?

So here is the code tidied up without logging:

class FooTidied[A, B, V] {
  def foo(a: A, b: B): V = {
    val v = bar(a)
    if(someCondition(b))
      car(a, b)
    else if(someOtherCondition(v))
      zoo(42)
    else
      v
  }

  def someCondition(b: B) =
    b.toString.length % 2 == 0

  def someOtherCondition(v: V) =
    v.toString.length % 2 == 1

  def bar(a: A): V = {
    error("todo")
  }

  def car(a: A, b: B): V = {
    error("todo")
  }
 
  def zoo(n: Int): V = {
    error("todo")
  }
}

2) Now we need to "add logging." If we have appropriate delineation, we really should just be able to "add it." Now the code looks like this:

sealed trait LogLevel {
  def log[A](a: A) = new {
    def ~>[B](b: B) = Logger(List((this, a)), b)
  }
}
case object Debug extends LogLevel
case object Warn extends LogLevel

class Foo[A, B, V] {
  def foo(a: A, b: B) = {
    val v = bar(a)
    if(someCondition(b))
      (Debug log (b + " has some condition")) ~> car(a, b)
    else if(someOtherCondition(v))
      (Warn log (v + " met some other condition")) ~> zoo(42)
    else
      Logger.unital(v)
  }

  def someCondition(b: B) =
    b.toString.length % 2 == 0

  def someOtherCondition(v: V) =
    v.toString.length % 2 == 1

  def bar(a: A): V = {
    error("todo")
  }

  def car(a: A, b: B): V = {
    error("todo")
  }
 
  def zoo(n: Int): V = {
    error("todo")
  }
}

3) Profit!

On 13/12/10 14:10, Ittay Dror wrote:
> Thank you for your replies, I
have some followup questions below.

>

> Tony Morris wrote:

>>

On 12/12/10 22:29, Ittay Dror wrote:
> Doesn't this mean that every

method that logs needs to return a



> Logger and so is every method that uses it, transitively?

No. Since Logger is a pointed functor, you have a function A =>
Logger[Log, A]. You can use this function on values for which there
"is no log."

> How do I convert a function such as the one below?:

> def foo(a: A, b: B) = {
>    var v = bar(a)
>    if(someCondition(b)) {
>      log.debug(b + " has some condition")
>      v = car(a, b)
>    }
>    if(someOtherCondition(v)) {
>      log.warn(v + " met some other condition")
>      v = zoo(42)
>    }
>    v
> }



> And the log will be actually

created at the end of the



> application?

No, see the flatMap method. Notice the appending of log values.
> Sorry for the confusion. I meant the log file (side effect).



> What if the application is a

service?

I'm not sure what this means. I shall assume that by "service" you
mean married to side-effects, in which case, all bets are off. I was
hoping to answer your question regarding "short code" (assuming you
mean more practical code) and relatively small memory footprint.

> Yeah, I was talking nonsense here. Please ignore.

> Ittay






> Ittay







> Tony Morris wrote: On 12/12/10 19:24, Ittay Dror wrote:



>>>>



>>>> What are the alternatives to get both short

code and small



>>>> memory footprint?



>>>>



> Hello, below is a compilable program and example run of the



> program (see comment) that should hopefully answer your

question.



















> trait Monoid[A] { def append(a1: A, a2: A): A def empty: A

}







> object Monoid { implicit def ListMonoid[A]: Monoid[List[A]]

= new



> Monoid[List[A]] { def append(a1: List[A], a2: List[A]) = a1

::: a2



> def empty = Nil } }







> case class Logger[LOG, A](log: LOG, value: A) { def

map[B](f: A =>



> B) = Logger(log, f(value))







> def flatMap[B](f: A => Logger[LOG, B])(implicit m:

Monoid[LOG]) =



> { val x = f(value) Logger(m.append(log, x.log), x.value) }







> // insert much more }







> object Logger { def unital[LOG, A](value: A)(implicit m:



> Monoid[LOG]) = Logger(m.empty, value)







> // insert much more }











> //// Begin example







> /* $ scala Main 456 RESULT: 1000







> LOG --- adding one to 456 converting int to string 457

Checking



> length of 457 for evenness Converting to 100 or 1000 using

false



> */ object Main { def main(args: Array[String]) { val x =



> args(0).toInt // parse int from command line







> val r = for(a<- addOne(x); b<- intString(a); c<-

lengthIsEven(b);



> d<- hundredOrThousand(c) ) yield d







> println("RESULT: " + r.value) println println("LOG")



> println("---") r.log foreach println }







> implicit def ListLogUtil[A](a: A) = new { def ~>[B](b:

B) =



> Logger(List(a), b) }







> def addOne(n: Int) = ("adding one to " + n) ~> (n + 1)







> def intString(n: Int) = ("converting int to string " + n)

~>



> n.toString







> def lengthIsEven(s: String) = ("Checking length of " + s +

" for



> evenness") ~> (s.length % 2 == 0)







> def hundredOrThousand(b: Boolean) = ("Converting to 100 or

1000



> using " + b) ~> (if(b) 100 else 1000) }







> -- Tony Morris http://tmorris.net/









>>

--
Tony Morris
http://tmorris.net/


Reply | Threaded
Open this post in threaded view
|

Re: verbose logger usage

Tony Morris
On 13/12/10 17:29, Ittay Dror wrote:
So now foo has the signature Logger[List[(LogLevel, String)], V] (right?). Then using it becomes awkward, doesn't it? How do I do this:
  bar(tidied.foo(a, b))
No, see the use of for-comprehension syntax. There are other combinators that also may make it even easier but I have left those out of the example.

Furthermore, say foo started as a non-logging function, now I want to add logging (e.g., to trace a bug).
See the unital function. I have updated the example here:
http://blog.tmorris.net/the-writer-monad-using-scala-example/

It includes a function for which there is no logging (hundredOrThousand).

Now its signature changed and so it means the module and all dependent modules need to be compiled, right?
Sure, if you change a function, it's signature may change. Importantly, you can take the previous function, leave it unmodified and *compose* it with logging to produce a new function.

Maybe even changing their code to at least import some function that makes the use of foo seem as before. Right?
There are various techniques to maintain the compositionality. One is to use the Identity monad and a transformer.


-- 
Tony Morris
http://tmorris.net/

Reply | Threaded
Open this post in threaded view
|

Re: verbose logger usage

Ittay Dror


Tony Morris wrote:
On 13/12/10 17:29, Ittay Dror wrote:
So now foo has the signature Logger[List[(LogLevel, String)], V] (right?). Then using it becomes awkward, doesn't it? How do I do this:
  bar(tidied.foo(a, b))
No, see the use of for-comprehension syntax. There are other combinators that also may make it even easier but I have left those out of the example.

Still, I need to do
for (f <- foo; b <- bar(f)) car(b)

instead of
car(bar(foo))




Furthermore, say foo started as a non-logging function, now I want to add logging (e.g., to trace a bug).
See the unital function. I have updated the example here:
http://blog.tmorris.net/the-writer-monad-using-scala-example/

It includes a function for which there is no logging (hundredOrThousand).

Now its signature changed and so it means the module and all dependent modules need to be compiled, right?
Sure, if you change a function, it's signature may change. Importantly, you can take the previous function, leave it unmodified and *compose* it with logging to produce a new function.

I just moved the signature change to another place. I'd still need to recompile everything. E.g., what do I do if my code is used as a 3rd party library? Adding debug logging means a major release?


Maybe even changing their code to at least import some function that makes the use of foo seem as before. Right?
There are various techniques to maintain the compositionality. One is to use the Identity monad and a transformer.

In Bar.scala:
object Bar {
    println(Foo.foo)
}

In Foo.scala
object Foo {
  def foo = // without logging
}

Now I add logging to foo. I need to go to Bar.scala and change the code so it will compile. Right? So adding debugging to foo means changing all code using it, which now means changing all that code, etc.

Ittay



-- 
Tony Morris
http://tmorris.net/

Reply | Threaded
Open this post in threaded view
|

Re: verbose logger usage

Viktor Klang
In reply to this post by Tony Morris


On Mon, Dec 13, 2010 at 6:01 AM, Tony Morris <[hidden email]> wrote:
No worries mate.

The answer to your question is:
1) First, tidy up the code. Logging and side-effecting are very different concepts. We should delineate them. A bit like "Aspect-Oriented Programming" and other such attempts at delineation, except we need something practical, right?

So here is the code tidied up without logging:

class FooTidied[A, B, V] {
  def foo(a: A, b: B): V = {
    val v = bar(a)
    if(someCondition(b))
      car(a, b)
    else if(someOtherCondition(v))
      zoo(42)
    else
      v
  }


Your conversion doesn't respect his original code, see the cunning use of "var v":

def foo(a: A, b: B) = {
   var v = bar(a)
   if(someCondition(b)) {
     log.debug(b + " has some condition")
     v = car(a, b)
   }
   if(someOtherCondition(v)) {
     log.warn(v + " met some other condition")
     v = zoo(42)
   }
   v
}

More accurate would be to do:

def foo(a: A, b: B) = {
   val v = if(someCondition(b)) bar(a)
               else car(a, b)

   if(someOtherCondition(v)) zoo(42)
   else v
}

//IF, and only if, bar(a) is not side-effecting

 

  def someCondition(b: B) =
    b.toString.length % 2 == 0

  def someOtherCondition(v: V) =
    v.toString.length % 2 == 1

  def bar(a: A): V = {
    error("todo")
  }

  def car(a: A, b: B): V = {
    error("todo")
  }
 
  def zoo(n: Int): V = {
    error("todo")
  }
}

2) Now we need to "add logging." If we have appropriate delineation, we really should just be able to "add it." Now the code looks like this:

sealed trait LogLevel {
  def log[A](a: A) = new {
    def ~>[B](b: B) = Logger(List((this, a)), b)
  }
}
case object Debug extends LogLevel
case object Warn extends LogLevel

class Foo[A, B, V] {

  def foo(a: A, b: B) = {
    val v = bar(a)
    if(someCondition(b))
      (Debug log (b + " has some condition")) ~> car(a, b)
    else if(someOtherCondition(v))
      (Warn log (v + " met some other condition")) ~> zoo(42)
    else
      Logger.unital(v)
  }

  def someCondition(b: B) =
    b.toString.length % 2 == 0

  def someOtherCondition(v: V) =
    v.toString.length % 2 == 1

  def bar(a: A): V = {
    error("todo")
  }

  def car(a: A, b: B): V = {
    error("todo")
  }
 
  def zoo(n: Int): V = {
    error("todo")
  }
}

3) Profit!


On 13/12/10 14:10, Ittay Dror wrote:
> Thank you for your replies, I have some followup questions below.
>
> Tony Morris wrote:
>>

On 12/12/10 22:29, Ittay Dror wrote:
> Doesn't this mean that every

method that logs needs to return a



> Logger and so is every method that uses it, transitively?

No. Since Logger is a pointed functor, you have a function A =>
Logger[Log, A]. You can use this function on values for which there
"is no log."

> How do I convert a function such as the one below?:

> def foo(a: A, b: B) = {
>    var v = bar(a)
>    if(someCondition(b)) {
>      log.debug(b + " has some condition")
>      v = car(a, b)
>    }
>    if(someOtherCondition(v)) {
>      log.warn(v + " met some other condition")
>      v = zoo(42)
>    }
>    v
> }



> And the log will be actually

created at the end of the



> application?

No, see the flatMap method. Notice the appending of log values.
> Sorry for the confusion. I meant the log file (side effect).



> What if the application is a

service?

I'm not sure what this means. I shall assume that by "service" you
mean married to side-effects, in which case, all bets are off. I was
hoping to answer your question regarding "short code" (assuming you
mean more practical code) and relatively small memory footprint.

> Yeah, I was talking nonsense here. Please ignore.

> Ittay






> Ittay







> Tony Morris wrote: On 12/12/10 19:24, Ittay Dror wrote:



>>>>



>>>> What are the alternatives to get both short

code and small



>>>> memory footprint?



>>>>



> Hello, below is a compilable program and example run of the



> program (see comment) that should hopefully answer your

question.



















> trait Monoid[A] { def append(a1: A, a2: A): A def empty: A

}







> object Monoid { implicit def ListMonoid[A]: Monoid[List[A]]

= new



> Monoid[List[A]] { def append(a1: List[A], a2: List[A]) = a1

::: a2



> def empty = Nil } }







> case class Logger[LOG, A](log: LOG, value: A) { def

map[B](f: A =>



> B) = Logger(log, f(value))







> def flatMap[B](f: A => Logger[LOG, B])(implicit m:

Monoid[LOG]) =



> { val x = f(value) Logger(m.append(log, x.log), x.value) }







> // insert much more }







> object Logger { def unital[LOG, A](value: A)(implicit m:



> Monoid[LOG]) = Logger(m.empty, value)







> // insert much more }











> //// Begin example







> /* $ scala Main 456 RESULT: 1000







> LOG --- adding one to 456 converting int to string 457

Checking



> length of 457 for evenness Converting to 100 or 1000 using

false



> */ object Main { def main(args: Array[String]) { val x =



> args(0).toInt // parse int from command line







> val r = for(a<- addOne(x); b<- intString(a); c<-

lengthIsEven(b);



> d<- hundredOrThousand(c) ) yield d







> println("RESULT: " + r.value) println println("LOG")



> println("---") r.log foreach println }







> implicit def ListLogUtil[A](a: A) = new { def ~>[B](b:

B) =



> Logger(List(a), b) }







> def addOne(n: Int) = ("adding one to " + n) ~> (n + 1)







> def intString(n: Int) = ("converting int to string " + n)

~>



> n.toString







> def lengthIsEven(s: String) = ("Checking length of " + s +

" for



> evenness") ~> (s.length % 2 == 0)







> def hundredOrThousand(b: Boolean) = ("Converting to 100 or

1000



> using " + b) ~> (if(b) 100 else 1000) }







> -- Tony Morris http://tmorris.net/









>>

--
Tony Morris
http://tmorris.net/





--
Viktor Klang,
Code Connoisseur
Work:   Scalable Solutions
Code:   github.com/viktorklang
Follow: twitter.com/viktorklang
Read:   klangism.tumblr.com

Reply | Threaded
Open this post in threaded view
|

Re: verbose logger usage

Tony Morris
In reply to this post by Ittay Dror

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

On 13/12/10 18:10, Ittay Dror wrote:
>
>
> Tony Morris wrote:
>> On 13/12/10 17:29, Ittay Dror wrote:
>>> So now foo has the signature Logger[List[(LogLevel, String)],
>>> V] (right?). Then using it becomes awkward, doesn't it? How do
>>> I do this: bar(tidied.foo(a, b))
>> No, see the use of for-comprehension syntax. There are other
>> combinators that also may make it even easier but I have left
>> those out of the example.
>
> Still, I need to do for (f <- foo; b <- bar(f)) car(b)
>
> instead of car(bar(foo))


car ? bar ? foo // requires Scalaz

or in my dream language with applicative functor comprehensions:

[|
    car
    bar
    foo
|]

>
>
>
>>>
>>> Furthermore, say foo started as a non-logging function, now I
>>> want to add logging (e.g., to trace a bug).
>> See the unital function. I have updated the example here:
>> http://blog.tmorris.net/the-writer-monad-using-scala-example/
>>
>> It includes a function for which there is no logging
>> (hundredOrThousand).
>>
>>> Now its signature changed and so it means the module and all
>>> dependent modules need to be compiled, right?
>> Sure, if you change a function, it's signature may change.
>> Importantly, you can take the previous function, leave it
>> unmodified and *compose* it with logging to produce a new
>> function.
>
> I just moved the signature change to another place. I'd still need
> to recompile everything. E.g., what do I do if my code is used as
> a 3rd party library? Adding debug logging means a major release?

Yes, changing the signature of a function means you need to recompile.
I don't understand why this is in contention. It is a *completely
different function*.  This is evident by its signature change.

Furthermore, it most practical that this (signature change) also holds
for functions that perform logging by printing. That it doesn't is a
mistake of scala's type system (google scala effect tracking).

To add; if it is claimed, as is so misguidedly often, that permitting
the addition of a print statement to an existing function is some form
of useful compositional property, then we are in big trouble.

>
>>
>>> Maybe even changing their code to at least import some
>>> function that makes the use of foo seem as before. Right?
>> There are various techniques to maintain the compositionality.
>> One is to use the Identity monad and a transformer.
>
> In Bar.scala: object Bar { println(Foo.foo) }
>
> In Foo.scala object Foo { def foo = // without logging }
>
> Now I add logging to foo. I need to go to Bar.scala and change the
> code so it will compile. Right?


No.

def loggedFoo = "Hi!" ~> Foo.foo


> So adding debugging to foo means changing all code using it, which
> now means changing all that code, etc.

This is a property (poor composition) of side-effects and is
orthogonal to the discussion at hand. If you remove the print and
produce a more appropriate example, then we can see what composition
really is all about and why you might consider delineating your code
nicely :)

- --
Tony Morris
http://tmorris.net/

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.10 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iEYEARECAAYFAk0F5rEACgkQmnpgrYe6r62PDACfSh50KGp9Qsjll1XWgPIf8VJ8
SUUAoMl6en9XMDJXJ/p1wYcfolQx7FKv
=wPKm
-----END PGP SIGNATURE-----

Reply | Threaded
Open this post in threaded view
|

Re: verbose logger usage

Tony Morris
In reply to this post by Viktor Klang

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

On 13/12/10 18:12, √iktor Klang wrote:
>
>
> On Mon, Dec 13, 2010 at 6:01 AM, Tony Morris <[hidden email]
> [hidden email]> wrote:
>
> No worries mate.
>
> The answer to your question is: 1) First, tidy up the code. Logging
> and side-effecting are very different concepts. We should delineate
> them. A bit like "Aspect-Oriented Programming" and other such
> attempts at delineation, except we need something practical,
> right?
>
> So here is the code tidied up without logging:
>
> class FooTidied[A, B, V] { def foo(a: A, b: B): V = { val v =
> bar(a) if(someCondition(b)) car(a, b) else
> if(someOtherCondition(v)) zoo(42) else v }
>
>
>
> Your conversion doesn't respect his original code, see the cunning
> use of "var v":
>
> def foo(a: A, b: B) = { var v = bar(a) if(someCondition(b)) {
> log.debug(b + " has some condition") v = car(a, b) }
> if(someOtherCondition(v)) { log.warn(v + " met some other
> condition") v = zoo(42) } v }
>
> More accurate would be to do:
>
> def foo(a: A, b: B) = { val v = if(someCondition(b)) bar(a) else
> car(a, b)
>
> if(someOtherCondition(v)) zoo(42) else v }
>

Good point. I write Haskell 8 hours a day. Untangling unnecessary
side-effects is too  depressing.


- --
Tony Morris
http://tmorris.net/

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.10 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iEYEARECAAYFAk0F5zMACgkQmnpgrYe6r60ocQCgpe2UXcfSMlkaEVeGU9hXvJ83
wRgAmgM5OVyQpAmKnDyBhFXCAJfsIKQ/
=J5di
-----END PGP SIGNATURE-----

Reply | Threaded
Open this post in threaded view
|

Re: verbose logger usage

Tony Morris
In reply to this post by Tony Morris
On 13/12/10 19:26, Tony Morris wrote:
> car ? bar ? foo // requires Scalaz
>
> or in my dream language with applicative functor comprehensions:
>
> [|
>     car
>     bar
>     foo
> |]
car ∘ bar ⊛ foo

--
Tony Morris
http://tmorris.net/


Reply | Threaded
Open this post in threaded view
|

RE: verbose logger usage

Det2
In reply to this post by Viktor Klang
> Your conversion doesn't respect his original code, see the cunning use
> of "var v":
>
> def foo(a: A, b: B) = {
>    var v = bar(a)
>    if(someCondition(b)) {
>      log.debug(b + " has some condition")
>      v = car(a, b)
>    }
>    if(someOtherCondition(v)) {
>      log.warn(v + " met some other condition")
>      v = zoo(42)
>    }
>    v
> }
>
> More accurate would be to do:
>
> def foo(a: A, b: B) = {
>    val v = if(someCondition(b)) bar(a)
>                else car(a, b)
>
>    if(someOtherCondition(v)) zoo(42)
>    else v
> }
>


Hmmm. Wouldn't that be:
Either:
 val v = if(someCondition(b)) car(a, b)
         else bar(a)

Or:
 val v = if( ! someCondition(b)) bar(a)
         else car(a, b)

?
Reply | Threaded
Open this post in threaded view
|

Re: verbose logger usage

Randall R Schulz-2
In reply to this post by Ittay Dror
On Sunday December 12 2010, Ittay Dror wrote:

> Hi All,
>
>
> Something that has bothered me since starting to use Scala is how to
> use logging.
>
> ...
>
> What are the alternatives to get both short code and small memory
> footprint?

I haven't followed closely, but I think the AOP techniques that Jonas
Bonér outlines in a few of his blogs might be helpful for you.

<http://jonasboner.com/2008/02/06/aop-style-mixin-composition-stacks-in-scala.html>
<http://jonasboner.com/2008/10/06/real-world-scala-dependency-injection-di.html>
<http://jonasboner.com/2008/12/09/real-world-scala-managing-cross-cutting-concerns-using-mixin-composition-and-aop/>


> Regards,
>
> Ittay


Randall Schulz