我有一个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)
答案 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更远。