2010-08-08 19 views
4

L'utilisation d'une construction de cas pour le moulage de type sécurisé se fait facilement en scala. Le code suivant garantit que square est appelée uniquement sur les objets qui ont un type correspondant.Est-ce qu'un type_ safe répond_to dans scala possible?

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

D'autre part, il y a des cas où il est pas aussi facile d'utiliser les informations de type pour la coulée et juste pour vérifier la disponibilité d'un {def square(Int): Int} serait suffisant. Y at-il une construction en scala qui permet de faire quelque chose de similaire à

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

En utilisant les paramètres de preuve implicites, il est possible de définir méthodes en fonction de la disponibilité d'autres méthodes. Est-il également possible de les appeler seulement quand ils sont définis?

+1

Vérifiez http://stackoverflow.com/questions/1988181/pattern-matching-structural-types-in-scala pour une question connexe –

+0

Intéressant, merci. Mais peut-être y a-t-il des avancées qui le rendent plus facile à utiliser maintenant? – Debilski

Répondre

5

frappe structurale exprime la disponibilité non héritage limitées des membres, donc si vous voulez une méthode pour accepter uniquement les valeurs qui portent une méthode particulière, disons def square(i: Int): Int, vous utilisez cette notation:

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 

Vous devez savoir cette typologie structurelle est implémentée via la réflexion, mais à partir de Scala 2.8, les informations de réflexion sont mises en cache sur le site d'invocation, classe par classe (classes réelles de valeurs fournies).

+0

Le fait qu'il soit implémenté via la réflexion signifie qu'il y a une pénalité de performance (les méthodes d'appel par réflexion sont plus lentes que les appels de méthode classiques). – Jesper

+0

Je pensais que c'était implicite et c'est pourquoi je l'ai mentionné. –

1

Il semble que les classes-types semblent être la norme pour appliquer une opération à de nombreux types différents. Il ne cherche pas les méthodes au moment de l'exécution (bien, il peut, mais pas le modèle pur), mais il pourrait fournir ce que vous voulez.

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) 

Si vous faites cela dans le REPL scala, assurez-vous que l'objet numérique est un véritable objet compagnon à trait numérique. Vous pouvez le faire en enveloppant la déclaration dans un autre objet, par exemple tmp, puis en important tmp._.

Ensuite, simplement appeler carré avec des valeurs différentes:

scala> square(2)  
res6: Int = 4 

scala> square(4.0) 
res7: Double = 16.0 

Scala fournit en fait une classe de type numérique pour une utilisation dans les calculs numériques, voir: http://www.scala-lang.org/api/current/scala/math/Numeric.html

j'ai écrit aussi un article sur le modèle de classe de type dans Scala et les méthodes de l'utiliser pour adapter plusieurs API ou faire plusieurs envois ici: http://suereth.blogspot.com/2010/07/monkey-patching-duck-typing-and-type.html

Si vous google pour "classe de type scala", vous devriez voir beaucoup d'inf ormation.

Partie 2 - Une réponse réelle à?

Si vous voulez vraiment un respond_to dans scala, vous êtes plutôt SOL. C'est parce que respond_to est vraiment un concept dynamique. Vous définissez une méthode sur une classe qui sera appelée si vous essayez d'appeler une méthode sur la classe qui n'existe pas. Scala ne fait pas abstraction des appels de méthode comme le font certaines langages JVM dynamiques. Cela signifie qu'il n'y a pas de hooks dans les appels de méthodes pour intercepter et interagir avec. Le mieux que vous puissiez faire est une forme d'adaptation d'interface, ou une sorte de hook post-compilation orienté aspect pour réécrire le bytecode pour vous. Utilisez peut utiliser peut également être utilisé dans certains cadres AOP: Le proxy dynamique.

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 

Maintenant, nous pouvons l'utiliser pour générer des objets sur n'importe quelle interface. Voyons voir ce que nous pouvons faire:

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

Vous voyez ce que nous avons fait là-bas? Lorsque le REPL essaie d'appeler toString sur le résultat d'une expression, il l'appelle sur notre proxy dynamique. Puisque le proxy est un espace réservé, l'appel réel est délégué à notre classe AllPowerfulProxy, comment affiche le message: "Vous voulez vraiment appeler toString?". Le REPL frappe ensuite le retour null et déclenche une exception. Vous voyez, l'utilisation de proxies dynamiques déplace les erreurs vers l'exécution, vous devez donc faire très attention lors de l'instanciation des objets et du retour des types corrects. En fonction de la complexité de votre système, vous devriez également vous préoccuper de classLoaders. Si jamais vous obtenez une ClassCastException de Foo à Foo, alors vous savez que vous êtes dans l'enfer du chargeur de classe.

Dans tous les cas, si vous avez d'autres questions sur les proxies dynamiques, n'hésitez pas à demander. Dans les langages à typage statique, il est probablement préférable d'utiliser des classes de type et de migrer vers des modèles de conception en les utilisant au lieu de ceux utilisant respond_to. (Vous serez surpris de ce que vous pouvez accomplir avec les classes de types et le système de types).