Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does type Dynamic work and how to use it?

Tags:

scala

I heard that with Dynamic it is somehow possible to do dynamic typing in Scala. But I can't imagine how that might look like or how it works.

I found out that one can inherit from trait Dynamic

class DynImpl extends Dynamic 

The API says that one can use it like this:

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

But when I try it out it doesn't work:

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

This is completely logical, because after looking to the sources, it turned out that this trait is completely empty. There is no method applyDynamic defined and I can't imagine how to implement it by myself.

Can someone show me what I need to do to make it to work?

like image 247
kiritsuku Avatar asked Apr 03 '13 23:04

kiritsuku


People also ask

How do dynamically typed languages work?

In Dynamically typed languages, variables are bound to objects at run-time by means of assignment statements, and it is possible to bind the same variables to objects of different types during the execution of the program. Dynamic type checking typically results in less optimized code than static type checking.

What is a dynamic typing?

In Dynamic Typing, type checking is performed at runtime. For example, Python is a dynamically typed language. It means that the type of a variable is allowed to change over its lifetime. Other dynamically typed languages are -Perl, Ruby, PHP, Javascript etc.

Where can you use the dynamic data type?

In C# 4.0, a new type is introduced that is known as a dynamic type. It is used to avoid the compile-time type checking. The compiler does not check the type of the dynamic type variable at compile time, instead of this, the compiler gets the type at the run time.

What is the use of dynamic type in C#?

C# 4 introduces a new type, dynamic . The type is a static type, but an object of type dynamic bypasses static type checking. In most cases, it functions like it has type object . At compile time, an element that is typed as dynamic is assumed to support any operation.


1 Answers

Scalas type Dynamic allows you to call methods on objects that don't exist or in other words it is a replica of "method missing" in dynamic languages.

It is correct, scala.Dynamic doesn't have any members, it is just a marker interface - the concrete implementation is filled-in by the compiler. As for Scalas String Interpolation feature there are well defined rules describing the generated implementation. In fact, one can implement four different methods:

  • selectDynamic - allows to write field accessors: foo.bar
  • updateDynamic - allows to write field updates: foo.bar = 0
  • applyDynamic - allows to call methods with arguments: foo.bar(0)
  • applyDynamicNamed - allows to call methods with named arguments: foo.bar(f = 0)

To use one of these methods it is enough to write a class that extends Dynamic and to implement the methods there:

class DynImpl extends Dynamic {   // method implementations here } 

Furthermore one need to add a

import scala.language.dynamics 

or set the compiler option -language:dynamics because the feature is hidden by default.

selectDynamic

selectDynamic is the easiest one to implement. The compiler translates a call of foo.bar to foo.selectDynamic("bar"), thus it is required that this method has an argument list expecting a 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 

As one can see, it is also possible to call the dynamic methods explicitly.

updateDynamic

Because updateDynamic is used to update a value this method needs to return Unit. Furthermore, the name of the field to update and its value are passed to different argument lists by the compiler:

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 

The code works as expected - it is possible to add methods at runtime to the code. On the other side, the code isn't typesafe anymore and if a method is called that doesn't exist this must be handled at runtime as well. In addition this code is not as useful as in dynamic languages because it is not possible to create the methods that should be called at runtime. This means that we can't do something like

val name = "foo" d.$name 

where d.$name would be transformed to d.foo at runtime. But this is not that bad because even in dynamic languages this is a dangerous feature.

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:

  • The official SIP proposal that introduced Dynamic into Scala
  • Practical uses of a Dynamic type in Scala - another question on SO (but very outdated)
like image 140
kiritsuku Avatar answered Oct 21 '22 08:10

kiritsuku