erikerlandson/coulomb

constant-factor syntax support depends on scala 3.4 (?)

Opened this issue · 1 comments

Syntax support for constant factor multiplication or division, aka 2 * q or q / 2, etc, using the new scala 3 extension feature, currently is not feasible.

SIP-54 will probably fix this, which lands in scala 3.4
scala/scala3#17660

Here is a simplified example of the failure:

object repro:
    case class Repro[V](value: V)

    object Repro:
        extension[V](r: Repro[V])
            inline def *(rr: Repro[V])(using
                num: scala.math.Numeric[V]
            ): Repro[V] =
                Repro(num.times(r.value, rr.value))

            inline def /(rr: Repro[V])(using
                num: scala.math.Fractional[V]
            ): Repro[V] =
                Repro(num.div(r.value, rr.value))

object extend:
    import repro.Repro

    extension(v: Int)
        inline def *[V](r: Repro[V])(using
            num: scala.math.Numeric[V]
        ): Repro[V] =
            Repro(num.times(num.fromInt(v), r.value))
 
        inline def /[V](r: Repro[V])(using
            num: scala.math.Fractional[V]
        ): Repro[V] =
            Repro(num.times(num.fromInt(v), r.value))

    extension[V](r: Repro[V])
       inline def *(v: Int)(using
            num: scala.math.Numeric[V]
        ): Repro[V] =
            Repro(num.times(r.value, num.fromInt(v)))

       inline def /(v: Int)(using
            num: scala.math.Fractional[V]
        ): Repro[V] =
            Repro(num.div(r.value, num.fromInt(v)))
scala> val r = Repro(2.0)
val r: coulomb.ops.syntax.coef.repro.Repro[Double] = Repro(2.0)
                                                                                                                                                                                                    
scala> 2 * r
val res0: coulomb.ops.syntax.coef.repro.Repro[Double] = Repro(4.0)
                                                                                                                                                                                                    
scala> r * r
-- [E007] Type Mismatch Error: -------------------------------------------------
1 |r * r
  |    ^
  |    Found:    (r : coulomb.ops.syntax.coef.repro.Repro[Double])
  |    Required: Int
  |
  | longer explanation available when compiling with `-explain`
1 error found
                                                                                                                                                                                                    
scala> r / 2
val res1: coulomb.ops.syntax.coef.repro.Repro[Double] = Repro(1.0)
                                                                                                                                                                                                    
scala> 2 / r
val res2: coulomb.ops.syntax.coef.repro.Repro[Double] = Repro(4.0)
                                                                                                                                                                                                    
scala> r / r
-- [E007] Type Mismatch Error: -------------------------------------------------
1 |r / r
  |    ^
  |    Found:    (r : coulomb.ops.syntax.coef.repro.Repro[Double])
  |    Required: Int
  |
  | longer explanation available when compiling with `-explain`
1 error found

Here's a prototype of what I'm thinking:

package coulomb.ops.syntax.coef

object all:
    export int.*
    export double.*

object int:
    extension(v: Int)
        inline def *[V, U](q: Quantity[V, U])(using
            vc: ValueConversion[Int, V],
            alg: MultiplicativeSemigroup[V]
        ): Quantity[V, U] =
            alg.times(vc(v), q.value).withUnit[U]

        transparent inline def /[V, U](q: Quantity[V, U])(using
            vc: ValueConversion[Int, V],
            alg: MultiplicativeGroup[V],
            su: SimplifiedUnit[1 / U]
        ): Quantity[V, su.UO] =
            alg.div(vc(v), q.value).withUnit[su.UO]

    extension[V, U](q: Quantity[V, U])
        inline def *(v: Int)(using
            vc: ValueConversion[Int, V],
            alg: MultiplicativeSemigroup[V]
        ): Quantity[V, U] =
            alg.times(q.value, vc(v)).withUnit[U]
 
        transparent inline def /(v: Int)(using
            vc: ValueConversion[Int, V],
            alg: MultiplicativeGroup[V]
        ): Quantity[V, U] =
            alg.div(q.value, vc(v)).withUnit[U]

object double:
    extension(v: Double)
        inline def *[V, U](q: Quantity[V, U])(using
            vc: ValueConversion[Double, V],
            alg: MultiplicativeSemigroup[V]
        ): Quantity[V, U] =
            alg.times(vc(v), q.value).withUnit[U]

    extension[V, U](q: Quantity[V, U])
        inline def *(v: Double)(using
            vc: ValueConversion[Double, V],
            alg: MultiplicativeSemigroup[V]
        ): Quantity[V, U] =
            alg.times(q.value, vc(v)).withUnit[U]