2012-03-20 144 views
1

我有以下代碼定義類型類。類型類和子類型

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

你期望'List(new A,new B)'有什麼類型? – 2012-03-20 13:59:02

+0

我希望'List(new A,new B)'有類型'List [A]' – Stephan 2012-03-22 09:16:48

+0

在這種情況下,當您將'toFoo'映射到'List [A]'上時,它顯然首先執行'Flicit [Foo [A]]'並獲取'AToFoo'隱式def,並將其用於每個元素。類型類專爲類型定向調度而設計,而不是用於價值定向調度,所以它們並不總是很好地與子類型匹配。 – 2012-03-22 18:07:27

回答

2

隱式解析是純粹的編譯時機制。在這裏區分子類型的簡單方法是在隱式內部進行匹配。完全刪除BToFoo,並有代替A版本處理這兩種情況下

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

當然,也可以委託給一個方法的層次結構的一部分。您可以委託A中的一個方法,在B中使用overidde,並且仍然具有List的類型類型工作,您無法添加方法。

您也可以考慮宣佈Foo逆變。

+0

我已經使用模式匹配實現了你的建議,但是我不得不在'AIsFoo'中檢查'A'的所有子節點,而不是直接爲任何子節點'X'寫入'XIsFoo' A. 宣稱'Foo'逆變是如何工作的?我試過 '特質Foo [-T] {def toFoo(x:T){: } – Stephan 2012-03-22 09:20:13