当前位置: 首页 > 知识库问答 >
问题:

Scala中对泛型类型进行模式匹配时为什么没有检测到类型检查错误

彭展
2023-03-14

定义以下结构:

sealed trait List[+A]
case object Nil extends List[Nothing]
case class Cons[A](head: A, tail: List[A]) extends List[A]

和下面的函数(请注意,第二行是类型检查错误点,它也是一个短路结果的算法错误,但它还是出现了,我想知道为什么类型检查没有发现它)

    def foldRight[A, B] (list: List[A], z: B)(f: (A,B) => B ): B = list match {
        case Nil => z
        case Cons(0, _) => z // problematic line
        case Cons(s, xs) => f(s, foldRight(xs, z) (f))
    }

我知道scala也有类型擦除的问题,但我很惊讶在这种情况下,在编译时却检测不到这一点?cons在本例中是cons[A],而z显然属于b类型?

听起来这里有一个很长的解释https://gist.github.com/jkpl/5279EE05CCA8CC1EC452FC26ACE5B68B,但读起来很长。如果有人能让它变得更简单:)

共有1个答案

许俊风
2023-03-14

为什么做出这些或那些关于语言设计的决定是基于意见的。

在Scala规范中,它是这样写的:

https://scala-lang.org/files/archive/spec/2.13/08-pattern-matching.html#type-parameter-inference-for-constructor-patterns

8.3.2构造函数html" target="_blank">模式的类型参数推断

假设构造函数模式(1,…,),其中class具有类型参数1,…,。这些类型参数的推断方式与类型化模式(_:[1,…,])的推断方式相同。

因此,对于模式cons(h,t)类型参数Acons[A](h,t)中的推断,就好像该模式是_:cons[A](运行时由于擦除而变为_:cons[_])。

在规范中有一个例子,说明如何为

class Term[A]
class Number(val n: Int) extends Term[Int]
def f[B](t: Term[B]): B = t match {
  case y: Number => y.n
}

这里解释了为什么模式y:number类型参数b(新的类型参数b)被推断为int

类似地,在我们的示例中,对于模式cons(0,_)(就好像它是_:cons[A])A推断为int

typer阶段(scalacoptions++=Seq(“-xprint:typer”,“-xprint-types”))之后,代码变为

def foldRight[A, B](list: App.List[A], z: B)(f: (A, B) => B): B = list{App.List[A]} match {
  case App.this{App.type}.Nil{App.Nil.type} => z{B}
  case (head: A, tail: App.List[A]): App.Cons[?A1](0{Int(0)}, _{App.List[A]}){App.Cons[?A1]} => z{B}
  case (head: A, tail: App.List[A]): App.Cons[?A2]((s @ _{A}){A}, (xs @ _{App.List[A]}){App.List[A]}){App.Cons[?A2]} => f.apply{(v1: A, v2: B): B}(s{A}, App.this{App.type}.foldRight{[A, B](list: App.List[A], z: B)(f: (A, B) => B): B}[A, B]{(list: App.List[A], z: B)(f: (A, B) => B): B}(xs{App.List[A]}, z{B}){(f: (A, B) => B): B}(f{(A, B) => B}){B}){B}
}{B}

erasure(scalacoptions+=“-xprint:erasure”)之后,它变成

def foldRight(list: App$List, z: Object, f: Function2): Object = {
  <synthetic> var rc9: Boolean = false;
  <synthetic> <stable> var x2: App$Cons = (null: App$Cons);
  {
    case <synthetic> val x1: App$List = list;
    case11(){
      if (App$Nil.==(x1))
        matchEnd10(z)
      else
        case12()
    };
    case12(){
      if (x1.$isInstanceOf[App$Cons]())
        {
          rc9 = true;
          x2 = (x1.$asInstanceOf[App$Cons](): App$Cons);
          {
            <synthetic> val p3: Object = x2.head();
            if (scala.Int.box(0).==(p3))
              matchEnd10(z)
            else
              case13()
          }
        }
      else
        case13()
    };
    case13(){
      if (rc9)
        {
          val s: Object = x2.head();
          val xs: App$List = x2.tail();
          matchEnd10(f.apply(s, App.this.foldRight(xs, z, f)))
        }
      else
        case14()
    };
    case14(){
      matchEnd10(throw new MatchError(x1))
    };
    matchEnd10(x: Object){
      x
    }
  }
};

实际上,如果我们在函数签名的上下文中添加类型参数a属于类型类eqnum的信息,那么类似于您的代码即使在Haskell中也可以编译

https://ideone.com/qqosi2

data List a = Nil | Cons a (List a)
 
foldRight :: (Eq a, Num a) => List a -> b -> (a -> b -> b) -> b
foldRight list z f = case list of
  Nil -> z
  Cons 0 _ -> z
  Cons s xs -> f s (foldRight xs z f)

类型类在Scala中不是一级公民。所以Scala不能请求类似于num的东西(在Scala中,所有东西都可以与==进行比较,所以eq是“自动的”)。

 类似资料:
  • 假设我们有一个泛型类: 然后,我们希望将与和的进行模式匹配:

  • 有四种不同的类型:地点、语言、技术和行业。每个类型都有一个存储库,可以返回这些类型的集合。例如位置列表。每个类型都有一个类型为String的name属性。有一个字符串列表。它可以包含位置、语言等名称。我想编写一个函数来查找那些与字符串列表名称匹配的类型实体(位置、语言、...)。我在想这样的事情: 这是不正确的,那么如何对集合进行查询,然后如何确定name属性是否存在呢?

  • 在一个actor中(这里我只是使用一个名为的常规对象,因为这个问题并不是akka特有的),我正在进行模式匹配并调用一个函数,该函数对我的有效负载类型进行操作。 由于我不理解的原因,使用和未应用的case类进行模式修补不会推断出的类型参数。在上面的代码中,我希望的类型为。但是,当我编译时,它认为类型是。 这是意料之中的行为吗?如果是的话,我对Scala中的类型推断有什么误解?

  • 现在,当我在地图上迭代时…我能以某种方式获得每个Class1对象的类型(K,V)吗??

  • 假设我有一个map。现在我只想从中获取条目并创建一个新的map与这些条目。 我正在尝试做以下事情: 这似乎可行,但我得到了一个警告:类型模式(String,Int)中的非变量类型参数字符串未选中,因为它已被擦除消除。 我怎样才能摆脱警告?

  • 我有一个从提取的代码,对于多个子类来说,这个代码看起来应该完全相同,所以我尽量避免重复。但是,实际上(见下文),scala认为是一个泛型的,返回的值类型为,当然,它没有和方法。 问题是在这里避免重复的适当方法是什么?我对将转换为字符串并不那么着迷,因为这段代码可以使用刚从字符串解析为AST的json。我开始考虑为我需要的三种类型编写包装器,并将这些类型的匹配和隐式转换器转换为包装器,然后为这些包装