使用案例构造进行类型安全的转换很容易在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")
}
使用隐式证据参数,可以定义方法,具体取决于其他方法的可用性。是否也可以在定义它们时调用它们?
答案 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。 (对于使用类型类和类型系统可以完成的任务,你会感到惊讶。)