Type Dynamic은 어떻게 작동하며 어떻게 사용합니까?


95

DynamicScala에서 어떻게 든 동적 타이핑을 할 수 있다고 들었습니다 . 그러나 나는 그것이 어떻게 생겼는지 또는 어떻게 작동하는지 상상할 수 없습니다.

나는 하나가 특성에서 물려받을 수 있음을 알았습니다. Dynamic

class DynImpl extends Dynamic

API는 하나 같이 사용할 수 있다고 말한다 :

foo.method ( "blah") ~~> foo.applyDynamic ( "method") ( "blah")

그러나 내가 그것을 시도하면 작동하지 않습니다.

scala> (new DynImpl).method("blah")
<console>:17: error: value applyDynamic is not a member of DynImpl
error after rewriting to new DynImpl().<applyDynamic: error>("method")
possible cause: maybe a wrong Dynamic method signature?
              (new DynImpl).method("blah")
               ^

소스를 살펴본 후이 특성이 완전히 비어 있음이 밝혀 졌기 때문에 이것은 완전히 논리적 입니다. applyDynamic정의 된 방법이 없으며 직접 구현하는 방법을 상상할 수 없습니다.

누군가가 내가 작동하도록하기 위해해야 ​​할 일을 보여줄 수 있습니까?

답변:


188

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더 많은 리소스가 있습니다.


1
확실히 훌륭한 답변과 Scala Power의 쇼케이스
Herrington Darkholme 2014 년

기능이 기본적으로 숨겨져있는 경우, 예를 들어 실험적이거나 다른 사람과 잘 작동하지 않을 수있는 경우 파워 라고 부르지 않습니까?
matanster 2015

Scala Dynamic의 성능에 대한 정보가 있습니까? 나는 Scala Reflection이 느리다는 것을 안다 (따라서 Scala-macro가 온다). Scala Dynamic을 사용하면 성능이 크게 저하됩니까?
windweller 2015-08-24

1
@AllenNie 내 대답에서 볼 수 있듯이 구현하는 방법에는 여러 가지가 있습니다. 매크로를 사용하면 컴파일 타임에 동적 호출이 해결되므로 더 이상 오버 헤드가 없습니다. 런타임에 do 검사를 사용하는 경우 올바른 코드 경로로 올바르게 디스패치하기 위해 매개 변수 검사를 수행해야합니다. 애플리케이션의 다른 매개 변수 검사보다 오버 헤드가 많아서는 안됩니다. 리플렉션을 사용하면 분명히 더 많은 오버 헤드가 발생하지만 애플리케이션 속도가 얼마나 느려지는지 직접 측정해야합니다.
kiritsuku

1
이 내 마음 불고있다 - "매크로는 우리가 모든 컴파일 시간 보증을 돌려 줄"
tksfz
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.