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

 幸运大树来了 发布于 2023-01-18 11:28

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]

Nikita Volko.. 5

您必须通过说o:Any清除有关类型的所有特定信息来理解,并且类型Any上的所有内容都是编译器对值的了解o.这就是为什么从那时起你只能依赖有关类型的运行时信息.

类似于case的表达式case o:List[Int]使用JVM的特殊instanceof运行时机制来解析.然而,您遇到的错误行为是由此机制仅考虑第一级类型(Listin List[Int])和忽略参数(Intin 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仅用于低级库中的特殊情况.

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

    Haskell的类型方法是在编译时使用它们来保证不会执行任何类型错误的操作,并且由于Haskell没有OO风格的子类型和动态调度,所以根本没有任何目的来保持类型.因此,数据被编译为仅包含正确值的存储结构,功能与其所经营的各类结构的烘焙知识汇编1,和只是一味地期望自己的论据有结构.这就是为什么如果你弄错了unsafeCoerce,你会得到像分段错误这样的有趣的东西,而不仅仅是一个运行时异常,说这个值不是预期的类型; 在运行时Haskell 不知道某个值是否属于任何给定类型.

    因此,Haskell不会给同等程序提供"正确答案",而是禁止您的程序不安全!Any在Haskell中没有任何类型可以向你施展你想要的任何类型.

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

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


    1除了多态函数,即假定没有关于他们的多态参数,并且因此可以基本上什么也不做与它们除了(如果有的话,与匹配的类型的类约束)将它们传递到其它多态函数.

    2类型类约束多态类型是唯一的例外.即使这样,如果你有一个值是某个类型类的成员的类型,你可以用它做的就是将它传递给其他函数,这些函数接受属于该类型类成员的任何类型的值.如果这些函数是在相关类型类之外定义的通用函数,它们将受到相同的限制.它只是类型类方法本身,它们实际上可以为类中的不同类型"做一些不同的事情",这是因为它们是在类中的一个特定类型上运行的一大堆单态定义的并集.您无法编写可获取多态值的代码,检查它以查看实例化的内容,然后决定要执行的操作.

    2023-01-18 11:31 回答
  • 您必须通过说o:Any清除有关类型的所有特定信息来理解,并且类型Any上的所有内容都是编译器对值的了解o.这就是为什么从那时起你只能依赖有关类型的运行时信息.

    类似于case的表达式case o:List[Int]使用JVM的特殊instanceof运行时机制来解析.然而,您遇到的错误行为是由此机制仅考虑第一级类型(Listin List[Int])和忽略参数(Intin 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仅用于低级库中的特殊情况.

    2023-01-18 11:31 回答
撰写答案
今天,你开发时遇到什么问题呢?
立即提问
热门标签
PHP1.CN | 中国最专业的PHP中文社区 | PNG素材下载 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有