Scala类型边界在match语句中未正确推断

时间:2014-04-29 01:53:16

标签: scala pattern-matching type-inference typeclass

我有一个case类,它接受带有有界类型的参数,但是当使用case-class extractor时,类型系统似乎正在丢失边界并推断出'Any'。

例如:

trait Expr[T]

case class IntLit(value:Int) extends Expr[Int]
case class GreaterThan[T <% Ordered[T]]( a:Expr[T], b:Expr[T] ) extends Expr[Boolean]

object TestRuntime {
    def execute[T]( expr:Expr[T] ):T = expr match {
        case IntLit(value)    => value

        // ---> This line fails because the compiler thinks a and b are Expr[Any]
        //      Instead of Expr[_ <% Ordered[_]]
        //      error: value > is not a member of Any
        case GreaterThan(a,b) => execute(a) > execute(b) 

        // ---> Whereas this line works correctly.
        /// EDIT: Actually, no it doesn't, it throws a ClassCastException!
        ///       T is Boolean,
        ///       Whereas we're expecting a new type U <: Ordered[U]
        case gt:GreaterThan[T] => execute(gt.a) > execute(gt.b)
    }
}

这只是Scalas类型推断的限制,还是我错过了什么?

我也尝试使用Ordering [T]类型类使用上下文边界来实现相同的结果(这样会更好)

case class GreaterThan[T : Ordering]( a:Expr[T], b:Expr[T] )

但是我无法弄清楚如何在不向GreaterThan本身添加方法的情况下访问match {}块中的类型类实例(这有点违背了为此目的使用类型类的观点。)

实际上,我正在尝试将此Haskell移植到Scala

{-# LANGUAGE GADTs #-} 
data Expr a where
    StringLit   :: String  -> Expr String
    IntLit      :: Int     -> Expr Int
    Equals      :: (Eq a)  => (Expr a) -> (Expr a) -> Expr Bool
    GreaterThan :: (Ord a) => (Expr a) -> (Expr a) -> Expr Bool

runExpr :: Expr a -> a
runExpr (IntLit i)        = i
runExpr (StringLit s)     = s
runExpr (Equals a b)      = (runExpr a) == (runExpr b)
runExpr (GreaterThan a b) = (runExpr a) >  (runExpr b)

1 个答案:

答案 0 :(得分:3)

有两个问题妨碍,一个是视图和上下文边界的范围,另一个是类型擦除。

<强> 1。 划定范围

这些:

case class GreaterThan[T <% Ordered[T]]( a:Expr[T], b:Expr[T]) extends Expr[Boolean]
case class GreaterThan[T : Ordering]( a:Expr[T], b:Expr[T]) extends Expr[Boolean]

是语法糖:

case class GreaterThan[T]( a:Expr[T], b:Expr[T])(implicit evidence: T => Ordered[T]) extends Expr[Boolean]
case class GreaterThan[T]( a:Expr[T], b:Expr[T])(implicit ordering: Ordering[T]) extends Expr[Boolean]

隐藏参数的范围限定在案例类中,并且无法在外部访问,正如您在尝试使用Ordering的解决方案时所发现的那样。在match声明中,它无法从>获取Ordered[T]

<强> 2。 键入Erasure

这句话:

case GreaterThan(a,b) => execute(a) > execute(b)

在运行时,代码可以发现匹配的Expr是GreaterThan,但是由于类型擦除,它无法知道此特定GreaterThan的类型参数是什么是。即使它可以,这也不会很远,因为视图和上下文边界是静态解决的 - 所有工作都是在编译时完成的。使用Ordered的解决方案,编译器必须找到一个适当的T => Ordered[T]以在编译时传递给GreaterThan的构造函数。然而,在execute内部的运行时,该解决方案都不会发生,并且相同的T => Ordered[T]可能甚至不在范围内。

第3。 溶液

没有任何方法可以解决这个问题,而不会暴露GreaterThan的隐含内容。你可以这样做:

case class GreaterThan[T]( a:Expr[T], b:Expr[T])(implicit val ordering: Ordering[T]) extends Expr[Boolean]

val前面的ordering可以在外面访问。

然后在match

case gt: GreaterThan[_] => gt.ordering.gt(execute(gt.a), execute(gt.b))

此时我们还不知道GreaterThan的类型参数是什么,但我们知道子表达式和Ordering都使用相同的类型参数化,因此我们可以安全地进行比较。

我对Haskell的内部知识并不深入,但我相信泛型类型实际上带有对包含其类型类的方法的表的引用。我们在这里使用Scala做同样的事情,但我们必须显式传递类型类对象。

另一个解决方案是使用访问者模式,但这会使你离原始的Haskell更远。