2012-03-12 7 views
19

Javaでプログラミングするとき、私は常に入力パラメーターとメソッドの戻り値を記録しますが、スカラーではメソッドの最後の行が戻り値になります。ので、私のような何かやっていますスカラーにログインするときの戻り値の保持方法

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

それがログに記録します。そして、私はとしてそれを使用

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 _ 
} 

:それは簡単にするために

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

を、私はユーティリティを書きます値を返し、それを返します。それは私のために働くが、厄介なようだ。私は古いJavaプログラマだが、scalaには新しいので、scalaでこれを行うもっと慣用的な方法があるかどうかわからない。あなたの助けのための


おかげで、今私はそれをSLF4Jからロガー、およびテストケースを渡すことができるように、私はFパラメータを追加ケストレルを使用してutilのより良いコンビネータ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)} 
} 

でmetionedを作成あなたは、基本的な考え方の権利を持っている

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

におけるいくつかの改善と、これが利用可能になってきましたか?はい、スカラではありません。そうです。 –

答えて

6

- あなたはそれを最大限便利にするために少しそれを整理する必要がありますされています。

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) 

今することができます

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

あなた自身を繰り返す必要はありません。この方法で(例えば二回無rs)。

+0

うわー、これは素晴らしいです!ありがとうございました! –

7

あなたはより良い、より汎用的なアプローチが好きなら、あなたは

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 
} 

を定義し、ケストレルコンビネータ(Kコンビネータ)と呼ばれて探しているものを

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

calcSomeResult() tap println 
+0

ありがとうございます。いいね。暗黙の変換についてもっと読む必要があると思います... –

+5

奇妙なシンボルではなく、同じ数の文字で 'tap'を定義することができます。これはRubyでまったく同じことをします。 –

+0

ありがとう、@RexKerr。私は少し名前を検索しましたが、Rubyについては考えていませんでした。 – Debilski

34

ようにそれを使用することができます:Kxy = x 。渡された値を返しながら、あらゆる種類の副作用操作(ロギングだけでなく)を行うことができます。 Scalaでhttps://github.com/raganwald/homoiconic/blob/master/2008-10-29/kestrel.markdown#readme

を読むそれを実装する最も簡単な方法は次のとおりです。

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

次に、あなたのようにあなたの印刷/ロギング機能を定義することができます。

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

など、それを使用します。

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

例の関数は1行になります。

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

OO表記が好きな人は、他の回答に示されているようにimplicitsを使用できますが、そのようなアプローチの問題は、何かを記録するたびに新しいオブジェクトを作成し、あなたはそれを頻繁にやります。しかし、完全を期すために、それは次のようになります。

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

が好き、それを使用します。

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

+1を拡張して、同等の関数と比較したときのOOソリューションの追加されたオーバーヘッドを強調表示します。私は、ロギング機能を控えめに使用しなければならないと考えています。このようなシナリオでは、構文上の利便性がスピードやメモリの使用よりも優先される可能性があります。 –

+1

Kestrelコンビネータの知識をお寄せいただきありがとうございます。私は今より良いutilを持つことができます –

+0

@诺铁あなたは大歓迎です。 – romusz

3

はあなたがすでにすべてのあなたのロガーの基本クラスを持っているとしましょう:

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

次に、あなたの可能性@@ロギングメソッドを使用して文字列を拡張する:

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 
    } 
    } 
} 
import ExpressionLog._ 

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

が印刷されます:

a = 1 
b = 2 
sum result = 3 

あなたがExpressionLogオブジェクトのメンバーをインポートする必要があると思いますロギングを使用するには、その後、あなたは簡単に以下の表記を使用して式をログに記録できこれは、Stringコンパイラで@@メソッドを呼び出すたびにStringにメソッドがなく、自動的に変換されるためです@@メソッドが定義された匿名型のオブジェクトに挿入します(stringToLog参照)。変換コンパイラの一部がimplicit parameterという希望のロガーを選択するため、この方法で毎回@@にログを渡す必要はありませんが、毎回どのロガーを使用する必要があるかを完全に制御できます。

@@メソッドがインフィックス表記法で使用されている場合は、highest priorityがあり、ログに記録されるものの理由を簡単に判断できます。

したがって、いずれかの方法で別のロガーを使用する場合はどうなりますか?これは非常に簡単です:

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) 

ウィル出力:すべての答え、長所と短所をコンパイル

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

、私は(コンテキストは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 
    } 
} 

LogAnyはAnyValですので、新しいオブジェクト作成のオーバーヘッドはありません。

あなたはこのようにそれを使用することができます。この実装に

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

どれ欠点:

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 

それとも、単に使い、結果への参照を必要としない場合は?

編集:

私は、AspectJのを使用しないのはなぜgist here