2009년 07월 08일
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() 메서드가 실행되지 않음을 알 수 있다.

test를 작성할 때 많이 사용하는 assert 함수도 by-name 파라미터로 구현되어 있다고 한다. 코드의 실행 순서를 제어할 수 있기 때문에 직접 제어 구조를 만들 때 유용하게 쓸 수 있는 기능이다.
# by | 2009/07/08 08:43 | 트랙백 | 덧글(0)





















☞ 내 이글루에 이 글과 관련된 글 쓰기 (트랙백 보내기) [도움말]