在List [T]上的模式匹配和在Scala与Haskell中设置[T]:类型擦除的效果 [英] Pattern matching on List[T] and Set[T] in Scala vs. Haskell: effects of type erasure
问题描述
这个Scala代码可以修复以产生正确答案吗?如果是,如何?
object TypeErasurePatternMatchQuestion extends App {
val li = List(1,2,3)
val ls =列表(1,2,3)
val si =设置(1,2,3)
val ss =设置(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]
您必须明白,通过说 o:任何
删除关于类型的所有具体信息,并进一步了解类型任何
是编译器知道的关于值的所有信息 o
。这就是为什么从这一点上,你只能依赖关于类型的运行时信息。
使用JVM的特殊<$ c $解决了 case o:List [Int]
c> instanceof 运行时机制。但是,您遇到的错误行为是由仅考虑一级类型( List > List [Int]
)并忽略参数( List [Int]
)中的参数( Int
)。这就是为什么它把 List [Int]
等同于 List [String]
。这个问题被称为泛型删除。
另一方面,Haskell执行完整类型的擦除,这在由Ben解答。
所以两种语言的问题都是一样的:我们需要提供关于类型及其参数的运行时信息。 / p>
在Scala 中,您可以使用反射库来实现该功能,该功能隐式解析信息:
import reflect.runtime。{universe => (t.tpe <: List [Int]
else if(t.tpe<: List [String]
else else if(t.tpe <: Set [Int]
else if(t.tpe <: Set [String]
else sys.error(Unexpected type)
println(whatIsIt(List(1, (1,2,3)))
输出:
列表[String]
Set [String]
Haskell 有一个非常不同的多态性方法。最重要的是,它没有子类型多态性(尽管这不是弱点),这就是为什么类型切换模式与您的示例中匹配的原因简直无关紧要。但是可以将Scala解决方案从上面翻译成Haskell:
{ - #LANGUAGE MultiWayIf,ScopedTypeVariables# - }
import Data.Dynamic
import Data.Set
whatIsIt :: Dynamic - >字符串
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 - > 设置字符串
|否则 - >错误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编程的典型场景很不相同。该语言的类型系统足够强大,可以解决极其复杂的问题,同时保持所有类型级别的信息(和安全性)。 动态
仅用于低级库中的特殊情况。
Would the Haskell equivalent of the code below produce correct answers?
Can this Scala code be fixed to produce correct answers ? If yes, how ?
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))
}
prints:
List[Int]
List[Int]
Set[Int]
Set[Int]
but I would expect it to print:
List[Int]
List[String]
Set[Int]
Set[String]
You must understand that by saying o:Any
you erase all the specific information about the type and further on the type Any
is all that the compiler knows about value o
. That's why from that point on you can only rely on the runtime information about the type.
The case-expressions like case o:List[Int]
are resolved using the JVM's special instanceof
runtime mechanism. However the buggy behaviour you experience is caused by this mechanism only taking the first-rank type into account (the List
in List[Int]
) and ignoring the parameters (the Int
in List[Int]
). That's why it treats List[Int]
as equal to List[String]
. This issue is known as "Generics Erasure".
Haskell on the other hand performs a complete type erasure, which is well explained in the answer by Ben.
So the problem in both languages is the same: we need to provide a runtime information about the type and its parameters.
In Scala you can achieve that using the "reflection" library, which resolves that information implicitly:
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")))
Output:
List[String]
Set[String]
Haskell has a very different approach to polymorphism. Above all, it does not have subtype polymorphism (it's not a weakness though), that's why the type-switching pattern matches as in your example are simply irrelevant. However it is possible to translate the Scala solution from above into Haskell quite closely:
{-# 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)
Output:
[Int]
[String]
Set String
However I must outline boldly that this is far from a typical scenario of Haskell programming. The language's type-system is powerful enough to solve extremely intricate problems while maintaining all the type-level information (and safety). Dynamic
is only used in very special cases in low-level libraries.
这篇关于在List [T]上的模式匹配和在Scala与Haskell中设置[T]:类型擦除的效果的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!