2012-03-12 10 views
19

Podczas programowania w java, zawsze loguję parametr wejściowy i zwracam wartość metody, ale w scala, ostatnia linia metody jest wartością zwracaną. więc muszę zrobić coś takiego:jak zachować wartość zwracaną podczas logowania w scala

def myFunc() = { 
    val rs = calcSomeResult() 
    logger.info("result is:" + rs) 
    rs 
} 

w celu uczynienia go łatwo, piszę użytkowy:

class LogUtil(val f: (String) => Unit) { 
def logWithValue[T](msg: String, value: T): T = { f(msg); value } 
} 

object LogUtil { 
    def withValue[T](f: String => Unit): ((String, T) => T) = new LogUtil(f).logWithValue _ 
} 

Następnie użyłem go jako:

val rs = calcSomeResult() 
withValue(logger.info)("result is:" + rs, rs) 

będzie zalogować wartość i zwróć ją. to działa dla mnie, ale wydaje się dziwne. ponieważ jestem starym programistą java, ale nowym w scala, nie wiem, czy jest to bardziej idiomatyczny sposób na zrobienie tego w scala.


dzięki za pomoc, teraz stworzyć lepsze f parametr util użyciu Kestrel COMBINATOR metioned przez romusz

object LogUtil { 
    def kestrel[A](x: A)(f: A => Unit): A = { f(x); x } 
    def logV[A](f: String => Unit)(s: String, x: A) = kestrel(x) { y => f(s + ": " + y)} 
} 

dodam więc, że mogę przekazać go do rejestratora z slf4j i sprawa testową jest:

class LogUtilSpec extends FlatSpec with ShouldMatchers { 
    val logger = LoggerFactory.getLogger(this.getClass()) 
    import LogUtil._ 

"LogUtil" should "print log info and keep the value, and the calc for value should only be called once" in { 
    def calcValue = { println("calcValue"); 100 } // to confirm it's called only once 
    val v = logV(logger.info)("result is", calcValue) 
    v should be === 100 
    } 
} 
+0

Dlaczego nie używać AspectJ? Tak, to nie Scala, to prawda. –

Odpowiedz

6

Masz podstawowy pomysł - po prostu musisz trochę posprzątać, aby było maksymalnie wygodnie.

class GenericLogger[A](a: A) { 
    def log(logger: String => Unit)(str: A => String): A = { logger(str(a)); a } 
} 
implicit def anything_can_log[A](a: A) = new GenericLogger(a) 

Teraz możesz

scala> (47+92).log(println)("The answer is " + _) 
The answer is 139 
res0: Int = 139 

W ten sposób nie trzeba powtarzać się (np nr rs dwukrotnie).

+0

Wow, to jest świetne! Dziękuję Ci! –

7

Jeśli lubisz bardziej ogólne podejście lepiej, można zdefiniować

implicit def idToSideEffect[A](a: A) = new { 
    def withSideEffect(fun: A => Unit): A = { fun(a); a } 
    def |!>(fun: A => Unit): A = withSideEffect(fun) // forward pipe-like 
    def tap(fun: A => Unit): A = withSideEffect(fun) // public demand & ruby standard 
} 

i używać go jak

calcSomeResult() |!> { rs => logger.info("result is:" + rs) } 

calcSomeResult() tap println 
+0

dziękuję. wygląda dobrze. Chyba muszę przeczytać więcej o niejawnej konwersji ... –

+5

Zamiast dziwnego symbolu, możesz w tej samej liczbie znaków zdefiniować 'tap', co robi dokładnie to samo w Ruby. –

+0

Dzięki, @RexKerr. Szukałem trochę nazwy, ale nie myślałem o Ruby. – Debilski

34

Co szukasz nazywa Kestrel syntezatora (K syntezatora): Kxy = x . Możesz wykonywać różnego rodzaju operacje na efektach ubocznych (nie tylko rejestrowanie), zwracając przekazaną do niego wartość. Czytaj https://github.com/raganwald/homoiconic/blob/master/2008-10-29/kestrel.markdown#readme

W Scala najprostsza droga do jego realizacji jest:

def kestrel[A](x: A)(f: A => Unit): A = { f(x); x } 

Następnie można zdefiniować funkcję drukowania/rejestrowania jako:

def logging[A](x: A) = kestrel(x)(println) 
def logging[A](s: String, x: A) = kestrel(x){ y => println(s + ": " + y) } 

i używać go lubię:

logging(1 + 2) + logging(3 + 4) 

Twoja przykładowa funkcja staje się jednolinijkowa:

def myFunc() = logging("result is", calcSomeResult()) 

