【问题标题】:Is a type-safe respond_to in scala possible?scala中的类型安全respond_to可能吗?
【发布时间】:2010-08-08 13:44:56
【问题描述】:

在 scala 中很容易使用 case 构造进行类型安全的转换。以下代码确保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")
}

使用隐式证据参数,可以根据其他方法的可用性定义方法。是不是也可以在定义的时候才调用?

【问题讨论】:

标签: scala casting case typechecking respond-to


【解决方案1】:

结构类型表示成员的不受继承约束的可用性,因此如果您希望一个方法只接受具有特定方法的值,例如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 开始,反射信息会在调用站点上逐个类地缓存(提供值的实际类)。

【讨论】:

  • 它通过反射实现的事实意味着存在性能损失(通过反射调用方法比常规方法调用慢)。
  • 我认为这是隐含的,这就是我提到它的原因。
【解决方案2】:

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

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 是 trait Numeric 的真正伴侣对象。您可以通过将声明包装在另一个对象中来做到这一点,比如 tmp,然后导入 tmp._。

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

scala> square(2)       
res6: Int = 4

scala> square(4.0)
res7: Double = 16.0

Scala 实际上提供了一个 Numeric 类型类用于数值计算,参见: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 type class”,你应该会看到很多信息。

第 2 部分 - 实际的响应?

如果你真的想在 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 返回并引发异常。您会看到,使用动态代理会将错误转移到运行时,因此您需要非常小心地实例化对象并返回正确的类型。根据系统的复杂性,您还应该担心类加载器。如果您曾经收到 Foo 到 Foo 的 ClassCastException,那么您就知道您正处于类加载器的地狱中。

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

【讨论】:

    猜你喜欢
    • 2018-11-28
    • 2016-06-18
    • 2011-08-14
    • 1970-01-01
    • 2016-02-04
    • 1970-01-01
    • 2015-02-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多