scala 3: value as a class parameter and valueof

Viewed 51

I am trying to specify a value as a class parameter:

import scala.collection.mutable

class MyMap[K,V,D <: V] {
  private val impl = mutable.Map[K, V]()
  def apply(k:K):V = impl.getOrElse(k, valueOf[D])
//                                     ^^^^^^^^^^ error
  def put(k:K,v:V):Unit = impl += k -> v
}

val m = MyMap[Int, Int, 0]()
m(100) // must return 0

For some reason, the above does not work. Instead, what works is the below code with this illogical unusedName:

import scala.collection.mutable

class MyMap[K,V,D <: V] {
  private val impl = mutable.Map[K, V]()
  def apply(k:K)(implicit unusedName:ValueOf[D]):V = impl.getOrElse(k, valueOf[D])
  def put(k:K,v:V):Unit = impl += k -> v
}

val m = MyMap[Int, Int, 0]()
m(100) // must return 0

I tried to pass the implicit agrument explicitly, which in theory should be possible, but it did not work as I would expect:

scala> m(2)(0)
-- [E007] Type Mismatch Error: ----
1 |m(2)(0)
  |     ^
  |     Found:    (0 : Int)
  |     Required: ValueOf[(0 : Int)]

scala> m(2)(ValueOf[0])
-- Error: --------------------------
1 |m(2)(ValueOf[0])
  |     ^^^^^^^^^^
  |     missing arguments for constructor ValueOf in class ValueOf

scala> m(2)(ValueOf[0](0))
val res7: Int = 0

Questions:

How does all this ValueOf/valueOf magic work?

Why didn't just valueOf[D] work? (It would be only logical)

UPDATE

the code referenced in the comments is:

import scala.collection.mutable

final class MyMap[K, V] private (default: V) {
  private val impl = mutable.Map.empty[K, V]
  
  def apply(k: K): V =
    impl.getOrElse(k, default)
  
  def put(k: K,v: V): Unit = {
    impl += k -> v
  }
}
object MyMap {
  def empty[K, V, D <: V](using ValueOf[D]): MyMap[K, V] =
    new MyMap(default = valueOf[D])
}

val m = MyMap.empty[Int, Int, 0]
m(100)

Code fragment 2:

import scala.collection.mutable

final class MyMap[K, V, D <: V](using ValueOf[D]) {
  private val default = valueOf[D]
  private val impl = mutable.Map.empty[K, V]
  
  def apply(k: K): V =
    impl.getOrElse(k, default)
  
  def put(k: K,v: V): Unit = {
    impl += k -> v
  }
}

val m = MyMap[Int, Int, 0]()
m(100)
1 Answers

How does all this ValueOf/valueOf magic work?

ValueOf[T] is the proposition that there exists one distinguished value of type T.

The witness instances for all reasonable types T are generated by the compiler automatically.

For each v: ValueOf[T], the member access v.value gives the distinguished value of type T.

The method valueOf[T] is just unpacking the witnesses ValueOf[T] into the values of type T.

It could be somewhat instructive to quickly re-build this mechanism from scratch:

case class MyValueOf[T](value: T)

// For ValueOf, these proofs are generated automatically by the compiler;
// Here, we have to do it ourselves
given MyValueOf[0] = MyValueOf(0)

def myValueOf[A](using mvo: MyValueOf[A]): A = mvo.value

println(summon[MyValueOf[0]]) // MyValueOf(0), the proof
println(myValueOf[0])         // 0, the value

Why didn't just valueOf[D] work? (It would be only logical)

Because, as the signature says, the method valueOf[D] requires a ValueOf[D] as implicit argument. The instances of ValueOf[D] can be generated when the D is known. It can not be generated for arbitrary generic type D (if that were the case, then your code would work for a Map[Int, Int, Nothing], which is clearly absurd).


Regarding your REPL experiments:

  • m(2)(0) clearly shouldn't work, because 0 is of type Int, not ValueOf[Int].
  • ValueOf[0] is interpreted as an incomplete constructor invocation. If you meant the type instead of the value, you would have to use summon to materialize the value for the type, i.e. m(2)(summon[ValueOf[0]])
  • ValueOf[0](0) is basically the same as what the compiler would generate anyway when asked to summon[ValueOf[0]].

Regarding your Map-wrapper:

You probably want to pass the default value once, when you create the map:

import scala.collection.mutable

class MyMap[K, V, D <: V](using defaultValueOf: ValueOf[D]) {
  private val impl = mutable.Map[K, V]()
  def apply(k: K): V = impl.getOrElse(k, valueOf[D])
  def put(k: K, v: V): Unit = impl += k -> v
}

val m = MyMap[Int, Int, 0]()
println(m(100)) // prints '0'

The wrapper itself does not seem to be very helpful, because there is already a method withDefault defined on all mutable maps.

Related