Scala隐式转换函数名称冲突

6

我正在使用Scala中的一个简单的复数类,并希望创建一个能够在复数、双精度浮点数和整数之间运作的加法函数。下面是一个简单的工作解决方案示例:

case class Complex(re: Double, im: Double) 

implicit def toComplex[A](n: A)(implicit f: A => Double): Complex = Complex(n, 0)

implicit class NumberWithAdd[A](n: A)(implicit f: A => Complex) {
    def add(m: Complex) = Complex(n.re + m.re, n.im + m.im)
}

注意,我故意没有在复杂情况类中包括添加功能。利用上述内容,我可以完成以下所有操作:
scala> val z = Complex(1, 2); val w = Complex(2, 3)
z: Complex = Complex(1.0,2.0)
w: Complex = Complex(2.0,3.0)

scala> z add w
res5: Complex = Complex(3.0,5.0)

scala> z add 1
res6: Complex = Complex(2.0,2.0)

scala> 1 add z
res7: Complex = Complex(2.0,2.0)

我想使用“+”代替“add”,但是这并不起作用。我遇到了以下错误:

Error:(14, 4) value + is not a member of A$A288.this.Complex
              z + 1
               ^

无论是z + w还是1 + z,都可以正常工作。

我想知道为什么将函数名称从“add”更改为“+”会破坏它?是否有其他方式可以获得此功能(而不仅仅是将添加函数放入复杂案例类中)?任何帮助将不胜感激。

编辑-动机

我正在尝试使用幺半群和其他代数结构。我希望能够将“...WithAdd”函数推广到任何具有相应幺半群的类上自动工作:

trait Monoid[A] {
    val identity: A

    def op(x: A, y: A): A
}

implicit class withOp[A](n: A)(implicit val monoid: Monoid[A]) {
    def +(m: A): A = monoid.op(n, m)
}

case class Complex(re: Double, im: Double) {
    override def toString: String = re + " + " + im + "i"
}

class ComplexMonoid extends Monoid[Complex] {
    val identity = Complex(0, 0)

    def op(z: Complex, w: Complex): Complex = {
        Complex(z.re + w.re, z.im + w.im)
    }
}

implicit val complexMonoid = new ComplexMonoid

使用上面的代码,我现在可以执行Complex(1, 2) + Complex(3, 1),得到Complex = 4.0 + 3.0i。这对于代码重用非常有用,因为我现在可以添加额外的函数到Monoid和withAdd函数中(例如将op应用n次于一个元素,给出乘法的幂函数),并且它会适用于任何具有相应monoid的case类。只有当涉及到复数和试图结合双精度浮点数、整数等时,我才遇到上述问题。

无法通过隐式转换添加已经存在的方法。https://dev59.com/clLTa4cB1Zd3GeqPcamc#4444122 - Jiri Kremser
你链接的问题试图隐式地覆盖具有相同签名的函数。Int、Double或我的Complex类中都没有一个带有类型为Complex的单个参数的+方法。 - Tom
1个回答

0

我会使用普通的class,而不是case class。这样就很容易创建方法来添加或减去这些复数,例如:

class Complex(val real : Double, val imag : Double) {

  def +(that: Complex) =
            new Complex(this.real + that.real, this.imag + that.imag)

  def -(that: Complex) =
            new Complex(this.real - that.real, this.imag - that.imag)

  override def toString = real + " + " + imag + "i"

}

源页面所示,现在它将支持类似于运算符重载的东西(实际上不是,因为+-是函数而不是运算符)。

implicit class NumberWithAdd及其方法+的问题在于相同的方法也存在于数字类中,例如IntDoubleNumberWithAdd+方法基本上允许您从可以转换为Complex的数字开始,并向该第一个项目添加Complex对象。也就是说,左手值可以是任何东西(只要它可以被转换),右手值必须是Complex

这对于w + z(不需要转换w)和1 + z(可以隐式地将Int转换为Complex)非常有效。但是对于z + 1,它会失败,因为在类Complex中没有+可用。 由于z + 1实际上是z.+(1),Scala将寻找其他可能匹配的类别Complex可以转换成+(i: Int)函数。 它还检查NumberWithAdd,该函数确实具有+函数,但该函数需要一个Complex作为右手值。(它将匹配需要Int作为右手值的函数。)存在其他名为+的函数,接受Int,但是从Complex到其它函数所需的左手值没有转换。

+的同样定义适用于它在(case)类Complex中的情况。在这种情况下,w + zz + 1都只是使用了那个定义。1 + z的情况现在稍微复杂了一些。因为Int没有接受Complex值的+函数,Scala会找到在Complex中存在的函数,并确定是否可以将Int转换成Complex。这可以通过隐式函数来实现,进行转换并执行函数。

当类NumberWithAdd中的函数+被重命名为add时,与Int中的函数混淆就不存在了,因为Int没有+函数。因此Scala会更努力地尝试应用函数add,并执行IntComplex的转换。即使您尝试1 add 2,它也会进行转换。

注意:我的解释可能无法完全描述实际的内部工作原理。

我知道这是一个解决方案,但我在我的问题中提到我不想将函数包含在 case 类中。我的问题的一部分是是否有其他方法来实现这个。 - Tom
@Tom 我对我的回答进行了扩展。这应该会更加清晰明了。 - Arjan
因此,看起来如果我们不能修改class Complex,那么我们只需要在NumberWithAdd类中添加def +(i: Int): Complex = this.+(toComplex(i))就可以使z + 1正常工作。但这只适用于z + 1(Int)。我们还需要添加相同类型的内容来处理z + 1L(Long)、z + 1.1(Double)等等。_叹气_ - jwvh
@Arjan 感谢您花时间详细阐述您的答案。看起来,获得一个不同的解决方案比我想象的要困难。 - Tom
@jwvh 我猜类似 IntDouble 这样的类之所以有几个函数 +,是因为它们接受不同的参数(并且具有不同的返回类型)。 - Arjan
@jvwh 您的解决方案可行,但可能会变得有点混乱。我将在我的问题中添加一个关于此背后动机的部分,这可能会阐明我的约束条件,即不能将其放在Complex类中(也说明了为什么您的解决方案会使事情变得更加复杂)。 - Tom

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接