【问题标题】:How to set up implicit conversion to allow arithmetic between numeric types?如何设置隐式转换以允许数值类型之间的算术运算?
【发布时间】:2010-06-21 22:37:44
【问题描述】:

我想实现一个类C 来存储各种数字类型的值以及布尔值。此外,我希望能够在类型之间对此类的实例进行操作,在必要时转换Int --> DoubleBoolean -> Int,即能够添加Boolean + BooleanInt + BooleanBoolean + IntInt + DoubleDouble + 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(即使我们指定了视图绑定A &lt;% T),其次,即使它能够转换this.n,在类型擦除之后,两个 + 方法变得不明确。

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

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

提前致谢!

【问题讨论】:

    标签: scala types numeric


    【解决方案1】:

    好的,丹尼尔!

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

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

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

    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
    

    【讨论】:

    • 啊哈!我明白这是如何工作的 - 谢谢!添加Boolean 确实很容易,而且Numeric 的LUB 对于Complex 的更改不会太难。我很好奇 - 您似乎已经准备好了这个解决方案,您是在什么情况下遇到这个问题的?我还尝试测试此解决方案的性能,总结一百万 C[Int] 似乎比一百万 Int 慢五倍......关于如何开始优化这个有什么想法吗?跨度>
    • 我在 IRC 上与 @extempore 讨论时围绕这个问题进行了讨论,它并没有解决我的特定问题。考虑到间接性,5 倍开销听起来还不错。您可以直接调用wc.a2bwc.a2c,而不是使用unify 方法。目前Numeric#plus 的输入和输出都被装箱了,希望未来版本的 Scala 能找到一种方法来解决@specialize 这个问题。
    • @retronym 实际上......我发起了那个讨论。 :-)
    • @ostolop Retronym 的解决方案来自我发起的有关相关问题的讨论。我的意图是获取List[AnyVal] 的数字并对其执行foldLeft。最后,我放弃了这种方法,因为对于整体问题有一个更简单的解决方案,可以完全避免它。
    • 明白了,我知道你是从哪里来的。我还不知道我们的整体问题是否会有更简单的解决方案:我们正在用 Scala 解释另一种语言,它有一个有趣的类型系统,旨在处理数据;它几乎完全基于列表,所以所有对象都是一些原始类型(int、double、character)的列表容器,它们之间的转换对用户来说相当透明......听起来很熟悉? :)
    【解决方案2】:

    有一个way 可以做到这一点,但我会留给retronym 来解释它,因为他编写了这个解决方案。 :-)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-04-04
      • 1970-01-01
      • 2010-11-04
      • 1970-01-01
      • 1970-01-01
      • 2017-03-23
      • 1970-01-01
      相关资源
      最近更新 更多