scala - 如何设置隐式转换以允许数字类型之间的算术?

标签 scala types numeric

我想实现一个类 C存储各种数字类型的值以及 bool 值。此外,我希望能够在类型之间操作此类的实例,并在必要时进行转换 Int --> DoubleBoolean -> Int ,即能够添加 Boolean + Boolean , Int + Boolean , Boolean + Int , Int + Double , Double + Double等等,尽可能返回尽可能小的类型( IntDouble )。

到目前为止我想出了这个:

abstract class SemiGroup[A] { def add(x:A, y:A):A }

class C[A] (val n:A) (implicit val s:SemiGroup[A]) {
  def +[T <% A](that:C[T]) = s.add(this.n, that.n)
}

object Test extends Application {
  implicit object IntSemiGroup extends SemiGroup[Int] { 
    def add(x: Int, y: Int):Int = x + y 
  }

  implicit object DoubleSemiGroup extends SemiGroup[Double] { 
    def add(x: Double, y: Double):Double = x + y 
  }

  implicit object BooleanSemiGroup extends SemiGroup[Boolean] { 
    def add(x: Boolean, y: Boolean):Boolean = true;
  }

  implicit def bool2int(b:Boolean):Int = if(b) 1 else 0

  val n = new C[Int](10)
  val d = new C[Double](10.5)
  val b = new C[Boolean](true)

  println(d + n)    // [1]
  println(n + n)    // [2]
  println(n + b)    // [3]
  // println(n + d)    [4] XXX - no implicit conversion of Double to Int exists
  // println(b + n)    [5] XXX - no implicit conversion of Int to Boolean exists
}

这适用于某些情况 (1, 2, 3),但不适用于 (4, 5)。原因是存在从低级到高级的隐含类型扩展,但反之则不然。从某种程度上来说,方法

def +[T <% A](that:C[T]) = s.add(this.n, that.n)

不知何故需要有一个看起来像这样的合作伙伴方法:

def +[T, A <% T](that:C[T]):T = that.s.add(this.n, that.n)

但是由于两个原因无法编译,首先编译器无法转换 this.n输入T (即使我们指定 View 绑定(bind) A <% T ),其次,即使它能够转换 this.n ,类型删除后两个 +方法变得不明确。

抱歉,这篇文章太长了。任何帮助将非常感激!否则,我似乎必须显式地写出所有类型之间的所有操作。如果我必须添加额外的类型(Complex 是菜单上的下一个......),它会变得很麻烦。

也许有人有另一种方法来实现这一切?感觉好像有一些简单的事情我忽略了。

提前致谢!

最佳答案

好吧,丹尼尔!

我已将解决方案限制为忽略 bool 值,并且仅适用于 AnyVals具有弱最小上界,其实例为 Numeric 。这些限制是任意的,您可以删除它们并编码您自己的类型之间的弱一致性关系 - a2b 的实现和a2c可以执行一些转换。

考虑隐式参数如何模拟继承(传递类型 (Derived => Base) 或弱一致性的隐式参数)很有趣。它们非常强大,尤其是当类型推断器可以帮助您时。

首先,我们需要一个类型类来表示所有类型对 A 的弱最小上界和B我们感兴趣的。

sealed trait WeakConformance[A <: AnyVal, B <: AnyVal, C] {
  implicit def aToC(a: A): C

  implicit def bToC(b: B): C
}

object WeakConformance {
  implicit def SameSame[T <: AnyVal]: WeakConformance[T, T, T] = new WeakConformance[T, T, T] {
    implicit def aToC(a: T): T = a

    implicit def bToC(b: T): T = b
  }

  implicit def IntDouble: WeakConformance[Int, Double, Double] = new WeakConformance[Int, Double, Double] {
    implicit def aToC(a: Int) = a

    implicit def bToC(b: Double) = b
  }

  implicit def DoubleInt: WeakConformance[Double, Int, Double] = new WeakConformance[Double, Int, Double] {
    implicit def aToC(a: Double) = a

    implicit def bToC(b: Int) = b
  }

  // More instances go here!


  def unify[A <: AnyVal, B <: AnyVal, C](a: A, b: B)(implicit ev: WeakConformance[A, B, C]): (C, C) = {
    import ev._
    (a: C, b: C)
  }
}

方法unify返回类型 C ,这是由类型推断器根据作为隐式参数提供的隐式值的可用性计算出的 ev .

我们可以将其插入到您的包装类 C 中,如下所示,还需要 Numeric[WeakLub]这样我们就可以添加值。

case class C[A <: AnyVal](val value:A) {
  import WeakConformance.unify
  def +[B <: AnyVal, WeakLub <: AnyVal](that:C[B])(implicit wc: WeakConformance[A, B, WeakLub], num: Numeric[WeakLub]): C[WeakLub] = { 
    val w = unify(value, that.value) match { case (x, y) => num.plus(x, y)}; 
    new C[WeakLub](w)
  }
}

最后,把它们放在一起:

object Test extends Application {
  val n = new C[Int](10)
  val d = new C[Double](10.5)

  // The type ascriptions aren't necessary, they are just here to 
  // prove the static type is the Weak LUB of the two sides.
  println(d + n: C[Double]) // C(20.5)
  println(n + n: C[Int])    // C(20)
  println(n + d: C[Double]) // C(20.5)
}

Test

关于scala - 如何设置隐式转换以允许数字类型之间的算术?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3088979/

相关文章:

mysql - 何时使用不同的数字数据类型 - TINYINT/SMALLINT/MEDIUMINT/INT/BIGINT - MySQL

Ada 中的 double ?

sql-server - 主键的 Sql 数据类型 - SQL Server?

c# - 将 int[] 转换为字节 : How to look at int[] as it was byte[]?

sqlite - 如何将 csv 文件导入到具有正确数据类型的 sqlite

c# - 将 '.ToString()' 与数值变量一起使用

scala - 如何通过 1 的位置列表解码 0 和 1 的列表?

scala - 即使在使用类型投影后为 Either 声明 Functor 时出现编译错误

scala - 为什么在没有子类型证据的情况下,这个多函数案例的隐式推导会失败?

scala - 'self' 在 Scala actor 中如何工作?