scala中的类型安全的respond_to可能吗?

时间:2010-08-08 13:44:57

标签: scala casting case typechecking respond-to

使用案例构造进行类型安全的转换很容易在scala中完成。以下代码确保仅在具有相应类型的对象上调用square

class O
class A extends O {
    def square(i: Int):Int = { i*i }
}
class B extends O {
    def square(d: Double):Double = { d*d }
}
class C extends O {}

def square(o: O) = o match {
    case a:A => print(a.square(3))
    case b:B => print(b.square(3.0))
    case c:C => print(9)
    case _ => print("9")
}

另一方面,有些情况下,使用类型信息进行投射并不容易,只需检查{def square(Int): Int}的可用性就足够了。 scala中是否有一个允许执行类似于

的构造
def square(o: O) = o match {
    case a:{def square(Int):Int} => print(a.square(3))
    case b:{def square(Double):Double} => print(b.square(3.0))
    case _ => print("9")
}

使用隐式证据参数,可以定义方法,具体取决于其他方法的可用性。是否也可以在定义它们时调用它们?

2 个答案:

答案 0 :(得分:5)

结构类型表示成员的非继承约束可用性,因此如果您希望方法只接受带有特定方法的值,例如def square(i: Int): Int,则使用此表示法:

class Squaring {
  type Squarable = { def square(i: Int): Int }
  def squareMe(s: Squarable): Int = s.square(17)
}

class CanSquare { def square(i: Int) = i * i }

val cs1 = new CanSquare
val s1 = new Squaring

printf("s1.squareMe(cs1)=%d%n", s1.squareMe(cs1))


s1.squareMe(cs1)=289

您应该知道结构类型是通过反射实现的,但是从Scala 2.8开始,反射信息在逐个类的基础上缓存在调用站点上(实际提供的值类)。

答案 1 :(得分:1)

似乎类型类似乎是将操作应用于许多不同类型的标准。它不是在运行时查找方法(嗯,它可以,但不是纯模式),但它可以提供你想要的。

trait Numeric[T] {
  def times(x :T, y : T) : T
}

object Numeric {
  implicit val doubleNumeric = new Numeric[Double] {
    def times(x : Double, y : Double) = x*y
  }
  implicit val intNumeric = new Numeric[Int] {
    def times(x : Int, y : Int) = x*y
  }
}

def square[A : Numeric](x : A) = implicitly[Numeric[A]].times(x,x)

如果您在scala REPL中执行此操作,请确保对象Numeric是一个真正的伴随对象以特征Numeric。您可以通过将声明包装在另一个对象(例如tmp)中,然后导入tmp ._。

来完成此操作

接下来,只需使用不同的值调用square:

scala> square(2)       
res6: Int = 4

scala> square(4.0)
res7: Double = 16.0

Scala实际上提供了一个用于数值计算的数字类型,请参阅:http://www.scala-lang.org/api/current/scala/math/Numeric.html

我还写了一篇关于Scala中类型类模式的文章,以及使用它来调整多个API或在此处执行多个调度的方法: http://suereth.blogspot.com/2010/07/monkey-patching-duck-typing-and-type.html

如果你谷歌搜索“scala类型”,你应该会看到很多信息。

第2部分 - 实际的respond_to?

如果你真的想在scala中使用respond_to,那你就有点SOL了。这是因为respond_to确实是一个动态的概念。您正在定义一个类的方法,如果您尝试在不存在的类上调用方法,则该方法将被调用。 Scala不像一些动态JVM语言那样抽象方法调用。这意味着没有钩子进入方法调用来拦截和交互。你可以做的最好的是一种接口适配,或者某种面向方面的后编译钩子,为你重写字节码。

我们可以使用一个神奇的部分,它也用于某些AOP框架:动态代理。

scala> object AllPowerfulProxy extends InvocationHandler {                      
     | def invoke(proxy : AnyRef, m : Method, args : Array[AnyRef]) : AnyRef = {
     | println(" You really want to call " + m.getName + "?")
     | null // Maliciously Evil!
     | }
     | }
defined module AllPowerfulProxy

scala> def spawn[A : Manifest] : A = {
     |   val mf = implicitly[Manifest[A]]        
     |   java.lang.reflect.Proxy.newProxyInstance(mf.erasure.getClassLoader,
     |                                            Array(mf.erasure),
     |                                            AllPowerfulProxy).asInstanceOf[A]
     | }
spawn: [A](implicit evidence$1: Manifest[A])A

现在我们可以使用它将对象生成到任何接口。让我们看看我们能做些什么:

scala> val x = spawn[TestInterface]
 You really want to call toString?
java.lang.NullPointerException
    at scala.runtime.ScalaRunTime$.stringOf(ScalaRunTime.scala:259)

你看我们在那里做了什么?当REPL尝试在表达式的结果上调用toString时,它会在我们的动态代理上调用它。由于代理是一个占位符,实际的调用被委托给我们的AllPowerfulProxy类,如何打印消息:“你真的想调用toString吗?”。然后REPL命中null返回并抛出异常。您会看到,使用动态代理将错误移动到运行时,因此您需要非常小心地实例化对象并返回正确的类型。根据系统的复杂程度,您还应该担心classLoaders。如果你得到Foo的ClassCastException到Foo,那么你知道你在类加载器地狱。

在任何情况下,如果您对动态代理有其他问题,请随时提出。在静态类型语言中,您可能最好使用类型类并使用它们迁移到设计模式而不是使用respond_to。 (对于使用类型类和类型系统可以完成的任务,你会感到惊讶。)