come mantenere il valore di ritorno quando si accede a scala

Durante la programmazione in java, registro sempre il parametro di input e il valore di ritorno di un metodo, ma in scala, l’ultima riga di un metodo è il valore di ritorno. quindi devo fare qualcosa come:

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

per semplificare, scrivo un’utilità:

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

Quindi l’ho usato come:

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

registrerà il valore e lo restituirà. funziona per me, ma sembra strano. siccome sono un vecchio programmatore java, ma nuovo su scala, non so se esiste un modo più idiomatico per farlo in scala.


grazie per il tuo aiuto, ora creo un util migliore utilizzando il combinatore di Kestrel messo a punto da 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)} } 

Aggiungo il parametro f in modo che possa passarlo un logger da slf4j, e il test case è:

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

    Quello che stai cercando si chiama combinatore di Kestrel (combinatore K): Kxy = x . È ansible eseguire tutti i tipi di operazioni con effetti collaterali (non solo la registrazione) mentre si restituisce il valore passato ad esso. Leggi https://github.com/raganwald/homoiconic/blob/master/2008-10-29/kestrel.markdown#readme

    In Scala il modo più semplice per implementarlo è:

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

    Quindi puoi definire la tua funzione di stampa / registrazione come:

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

    E usalo come:

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

    la tua funzione di esempio diventa una sola riga:

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

    Se si preferisce la notazione OO, è ansible utilizzare gli impliciti come mostrato in altre risposte, ma il problema con tale approccio è che si creerà un nuovo object ogni volta che si desidera registrare qualcosa, il che potrebbe causare un peggioramento delle prestazioni se lo si fa abbastanza spesso. Ma per completezza, assomiglia a questo:

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

    Usalo come:

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

    Se ti piace un approccio più generico meglio, puoi definire

     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 } 

    e usalo come

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

    Hai l’idea di base giusta – devi solo riordinarla un po ‘per renderla estremamente comoda.

     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) 

    Ora puoi

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

    In questo modo non è necessario ripetere te stesso (ad esempio, non rs due volte).

    Supponiamo che tu abbia già una class base per tutti i logger:

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

    Quindi puoi estendere String con il metodo di registrazione @@ :

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

    Per utilizzare la registrazione è necessario importare i membri dell’object ExpressionLog e quindi è ansible registrare facilmente le espressioni utilizzando la seguente notazione:

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

    Stamperà:

     a = 1 b = 2 sum result = 3 

    Ciò funziona perché ogni volta che si chiama un metodo @@ su un compilatore String realizza che String non ha il metodo e lo converte in modo invisibile in un object con tipo anonimo che ha il metodo @@ definito (vedere stringToLog ). Come parte del compilatore di conversione, seleziona il logger desiderato come parametro implicito , in questo modo non è necessario continuare a trasmettere il logger a @@ ogni volta che si mantiene il controllo completo su quale logger deve essere utilizzato ogni volta.

    Per quanto riguarda la precedenza quando il metodo @@ è usato nella notazione infix ha la massima priorità, rendendo più facile ragionare su ciò che verrà registrato.

    Quindi, cosa succederebbe se volessi utilizzare un altro logger in uno dei tuoi metodi? Questo è molto semplice:

     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) 

    Produrrà:

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

    Compilando tutte le risposte, i pro e i contro, mi è venuto in mente questo (il contesto è un’applicazione 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 } } 

    Come puoi vedere, LogAny è un AnyVal quindi non ci dovrebbe essere alcun sovraccarico di creazione di nuovi oggetti.

    Puoi usarlo in questo modo:

     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 

    Oppure se non hai bisogno di un riferimento al risultato, usa semplicemente:

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

    Qualche svantaggio di questa implementazione?

    Modificare:

    L’ho reso disponibile con alcuni miglioramenti in un punto qui