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

发布于 2024-09-06 23:37:55 字数 1908 浏览 5 评论 0原文

我想实现一个类 C 来存储各种数字类型以及布尔值的值。此外,我希望能够在类型之间操作此类的实例,并在必要时进行转换 Int -->双精度型和布尔型 -> 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 <% T),其次,即使它能够在类型之后转换 this.n擦除两个+方法变得不明确。

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

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

提前致谢!

I'd like to implement a class C to store values of various numeric types, as well as boolean. Furthermore, I'd like to be able to operate on instances of this class, between types, converting where necessary Int --> Double and Boolean -> Int, i.e., to be able to add Boolean + Boolean, Int + Boolean, Boolean + Int, Int + Double, Double + Double etc., returning the smallest possible type (Int or Double) whenever possible.

So far I came up with this:

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
}

This works for some cases (1, 2, 3) but doesn't for (4, 5). The reason is that there is implicit widening of type from lower to higher, but not the other way. In a way, the method

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

somehow needs to have a partner method that would look something like:

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

but that does not compile for two reasons, firstly that the compiler cannot convert this.n to type T (even though we specify view bound A <% T), and, secondly, that even if it were able to convert this.n, after type erasure the two + methods become ambiguous.

Sorry this is so long. Any help would be much appreciated! Otherwise it seems I have to write out all the operations between all the types explicitly. And it would get hairy if I had to add extra types (Complex is next on the menu...).

Maybe someone has another way to achieve all this altogether? Feels like there's something simple I'm overlooking.

Thanks in advance!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

各自安好 2024-09-13 23:37:55

好吧,丹尼尔!

我已将解决方案限制为忽略布尔值,并且仅适用于具有弱最小上界且具有 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< /code>,它是由类型推断器根据隐式值的可用性计算出来的,作为隐式参数 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

Okay then, Daniel!

I've restricted the solution to ignore Boolean, and only work with AnyVals that have a weak Least Upper Bound that has an instance of Numeric. These restrictions are arbitrary, you could remove them and encode your own weak conformance relationship between types -- the implementation of a2b and a2c could perform some conversion.

Its interesting to consider how implicit parameters can simulate inheritance (passing implicit parameters of type (Derived => Base) or Weak Conformance. They are really powerful, especially when the type inferencer helps you out.

First, we need a type class to represent the Weak Least Upper Bound of all pairs of types A and B that we are interested in.

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)
  }
}

The method unify returns type C, which is figured out by the type inferencer based on availability of implicit values to provide as the implicit argument ev.

We can plug this into your wrapper class C as follows, also requiring a Numeric[WeakLub] so we can add the values.

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)
  }
}

And finally, putting it all together:

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
月光色 2024-09-13 23:37:55

有一种方法可以做到这一点,但我将其留给retronym 来解释一下,因为他写了这个解决方案。 :-)

There's a way to do that, but I'll leave it to retronym to explain it, since he wrote this solution. :-)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文