복붙노트

[SCALA] 스칼라 로그인 할 때 어떻게 반환 값을 유지

SCALA

스칼라 로그인 할 때 어떻게 반환 값을 유지

자바 프로그래밍 할 때, 나는 항상 입력 매개 변수를 기록하고 방법의 값을 반환하지만, 스칼라, 방법의 마지막 줄은 리턴 값이다. 그래서 내가 좋아하는 뭔가를해야 :

def myFunc() = {
  val rs = calcSomeResult()
  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 _
}

그리고 나는 그것을 사용 :

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

이 값을 기록하고 반환합니다. 그것은 나를 위해 작동하지만 이상한 것 같다. 나는 오래된 자바 프로그래머,하지만 스칼라 새로운 오전, 나는 스칼라에서이 작업을 수행 할 수있는 더 관용적 방법이 있는지 모른다.

덕분에 당신의 도움을 위해, 지금은 더 나은 황조롱이 연결자를 사용 util을 romusz에 의해 metioned 만들

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

I는 그것을 SLF4J에서 로거를 통과 할 수 있도록 I는 F 매개 변수를 추가하고, 테스트 케이스이다 :

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

해결법

  1. ==============================

    1.Kxy의 =의 X : 당신이 찾고있는 황조롱이의 콤비 (K 연결자)라고합니다. 당신은 전달 된 값을 반환하면서 부작용 작업 (뿐만 아니라 로깅) 모든 종류의 작업을 수행 할 수 있습니다. https://github.com/raganwald/homoiconic/blob/master/2008-10-29/kestrel.markdown#readme 읽기

    Kxy의 =의 X : 당신이 찾고있는 황조롱이의 콤비 (K 연결자)라고합니다. 당신은 전달 된 값을 반환하면서 부작용 작업 (뿐만 아니라 로깅) 모든 종류의 작업을 수행 할 수 있습니다. 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)
    

    귀하의 예제 함수는 한 줄이된다 :

    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")
    
  2. ==============================

    2.당신은 더 나은 더 일반적인 접근 방식을 좋아한다면, 당신은 정의 할 수 있습니다

    당신은 더 나은 더 일반적인 접근 방식을 좋아한다면, 당신은 정의 할 수 있습니다

    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
    }
    

    와 같이 사용

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

    3.당신은 기본 아이디어 권리가 있습니다 - 당신은 그냥 최대한 편안함을 위해 조금을 정돈 할 필요가있다.

    당신은 기본 아이디어 권리가 있습니다 - 당신은 그냥 최대한 편안함을 위해 조금을 정돈 할 필요가있다.

    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 회).

  4. ==============================

    4.의 이미 모든 당신 로거에 대한 기본 클래스가 있다고 가정 해 봅시다 :

    의 이미 모든 당신 로거에 대한 기본 클래스가 있다고 가정 해 봅시다 :

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

    로깅을 사용하려면 ExpressionLog 개체의 멤버를 가져해야 할 것이다 그리고 당신은 쉽게 다음과 같은 표기법을 사용하여 식을 로그온 할 수 :

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

    인쇄합니다 :

    이렇게하면 문자열 컴파일러에 @@ 메서드를 호출 할 때마다 문자열이 방법이 없음을 실현하기 때문에 작동하고 자동으로 정의 @@ 방법 (stringToLog 참조)가 익명의 유형 객체로 변환합니다. 변환 컴파일러의 일부가 암시 적 매개 변수로 원하는 로거를 선택합니다,이 방법은 아직 당신이 로거는 모든 시간을 사용할 필요가있는 완전한 제어권을 유지하려면 @@ 모든 시간 로거에 전달 유지할 필요가 없습니다.

    @@ 방법은 중위 표기법을 사용하는 경우 지금까지 우선 순위가 간다 그것은 쉽게 기록됩니다 것을 추론 할 수있게 가장 높은 우선 순위를 가지고있다.

    그래서 당신은 당신의 방법 중 하나에서 다른 로거를 사용하는 것을 원한다면? 이것은 매우 간단하다 :

    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)
    

    윌 출력 :

  5. ==============================

    5.모든 해답, 장점과 단점을 컴파일, 나는 (상황이 재생 응용 프로그램)이 함께했다 :

    모든 해답, 장점과 단점을 컴파일, 나는 (상황이 재생 응용 프로그램)이 함께했다 :

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

    당신은 결과에 대한 참조가 필요하지 않은 경우 또는, 단지 사용

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

    이 구현에 대한 모든 단점?

    편집하다:

    여기 요지에 약간의 개선이 가능했습니다

  6. ==============================

    6.스칼라 2.13부터 체이닝 연산 탭은 원래의 값으로 되돌 리면서 값에 (이 경우에 일부 기록) 부작용을 적용 할 수있다 :

    스칼라 2.13부터 체이닝 연산 탭은 원래의 값으로 되돌 리면서 값에 (이 경우에 일부 기록) 부작용을 적용 할 수있다 :

    예를 들어 :

    scala> val a = 42.tap(println)
    42
    a: Int = 42
    

    또는 우리의 경우 :

    import scala.util.chaining._
    
    def myFunc() = calcSomeResult().tap(x => logger.info(s"result is: $x"))
    
  7. from https://stackoverflow.com/questions/9671620/how-to-keep-return-value-when-logging-in-scala by cc-by-sa and MIT license