code

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

codestyles 2020. 9. 3. 19:20
반응형

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


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정의 된 방법이 없으며 직접 구현하는 방법을 상상할 수 없습니다.

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


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. 그러나 이것은 동적 언어에서도 위험한 기능이기 때문에 그렇게 나쁘지 않습니다.

Another thing to note here, is that updateDynamic needs to be implemented together with selectDynamic. If we don't do this we will get a compile error - this rule is similar to the implementation of a Setter, which only works if there is a Getter with the same name.

applyDynamic

The ability to call methods with arguments is provided by 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

The name of the method and its arguments again are separated to different parameter lists. We can call arbitrary methods with an arbitrary number of arguments if we want but if we want to call a method without any parentheses we need to implement selectDynamic.

Hint: It is also possible to use apply-syntax with applyDynamic:

scala> d(5)
res1: String = method 'apply' called with arguments '5'

applyDynamicNamed

The last available method allows us to name our arguments if we want:

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)'

The difference in the method signature is that applyDynamicNamed expects tuples of the form (String, A) where A is an arbitrary type.


All of the above methods have in common that their parameters can be parameterized:

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

Luckily, it is also possible to add implicit arguments - if we add a TypeTag context bound we can easily check the types of the arguments. And the best thing is that even the return type is correct - even though we had to add some casts.

But Scala would not be Scala when there is no way to find a way around such flaws. In our case we can use type classes to avoid the casts:

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: _*)
  }

}

While the implementation doesn't look that nice, its power can't be questioned:

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

At the top of all, it is also possible to combine Dynamic with macros:

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")
                       ^

Macros give us back all compile time guarantees and while it is not that useful in the above case, maybe it can be very useful for some Scala DSLs.

If you want to get even more information about Dynamic there are some more resources:

참고URL : https://stackoverflow.com/questions/15799811/how-does-type-dynamic-work-and-how-to-use-it

반응형