Возможно ли безопасное с типом response_to в scala?
Использование конструкции case для безопасного приведения типов легко выполняется в 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 ответа
Структурная типизация выражает доступность элементов, не связанных с наследованием, поэтому, если вы хотите, чтобы метод принимал только значения, несущие определенный метод, скажем, 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 информация об отражении кэшируется на сайте вызова для каждого класса в отдельности (фактические классы предоставленных значений).
Кажется, что классы типов являются стандартом применения операции ко многим различным типам. Это не поиск методов во время выполнения (ну, может, но не в чистом виде), но это может обеспечить то, что вы хотите.
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._.
Далее просто назовите квадрат с разными значениями:
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 - фактический response_to?
Если вы действительно хотели ответить в scala, вы в некотором роде SOL. Это потому, что response_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 тогда достигает нулевого возврата и выдает исключение. Видите ли, использование динамических прокси перемещает ошибки во время выполнения, поэтому вы должны быть очень осторожны с созданием экземпляров объектов и возвращением правильных типов. В зависимости от сложности вашей системы вы должны также беспокоиться о загрузчиках классов. Если вы когда-нибудь получите ClassCastException от Foo для Foo, то вы знаете, что находитесь в аду загрузчика классов.
В любом случае, если у вас есть другие вопросы о динамических прокси, не стесняйтесь задавать. В статически типизированных языках вам, вероятно, лучше использовать классы типов и переходить к разработке шаблонов, используя их вместо тех, которые используют response_to. (Вы будете удивлены, что вы можете сделать с помощью классов типов и системы типов).