scala에서 Lazy Evaluation - by-name Parameter

scala에는 by-name 파라미터(parameter)라는 것이 있다. 메서드의 파라미터를 Lazy Evaluation 할 수 있는 기능이다. 즉 파라미터를 by-name으로 정의하면 메서드로 넘겨질 때 값이 평가되지 않고 메서드 내부에서 실제 필요로 할 때 평가된다.

by-name 파라미터라는 말이 생소하게 들렸는데 찾아보니 Algol 60에 구현되었던 기능이며 Lazy Evaluatoin을 구현하는 여러 전략 중 하나라고 한다.(Wikipedia 설명)

scala에서는 => T 형식으로 파라미터 타입을 지정하면 by-name 파라미터가 된다. 실제로는 인자없는 함수로 구현되어 있는데 메서드 호출시 인자를 간단하게 쓸 수 있게 한 거라 보면 된다.

그러면 어떤 경우에 이런 기능을 사용하면 좋을까? 대표적인 예로 로깅(logging) 기능이 있겠다.

log4j 와 같은 로그 라이브러리에는 로그 레벨이라는 것이 있어서 로깅할 레벨을 지정하면 그 레벨보다 낮은 단계의 로그는 하지 않고 무시된다. 그런데 메서드가 호출될 때 파라미터는 반드시 평가되므로 로그 메서드에 넘기는 파라미터 값을 구성하기 위한 비용이 만만치가 않을 경우 성능상 문제가 된다. 이를 방지하기 위해 log4j를 이용할 때는 다음과 같이 코드를 작성하기도 한다.

if (logger.isDebugEnabled()) {
  logger.debug(some.expensiveMethod());
}

이 코드는 잘 동작하지만 코드 작성이 너무 번거롭다할 수 있다. 이런 경우에 Lazy Evaluation이 필요하다. 이 경우는 scala로 쉽게 해결할 수 있다. 다음과 같이 log4j를 감싸면 된다(개념만 간단히 보이기 위해 코드에 일부러 중복을 놔두었다).

//Logger.scala:
package corund.logger

import org.apache.log4j.Level

class Logger(cat: String) {
  val payload = org.apache.log4j.Logger.getLogger(cat)

  def debug(msg: => String) {
    if (payload.isDebugEnabled()) {
      debug(msg)
    }
  }

  def info(msg: => String) {
    if (payload.isInfoEnabled()) {
      info(msg)
    }
  }

  def warn(msg: => String) {
    if (payload.isEnabledFor(Level.WARN)) {
      warn(msg)
    }
  }
  // error 메서드 등 생략 ....
}

object Logger {
  def apply(cat: String): Logger = new Logger(cat)
}

이 Logger 클래스를 다음과 같이 실행해 보면

//Logging.scala
package corund.run

import org.apache.log4j.BasicConfigurator
import org.apache.log4j.Level
import corund.logger.Logger

object Logging {
  def main(args: Array[String]) {
    BasicConfigurator.configure()
    org.apache.log4j.Logger.getRootLogger().setLevel(Level.WARN)
    val logger = Logger("corund.run.Logging")

    class Data(str: String) {
      def expensive() = {
        println("Invoked! - " + str)
        str
      }
    }

    val data = new Data("expensive object")

    logger.debug("debug: " + data.expensive())
    logger.info("info: " + data.expensive())
    logger.warn("warn: " + data.expensive())
    logger.error("error: " + data.expensive())
  }
}

결과는 아래와 같다. warn 레벨 아래에서는 data.expensive() 메서드가 실행되지 않음을 알 수 있다.

Lazy Evaluation

test를 작성할 때 많이 사용하는 assert 함수도 by-name 파라미터로 구현되어 있다고 한다. 코드의 실행 순서를 제어할 수 있기 때문에 직접 제어 구조를 만들 때 유용하게 쓸 수 있는 기능이다.

2009-07-08 08:43 | Permlink | Comments
blog comments powered by Disqus

About

Laser Keyboard

I'm Jin Kim. Live in Seoul, Korea. Software Developer. Java, Perl, Scala, Common Lisp. Mainly programming in web program and its backend data store. Interested in data processing and distributed computing.

Archive

RSS