Scala:案例类取消应用与手动实现和类型擦除

发布于 2024-12-25 15:12:55 字数 2115 浏览 1 评论 0原文

我试图了解 Scala 对案例类的作用,使它们在某种程度上不受类型擦除警告的影响。

假设我们有以下简单的类结构。它基本上是一个Either

abstract class BlackOrWhite[A, B]

case class Black[A,B]( val left: A ) extends BlackOrWhite[A,B]

case class White[A,B]( val right: B ) extends BlackOrWhite[A,B]

并且您尝试像这样使用它:

object Main extends App {

    def echo[A,B] ( input: BlackOrWhite[A,B] ) = input match {
        case Black(left) => println( "Black: " + left )
        case White(right) => println( "White: " + right )
    }

    echo( Black[String, Int]( "String!" ) )
    echo( White[String, Int]( 1234 ) )
}

一切都编译并运行,没有任何问题。但是,当我尝试自己实现 unapply 方法时,编译器会抛出警告。我将以下类结构与上面相同的 Main 类一起使用:

abstract class BlackOrWhite[A, B]

case class Black[A,B]( val left: A ) extends BlackOrWhite[A,B]

object White {

    def apply[A,B]( right: B ): White[A,B] = new White[A,B](right)

    def unapply[B]( value: White[_,B] ): Option[B] = Some( value.right )

}

class White[A,B]( val right: B ) extends BlackOrWhite[A,B]

使用 -unchecked 标志进行编译会发出以下警告:

[info] Compiling 1 Scala source to target/scala-2.9.1.final/classes...
[warn] src/main/scala/Test.scala:41: non variable type-argument B in type pattern main.scala.White[_, B] is unchecked since it is eliminated by erasure
[warn]         case White(right) => println( "White: " + right )
[warn]                   ^
[warn] one warning found
[info] Running main.scala.Main

现在,我了解类型擦除,并且我已经尝试过使用 Manifests 来绕过警告(到目前为止还没有效果),但这两种实现之间有什么区别?案例类是否正在执行我需要添加的操作?可以用Manifests来规避这个问题吗?

我什至尝试在打开 -Xprint:typer 标志的情况下通过 scala 编译器运行案例类实现,但 unapply 方法看起来非常像我预期的那样:

case <synthetic> def unapply[A >: Nothing <: Any, B >: Nothing <: Any](x$0: $iw.$iw.White[A,B]): Option[B] = if (x$0.==(null))
    scala.this.None
else
    scala.Some.apply[B](x$0.right);

I'm trying to understand what Scala does with Case Classes that makes them somehow immune to type erasure warnings.

Let's say we have the following, simple class structure. It's basically an Either:

abstract class BlackOrWhite[A, B]

case class Black[A,B]( val left: A ) extends BlackOrWhite[A,B]

case class White[A,B]( val right: B ) extends BlackOrWhite[A,B]

And you're trying to use it like this:

object Main extends App {

    def echo[A,B] ( input: BlackOrWhite[A,B] ) = input match {
        case Black(left) => println( "Black: " + left )
        case White(right) => println( "White: " + right )
    }

    echo( Black[String, Int]( "String!" ) )
    echo( White[String, Int]( 1234 ) )
}

Everything compiles and runs without any problems. However, when I try implementing the unapply method myself, the compiler throws a warning. I used the following class structure with the same Main class above:

abstract class BlackOrWhite[A, B]

case class Black[A,B]( val left: A ) extends BlackOrWhite[A,B]

object White {

    def apply[A,B]( right: B ): White[A,B] = new White[A,B](right)

    def unapply[B]( value: White[_,B] ): Option[B] = Some( value.right )

}

class White[A,B]( val right: B ) extends BlackOrWhite[A,B]

Compiling that with the -unchecked flag issues the following warning:

[info] Compiling 1 Scala source to target/scala-2.9.1.final/classes...
[warn] src/main/scala/Test.scala:41: non variable type-argument B in type pattern main.scala.White[_, B] is unchecked since it is eliminated by erasure
[warn]         case White(right) => println( "White: " + right )
[warn]                   ^
[warn] one warning found
[info] Running main.scala.Main

Now, I understand type erasure and I've tried to get around the warning with Manifests (to no avail so far), but what is the difference between the two implementations? Are case classes doing something that I need to add in? Can this be circumvented with Manifests?

