【发布时间】: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
如果有人有一些很好的示例材料(教程)来说明那些该死的讨厌的可变参数如何在 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