【问题标题】:How do I inherit shared code in a Scala unapply function?如何在 Scala unapply 函数中继承共享代码?
【发布时间】:2013-06-30 02:38:08
【问题描述】:

我有带有一些样板的 Scala 代码,我认为它是 Scala,所以我一定做错了什么。我需要一些帮助来弄清楚如何删除冗余。

trait Number {
  val x: Int
}

case class EvenNumber(x: Int) extends Number

object EvenNumber {
  def unapply(s: String): Option[EvenNumber] = {
    val x = s.toInt
    if (x % 2 == 0) Some(EvenNumber(x))
    else None
  }
}

case class OddNumber(x: Int) extends Number

object OddNumber {
  def unapply(s: String): Option[OddNumber] = {
    val x = s.toInt
    if (x % 2 == 1) Some(OddNumber(x))
    else None
  }
}

在这个简单的例子中,偶数和奇数是一般数字类型的子类型。偶数和奇数都有提取器,可以从字符串中创建它们。这支持以下用例。

scala> "4" match {case EvenNumber(n) => n;case _ => None}
// returns EvenNumber(4)

scala> "5" match {case EvenNumber(n) => n;case _ => None}
// returns None

scala> "4" match {case OddNumber(n) => n;case _ => None}
// returns None

scala> "5" match {case OddNumber(n) => n;case _ => None}
// returns OddNumber(5)

除了x % 2 操作的结果(01)和提取的类型(EvenNumberOddNumber)之外,这两个提取器的源代码是相同的。我希望能够编写一次源代码并对这两个值进行参数化,但我不知道如何。我尝试了各种类型参数化都无济于事。

Stackoverflow 问题“How to use extractor in polymorphic unapply?”是相关但不同的,因为我的实现类不是通过它们包含的类型来区分,而是通过它们识别的字符串输入来区分。


除了原始帖子之外,这是包含我收到的 cmets 的代码的修订版本。 (通常情况下,第一轮答案帮助我弄清楚我的真正问题是什么。)

import scala.util.Try

trait Number {
  val x: Int
}

object NumberParser {
  def parse[N <: Number](s: String, remainder: Int, n: Int => N): Option[N] =
    Try {s.toInt}.toOption.filter(_ % 2 == remainder).map(n(_))
}

case class EvenNumber(x: Int) extends Number

object EvenNumber {
  def unapply(s: String): Option[EvenNumber] = NumberParser.parse(s, 0, EvenNumber(_))
}

case class OddNumber(x: Int) extends Number

object OddNumber {
  def unapply(s: String): Option[OddNumber] = NumberParser.parse(s, 1, OddNumber(_))
}

分解出静态NumberParser.parse 函数是一个合理的解决方案。我仍然希望有语法糖来避免我在所有案例类中重复 unapply 行,因为在一个更复杂的示例中,有两个以上可能会变得丑陋。有谁知道这样做的方法吗?

更关键的是,我真正想要支持的用例如下。

 scala> "5" match {case EvenNumber(n) =>n;case OddNumber(n) => n;case _ => None}
 // returns OddNumber(5)

 scala> "4" match {case EvenNumber(n) =>n;case OddNumber(n) => n;case _ => None}
 // returns EvenNumber(4)

 scala> "x" match {case EvenNumber(n) =>n;case OddNumber(n) => n;case _ => None}
 // returns None

同样,这对于两种情况都很好,但在有两个以上的不同应用程序中,它可能变得难以管理。我想写一个案例

s match {case Number(n) => n; case _ => None}

如上返回OddNumber(5)EvenNumber(4)None。 我不知道如何编写我的Number 超类型来支持这一点。在 Scala 中可以吗?


编辑:在“Runtime Polymorphism with Scala Extractors”中写了我的最终答案的描述和附加评论。

【问题讨论】:

  • 查看我对Number 实施的更新答案。

标签: scala polymorphism unapply


【解决方案1】:

为什么要继承?

object Mod2Number {
  def parse[A <: Number](s: String, i: Int, n: Int => A) = {
    val x = s.toInt
    if (x % 2 == i) Some(n(x))
    else None
  }
}

case class EvenNumber(x: Int) extends Number
object EvenNumber {
  def unapply(s: String) = Mod2Number.parse(s, 0, n => EvenNumber(n))
}

但是,即使那噪音太大,您也可以更进一步:

trait Makes[A <: Number] { 
  def apply(i: Int): A
  def mod: Int
  def unapply(s: String): Option[A] = {
    val x = s.toInt
    if (x % 2 == mod) Some(apply(x))
    else None
  }
}

case class EvenNumber(x: Int) extends Number
object EvenNumber extends Makes[EvenNumber] { def mod = 0 }

【讨论】:

  • 这行得通,并且大致是我在导致这篇文章的原始应用程序中提出的解决方案。然而,在那个原始应用程序中,只有两个具体的类,但它们的列表很长,所以即使是 unapply 函数的重复代码也会变得难看。
  • @W.P.McNeill - 我已经编辑了一个更丑陋的解决方案。
【解决方案2】:

我想你应该在 toInt 上捕获异常 - 模式匹配中的异常很奇怪。

object EvenNumber {
  def unapply(s: String): Option[Int] = Try{s.toInt}.toOption.filter{_ % 2 == 0}
}

object OddNumber {
  def unapply(s: String): Option[Int] = Try{s.toInt}.toOption.filter{_ % 2 == 1}
}

您可以提取类似的代码,但我认为它在这里没有用:

class IntFilter(f: Int => Boolean) {
  def unapply(s: String): Option[Int] = Try{s.toInt}.toOption.filter(f)
}

object EvenNumber extend IntFilter(_ % 2 == 0)

object OddNumber extend IntFilter(_ % 2 == 1)

对于已编辑的问题:

s match {case Number(n) => n; case _ => None}

你可以像这样创建对象Number

object Number{
  def unapply(s: String): Option[Number] = Try{s.toInt}.toOption.collect{
    case i if i % 2 == 0 => EvenNumber(i)
    case i if i % 2 == 1 => OddNumber(i)
  }
}

【讨论】:

  • 请注意filter 上的Try 会带来巨大 的性能损失,因为您必须创建整个堆栈跟踪才能拒绝您的价值!在你toOption之后过滤更好。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多