Scalas 유형을 Dynamic
사용하면 존재하지 않는 객체에 대한 메서드를 호출 할 수 있습니다. 즉, 동적 언어에서 "메서드 누락"의 복제본입니다.
정확하고, scala.Dynamic
멤버가 없으며, 마커 인터페이스 일뿐입니다. 구체적인 구현은 컴파일러에 의해 채워집니다. Scalas String Interpolation 기능의 경우 생성 된 구현을 설명하는 잘 정의 된 규칙이 있습니다. 실제로 네 가지 방법을 구현할 수 있습니다.
selectDynamic
-필드 접근자를 작성할 수 있습니다. foo.bar
updateDynamic
-필드 업데이트를 작성할 수 있습니다. foo.bar = 0
applyDynamic
-인수를 사용하여 메서드를 호출 할 수 있습니다. foo.bar(0)
applyDynamicNamed
-명명 된 인수로 메서드를 호출 할 수 있습니다. foo.bar(f = 0)
이러한 메서드 중 하나를 사용하려면 확장하는 클래스를 작성하고 Dynamic
거기에 메서드를 구현하면됩니다.
class DynImpl extends Dynamic {
// method implementations here
}
또한 하나를 추가해야
import scala.language.dynamics
또는 -language:dynamics
기능이 기본적으로 숨겨져 있으므로 컴파일러 옵션을 설정하십시오 .
selectDynamic
selectDynamic
구현하기 가장 쉬운 방법입니다. 컴파일러의 호출 변환 foo.bar
에를 foo.selectDynamic("bar")
따라서이 방법은 예상 인수 목록을 가지고 요구된다 String
:
class DynImpl extends Dynamic {
def selectDynamic(name: String) = name
}
scala> val d = new DynImpl
d: DynImpl = DynImpl@6040af64
scala> d.foo
res37: String = foo
scala> d.bar
res38: String = bar
scala> d.selectDynamic("foo")
res54: String = foo
보시다시피 동적 메서드를 명시 적으로 호출하는 것도 가능합니다.
updateDynamic
updateDynamic
는 값을 업데이트하는 데 사용 되기 때문에이 메서드는 반환해야합니다 Unit
. 또한 업데이트 할 필드의 이름과 해당 값은 컴파일러에 의해 다른 인수 목록으로 전달됩니다.
class DynImpl extends Dynamic {
var map = Map.empty[String, Any]
def selectDynamic(name: String) =
map get name getOrElse sys.error("method not found")
def updateDynamic(name: String)(value: Any) {
map += name -> value
}
}
scala> val d = new DynImpl
d: DynImpl = DynImpl@7711a38f
scala> d.foo
java.lang.RuntimeException: method not found
scala> d.foo = 10
d.foo: Any = 10
scala> d.foo
res56: Any = 10
코드는 예상대로 작동합니다. 런타임에 코드에 메서드를 추가 할 수 있습니다. 반면에 코드는 더 이상 형식이 안전하지 않으며 존재하지 않는 메서드가 호출되면 런타임에도 처리되어야합니다. 또한이 코드는 런타임에 호출되어야하는 메서드를 만들 수 없기 때문에 동적 언어에서만큼 유용하지 않습니다. 이것은 우리가 다음과 같은 것을 할 수 없다는 것을 의미합니다.
val name = "foo"
d.$name
런타임에 d.$name
변환 되는 위치 d.foo
. 그러나 이것은 동적 언어에서도 위험한 기능이기 때문에 그렇게 나쁘지 않습니다.
여기에서 주목해야 할 또 다른 점은,이다 updateDynamic
요구가 함께 구현 될 selectDynamic
. 이렇게하지 않으면 컴파일 오류가 발생합니다.이 규칙은 동일한 이름의 Getter가있는 경우에만 작동하는 Setter의 구현과 유사합니다.
applyDynamic
인수로 메서드를 호출하는 기능은 다음에서 제공합니다 applyDynamic
.
class DynImpl extends Dynamic {
def applyDynamic(name: String)(args: Any*) =
s"method '$name' called with arguments ${args.mkString("'", "', '", "'")}"
}
scala> val d = new DynImpl
d: DynImpl = DynImpl@766bd19d
scala> d.ints(1, 2, 3)
res68: String = method 'ints' called with arguments '1', '2', '3'
scala> d.foo()
res69: String = method 'foo' called with arguments ''
scala> d.foo
<console>:19: error: value selectDynamic is not a member of DynImpl
메서드의 이름과 인수는 다시 다른 매개 변수 목록으로 구분됩니다. 원하는 경우 임의의 수의 인수를 사용하여 임의의 메서드를 호출 할 수 있지만 괄호없이 메서드를 호출하려면를 구현해야합니다 selectDynamic
.
힌트 : 다음과 함께 apply-syntax를 사용할 수도 있습니다 applyDynamic
.
scala> d(5)
res1: String = method 'apply' called with arguments '5'
applyDynamicNamed
마지막으로 사용 가능한 메서드를 사용하면 원하는 경우 인수의 이름을 지정할 수 있습니다.
class DynImpl extends Dynamic {
def applyDynamicNamed(name: String)(args: (String, Any)*) =
s"method '$name' called with arguments ${args.mkString("'", "', '", "'")}"
}
scala> val d = new DynImpl
d: DynImpl = DynImpl@123810d1
scala> d.ints(i1 = 1, i2 = 2, 3)
res73: String = method 'ints' called with arguments '(i1,1)', '(i2,2)', '(,3)'
메소드 서명의 차이이다 applyDynamicNamed
형태에게 기대 튜플 (String, A)
여기서 A
임의의 타입이다.
위의 모든 메서드는 매개 변수를 매개 변수화 할 수 있다는 공통점이 있습니다.
class DynImpl extends Dynamic {
import reflect.runtime.universe._
def applyDynamic[A : TypeTag](name: String)(args: A*): A = name match {
case "sum" if typeOf[A] =:= typeOf[Int] =>
args.asInstanceOf[Seq[Int]].sum.asInstanceOf[A]
case "concat" if typeOf[A] =:= typeOf[String] =>
args.mkString.asInstanceOf[A]
}
}
scala> val d = new DynImpl
d: DynImpl = DynImpl@5d98e533
scala> d.sum(1, 2, 3)
res0: Int = 6
scala> d.concat("a", "b", "c")
res1: String = abc
운 좋게도 암시 적 인수를 추가하는 것도 가능합니다. TypeTag
컨텍스트 바인딩을 추가 하면 인수의 유형을 쉽게 확인할 수 있습니다. 그리고 가장 좋은 점은 캐스트를 추가해야했지만 반환 유형도 정확하다는 것입니다.
그러나 Scala는 그러한 결함을 해결할 방법이 없을 때 Scala가 아닙니다. 우리의 경우 형 클래스를 사용하여 캐스트를 피할 수 있습니다.
object DynTypes {
sealed abstract class DynType[A] {
def exec(as: A*): A
}
implicit object SumType extends DynType[Int] {
def exec(as: Int*): Int = as.sum
}
implicit object ConcatType extends DynType[String] {
def exec(as: String*): String = as.mkString
}
}
class DynImpl extends Dynamic {
import reflect.runtime.universe._
import DynTypes._
def applyDynamic[A : TypeTag : DynType](name: String)(args: A*): A = name match {
case "sum" if typeOf[A] =:= typeOf[Int] =>
implicitly[DynType[A]].exec(args: _*)
case "concat" if typeOf[A] =:= typeOf[String] =>
implicitly[DynType[A]].exec(args: _*)
}
}
구현이 그다지 좋아 보이지는 않지만 그 힘은 의심 할 수 없습니다.
scala> val d = new DynImpl
d: DynImpl = DynImpl@24a519a2
scala> d.sum(1, 2, 3)
res89: Int = 6
scala> d.concat("a", "b", "c")
res90: String = abc
무엇보다도 Dynamic
매크로와 결합 하는 것도 가능 합니다.
class DynImpl extends Dynamic {
import language.experimental.macros
def applyDynamic[A](name: String)(args: A*): A = macro DynImpl.applyDynamic[A]
}
object DynImpl {
import reflect.macros.Context
import DynTypes._
def applyDynamic[A : c.WeakTypeTag](c: Context)(name: c.Expr[String])(args: c.Expr[A]*) = {
import c.universe._
val Literal(Constant(defName: String)) = name.tree
val res = defName match {
case "sum" if weakTypeOf[A] =:= weakTypeOf[Int] =>
val seq = args map(_.tree) map { case Literal(Constant(c: Int)) => c }
implicitly[DynType[Int]].exec(seq: _*)
case "concat" if weakTypeOf[A] =:= weakTypeOf[String] =>
val seq = args map(_.tree) map { case Literal(Constant(c: String)) => c }
implicitly[DynType[String]].exec(seq: _*)
case _ =>
val seq = args map(_.tree) map { case Literal(Constant(c)) => c }
c.abort(c.enclosingPosition, s"method '$defName' with args ${seq.mkString("'", "', '", "'")} doesn't exist")
}
c.Expr(Literal(Constant(res)))
}
}
scala> val d = new DynImpl
d: DynImpl = DynImpl@c487600
scala> d.sum(1, 2, 3)
res0: Int = 6
scala> d.concat("a", "b", "c")
res1: String = abc
scala> d.noexist("a", "b", "c")
<console>:11: error: method 'noexist' with args 'a', 'b', 'c' doesn't exist
d.noexist("a", "b", "c")
^
매크로는 모든 컴파일 시간 보장을 제공하며 위의 경우에는 유용하지 않지만 일부 Scala DSL에는 매우 유용 할 수 있습니다.
더 많은 정보를 얻으려면 Dynamic
더 많은 리소스가 있습니다.