Monday, February 8, 2010

Dynamic calls using Structural Types

Using reflection can be a real pain in Java since the API is a Java API and consists of many gets and searches through collections not to mention so many exceptions that need to be handled. In Scala there is a wonderful way to clean up a reflective call down to a single line (assuming you don't want to worry about handling exceptions.) Here structural typing can really be a pleasure.
  1. // I am assigning a string to an Any reference
  2. scala> val s:Any = "hello :D"
  3. s: Any = hello :D
  4. // Any does not have a length method
  5. scala> s.length
  6. < console>:6: error: value length is not a member of Any
  7.        s.length
  8.          ^
  9. /*
  10. But I can cast it to a structural type with a length method
  11. */
  12. scala> s.asInstanceOf[{def length:Int}].length
  13. res2: Int = 8

There are restrictions to this. For example implicits will not work:
  1. /*
  2. The method r is part of StringLike (or RichString in Scala 2.7) 
  3. and there is an implicit conversion from String to RichString/StringLike.
  4. The structural type does not try to apply the implicits because implicits are a
  5. compile time artifact and that information is not kept at run time.  Perhaps this
  6. will be added in the future but it is not present now.
  7. */
  8. scala> s.asInstanceOf[{def r:util.matching.Regex}].r
  9. java.lang.NoSuchMethodException: java.lang.String.r()

More examples:
  1. scala> val s:Any = "hello :D"
  2. s: Any = hello :D
  3. scala> s.asInstanceOf[{def charAt(x:Int):Char}].charAt(2)
  4. res9: Char = l
  5. /*
  6. This is interesting.  The actual method returns Char, but Char is
  7. compatible with Any so this cast works.
  8. */
  9. scala> s.asInstanceOf[{def charAt(x:Int):Any}].charAt(2) 
  10. res10: Any = l
  11. // Now lets test to see if that lenience is available for parameters:
  12. scala> class X 
  13. defined class X
  14. scala> class Y extends X
  15. defined class Y
  16. scala> class T {
  17.      | def x(x:X):Int = 1
  18.      | }
  19. defined class T
  20. scala> val a:Any = new T()
  21. a: Any = T@687c3b99
  22. scala> a.asInstanceOf[{def x(x:X):Any}].x(new Y())
  23. res11: Any = 1
  24. /*
  25. Ok so return values can be subclasses but not params
  26. */
  27. scala> a.asInstanceOf[{def x(x:Y):Any}].x(new Y())
  28. java.lang.NoSuchMethodException: T.x(Y)
  29. at java.lang.Class.getMethod(Class.ja

Use with care :-D

1 comment:

  1. This is neat. I had not realized one could do this!

    ReplyDelete