My goal is to equip a variety of types (Timestamps, Dates, etc.) with nice properties they might not have by default (ordering, -, etc.). I'm doing something like this:
trait NiceProperties[T] {
def -(t: T): Double
def +(d: Double): T
...
}
implicit class BetterTimestamp(val t: Timestamp) extends NiceProperties[Timestamp] {
override def -(Timestamp): ...
}
This all works fine until I need to pass it into a function that assumes NiceProperties:
def myUtil[T](t: NiceProperties[T]): T = {
(t + 1.0) + 1.0
}
This now fails, because the function lacks the implicit evidence that the class T can be implicitly upcast to NiceProperties[T], so it can't add (t + 1.0): T to a double.
Is there a way to pass evidence for an implicit class into a function? Alternatively, is there a better pattern for this?
You could solve your issue by turning your NiceProperties[T] into a class that knows how to add, sum, ... two values of type T:
trait NiceProperties[T] {
def subtract(a: T, b: T): Double
def add(a: T, d: Double): T
}
You can now create an implicit NiceProperties object or val for Timestamps, Dates, ...
object BetterTimestamp extends NiceProperties[Timestamp] {
def subtract(a: Timestamp, b: Timestamp): Double = ???
def add(a: Timestamp, d: Double): Timestamp = ???
}
In your example method you'll request an implicit NiceProperties[T] which does the operations for your.
def myUtil[T](t: T)(implicit prop: NiceProperties[T]): T = {
prop.add(prop.add(t, 1.0), 1.0)
}
Since this is ugly, you can use an implicit class to add the +, -, ... operators to any class where an implicit NiceProperties[T] is available:
implicit class NicePropertiesOps[T](t: T)(implicit prop: NiceProperties[T]) {
def +(d: Double): T = prop.add(t, d)
def -(b: T): Double = prop.subtract(t, b)
}
Now your example from above should work almost as you described.
def myUtil[T : NiceProperties](t: T): T = {
(t + 1.0) + 1.0
}
https://scastie.scala-lang.org/0D1Y9sE5S5mrzm9coZPMWw
@Aki's answer is completely correct. Here is just an alternative approach of bringing the conversion into scope. This way is used in Numeric typeclass.
class Timestamp
trait NiceProperties[T] {
def subtract(a: T, b: T): Double
def add(a: T, d: Double): T
implicit class Ops(t:T) {
def +(d: Double): T = add(t, d)
def -(b: T): Double = subtract(t, b)
}
}
implicit object BetterTimestamp extends NiceProperties[Timestamp] {
def subtract(a: Timestamp, b: Timestamp): Double = ???
def add(a: Timestamp, d: Double): Timestamp = ???
}
def myUtil[T](t: T)(implicit prop: NiceProperties[T]): T = {
import prop._
(t + 1.0) + 1.0
}
and one more approach just of fun. This is how to avoid import:
trait NiceProperties[T] extends (T => Ops[T]) {
def subtract(a: T, b: T): Double
def add(a: T, d: Double): T
implicit val v = this
def apply(t:T) = new Ops(t)
}
class Ops[T](t:T)(implicit prop: NiceProperties[T]) {
def +(d: Double): T = prop.add(t, d)
def -(b: T): Double = prop.subtract(t, b)
}
implicit object BetterTimestamp extends NiceProperties[Timestamp] {
def subtract(a: Timestamp, b: Timestamp): Double = ???
def add(a: Timestamp, d: Double): Timestamp = ???
}
def myUtil[T:NiceProperties](t: T): T = {
(t + 1.0) + 1.0
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With