scalaimplicit-conversionimplicitvalue-class

Scala implicit conversions and mkNumericOps with value classes


I am trying to add numeric operations to a value class that I have defined called Quantity. Code that I am using this is as follows...

import scala.language.implicitConversions

case class Quantity(value: Double) extends AnyVal

object Quantity {
  implicit def mkNumericOps(lhs: Quantity): QuantityIsNumeric.Ops = QuantityIsNumeric.mkNumericOps(lhs)
}

object QuantityIsNumeric extends Numeric[Quantity] {

  def plus(x: Quantity, y: Quantity): Quantity = Quantity(x.value + y.value)

  def minus(x: Quantity, y: Quantity): Quantity = Quantity(x.value - y.value)

  def times(x: Quantity, y: Quantity): Quantity = Quantity(x.value * y.value)

  def negate(x: Quantity): Quantity = Quantity(-x.value)

  def fromInt(x: Int): Quantity = Quantity(x.toDouble)

  def toInt(x: Quantity): Int = x.value.toInt

  def toLong(x: Quantity): Long = x.value.toLong

  def toFloat(x: Quantity): Float = x.value.toFloat

  def toDouble(x: Quantity): Double = x.value

  def compare(x: Quantity, y: Quantity): Int = x.value compare y.value
}

I use this code as follows...

class SortedAskOrders[T <: Tradable] private(orders: immutable.TreeSet[LimitAskOrder[T]], val numberUnits: Quantity) {

  def + (order: LimitAskOrder[T]): SortedAskOrders[T] = {
    new SortedAskOrders(orders + order, numberUnits + order.quantity)
  }

  def - (order: LimitAskOrder[T]): SortedAskOrders[T] = {
    new SortedAskOrders(orders - order, numberUnits - order.quantity)
  }

  def head: LimitAskOrder[T] = orders.head
  def tail: SortedAskOrders[T] = new SortedAskOrders(orders.tail, numberUnits - head.quantity)
}

...when I try and compile this code I get the following error..

Error:(29, 63) type mismatch;
 found   : org.economicsl.auctions.Quantity
 required: String
      new SortedAskOrders(orders + order, numberUnits + order.quantity)

The following implementation of the + method which explicitly uses implicit conversions (which I thought should already be in scope!) works.

def + (order: LimitAskOrder[T]): SortedAskOrders[T] = {
  new SortedAskOrders(orders + order, Quantity.mkNumericOps(numberUnits) + order.quantity)
}

The compiler does not seem to be able to find the implicit conversion for the numeric + operator. Thoughts?

I thought that it would be pretty standard to use implicit conversions and the Numeric trait to create numeric operations for a value class. What am I doing wrong?


Solution

  • The issue is that while you've provided a conversion that supports the enriched operations, it has a lower priority than scala.Predef.any2stringadd. You can confirm this by shadowing the any2stringadd name with an implementation that's not applicable here:

    scala> implicit def any2stringadd(i: Int): Int = i
    any2stringadd: (i: Int)Int
    
    scala> def add(a: Quantity, b: Quantity): Quantity = a + b
    add: (a: Quantity, b: Quantity)Quantity
    

    Imported implicits will always take precedence over implicits defined in companion objects, and Predef is implicitly imported in all your source files (unless you've enabled -Yno-predef, which I'd highly recommend, at least for library code).

    Unless you're willing to turn off Predef, the only way around this is to import the conversion (and even if you can turn off Predef, your users may not be able or willing to).

    As a side note, you can make this code a lot more idiomatic by using Numeric as a type class:

    case class Quantity(value: Double) extends AnyVal
    
    object Quantity {
      implicit val quantityNumeric: Numeric[Quantity] = new Numeric[Quantity] {
        def plus(x: Quantity, y: Quantity): Quantity = Quantity(x.value + y.value)
        def minus(x: Quantity, y: Quantity): Quantity = Quantity(x.value - y.value)
        def times(x: Quantity, y: Quantity): Quantity = Quantity(x.value * y.value)
        def negate(x: Quantity): Quantity = Quantity(-x.value)
        def fromInt(x: Int): Quantity = Quantity(x.toDouble)
        def toInt(x: Quantity): Int = x.value.toInt
        def toLong(x: Quantity): Long = x.value.toLong
        def toFloat(x: Quantity): Float = x.value.toFloat
        def toDouble(x: Quantity): Double = x.value
        def compare(x: Quantity, y: Quantity): Int = x.value compare y.value
      }
    }
    

    I.e., instead of having an object instantiating Numeric and using its ops instance explicitly, you simply provide an implicit instance of the Numeric type class in your companion object. Now you need an import for any use of the ops syntax methods:

    scala> import Numeric.Implicits._
    import Numeric.Implicits._
    
    scala> def add(a: Quantity, b: Quantity): Quantity = a + b
    add: (a: Quantity, b: Quantity)Quantity
    

    But this is a standard import that other Scala users are more likely to know about, rather than a custom thing that you have to explain separately.