2011-05-23 117 views
1

我想在scalaz庫中使用scala中的monad做些事情,並且在使用子類型工作時遇到了一些麻煩。Scala子類型+隱式轉換問題

我已經開始定義自己的monad。讓它爲簡單起見身份單子:

import scalaz._ 
import Scalaz._ 

class Id[+A] (val value : A) { } 

implicit object IdMonad extends Monad[Id] { 
    override def pure[A](a : => A) = new Id(a) 
    override def bind[A, B](a : Id[A], f : A => Id[B]) = f(a.value) 
} 

接下來,我有一些額外的功能擴展它:

class ExtendedId[A] (value : A, val printer : A => String) extends Id[A](value) { } 

有了這個額外的功能,ExtendedId不是一個單子了。

現在我想用ExtendedId[A]類型的對象作爲Id[A]

def increment1(v : ExtendedId[Int]) : Id[Int] = { 
    for(v <- v) yield v + 1; 
    // ^
    // error: could not find implicit value for parameter t: scalaz.Functor[test.package.ExtendedId] 
} 

注意,我瞭解到,自從ExtendedId不是一個單子,最好我能得到作爲輸出Id[Int],我那好吧!但不幸的是,該代碼仍然無法編譯。

然而,這一個作用:

def asId[A](a : ExtendedId[A]) : Id[A] = a 

def increment2(v : ExtendedId[Int]) { 
    for(v <- asId(v)) yield v + 1; 
} 

這裏,asId功能的確沒有什麼比上溯造型它的參數從ExtendedId[A]Id[A]更多。它似乎應該是完全多餘的,但事實並非如此。

這是怎麼發生的?確實存在從Id[A]到包含map的對象的隱式轉換,並且顯然確實存在從ExtendedId[A]Id[A]的簡單隱式轉換。那麼,爲什麼編譯器無法合併它們?

回答

0

發生這種情況是因爲Scalaz未將Monad定義爲在其第一個類型參數(或更準確地說,類型構造函數參數)中協變。換句話說,Monad[A]被認爲是與Monad[B]完全不同的類型,即使A <: B。 (More on covariance and contravariance

有很好的理由爲什麼Monad是不變的。一個是:如果你讓編譯器相信Monad[Id]實際上也是有效的Monad[ExtendedId],那麼在某個時刻你肯定會遇到問題 - 其中一個是無論調用哪個pure,編譯器都會推斷出結果類型爲ExtendedId ,而只有Id將被退回。

認爲沒有乾淨地解決這一問題的技術 - 比定義Monad[ExtendedId],或類似的東西

implicit def idMonad[A[_] <: Id[_]]: Monad[A] = ... 

這的確是能夠返回正確的單子爲Id所有子類等。

+0

由於'ExtendedId'不是一個monad,我無法定義'Monad [ExtendedId]'''''ExtendedId''沒有有用的'bind'實現('printer'值使它不可能)。我能做的是將'ExtendedId [A]'強制轉換爲'Id [A]',但我不明白編譯器爲什麼不這樣做。我的意思是,上傳是所有隱式轉換的母體,並且它不是隱式執行的。 – Rotsor 2011-05-23 10:56:00

+0

註釋'[A <:Id]'似乎無效,因爲'Id'類型構造函數需要參數。並且'[A [_] <:Id [_]]'抱怨某些涉及類型A的「非法循環引用」。 – Rotsor 2011-05-23 11:02:21

+0

@Rostor你說得對,我弄亂了我的類型參數。我編輯了我的答案。正如我寫的,編譯器不會像你所描述的那樣轉換事物,因爲「Monad」不是協變的;即「Monad」的定義明確指出,在實踐中,即使「B <:A」,「Monad [A]」也不是「Monad [B]」。如果明確演員,編譯器,你知道它對你來說是安全的 - 但它不是在斯卡拉茲單子的一般情況。 – 2011-05-23 11:36:34