2012-03-14 2 views
10

Supposons que je veux écrire une classe de cas Stepper comme suit:classe de cas et Linéarisation des traits

case class Stepper(step: Int) {def apply(x: Int) = x + step} 

Il est livré avec une belle mise en œuvre toString:

scala> Stepper(42).toString 
res0: String = Stepper(42) 

mais ce n'est pas vraiment une fonction :

scala> Some(2) map Stepper(2) 
<console>:10: error: type mismatch; 
found : Stepper 
required: Int => ? 
       Some(2) map Stepper(2) 

Une solution de contournement consiste à implémenter le Function trait ...

case class Stepper(step: Int) extends (Int => Int) {def apply(x: Int) = x + step} 

Mais alors, je ne peux pas avoir gratuitement une belle mise en œuvre toString plus:

scala> Stepper(42).toString 
res2: java.lang.String = <function1> 

Ensuite, la question est la suivante: je peux avoir le meilleur de ces deux mondes ? Y at-il une solution où j'ai la belle toString mise en œuvre gratuitement et une mise en œuvre de trait Function. En d'autres termes, existe-t-il un moyen d'appliquer la linéarisation de telle sorte que le sucre syntaxique soit enfin appliqué?

Répondre

8

La question n'est pas vraiment liée à la linéarisation. Dans les classes de cas toString est une méthode générée automatiquement par le compilateur si et seulement si Any.toString n'est pas écrasé dans le type de fin.

Cependant, la réponse est en partie à voir avec Linéarisation - nous devons passer outre Function1.toString avec la méthode qui aurait été généré par le compilateur sinon pour la version introduite par Function1:

trait ProperName extends Product { 
    override lazy val toString = scala.runtime.ScalaRunTime._toString(this) 
} 

// now just mix in ProperName and... magic! 
case class Stepper(step: Int) extends (Int => Int) with ProperName { 
    def apply(x:Int) = x+step 
} 

Puis

println(Some(2) map Stepper(2)) 
println(Stepper(2)) 

produira

Some(4) 
Stepper(2) 

Mise à jour

Voici une version de ProperName trait qui ne repose pas sur la méthode API non documentée:

trait ProperName extends Product { 
    override lazy val toString = { 
    val caseFields = { 
     val arity = productArity 
     def fields(from: Int): List[Any] = 
     if (from == arity) List() 
     else productElement(from) :: fields(from + 1) 
     fields(0) 
    } 
    caseFields.mkString(productPrefix + "(", ",", ")") 
    } 
} 

Alternative toString mise en œuvre est dérivé du code source pour le _toString d'origine méthode scala.runtime.ScalaRunTime._toString.

Veuillez noter que cette implémentation alternative est toujours basée sur l'hypothèse qu'une classe de cas étend toujours le trait Product. Bien que ce dernier soit vrai à partir de Scala 2.9.0 et est un fait connu et sur lequel s'appuient certains membres de la communauté Scala, il n'est pas formellement documenté dans le cadre de Scala Language Spec.

+0

Oui, ce n'est pas vraiment la linéarisation, mais je n'ai trouvé aucun autre nom approprié pour cela. Et c'était le genre de truc auquel je m'attendais, merci. – Nicolas

+0

@Nicolas Je vous comprends très bien, je me trouve souvent que c'est souvent difficile de décrire un problème exactement quand je ne suis pas sûr de ce qui se passe. –

+0

@Vlad: J'ai toujours évité d'utiliser quoi que ce soit de 'scala.runtime' qui n'apparaisse pas dans les docs de l'API Scala. Je suis d'accord que c'est une solution de contournement astucieuse, mais pensez-vous vraiment que cela en vaut la peine, étant donné qu'il existe des solutions tout aussi bonnes qui utilisent de vieilles fonctionnalités de langage Scala? –

2

EDIT: Qu'en est-il de substituer toString?

case class Stepper(step: Int) extends (Int => Int) { 
    def apply(x: Int) = x + step 
    override def toString = "Stepper(" + step + ")" 
} 
+0

Oui, je le sais. mais je ne veux vraiment pas ajouter un s'appliquent (imaginez-le dans un DSL). – Nicolas

+0

Oh, d'accord. Celui-ci, ça va? –

+0

@TalPressman vous avez oublié d'étendre Fonction1 –

1

Vous pouvez utiliser une conversion implicite d'avoir Stepper traité comme une fonction si nécessaire:

case class Stepper(step: Int) { def apply(x: Int) = x + step } 

implicit def s2f(s: Stepper) = new Function[Int, Int] { 
    def apply(x: Int) = s.apply(x) 
} 

Maintenant, vous obtenez toString de la classe de cas lorsque vous appelez Stepper(42).toString, mais Some(2) map Stepper(2) fonctionne aussi comme on le souhaite.

(Notez que j'ai été plus bavard que nécessaire ci-dessus pour garder la mécanique claire.Vous pouvez également écrire implicit def s2f(s: Stepper) = s.apply _ ou un certain nombre d'autres formulations plus concises).

+0

Oui, mais construire cette implicite est plus douloureux que de réécrire toString;) – Nicolas

+0

@Nicolas: Vraiment? C'est 40 caractères. Cela semble beaucoup moins "pénible" que d'utiliser un bit non documenté de l'API d'exécution. –

+0

Ce n'est pas une question de nombre de caractères, c'est une question d'ajouter du code "non-sec" dans une classe. La solution 'ScalaRuntime' n'est pas parfaite, elle n'est clairement pas prête pour la production mais elle va dans une direction DRY. BTW, je l'ai d'abord comparé à réécrire toString (voir la réponse de Tal Pressman) – Nicolas

Questions connexes