您必须了解,通过说o:Any
您删除了有关类型的所有特定信息,并且进一步了解类型Any
是编译器所知道的关于 value 的全部信息o
。这就是为什么从那时起您只能依赖有关类型的运行时信息。
case-expression likecase o:List[Int]
是使用 JVM 的特殊instanceof
运行时机制解决的。但是,您遇到的错误行为是由这种机制引起的,它只考虑了第一类类型(List
in List[Int]
)并忽略了参数(Int
in List[Int]
)。这就是为什么它被视为List[Int]
等于List[String]
。这个问题被称为“Generics Erasure”。
另一方面,Haskell 执行完整的类型擦除,这在Ben 的回答中得到了很好的解释。
所以这两种语言的问题是一样的:我们需要提供关于类型及其参数的运行时信息。
在 Scala中,您可以使用“反射”库来实现这一点,它隐式地解析该信息:
import reflect.runtime.{universe => ru}
def whatIsIt[T](o : T)(implicit t : ru.TypeTag[T]) =
if( t.tpe <:< ru.typeOf[List[Int]] )
"List[Int]"
else if ( t.tpe <:< ru.typeOf[List[String]] )
"List[String]"
else if ( t.tpe <:< ru.typeOf[Set[Int]] )
"Set[Int]"
else if ( t.tpe <:< ru.typeOf[Set[String]] )
"Set[String]"
else sys.error("Unexpected type")
println(whatIsIt(List("1","2","3")))
println(whatIsIt(Set("1","2","3")))
输出:
List[String]
Set[String]
Haskell对多态性有一种非常不同的方法。最重要的是,它没有子类型多态性(虽然它不是一个弱点),这就是为什么你的例子中的类型切换模式匹配是无关紧要的。但是,可以将上面的 Scala 解决方案非常紧密地翻译成 Haskell:
{-# LANGUAGE MultiWayIf, ScopedTypeVariables #-}
import Data.Dynamic
import Data.Set
whatIsIt :: Dynamic -> String
whatIsIt a =
if | Just (_ :: [Int]) <- fromDynamic a -> "[Int]"
| Just (_ :: [String]) <- fromDynamic a -> "[String]"
| Just (_ :: Set Int) <- fromDynamic a -> "Set Int"
| Just (_ :: Set String) <- fromDynamic a -> "Set String"
| otherwise -> error "Unexpected type"
main = do
putStrLn $ whatIsIt $ toDyn ([1, 2, 3] :: [Int])
putStrLn $ whatIsIt $ toDyn (["1", "2", "3"] :: [String])
putStrLn $ whatIsIt $ toDyn (Data.Set.fromList ["1", "2", "3"] :: Set String)
输出:
[Int]
[String]
Set String
但是,我必须大胆地指出,这与 Haskell 编程的典型场景相去甚远。该语言的类型系统足够强大,可以解决极其复杂的问题,同时保持所有类型级别的信息(和安全性)。Dynamic
仅在低级库中非常特殊的情况下使用。