3
votes

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?

1
Looks like you need import Quantity._ on top of your fileYuriy Gatilin
@YuriyGatilin But even without the import statement the compiler is able to find the implicit conversion for the subtraction operator. Which makes me think that something else is going on...davidrpugh
This is not an issue with the implicit being in/out of scope. The other ops (-, *, etc.) appear to compile without complaint. No, it's pretty obvious that the compiler isn't looking for the implicit because + is the string concat op and, since everything has a string representation, it doesn't need the implicit. I know of at least one work-around but I was hoping someone with deeper understanding of this would jump in with a clean and simple means of turning off the string-concat.jwvh

1 Answers

3
votes

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.