2011-05-23 96 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