Outsider's Dev Story

Stay Hungry. Stay Foolish. Don't Be Satisfied.
RetroTech 팟캐스트 44BITS 팟캐스트

Scala 2.10의 새로운 기능 : Dynamic 타입

스칼라는 정적 타입언어이고 정적 타입이 가진 장점들이 있는 반면 동적타입이 가진 장점들도 있다. 스칼라도 타입을 유연하게 사용할 수 있는 여러 방법을 제공하고 있지는 하지만 obj.props와 같이 사용하면 objprops라는 멤버가 정의되어 있거나 implicit 타입변환을 할 수 있는 등 리시버(여기서는 obj)가 해당 멤버를 정적으로 알고 있을 때만 사용할 수 있다. 이부분을 해결하기 위해서 Scala는 동적타입 언어처럼 사용할 수 있는 Dynamic 타입이 추가했는데 2.9에서는 -Xexperimental 옵션을 붙혀야 사용할 수 있었지만 2.10에서는 기본으로 사용할 수 있게 되었다.(Dynamic에 대한 글이 많지 않아서 Scala 2.10: class OhMy extends Dynamic !를 참고했다.)


Dynamic 타입
Dynamic 타입을 사용하려면 scala.Dynamic trait를 확장해야 한다.

scala> class Example extends Dynamic
error: extension of type scala.Dynamic needs to be enabled
by making the implicit value language.dynamics visible.
This can be achieved by adding the import clause 'import scala.language.dynamics'
or by setting the compiler option -language:dynamics.
See the Scala docs for value scala.language.dynamics for a discussion
why the feature needs to be explicitly enabled.

위와 같이 Dynamic을 확장한 클래스를 정의하면 오류가 발생한다. Dynamic 타입을 사용하려면 Dynamic 기능을 활성화해야 하는데 이는 scala.language.dynamics를 임포트해야한다.(또는 컴파일 옵션에 -language:dynamics를 추가해서) 이렇게 사용하는 이유는 동적으로 멤버를 선택하는 기능이 정적 타입체크 기능을 저하시키고 동적 멤버 선택이 경우에 따라서는 리플렉션을 사용하는데 리플렉션을 사용할 수 없는 플랫폼이 있기 때문이다. Dynamic 기능을 활성화 하지 않으면 Dynamic을 기반 트레이트로 가진 클래스, 트레이트, 오프젝트를 정의할 수 없다.

scala> import scala.language.dynamics
import scala.language.dynamics

scala> class Example extends Dynamic
defined class Example

위와 같이 import scala.language.dynamics를 사용하고 나면 오류없이 Dynamic 트레이트를 확장한 클래스를 정의할 수 있다.

qual.sel과 같이 사용했을 때 타입 확인에 실패하고 리시버인 qual의 타입이 scala.Dynamic이고 selapplyDynamic, applyDynamicNamed, selectDynamic, updateDynamic이 아닌 경우 다음과 같이 재작성한다.

  • qual.sel에 뒤에 타입 인자리스트 [TS](선택적이다)와 인자리스트 (arg1, ..., argN)이(이때 인자가 이름있는 인자가 아니고 argN이 sequence 인자가 아니어야 한다) 오는 경우 즉 qual.sel[Ts](arg1, ..., argN)qual.applyDynamic[Ts]("sel")(arg1, ..., argN)으로 재작성한다.
  • qual.sel에 뒤에 타입 인자리스트 [TS](선택적이다)와 이름있는 인자리스트 (x1 = arg1, ..., xN = argN)이(일부는 이름있는 인자가 아니더라도) 오는 경우 즉, qual.sel[Ts](x1 = arg1, …, xN = argN)qual.applyDynamicNamed[Ts]("sel")(xs1 -> arg1, …, xsN -> argN)으로 재작성 한다.
  • qual.sel뒤에 인자나 할당 오퍼레이터가 없는 경우 즉, qual.selqual.selectDynamic[Ts]("sel")로 재작성한다.
  • qual.sel이 할당의 좌변에 오는 경우 즉, qual.sel = exprqual.updateDynamic(“sel”)(expr)로 재작성한다.
