如何使用应用/取消应用方法重现案例类行为? [英] How to reproduce case class behaviour with apply/unapply methods?

查看:37
本文介绍了如何使用应用/取消应用方法重现案例类行为?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我尝试用普通类和伴随对象替换 case 类,突然出现类型错误.

I tried to replace case class with mundane class and companion object and suddenly get type error.

编译良好的代码(综合示例):

Code that compiles fine (synthetic example):

trait Elem[A,B] {
  def ::[C](other : Elem[C,A]) : Elem[C,B] = other match {
    case Chain(head, tail) => Chain(head, tail :: this)
    case simple => Chain(simple, this)
  }
}
class Simple[A,B] extends Elem[A,B]
final case class Chain[A,B,C](head : Elem[A,B], tail : Elem[B,C]) extends Elem[A,C]

更改最后一个定义:

final class Chain[A,B,C](val head : Elem[A,B], val tail : Elem[B,C]) extends Elem[A,C]
object Chain {
  def unapply[A,B,C](src : Chain[A,B,C]) : Option[(Elem[A,B], Elem[B,C])] =
    Some( (src.head, src.tail) )
  def apply[A,B,C](head : Elem[A,B], tail : Elem[B,C]) : Chain[A,B,C] =
    new Chain(head, tail)
}

但是那个看似等效的代码让编译器发出错误:

But that seemingly equivalent code make compiler emit errors:

CaseMystery.scala:17: error: type mismatch;
 found   : test.casemystery.Fail.Elem[A,B] where type B, type A >: C <: C
 required: test.casemystery.Fail.Elem[A,Any] where type A >: C <: C
Note: B <: Any, but trait Elem is invariant in type B.
You may wish to define B as +B instead. (SLS 4.5)
      case Chain(head, tail) => Chain(head, tail :: this)
                                  ^
CaseMystery.scala:17: error: type mismatch;
 found   : test.casemystery.Fail.Elem[B(in method ::),B(in trait Elem)] where type B(in method ::)
 required: test.casemystery.Fail.Elem[Any,B(in trait Elem)]
Note: B <: Any, but trait Elem is invariant in type A.
You may wish to define A as +A instead. (SLS 4.5)
      case Chain(head, tail) => Chain(head, tail :: this)
                                             ^
two errors found

使用 case 语句隐式创建的方法与普通类的显式编写方法有什么区别?

What is the difference between implicitly created method with the case statement and explicitly written methods for mundane class?

推荐答案

这个答案最终比我预期的要长.如果您只想了解类型推断发生的事情,请跳到最后.否则,您将被引导完成获得答案的步骤.

This answer ended up being longer than I expected. If you just want the guts of what is happening with type inference, skip to the end. Otherwise, you get led through the steps of getting to the answer.

在这种情况下,尽管我不愿意承认,但案例类真的很神奇.特别是,它们在类型检查器级别得到特殊处理(我认为我们可以同意,如果您的代码通过了那个阶段,它就可以工作——您甚至可以对其进行足够多的强制转换以使其工作).

In this case, as much as I hate to admit it, case classes really are magic. In particular, they get special treatment at the type checker level (I think we can agree that your code would work if it got past that phase - you might even be able to throw enough casts at it to make that work).

出人意料的是,问题不在于Chain 类本身,而在于使用它的地方,特别是模式匹配部分.例如,考虑案例类

The problem is, surprisingly enough, not in the class Chain itself, but in the places it is used, specifically in the pattern matching part. For example, consider the case class

case class Clazz(field: Int)

然后,您希望以下内容是等效的:

Then, you expect the following to be equivalent:

Clazz(3) match { case Clazz(i) => i }
// vs
val v = Clazz.unapply(Clazz(3))
if (v.isDefined) v.get else throw new Exception("No match")

但是,Scala 想要更聪明并对此进行优化.特别是,这个 unapply 方法几乎永远不会失败(让我们暂时忽略 null)并且可能经常使用,所以 Scala 想要完全避免它并且只是提取字段,因为它通常会获取对象的任何成员.正如我的编译器教授喜欢说的那样,编译器是一种不会被抓住的作弊艺术".

