Scala与Haskell中List [T]和Set [T]上的模式匹配:类型擦除的效果

时间:2014-03-13 18:56:04

标签: 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[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]

3 个答案:

答案 0 :(得分:5)

您必须明白,通过说o:Any,您删除了有关类型的所有特定信息,并且类型Any上的更多内容是编译器对值o所知道的全部内容。这就是为什么从那时起你只能依赖有关类型的运行时信息。

使用JVM的特殊case o:List[Int]运行时机制解析像instanceof这样的case表达式。但是,您遇到的错误行为是由此机制引起的,只考虑第一级类型(List中的List[Int])并忽略参数(Int List[Int] })。这就是为什么它将List[Int]视为等于List[String]的原因。这个问题被称为" Generics Erasure"。

另一方面,Haskell执行完整的类型擦除,answer by 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仅用于低级库中非常特殊的情况。

答案 1 :(得分:5)

GHC比JVM做更多的类型擦除;在运行时,类型完全消失(不仅仅是类型参数)。

Haskell的类型方法是在编译时使用它们来保证不会执行任何错误操作,并且因为Haskell没有OO-样式子类型和动态调度,没有任何目的可以保持类型。所以数据被编译成一个只包含正确值的内存结构,并且函数是用它们运行的​​类型结构的烘焙知识编译的 1 ,并且只是盲目地期望它们的参数有那种结构。这就是为什么如果你错误地使用unsafeCoerce,你会得到像分段错误这样的乐趣,而不仅仅是运行时异常,说这个值不是预期的类型;在运行时Haskell 不知道值是否属于任何给定类型。

因此,而不是Haskell给出正确答案&#34;对于等效的程序,Haskell 禁止你的程序是不安全的! Haskell中没有Any类型,您可以将其转换为您想要的任何内容。

这不是100%真实;在Haskell和Scala中,有一些方法可以在运行时保持类型信息的存活。本质上,它是通过创建表示类型的普通数据结构并将它们传递给那些类型的值来完成的,因此在运行时您可以引用类型表示对象以获取有关该类型的信息。另一个对象的类型。这两种语言都有库和语言设施,可以让您在更高(更原则)的级别使用此机制,以便更安全地使用它。因为它需要传递类型令牌,所以你必须选择&#34;选择加入&#34;对于这些功能,您的调用者必须知道它以向您传递所需类型的令牌(无论是隐式还是显式地执行令牌的实际生成和传递)。

在不使用此类功能的情况下,Haskell无法对类型为List IntSet String的值进行模式匹配,以找出它是哪一个。您要么使用单形类型,在这种情况下它只能 一种类型而其他类型将被拒绝,或者您使用的是多态类型,在这种情况下您可以使用只应用代码来执行相同的操作 2 ,无论哪个具体类型实例化多态类型。


1 除了多态函数,它假设没有关于它们的多态参数,因此除了将它们传递给其他多态函数(具有匹配类型)之外基本上什么都不做类约束,如果有的话。)

2 类型类约束多态类型是唯一的例外。即使这样,如果你的某个类型的类型是某个类型类的成员,那么你可以用它来传递给接受任何类型值的其他函数这是该类型类的成员。如果这些函数是在所讨论的类型类之外定义的一般函数,那么它们将受到相同的限制。它只是类型类方法本身,实际上可以做一些不同的事情&#34;对于类中的不同类型,这是因为它们是在类中的一个特定类型上运行的一大堆单态定义的并集。你不能编写可以获取多态值的代码,检查它以查看实例化的内容,然后决定要做什么。

答案 2 :(得分:3)

当然,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]