【问题标题】:Scala: Matching vararg (repeating arguments) by type of stored elementsScala:按存储元素的类型匹配可变参数(重复参数)
【发布时间】:2020-08-21 08:16:27
【问题描述】:

我最近开始学习 Scala,目前正在学习教程。我想要 2 个 Rational Arithmetics 的实现。我有 IRational 特质和 2 个实现它的类:Rational 和 RationalAbstraction。大多数功能是相同的,因此我在 trait 中实现了默认行为,但我需要获得正确的构造函数——对于 Rational 或 RationalAbstraction。为此我有一个功能:

def constructorImpl(numerator: Int, denominator: Int, first: IRational, irationals: IRational*): IRational = {
  println(s"first class: ${first.getClass.getSimpleName}, irationals class: ${irationals.getClass.getSimpleName}")
  first match {
    case rational: Rational => irationals match {
      case rationals: Seq[Rational] => new Rational(numerator, denominator)
      case _ => throw new UnimplementedCaseException(this, "constructorImpl", first +: irationals: _*)
    }
    case abstraction: RationalAbstraction => irationals match {
      case abstractions: Seq[RationalAbstraction] => new RationalAbstraction(numerator, denominator)
      case _ => throw new UnimplementedCaseException(this, "constructorImpl", first +: irationals: _*)
    }
  case _ => throw new UnimplementedCaseException(this, "constructorImpl", first +: irationals: _*)
  }
}

很遗憾,它不起作用。
case rationals: Seq[Rational] => new Rational(numerator, denominator)
不匹配包含 Rational 的可变参数,也允许 RationalAbstraction。
这是为什么? 如何按类型匹配可变参数?
我是否需要编写函数来解开无理数:_* 并检查头部的(第一个元素)类型?

这是项目 github 存储库: https://github.com/axal25/LearnScalaMavenBasics

调用函数测试用例[256行]: https://github.com/axal25/LearnScalaMavenBasics/blob/master/src/main/scala/org/exercises/scala/ool/ObjectOrientedProgramming.scala

功能实现【106行】: https://github.com/axal25/LearnScalaMavenBasics/blob/master/src/main/scala/org/exercises/scala/ool/arith/ration/IRational.scala

如果有人有一些很好的示例材料(教程)来说明那些该死的讨厌的可变参数如何在 Scala 中工作,我将不胜感激。

编辑:
constructorImpl 方法的目的是为算术运算(add/+、sub/-.mul/*、div//)选择正确的构造函数。
first: Irational 参数是运算的第一个参数(add , sub, mul, div)。
irationals: Irational* 参数是操作的第 2、3、... n 个参数(add、sub、mul、div)。
有些操作需要 2 个 IRational 实现对象,有些需要 1 个 IRational impl 对象。例如 Int,但总是至少有 1 个 IRational impl 对象。因此,选择正确的构造函数取决于那些 IRational impl 对象,并要求它们具有相同的实现。如果 IRational 实现的 2 个(或更多)对象具有不同的实现(Rational 和 RationalAbstraction 的组合),我们不知道要调用哪个构造函数,所以应该抛出异常。

此层次结构中的解决方案(不使用泛型):

  def constructorImpl(numerator: Int, denominator: Int, first: IRational, irationals: IRational*): IRational = {
    println(s"first class: ${first.getClass.getSimpleName}, irationals class: ${irationals.getClass.getSimpleName}")

    @scala.annotation.tailrec
    def isSeqElementsOfTypeSameAsFirst(first: Any, irationals: Any*): Boolean = irationals match {
      case Seq() => true
      case Seq(head, tail@_*) => {
        if (first.getClass == head.getClass) isSeqElementsOfTypeSameAsFirst(first, tail: _*)
        else false
      }
      case _ => false
    }

    if (isSeqElementsOfTypeSameAsFirst(first, irationals: _*)) {
      first match {
        case rational: Rational => new Rational(numerator, denominator)
        case abstraction: RationalAbstraction => new RationalAbstraction(numerator, denominator)
        case _ => throw new UnimplementedCaseException(this, "constructorImpl", first +: irationals: _*)
      }
    }
    else throw new MixingIRationalImplementationException(first, irationals: _*)
  }

问题提交:https://github.com/axal25/LearnScalaMavenBasics/commit/c5a113b0361d8632bb39bbfc7ed7f7cd329a2da1
解决方案提交:https://github.com/axal25/LearnScalaMavenBasics/commit/1920128ba2aedac4fa9671311ec56dcc09dc7483

【问题讨论】:

  • 您将从不同的位置调用不同的运算符,并且每个位置都知道需要多少个参数,因此重载似乎比单个 varargs 方法更好地处理这个问题。还可以考虑将关键操作放在trait 中,并在具体类中提供实现。
  • 也许你想要def foo[R <: IRational](first: R, irrationals: Seq[R]): R 这样的东西,这样你可以确保可变参数在编译时是相同的类型,而不是在运行时抛出异常。无论如何,你似乎有一些失败的抽象,你可能想了解更多关于 OOP 并重新思考你的设计。
  • 就像我之前所说的,我最近开始学习 Scala。学习新语言并同时正确使用 FP 和 OOP 对我来说并非易事。在这个时候,我并不真正理解你写的函数定义(我猜它是一个通用函数),我正在即兴创作并试图通过修补来学习。我专注于学习 Scala 的 caviouts(或者更确切地说是基础知识)。如何调用 R 类型的构造函数(带有 2 个参数)?
  • @Tim 我不认为以完全相同的方式实现 trait IRational 的抽象方法是有意义的。显然,我可以在 IRational constructorImpl 中定义抽象方法并在 Rational 和 RationalAbstraction 中实现它,但它只依赖于 1 个参数。我认为 Luis Miguel Mejía Suárez 的想法(如果它真的是通用方法)是最好的方法(总的来说)。但是我知道我想弄清楚可变参数。我相信我会有时间学习泛型。
  • 如果您只是学习 Scala,那么 varargs 是一个不好的起点。我已经在这里回答了这个问题,所以最好创建一个关于解决方案其他方面的新问题。但我不会将抽象方法的想法作为一个想法太快地否定,因为它们是为同一基类的两个子类实现不同行为的标准方法,这就是您的代码正在做的事情。

标签: scala types arguments repeat variadic-functions


【解决方案1】:

我是否需要编写函数来解开无理数:_* 并检查头部的(第一个元素)类型?

是的,您需要检查每个元素。 irationals 可以包含IRational 的任何子类的元素,并且它们不必都是相同的子类型,因此您需要检查每一个。但是不清楚irationals的目的是什么,所以问题需要详细一点。

【讨论】:

  • 嗨,我在我的问题中添加了编辑。我希望它阐明了此功能的目的。你能解释一下为什么匹配对可变参数不起作用吗?如果匹配不起作用 - 函数类型要求的参数是否有效?您是说 irationals: IRational* 保证序列的元素是 IRational 类型,但为什么 case rationals: Seq[Rational] => 不保证元素是 Rational 类型。
  • 匹配只适用于类型,而Seq[IRational] 的实例仅包含Rational 类型的元素这一事实并未封装在类型信息中。由于类型擦除,也无法匹配集合中的元素类型。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-10-31
  • 2016-05-12
  • 2014-07-01
  • 2021-09-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多