But, Scala wants to be more clever and optimize this. In particular, this unapply method pretty can pretty much never fail (let's ignore null for now) and is probably used a lot, so Scala wants to avoid it altogether and just extract the fields as it usually would get any member of an object. As my compiler professor is fond of saying, "compilers are the art of cheating without getting caught".

然而这里的类型检查器有所不同.问题出在

Yet here there is a difference in the type-checker. The problem is in

def ::[Z, X](other : Elem[Z, X]) : Elem[Z, Y] = other match {
  case Chain(head, tail) => Chain(head, tail :: this)
  case simple => Chain(simple, this)
}

如果您使用 -Xprint:typer 进行编译,您将看到类型检查器看到的内容.案例类版本有

If you compile with -Xprint:typer you'll see what the type checker sees. The case class version has

def ::[C](other: Elem[C,A]): Elem[C,B] = other match {
  case (head: Elem[C,Any], tail: Elem[Any,A])Chain[C,Any,A]((head @ _), (tail @ _)) => Chain.apply[C, Any, B](head, {
    <synthetic> <artifact> val x$1: Elem[Any,A] = tail;
    this.::[Any](x$1)
  })
  case (simple @ _) => Chain.apply[C, A, B](simple, this)
}

虽然普通班有

def ::[C](other: Elem[C,A]): Elem[C,B] = other match {
  case Chain.unapply[A, B, C](<unapply-selector>) <unapply> ((head @ _), (tail @ _)) => Chain.apply[A, Any, B](<head: error>, {
    <synthetic> <artifact> val x$1: Elem[_, _ >: A <: A] = tail;
    this.::[B](x$1)
  })
  case (simple @ _) => Chain.apply[C, A, B](simple, this)
}

所以类型检查器实际上得到了一个不同的(特殊的)case 构造.

So the type checker actually gets a different (special) case construct.

只是为了好玩,我们可以检查下一阶段 -Xprint:patmat 会发生什么,它扩展了模式(尽管在这里这些不再是真正有效的 Scala 程序的事实真的变得很痛苦).首先,case类有

Just for fun, we can check what happens at the next phase -Xprint:patmat which expands out patterns (although here the fact that these are no longer really valid Scala programs really becomes painful). First, the case class has

def ::[C](other: Elem[C,A]): Elem[C,B] = {
  case <synthetic> val x1: Elem[C,A] = other;
  case5(){
    if (x1.isInstanceOf[Chain[C,Any,A]])
      {
        <synthetic> val x2: Chain[C,Any,A] = (x1.asInstanceOf[Chain[C,Any,A]]: Chain[C,Any,A]);
        {
          val head: Elem[C,Any] = x2.head;
          val tail: Elem[Any,A] = x2.tail;
          matchEnd4(Chain.apply[C, Any, B](head, {
            <synthetic> <artifact> val x$1: Elem[Any,A] = tail;
            this.::[Any](x$1)
          }))
        }
      }
    else
      case6()
  };
  case6(){
    matchEnd4(Chain.apply[C, A, B](x1, this))
  };
  matchEnd4(x: Elem[C,B]){
    x
  }
}

尽管这里有很多东西令人困惑,但请注意,我们从不使用unapply 方法!对于非 case 类版本,我将使用来自 user1303559 的工作代码:

Although a lot of stuff is confusing here, notice that we never use the unapply method! For the non-case class version, I'll use the working code from user1303559:

def ::[Z, XX >: X](other: Elem[Z,XX]): Elem[Z,Y] = {
  case <synthetic> val x1: Elem[Z,XX] = other;
  case6(){
    if (x1.isInstanceOf[Chain[A,B,C]])
      {
        <synthetic> val x2: Chain[A,B,C] = (x1.asInstanceOf[Chain[A,B,C]]: Chain[A,B,C]);
        {
          <synthetic> val o8: Option[(Elem[A,B], Elem[B,C])] = Chain.unapply[A, B, C](x2);
          if (o8.isEmpty.unary_!)
            {
              val head: Elem[Z,Any] = o8.get._1;
              val tail: Elem[Any,XX] = o8.get._2;
              matchEnd5(Chain.apply[Z, Any, Y](head, {
                <synthetic> <artifact> val x$1: Elem[Any,XX] = tail;
                this.::[Any, XX](x$1)
              }))
            }
          else
            case7()
        }
      }
    else
      case7()
  };
  case7(){
    matchEnd5(Chain.apply[Z, XX, Y](x1, this))
  };
  matchEnd5(x: Elem[Z,Y]){
    x
  }
}

在这里,果然,unapply 方法出现了.

And here, sure enough, the unapply method makes an appearance.

当然,Scala 实际上并没有作弊——这种行为都在规范中.特别是,我们看到 构造函数模式 从中受益的案例类有点特殊,因为除其他外,它们是 irrefutable(与我上面所说的有关 Scala 不想使用 unapply 方法的内容有关,因为它知道"它只是在提取字段).

Of course, Scala doesn't actually cheat - this behavior is all in the specification. In particular, we see that constructor patterns from which case classes benefit are kind of special, since, amongst other things, they are irrefutable (related to what I was saying above about Scala not wanting to use the unapply method since it "knows" it is just extracting the fields).

我们真正感兴趣的部分是 8.3.2 构造函数模式的类型参数推断.常规类和案例类的区别在于,当Chain是案例类时,Chain模式是构造器模式",否则只是常规模式.构造器模式

The part that really interests us though is 8.3.2 Type parameter inference for constructor patterns. The difference between the regular class and the case class is that Chain pattern is a "constructor pattern" when Chain is a case class, and just a regular pattern otherwise. The constructor pattern

other match {
  case Chain(head, tail) => Chain(head, tail :: this)
  case simple => Chain(simple, this)
}

最终被打字

other match {
  case _: Chain[a1,a2,a3] => ...
}

然后,基于 other: Elem[C,A] 来自参数类型和 Chain[a1,a2,a3] 扩展 Elem[a1,a3] 的事实],我们得到 a1Ca3Aa2 可以是任何东西,Any 也是.因此,为什么案例类的 -Xprint:typer 输出中的类型中有一个 Chain[C,Any,A] .这会进行类型检查.

Then, based on the fact that other: Elem[C,A] from the argument types and the fact that Chain[a1,a2,a3] extends Elem[a1,a3], we get that a1 is C, a3 is A and a2 can by anything, so is Any. Hence why the types in the output of -Xprint:typer for the case class has an Chain[C,Any,A] in it. This does type check.

然而,构造器模式是特定于案例类的,所以不 - 这里没有办法模仿案例类的行为.

However, constructor patterns are specific to case classes, so no - there is no way to imitate the case class behavior here.

构造器模式的形式为 c(p1,…,pn),其中 n≥0.它由一个稳定的标识符 c 组成,后跟元素模式p1,...,pn.构造函数 c 是一个简单的或限定的名称,它表示一个 case 类.

A constructor pattern is of the form c(p1,…,pn) where n≥0. It consists of a stable identifier c, followed by element patterns p1,…,pn. The constructor c is a simple or qualified name which denotes a case class.

这篇关于如何使用应用/取消应用方法重现案例类行为?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