2012-03-20 1 views
1

J'ai le code suivant définissant une classe de type.Typeclasses et sous-types

trait Foo[T] { 
    def toFoo(x: T): String 
} 

trait Foos { 
    def toFoo[T](f: T => String): Foo[T] = new Foo[T] { 
    def toFoo(x: T): String = f(x) 
    } 
} 

object Foo extends Foos { 
    def toFoo[A: Foo](a: A) = implicitly[Foo[A]].toFoo(a) 
    implicit def AToFoo: Foo[A] = toFoo { c => 
    "A" 
    } 
    implicit def BToFoo[T]: Foo[B] = toFoo { c => 
    "B" 
    } 

    implicit def ListToFoo[T: Foo]: Foo[List[T]] = toFoo { c => 
    c.map(toFoo(_)). 
    } 
} 

class A 
class B extends A 

Maintenant, si je fais si je toFoo(List(new A, new B) je reçois List("A", "A") au lieu de List("A", "B"). Comment puis-je m'assurer que la méthode BtoFoo est utilisée plutôt que AToFoo pour les classes de type B?

+0

Quel type attendez-vous de List (new A, new B) '? –

+0

Je m'attends à 'List (new A, new B)' avoir le type 'List [A]' – Stephan

+0

dans ce cas, quand vous mappez 'toFoo' sur la' List [A] ', il commence apparemment implicitement par [Foo [A]] 'et obtient la définition implicite de' AToFoo', et l'utilise pour chaque élément. Les classes de type sont conçues pour une diffusion orientée type, et non pour une diffusion orientée valeur, de sorte qu'elles ne jouent pas toujours bien avec le sous-typage. –

Répondre

2

La résolution implicite est un mécanisme de compilation pure. Le moyen facile de faire la distinction entre les sous-types ici est de faire correspondre à l'intérieur de l'implicite. Retirer BToFoo tout à fait et ont plutôt l'accord version A avec les deux cas

implicit val AIsFoo : Foo[A] = toFoo { 
    case b: B => "B" 
    case a: A => "A" 
} 

Bien sûr, il est également possible de déléguer à une méthode pour une partie de la hiérarchie. Vous pouvez déléguer à une méthode dans A, overidde dans B, et toujours avoir le type de classe de travail pour la liste, où vous ne pouvez pas ajouter une méthode.

Vous pouvez également envisager de déclarer Foo contravariant.

+0

J'ai implémenté votre suggestion en utilisant la correspondance de modèle mais il ne me semble pas très élégant de devoir vérifier tous les enfants de 'A' dans' AIsFoo' plutôt que d'écrire 'XIsFoo' directement pour tout enfant' X' de A. Comment fonctionne la déclaration de 'Foo' contravariant? J'ai essayé 'trait Foo [-T] { def toFoo (x: T): Chaîne }' – Stephan