I even tried running the case class implementation through the scala compiler with the -Xprint:typer flag turned on, but the unapply method looks pretty much like I expected:

case <synthetic> def unapply[A >: Nothing <: Any, B >: Nothing <: Any](x$0: $iw.$iw.White[A,B]): Option[B] = if (x$0.==(null))
    scala.this.None
else
    scala.Some.apply[B](x$0.right);

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

私藏温柔 2025-01-01 15:12:55

我无法给出完整的答案,但我可以告诉您,即使编译器为案例类生成 unapply 方法,当它在案例类上进行模式匹配时,它不会使用该 unapply 方法。如果您尝试使用内置大小写匹配和 unapply 方法来尝试 -Ybrowse:typer,您将看到生成了一个非常不同的语法树(对于 match代码>) 取决于所使用的。您还可以浏览后面的阶段并看到差异仍然存在。

我不确定为什么 Scala 不使用内置的 unapply,尽管这可能是出于您提出的原因。我不知道如何为自己的unapply绕过它。但这就是 Scala 似乎神奇地避免了这个问题的原因。

经过实验,显然这个版本的 unapply 有效,尽管我对为什么有点困惑:

def unapply[A,B](value: BlackOrWhite[A,B]): Option[B] = value match {
    case w: White[_,_] => Some(w.right)
    case _ => None
}

unapply 的困难在于,编译器必须以某种方式确信,如果a White[A,B] 扩展 BlackOrWhite[C,D]BD 相同,显然编译器能够在这个版本中计算出来,但在你的。不知道为什么。

I cannot give a complete answer, but I can tell you that even though the compiler generates an unapply method for case classes, when it pattern matches on a case class it does not use that unapply method. If you try -Ybrowse:typer using both builtin case matching and your unapply method, you will see a very different syntax tree is produced (for the match) depending on which is used. You can also browse the later phases and see that the difference remains.

Why Scala does not use the builtin unapply I am not sure, though it may be for the reason you bring up. And how to get around it for your own unapply I have no idea. But this is the reason Scala seems to magically avoid the problem.

After experimenting, apparently this version of unapply works, though I'm a bit confused about why:

def unapply[A,B](value: BlackOrWhite[A,B]): Option[B] = value match {
    case w: White[_,_] => Some(w.right)
    case _ => None
}

The difficulty with your unapply is that somehow the compiler has to be convinced that if a White[A,B] extends a BlackOrWhite[C,D] then B is the same as D, which apparently the compiler is able to figure out in this version but not in yours. Not sure why.

北凤男飞 2025-01-01 15:12:55

我无法给您有关案例类匹配和取消申请之间区别的答案。然而,在他们的书(Odersky、Spoon、Venners)“Scala 编程”第 2 章 chptr 26.6“提取器与案例类”中,他们写道:

“它们(案例类)通常会导致更有效的模式匹配
比提取器,因为 Scala 编译器可以优化模式
案例类比提取器上的模式要好得多。这是
因为案例类的机制是固定的,而不适用的
或提取器中的 unapplySeq 方法几乎可以做任何事情。第三,
如果您的案例类继承自密封基类,则 Scala
编译器将检查我们的模式匹配是否详尽,并将
如果某个可能值的组合没有被覆盖,则会抱怨
图案。对于提取器来说,没有这样的详尽检查。”

这对我来说,这两者比乍一看所预期的有更大的不同,但没有具体说明确切的差异是什么。

I can't give you the answer on the difference between case class match and unapply. However in their book (Odersky, Spoon, Venners) "Programming in Scala" 2nd chptr 26.6 "Extractors versus case classes" they write:

"they (case classes) usually lead to more efficient pattern matches
than extractors, because the Scala compiler can optimize patterns over
case classes much better than patterns over extractors. This is
because the mechanisms of case classes are fixed, whereas an unapply
or unapplySeq method in an extractor could do almost anything. Third,
if your case classes inherit from a sealed base class, the Scala
compiler will check our pattern matches for exhaustiveness and will
complain if some combination of possible values is not covered by a
pattern. No such exhaustiveness checks are available for extractors."

Which says to me that the two are more different than one would expect at first glance, however without being specific on what the exact differences are.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文