Jeśli wolisz notację OO, możesz użyć implikacji, jak pokazano w innych odpowiedziach, ale problem z takim podejściem polega na tym, że za każdym razem, gdy chcesz coś zarejestrować, utworzysz nowy obiekt, co może spowodować obniżenie wydajności, jeśli robisz to dość często.Ale pod względem kompletności, wygląda to tak:

implicit def anyToLogging[A](a: A) = new { 
    def log = logging(a) 
    def log(msg: String) = logging(msg, a) 
} 

używać go jak:

def myFunc() = calcSomeResult().log("result is") 
+3

+1 dla podświetlenia dodanego narzutów roztworu OO w porównaniu z odpowiednikiem funkcjonalnym. Uważam, że funkcja rejestrowania musi być używana oszczędnie iw takich sytuacjach wygoda syntaktyczna może mieć pierwszeństwo przed szybkością lub wykorzystaniem pamięci. –

+1

dzięki za wiedzę o kombinatorze Kestrel, mogę teraz lepiej wykorzystać –

+0

@ 诺 铁 Nie ma za co. – romusz

3

Powiedzmy masz już klasę bazową dla was wszystkich rejestratorów:

abstract class Logger { 
    def info(msg:String):Unit 
} 

następnie można przedłużyć ciąg za pomocą metody rejestrowania @@:

object ExpressionLog { 
    // default logger 
    implicit val logger = new Logger { 
    def info(s:String) {println(s)} 
    } 

    // adding @@ method to all String objects 
    implicit def stringToLog (msg: String) (implicit logger: Logger) = new { 
    def @@ [T] (exp: T) = { 
     logger.info(msg + " = " + exp) 
     exp 
    } 
    } 
} 

Aby użyć rejestrowanie trzeba było importować członków ExpressionLog obiektu, a następnie za pomocą wyrażeń można następującą notację łatwo zalogować:

import ExpressionLog._ 

def sum (a:Int, b:Int) = "sum result" @@ (a+b) 
val c = sum("a" @@ 1, "b" @@2) 

wypisze:

a = 1 
b = 2 
sum result = 3 

Dzieje się tak, ponieważ za każdym razem, gdy wywołujesz metodę @@ na kompilatorze String, zdajesz sobie sprawę, że String nie ma metody i cicho konwertuje s to obiekt z anonimowym typem, który ma zdefiniowaną metodę @@ (patrz stringToLog). W ramach kompilatora konwersji wybiera żądany program rejestrujący jako implicit parameter, dzięki czemu nie musisz ciągle przekazywać rejestratora do @@ za każdym razem, gdy zachowujesz pełną kontrolę nad tym, który program rejestrujący musi być używany za każdym razem.

O ile pierwszeństwo ma metoda @@ w notacji infiksowej, ma ona wartość highest priority, co ułatwia ustalenie, co zostanie zarejestrowane.

A co jeśli chcesz użyć innego programu rejestrującego w jednej ze swoich metod? Jest to bardzo proste:

import ExpressionLog.{logger=>_,_} // import everything but default logger 
// define specific local logger 
// this can be as simple as: implicit val logger = new MyLogger 
implicit val logger = new Logger { 
    var lineno = 1 
    def info(s:String) { 
    println("%03d".format(lineno) + ": " + s) 
    lineno+=1 
    } 
} 

// start logging 
def sum (a:Int, b:Int) = a+b 
val c = "sum result" @@ sum("a" @@ 1, "b" @@2) 

wyświetli:

001: a = 1 
002: b = 2 
003: sum result = 3 
1

Kompilacja wszystkie odpowiedzi, wady i zalety, wymyśliłem ten (kontekst jest aplikacją Play):

import play.api.LoggerLike 

object LogUtils { 

implicit class LogAny2[T](val value : T) extends AnyVal { 

    def @@(str : String)(implicit logger : LoggerLike) : T = { 
     logger.debug(str); 
     value 
    } 

    def @@(f : T => String)(implicit logger : LoggerLike) : T = { 
     logger.debug(f(value)) 
     value 
    } 
} 

Jak widać, LogAny to AnyVal, więc nie powinno być żadnych kosztów związanych z tworzeniem nowych obiektów.

Można go używać tak:

scala> import utils.LogUtils._ 
scala> val a = 5 
scala> val b = 7 
scala> implicit val logger = play.api.Logger 

scala> val c = a + b @@ { c => s"result of $a + $b = $c" } 
c: Int = 12 

Lub jeśli nie trzeba odniesienie do wyniku, wystarczy użyć:

scala> val c = a + b @@ "Finished this very complex calculation" 
c: Int = 12 

Wszelkie downsides do tej realizacji?

Edit:

Zrobiłem to dostępne z niektórych ulepszeń w gist here

Powiązane problemy