2012-03-20 8 views
1

У меня есть следующий код, определяющий класс типа.Typeclasses и подтипирование

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 

Теперь, если у меня есть, если я toFoo(List(new A, new B) я получаю List("A", "A") вместо List("A", "B"). Как я могу убедиться, что используется метод BtoFoo, а не AToFoo для классов с типом B?

+0

Какой тип вы ожидаете? Список (новый A, новый B) 'есть? –

+0

Я ожидаю, что 'List (new A, new B)' будет иметь тип 'List [A]' – Stephan

+0

в этом случае, когда вы сопоставляете 'toFoo' над' List [A] ', он, по-видимому, сначала выполняет' неявно [Foo [A]] 'и получает' AToFoo' неявный def и использует его для каждого элемента. Typeclasses предназначены для отправки по типу, а не для ориентированной на стоимость отправки, поэтому они не всегда хорошо играют с подтипированием. –

ответ

2

неявное разрешение - это чисто механизм компиляции. Легкий способ отличить подтипы здесь - это совпадение внутри неявного. Удалить BToFoo вообще и иметь вместо этого A версии сделки с обоих случаях

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

Конечно, это также можно делегировать к способу части иерархии. Вы можете делегировать метод в A, overidde в B и все еще иметь работу с классом для списка, где вы не можете добавить метод.

Вы также можете рассмотреть вопрос об объявлении Foo контравариантным.

+0

Я внедрил ваше предложение, используя сопоставление с образцом, но мне не кажется очень изящным, что я должен проверить всех детей 'A' в' AIsFoo', а не писать 'XIsFoo' непосредственно для любого дочернего' X' от A. Как объявляется контравариантная работа Foo? Я попробовал 'trait Foo [-T] { def toFoo (x: T): String }' – Stephan