Scala 如何设置隐式转换以允许数值类型之间的算术运算?

Scala 如何设置隐式转换以允许数值类型之间的算术运算?,scala,types,numeric,Scala,Types,Numeric,我想实现一个类C,以存储各种数字类型的值以及布尔值。此外,我希望能够在类型之间对此类的实例进行操作,在必要时转换Int>Double和Boolean->Int,即能够添加Boolean+Boolean,Int+Boolean,Boolean+Int,Int+Double,Double+Double等。,尽可能返回尽可能小的类型(Int或Double) 到目前为止,我得出了以下结论: abstract class SemiGroup[A] { def add(x:A, y:A):A } clas

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

到目前为止,我得出了以下结论:

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
}
抽象类半群[A]{def add(x:A,y:A):A}
类C[A](val n:A)(隐式val s:半群[A]){
def+[T这样做是有必要的,但我会让它来解释,因为他写了这个解决方案。:-

好吧,丹尼尔

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

有趣的是考虑隐式参数如何模拟继承(传递类型的隐式参数(派生=基)或弱一致性)。它们确实是强大的,特别是当类型推断器帮助你的时候。

首先,我们需要一个类型类来表示我们感兴趣的所有类型对
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)
  }
}
最后,把这一切放在一起:

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

啊哈!我明白这是怎么回事了-谢谢!添加一个
布尔值
,事实证明很容易,而且
数字
的润滑油对于
复杂的
来说不会太难更改。我很好奇-你似乎已经准备好了这个解决方案,在什么情况下遇到这个问题了?我还试着测试了性能此解决方案的rmance和总计一百万
C[Int]
似乎比一百万
Int
慢五倍左右……你有没有想过如何开始优化它?我在IRC上与@extempore的讨论中就讨论过这个问题,它并没有解决我的一个特定问题。考虑到间接性,5x开销听起来不太糟糕。你可以调用
wc.a2b
,然后直接使用
wc.a2c
而不是使用
unify
方法。目前
Numeric#plus
的输入和输出都是装箱的,希望未来版本的Scala能够找到一种方法来@specialized解决这个问题。@retronym实际上…我发起了这个讨论。:-)@ostolop Retronym的解决方案来自于我发起的关于一个相关问题的讨论。我的意图是获取一个
列表[AnyVal]
的数字,并对其执行
折叠操作。最后,我放弃了这种方法,因为有一种更简单的解决方案可以完全避免这个问题。明白了,我知道你是从哪里来的。我还不知道我们的整个问题是否会有一个更简单的解决方案:我们正在用Scala另一种语言进行解释,这是有一个有趣的类型系统,旨在处理数据;它几乎完全基于列表,因此所有对象都是一些基本类型(int、double、character)的列表容器,它们之间的转换对用户来说相当透明……听起来很熟悉吗?:)
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)
  }
}
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