Scala与Haskell中列表[T]和集合[T]的模式匹配:类型擦除的影响
下面代码的Haskell等价物会产生正确的答案吗 这个Scala代码可以修复以产生正确的答案吗?如果是,如何进行Scala与Haskell中列表[T]和集合[T]的模式匹配:类型擦除的影响,scala,haskell,pattern-matching,type-erasure,erasure,Scala,Haskell,Pattern Matching,Type Erasure,Erasure,下面代码的Haskell等价物会产生正确的答案吗 这个Scala代码可以修复以产生正确的答案吗?如果是,如何进行 object TypeErasurePatternMatchQuestion extends App { val li=List(1,2,3) val ls=List("1","2","3") val si=Set(1,2,3) val ss=Set("1","2","3") def whatIsIt(o:Any)=o match{ case o:List
object TypeErasurePatternMatchQuestion extends App {
val li=List(1,2,3)
val ls=List("1","2","3")
val si=Set(1,2,3)
val ss=Set("1","2","3")
def whatIsIt(o:Any)=o match{
case o:List[Int] => "List[Int]"
case o:List[String] => "List[String]"
case o:Set[Int] => "Set[Int]"
case o:Set[String] => "Set[String]"
}
println(whatIsIt(li))
println(whatIsIt(ls))
println(whatIsIt(si))
println(whatIsIt(ss))
}
印刷品:
List[Int]
List[Int]
Set[Int]
Set[Int]
但我希望它能打印:
List[Int]
List[String]
Set[Int]
Set[String]
当然,Haskell会打印出正确的答案:
import Data.Set
import Data.Typeable
main = do
let li=[1,2,3]
let ls=["1","2","3"]
let si=Data.Set.fromList[1,2,3]
let ss=Data.Set.fromList["1","2","3"]
print $ typeOf li
print $ typeOf ls
print $ typeOf si
print $ typeOf ss
印刷品
[Integer]
[[Char]]
Set Integer
Set [Char]
您必须理解,通过说
o:Any
可以删除有关类型的所有特定信息,并且进一步说,类型Any
是编译器所知道的关于valueo
的所有信息。这就是为什么从那时起,您只能依赖于有关类型的运行时信息
像case o:List[Int]
这样的case表达式是使用JVM的特殊instanceof
运行时机制解析的。但是,您遇到的错误行为是由于该机制只考虑了第一列类型(在List[Int]
中的List
),而忽略了参数(在List[Int]
中的Int
)。这就是为什么它将List[Int]
视为等同于List[String]
。这个问题被称为“泛型擦除”
另一方面,Haskell执行完整的类型擦除,这在中有很好的解释
因此,这两种语言中的问题是相同的:我们需要提供有关类型及其参数的运行时信息
在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")))
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)
然而,我必须大胆地指出,这远远不是Haskell编程的典型场景。该语言的类型系统功能强大,足以解决极其复杂的问题,同时维护所有类型级别的信息(和安全性)<代码>动态仅在低级库中的非常特殊的情况下使用。GHC比JVM执行更多的类型擦除;在运行时,类型完全消失(不仅仅是类型参数) Haskell处理类型的方法是在编译时使用它们来保证不会执行任何类型错误的操作,而且由于Haskell没有OO风格的子类型和动态分派,所以保留类型根本没有任何意义。因此,数据被编译成一个只包含正确值的内存结构,而函数则是根据它们所操作的类型1的结构知识编译的,只是盲目地期望它们的参数具有这种结构。这就是为什么如果你错误地处理
unsafeccerce
,你会得到一些有趣的事情,比如分段错误,而不仅仅是一个运行时异常,说值不是预期的类型;在运行时,Haskell不知道值是否属于任何给定类型
因此,Haskell不允许您的程序不安全,而不是为等效程序提供“正确答案”!Haskell中没有任何Any
类型,您可以对其进行任意转换
这不是百分之百正确的;在Haskell和Scala中,都有在运行时保持类型信息活动的方法。基本上,它是通过创建表示类型的普通数据结构来完成的,并将它们一起传递给这些类型的值,因此在运行时,您可以参考类型表示对象以获取有关另一个对象类型的信息。这两种语言都有库和语言工具,让您可以在更高(更具原则性)的级别上使用此机制,以便更容易安全地使用。因为它需要传递类型令牌,所以您必须“选择加入”此类功能,并且您的调用者必须知道它才能传递您所需的类型令牌(无论令牌的实际生成和传递是隐式还是显式完成的)
如果不使用这些功能,Haskell就无法对类型可能为List Int
或Set String
的值进行模式匹配,以确定它是哪一个。或者您使用的是单态类型,在这种情况下,它只能是一种类型,其他类型将被拒绝;或者您使用的是多态类型,在这种情况下,您只能对其应用执行相同操作的代码2,而不管哪个具体类型实例化了多态类型
1除多态函数外,多态函数对其多态参数不作任何假设,因此除了将其传递给其他多态函数(具有匹配的类型类约束,如果有)外,基本上不能对其执行任何操作
2类型类约束的多态类型是唯一的例外。即使这样,如果你有一个值,一个是某个类型类成员的类型,你所能做的就是把它传递给其他函数,这些函数接受属于该类型类成员的任何类型的值。如果这些函数是在所讨论的类型类之外定义的一般函数,它们将受到相同的限制。只有类型类方法本身才能对类中的不同类型“做一些不同的事情”,这是因为它们是对类中一个特定类型进行操作的一系列单态定义的联合。你不能编写代码来获取多态值,检查它以查看它是用什么实例化的,然后决定做什么。谢谢,所以模式匹配方法在Haskell中也会起作用吗?值得注意的是,
Data.Typeable
可以说与Haskell的精神背道而驰,它有类型擦除功能,并且在模式匹配中不提供类型大小写。所以您也不能在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