类型构造函数的Scala类型推断

时间:2011-12-03 18:19:53

标签: generics scala type-inference higher-kinded-types gadt

我有一个关于Scala类型构造函数的类型推理的问题。我正在运行Scala 2.9.1 ......

假设我定义了树:

 sealed trait Tree[C[_], A]
 case class Leaf[C[_], A](a: A) extends Tree[C, A]
 case class Node[C[_], A](a: A, c: C[Tree[C, A]]) extends Tree[C, A]

根据我的树定义定义了一个BinaryTree:

 type Pair[A] = (A, A)
 type BinaryTree[A] = Tree[Pair, A]

我现在可以定义一个BinaryTree整数:

 val tree: BinaryTree[Int] = Node[Pair, Int](1, (Leaf(2), Leaf(3)))

这个问题是每当我实例化Node时我都必须提供类型参数。

所以,如果这样做:

 val tree: BinaryTree[Int] = Node(1, (Leaf(2), Leaf(3)))

我收到错误:

error: no type parameters for method apply: (a: A, c: C[Tree[C,A]])Node[C,A] in 
object Node exist so that it can be applied to arguments (Int, (Leaf[Pair,Int], Leaf[Pair,Int]))
 --- because ---
 argument expression's type is not compatible with formal parameter type;
 found   : (Leaf[Pair,Int], Leaf[Pair,Int])
 required: ?C[Tree[?C,?A]]
   val tree: BinaryTree[Int] = Node(1, (Leaf(2), Leaf(3)))
                               ^

我有什么方法可以强制进行类型检查,这样我就不必明确提供Node的类型了吗?

谢谢!


<小时/> 在didierd的评论后修改

如果我理解正确,声明

 type Pair[A] = (A, A)

在我的原始问题中不起作用,因为这个Pair声明只是Tuple2类型构造函数的语法糖(需要两个类型参数)。这会导致类型推断器失败。

如果我声明我自己的Pair类(正如didierd在他的回答中所暗示的那样),我成功地使树正常工作。

// Assume same Tree/Leaf/Node definition given above
case class MyPair[A](_1: A, _2: A)
type BinaryTree[A] = Tree[MyPair, A]

然后我可以这样做......

scala> val t: BinaryTree[Int] = Leaf(3)
t: BinaryTree[Int] = Leaf(3)

scala> val t2: BinaryTree[Int] = Node(1, MyPair(Leaf(2), Leaf(3)))
t2: BinaryTree[Int] = Node(1,MyPair(Leaf(2),Leaf(3)))

我知道didierd在传递过程中提到了这个解决方案,但这似乎表达了我想要的方式。请让我知道你的想法!

2 个答案:

答案 0 :(得分:6)

首先推断C[X] = (X,X)是个问题。假设您在编译器期望(String, String)的某处传递C[String]并且必须推断CC[X]可以是(X, X)(X, String)(String, X)甚至是(String, String) X幻像。

声明别名Pair没有帮助。我相信您必须声明case class Pair[A](_1: A, _2: A) - 授予,推断C[X] = Pair[String]X幻象仍然是可能的,但幸运的是,编译器不这样做。

但是,当你写Tree(1, Pair(Leaf(2), Leaf(3))时,它不会推断出叶子中的C。我不太清楚为什么。但无论如何,当你简单地写val l = Leaf(2)时,它无法推断它。

我认为你可以通过使一切协变来实现目标

sealed trait Tree[+C[+X], +A]
case class Leaf[+A](a: A) extends Tree[Nothing, A]
case class Node[+C[+X], +A](a: A, c: C[Tree[C,A]]) extends Tree[C,A]

使用协方差,您从Leaf中删除C,因此无需推断

case class Pair[+A](left: A, right: A)

val tree = Node(1, Pair(Leaf(2), Node(3, Pair(Leaf(3), Leaf(4)))))
tree: Node[Pair,Int] = Node(1,Pair(Leaf(2),Node(3,Pair(Leaf(3),Leaf(4)))))

侧面评论,

是不是更有意义
case object Empty extends Tree[Nothing, Nothing]

而不是Leaf。使用Leaf,您可以获得二叉树的形状。


关于您的评论

更新

首先不要用幻影类型打扰太多,我不应该提到它。如果您定义类型T[X]并且{T}的定义中没有出现X,则称为幻像类型。可以使用它编写聪明的代码,以确保在编译时证明某些属性,但这不是重点。

事实上,当scala编译器给出一些类型T和X,并且必须推断C,这样C [X]是(超类型)T - 在我的例子中,那就是T =(String ,String)和X = String - 仅当T(或超类型)是具有一个类型参数的类型的参数化时,它才有效。更一般地,与类型参数C一样多的类型参数具有。由于C有一个而Tuple2有两个(你已经定义了一个别名对不计),它无法工作。

我试图指出的是,如果没有这样的规则,编译器会为C提供许多选择。如果我知道(String, Int)C[String],并且我必须猜测C是什么,那么我认为C[X](X, Int)。当你编写如果传递(String,Int),如果推断(Any,Any)则不会,它没有意义,因为它试图推断一个类型构造函数。答案必须是C[X] = something with X(除了X是幽灵的可能性)。完全不同的是Pair[X] = (String, Int)并且必须推断X。确实,它会推断X = Any。所以给定C [String] =(String,String),C [X] =(X,String)就像C [X] =(X,X)那样是一个解决方案。

关于List的第二条评论,一旦您定义了Pair(上面的答案中的第三段),它与case class Pair同样存在同样的问题,即它会在您CLeaf(2)时,不会推断出C。这是协方差开始的地方,省去了Leaf中的参数C,因此需要推断它。

答案 1 :(得分:3)

我遇到的唯一变化是注释Pair参数。其他人我肯定会有其他想法。

object BinaryTreeTest {
  sealed trait Tree[C[_], A]
  case class Leaf[C[_], A](a: A) extends Tree[C, A]
  case class Node[C[_], A](a: A, c: C[Tree[C, A]]) extends Tree[C, A]

  type Pair[A] = (A, A)
  type BinaryTree[A] = Tree[Pair, A]

  val p: Pair[Tree[Pair, Int]] = (Leaf(2), Leaf(3))    
  val t: BinaryTree[Int] = Node(1, p)    
}