글로 쓰면 복잡한데 하나씩 차례대로 보자.


applyDynamic

qual.sel에 뒤에 타입 인자리스트 [TS](선택적이다)와 인자리스트 (arg1, ..., argN)이(이때 인자가 이름있는 인자가 아니고 argN이 sequence 인자가 아니어야 한다) 오는 경우 즉 qual.sel[Ts](arg1, ..., argN)을 qual.applyDynamic[Ts]("sel")(arg1, ..., argN)으로 재작성한다.

//test1.scala
import scala.language.dynamics

object Example extends Dynamic {
  def applyDynamic(methodName: String)(args: Any*) {
    println("methodName: " + methodName)
    println("args: "+ args.mkString(","))
  }
}

Example.notExistMethod("outsider", "scala", 2013)

위와 같이 작성한 코드를 실행하면 다음과 같이 실행된다.

$ scala test1.scala
methodName: notExistMethod
args: outsider,scala,2013

Example 객체에 notExistMethod라는 메서드가 존재하지 않지만 오류없이 동작하는 것을 볼 수 있다. 여기서는 받은 메서드이름과 인자를 차례대로 출력했다. 이를 이용해서 동적타입의 메서드처럼 사용할 수 있다.


applyDynamicNamed

qual.sel에 뒤에 타입 인자리스트 [TS](선택적이다)와 named 인자리스트 (x1 = arg1, ..., xN = argN)이(일부는 named 인자가 아니더라도) 오는 경우 즉, qual.sel[Ts](x1 = arg1, …, xN = argN)를 qual.applyDynamicNamed[Ts]("sel")(xs1 -> arg1, …, xsN -> argN)으로 재작성 한다.

//test2.scala
import scala.language.dynamics

object Example extends Dynamic {
  def applyDynamicNamed(methodName: String)(args: (String, Any)*) {
    println("methodName: " + methodName)
    println("args: ")
    for (i <- args) yield println("  " + i._1 + " : " + i._2)
  }
}

Example.notExistMethod(nickname = "Outsider", city = "Seoul")

위의 코드를 다음과 같이 실행한다.

$ scala test2.scala
methodName: notExistMethod
args:
  nickname : Outsider
  city : Seoul

앞의 메서드와 비슷하지만 이번에는 인자가 named 인자이다. 이러한 경우 앞에서와 동일하게 메서드 이름을 출력하고 튜플로 받은 인자를 차례대로 출력했다.


selectDynamic

qual.sel뒤에 인자나 할당 오퍼레이터가 없는 경우 즉, qual.sel을 qual.selectDynamic[Ts]("sel")로 재작성한다.

//test3.scala
import scala.language.dynamics

object Example extends Dynamic {
  def selectDynamic(field: String): String = "I have a " + field + " member!"
}

println(Example.notExist)
println(Example.outsider)
println(Example.nephilim)

메서드가 아닌 존재하지 않은 멤버변수를 호출한 경우이다. 이 경우 위처럼 selectDynamic이 적용되고 다음과 같이 출력된다.

$ scala test3.scala
I have a notExist member!
I have a outsider member!
I have a nephilim member!


updateDynamic

qual.sel이 할당의 좌변에 오는 경우 즉, qual.sel = expr를 qual.updateDynamic(“sel”)(expr)로 재작성한다.

$ test4.scala 
import scala.language.dynamics
import scala.collection.mutable

object Example extends Dynamic {
  private var map = mutable.Map[String, Any]()

  def selectDynamic(key: String) = map(key)
  def updateDynamic(key: String)(value: Any) { map(key) = value }
}

Example.a = "scala"
println(Example.a)

Example.b = "hello"
println(Example.b)

이번에는 동적 멤버에 할당을 하기 우해서 updateDynamic을 사용했고 받은 값을 map에 저장한 뒤에 해당 값을 출력하기 위해서 selectDynamic을 정의했다.

$ scala test4.scala
Example.a: Any = scala
scala
Example.b: Any = hello
hello

a, b가 존재하지 않는 필드이지만 동적으로 할당하고 가져올 수 있다.
2013/02/09 20:28 2013/02/09 20:28