unclear to me if this is in fact the same question as here or here, apologies if this is a duplicate.
i would like to define a type Ordinate
which is simply an Int under-the-hood:
package world
opaque type Ordinate = Int
given Ordering[Ordinate] with {
def compare(x: Ordinate, y: Ordinate): Int = x.compare(y)
}
i would like to be able to leverage the Numeric[Int]
and Ordering[Int]
methods so that it would be easy to define methods such as
package world
import Ordinate.given
class Boundary(dims: List[(Ordinate, Ordinate)]) {
def contains(o: Ordinate, dimension: Int): Boolean = {
val (min, max) = dims(dimension)
min <= o && o <= max
}
}
...forgetting for the meantime that this would blow up if dims was empty, dimension < 0
or dims.length <= dimension
.
when i try and set this up, i get compiler errors at the call site:
value <= is not a member of world.Ordinate, but could be made available as an extension method.
One of the following imports might fix the problem:
import world.given_Ordering_Ordinate.mkOrderingOps
import math.Ordering.Implicits.infixOrderingOps
import math.Ordered.orderingToOrdered
more generally, it would be wicked cool if this were the case without any special given imports for files in the same package as Ordinate
and even better, across the codebase. but that may be an anti-pattern that i've carried forward from my Scala 2 coding.
explicit given imports may be a better pattern but i'm still learning Scala 3 from Scala 2 here. i know if i created an implicit val o = Ordering.by(...)
in the companion object of Ordinate
in Scala 2, with Ordinate
as a value class, i would get the effect i'm looking for (zero-cost type abstraction numeric behaviors).
anyhow, i'm guessing i'm just missing a small detail here, thank you for reading and for any help.
CodePudding user response:
Scala 3 has revised the rules for infix operators so that the author must (explicitly) expose infix operations such as x: T <= y: T
for some custom type T
.
I've found two ways to address this for an opaque type, both with drawbacks:
- at the call site, have
import math.Ordering.Implicits.infixOrderingOps
in scope, which brings in a given instance that convertsOrdering[T]
into infix comparators. drawback: any file that wants these comparators needs the import line, adding more import boilerplate as the number of files using this opaque type increases.
package world
import Ordinate.given
import math.Ordering.Implicits.infixOrderingOps // <-- add this line
class Boundary(dims: List[(Ordinate, Ordinate)]) {
def contains(o: Ordinate, dimension: Int): Boolean = {
val (min, max) = dims(dimension)
min <= o && o <= max
}
}
- add an infix extension method for each comparator you want to expose. drawback here is boilerplate of having to write out the very thing we're trying not to duplicate in each file.
type Ordinate = Int
object Ordinate {
extension (o: Ordinate) {
infix def <=(x: Ordinate): Boolean = o <= x // <-- add 'infix' here
}
}
i'm guessing for those more experienced with large programs, these drawbacks are better than the drawbacks associated with anything more than this least permission approach to givens. but this still doesn't seem to deliver on the promise of opaque types as a zero-cost abstraction for numeric types. what seems to be missing is something like "import a given and treat it's methods as infix for my type".