4

在 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")
}

另一方面,在某些情况下,使用类型信息进行强制转换并不容易,仅检查 a 的可用性{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")
}

使用隐式证据参数,可以根据其他方法的可用性来定义方法。是否也可以仅在定义它们时调用它们?

4

2 回答 2

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 开始,反射信息在调用站点上逐个类地缓存(提供值的实际类)。

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

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

于 2010-08-10T01:36:14.267 回答