I am trying to create a typeclass Default
that supplies the default value for a given type. Here is what I have come up with so far:
trait Default[A] {
def value: A
}
object Default {
def withValue[A](a: A) = new Default[A] {
def value = a
}
def default[A : Default]: A = implicitly[Default[A]].value
implicit val forBoolean = withValue(false)
implicit def forNumeric[A : Numeric] =
withValue(implicitly[Numeric[A]].zero)
implicit val forChar = withValue(' ')
implicit val forString = withValue("")
implicit def forOption[A] = withValue(None : Option[A])
implicit def forAnyRef[A >: Null] = withValue(null : A)
}
case class Person(name: String, age: Int)
case class Point(x: Double, y: Double)
object Point {
implicit val pointDefault = Default withValue Point(0.0, 0.0)
}
object Main {
def main(args: Array[String]): Unit = {
import Default.default
println(default[Int])
println(default[BigDecimal])
println(default[Option[String]])
println(default[String])
println(default[Person])
println(default[Point])
}
}
The above implementation behaves as expected, except for the cases of BigInt
and BigDecimal
(and other user defined types that are instances of Numeric
) where it gives null
instead of zero. What should I do so that forNumeric
takes precedence over forAnyRef
and I get the behavior I expect?
What Are Implicit Parameters? Implicit parameters are similar to regular method parameters, except they could be passed to a method silently without going through the regular parameters list. A method can define a list of implicit parameters, that is placed after the list of regular parameters.
The implicit parameter in Java is the object that the method belongs to. It's passed by specifying the reference or variable of the object before the name of the method. An implicit parameter is opposite to an explicit parameter, which is passed when specifying the parameter in the parenthesis of a method call.
Implicit parameters are the parameters that are passed to a function with implicit keyword in Scala, which means the values will be taken from the context in which they are called.
The forAnyRef
implicit is chosen because it is more specific than forNumeric
according to §6.26.3 “Overloading Resolution” of the Scala reference. There is a way to reduce its priority by moving it to a trait that Default
extends, like this:
trait LowerPriorityImplicits extends LowestPriorityImplicits {
this: Default.type =>
implicit def forAnyRef[A >: Null] = withValue(null: A)
}
object Default extends LowerPriorityImplicits {
// as before, without forAnyRef
}
But that's only part of the trick, because now both forAnyRef
and forNumeric
are as specific as each other, and you'll get an ambiguous-implicit error. Why is that? Well, forAnyRef
gets an extra specificity point because it has a non-trivial constraint on A
: A >: Null
. What you can do then, to add a nontrivial constraint to forNumeric
, is to double it in Default
:
implicit def forNumericVal[A <: AnyVal: Numeric] = withValue(implicitly[Numeric[A]].zero)
implicit def forNumericRef[A <: AnyRef: Numeric] = withValue(implicitly[Numeric[A]].zero)
Now, this additional constraint makes forNumericVal
and forNumericRef
more specific that forAnyRef
for types where a Numeric
is available.
Here is another way to solve the problem, doesn't require any code duplication:
trait Default[A] {
def value: A
}
object Default extends LowPriorityImplicits {
def withValue[A](a: A) = new Default[A] {
def value = a
}
def default[A : Default]: A = implicitly[Default[A]].value
implicit val forBoolean = withValue(false)
implicit def forNumeric[A : Numeric] =
withValue(implicitly[Numeric[A]].zero)
implicit val forChar = withValue(' ')
implicit val forString = withValue("")
implicit def forOption[A] = withValue(None : Option[A])
}
trait LowPriorityImplicits { this: Default.type =>
implicit def forAnyRef[A](implicit ev: Null <:< A) = withValue(null : A)
}
case class Person(name: String, age: Int)
case class Point(x: Double, y: Double)
object Point {
implicit val pointDefault = Default withValue Point(0.0, 0.0)
}
object Main {
import Default.default
def main(args: Array[String]): Unit = {
println(default[Int])
println(default[BigDecimal])
println(default[Option[String]])
println(default[String])
println(default[Person])
println(default[Point])
}
}
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