Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Requiring Scala implicit class in a function

Tags:

scala

implicit

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?

like image 684
mwlon Avatar asked Sep 04 '26 09:09

mwlon


2 Answers

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

like image 112
Aki Avatar answered Sep 07 '26 06:09

Aki


@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
}
like image 22
Bogdan Vakulenko Avatar answered Sep 07 '26 07:09

Bogdan Vakulenko



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!