2010-06-28 51 views
7

应该在不需要this上的显式类型定义的情况下编译以下代码:根据参数值和函数参数类型推断出一个共同的超类型

def prepList[B >: A](prefix: PlayList[B]) : PlayList[B] = 
    prefix.foldr(this: PlayList[B])((node, suffix) => suffix.prepNode(node)) 

在我看来,类型应该能够推断。这只是Scala编译器的一个限制,还是有类型理论的原因,这是不能做到的?对于Scala类型推理者可以处理什么,我还没有感觉到。

通过该方法工作:

  • B >: A通过定义
  • this已键入PlayList[A],这是PlayList[B]亚型因为B >: A和播放列表是在A协变。
  • node有型号B,参数类型prefix
  • 功能参数f的第二个参数foldrfoldr的第一个参数具有相同类型(声明为B)。
  • 因此suffixthis具有相同的类型,所以特别是它是PlayList[A]。由于B >: A,suffix.prepNode()需要B

我想编译器看到suffix.prepNode(node)地区是合法的node具有类型B。只有在调用foldr或在该调用中引用this时明确指定类型,才能够做到这一点。

有趣的是,如果我在功能参数指定明确类型作为(node: B, suffix: PlayList[B]),仍是在参数的方法调用suffix.prepNode(node)产生的类型不匹配的错误:​​

我使用的Scala 2.8 RC6。下面完整的例子,所讨论的行是8行

sealed abstract class PlayList[+A] { 
    import PlayList._ 
    def foldr[B](b: B)(f: (A, B) => B): B 

    def prepNode[B >: A](b: B): PlayList[B] = nel(b, this) 
    def prepList[B >: A](prefix: PlayList[B]): PlayList[B] = 
    // need to specify type here explicitly 
    prefix.foldr(this: PlayList[B])((node, suffix) => suffix.prepNode(node)) 

    override def toString = foldr("")((node, string) => node + "::" + string) 
} 

object PlayList { 
    def nil[A]: PlayList[A] = Nil 
    def nel[A](head: A, tail: PlayList[A]): PlayList[A] = Nel(head, tail) 
    def nel[A](as: A*): PlayList[A] = as.foldRight(nil[A])((a, l) => l.prepNode(a)) 
} 

case object Nil extends PlayList[Nothing] { 
    def foldr[B](b: B)(f: (Nothing, B) => B) = b 
} 
case class Nel[+A](head: A, tail: PlayList[A]) extends PlayList[A] { 
    def foldr[B](b: B)(f: (A, B) => B) = f(head, tail.foldr(b)(f)) 
} 

编辑:第二通汇编推理尝试步骤

  • 重命名为清楚起见,需要foldr类型(T)((U, T) => T)参数。我们试图推断UT类型的值。
  • 第一个参数与foldr和函数的第二个参数之间有关系 - 它们是同一个东西,T。 (在部分答案丹尼尔。)
  • 类型我们传递那些参数this: PlayList[A]suffix: PlayList[B]
  • 这样的对象,因为B >: A,最具体的公用超类型是PlayList[B];因此我们有T == PlayList[B]注意我们不需要UT之间的任何关系来推断这一点。

这是我卡住:

  • 从编译错误消息,在inferencer明确认为node具有类型B(即U == B)。
  • 我看不出U == B如何从suffix的类型参数中推断出它的结论。 (scala编译器可以做到这一点吗?)
  • 如果推理的步骤是发生了什么,那么它遵循U == B,我们已经成功编译。那么哪一步出错?

编辑2:在重命名foldr参数类型以上我错过了U == A根据定义,它是PlayList类的类型参数。我认为这仍然与上述步骤一致,因为我们将它称为PlayList[B]的实例。

所以在呼叫站点,T == PlayList[B]作为最不常见的超级类型的一对事物,而U == B的定义为foldr。这似乎不够简洁缩小到几个选项:

  • 编译器无法解决这些多种类型和计算上限B
  • 错误的,从返回类型的foldrPlayList[B]键入的获得参数prepNode(持怀疑态度)

回答

3

我不是类型专家,但是当我尝试推断时,会发生什么情况。

((node, suffix) => suffix.prepNode(node))返回某种未知类型PlayList[T],其中T延伸A.它作为参数传递给foldr,它返回传递给它的函数的类型(PlayList[T],其中T扩展A)。这应该是PlayList[B]

所以我的猜测是,this:PlayList[B]是必要的,以表明T和B是相关的。

可能您需要让PlayList参数化为两种类型PlayList[+A, B >: A],因为您有prepNode和propList,它们似乎适用于扩展A的相同类型。

换种说法,你原来的类定义可能已被这样定义:

def prepNode[T >: A](b: T): PlayList[T] 
def prepList[U >: A](prefix: PlayList[U]): PlayList[U] 

但你在这两种情况下使用B和编译器不知道T和U是相同的。


编辑,你可以玩的-explaintypes选项,看看你的编译器不依赖于什么类型的提示。以下是解释类型的输出并删除了:PlayList[B](含2.8.0。RC1):

$ scalac -explaintypes -d classes Infer.scala 
found : node.type (with underlying type B) 
required: A 
    prefix.foldr(this)((node, suffix) => suffix.prepNode(node)) 
                 ^
node.type <: A? 
    node.type <: Nothing? 
    B <: Nothing? 
     <notype> <: Nothing? 
     false 
     Any <: Nothing? 
     <notype> <: Nothing? 
     false 
     false 
    false 
    false 
    B <: A? 
    B <: Nothing? 
     <notype> <: Nothing? 
     false 
     Any <: Nothing? 
     <notype> <: Nothing? 
     false 
     false 
    false 
    Any <: A? 
     Any <: Nothing? 
     <notype> <: Nothing? 
     false 
     false 
    false 
    false 
false 

希望这有助于说明问题。可能是什么时候什么时候可以推断,什么时候推断不会有帮助。

+0

你是对的,我正在寻找编译器来看这些应该是相同的,在某种意义上,这个调用,但总的来说T和U是不一样的:考虑这种情况下,我称之为两种方法分开。该声明中的B只是该方法范围内使用的绑定类型的名称。在这种情况下,我的理解是PlayList [B]是这个和后缀的常见超类型,因此应该被推断为两者的类型。也就是说,prepNode的形式类型参数B在该调用中应该与prepList的实际类型参数B具有相同的值。 – 2010-06-28 14:40:23

+0

然后可能是我的建议是不正确的。不过,似乎不得不指定'this:PlayList [B]'对我来说是有意义的。 – huynhjl 2010-06-28 15:37:07

1

的问题是,foldr不指定B >: A,所以,只要foldr而言,有它自己的AB类型之间没有任何关系。至于foldr而言,suffixnode是完全不相关的 - 即使您碰巧已经将相关参数传递给它。

+0

我不确定我关注。一般来说,我们不希望对foldr的声明(当然还有其他有效的用法,其中B与A没有关系)存在这种约束,并且在这种情况下我没有看到我们需要它来解析类型。我已经更新了这个问题,试图通过...推理 – 2010-06-28 16:42:07