I have made a custom collection called Matrix
that stores a matrix in the form of a uni-dimensional List
(this is sort of a requirement, has to be that way). The thing is, I want to implement this collection adapted for generics. But when I execute it and print the returned collection, it gives null
values.
I have created an external class to help with the custom default values for each type.
Now, the snippets:
Output:
> sbt run
[info] welcome to sbt 1.4.9 (N/A Java 15.0.2)
[info] loading project definition from /home/pace/Documents/git/1024-scala/1024-scala/project
[info] loading settings for project root from build.sbt ...
[info] set current project to scalatest-example (in build file:/home/pace/Documents/git/1024-scala/1024-scala/)
[info] compiling 1 Scala source to /home/pace/Documents/git/1024-scala/1024-scala/target/scala-2.13/classes ...
[info] done compiling
[info] running matrix.MainClass
Matrix(null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null)
[success] Total time: 4 s, completed May 1, 2021, 9:10:28 PM
The Matrix collection implementation:
package matrix
import scala.collection.Iterable
import scala.collection.Iterator
import scala.collection.AbstractIterator
import defaultValues.Default
class Matrix[A] private (val col: Int, val row: Int, elems: List[A])
extends Iterable[A] { self =>
def this() = this(col = 4, row = 4, elems = List.fill(4 * 4)(Default.value[A]))
def this(s: Int) = this(col = s, row = s, elems = List.fill(s * s)(Default.value[A]))
def this(c: Int, r: Int) = this(col = c, row = r, elems = List.fill(c * r)(Default.value[A]))
def apply(i: Int): A = elems(i).asInstanceOf[A]
def iterator: Iterator[A] = new AbstractIterator[A] {
private var current = 0
def hasNext = current < elems.size
def next(): A = {
val elem = self(current)
current += 1
elem
}
}
override def className = "Matrix"
}
object MainClass extends App {
val m = new Matrix[Int]()
println(m)
}
And the way I obtain the default values for each type (got it from other SO thread, and I've checked it works in Scala REPL
):
package defaultValues
import scala.collection.immutable
class Default[+A](val default: A)
trait LowerPriorityImplicits {
// Stop AnyRefs from clashing with AnyVals
implicit def defaultNull[A <: AnyRef]: Default[A] = new Default[A](null.asInstanceOf[A])
}
object Default extends LowerPriorityImplicits {
implicit object DefaultString extends Default[String]("")
implicit object DefaultDouble extends Default[Double](0.0)
implicit object DefaultFloat extends Default[Float](0.0F)
implicit object DefaultInt extends Default[Int](0)
implicit object DefaultLong extends Default[Long](0L)
implicit object DefaultShort extends Default[Short](0)
implicit object DefaultByte extends Default[Byte](0)
implicit object DefaultBoolean extends Default[Boolean](false)
implicit object DefaultUnit extends Default[Unit](())
implicit def defaultSeq[A]: Default[immutable.Seq[A]] = new Default[immutable.Seq[A]](immutable.Seq())
implicit def defaultSet[A]: Default[Set[A]] = new Default[Set[A]](Set())
implicit def defaultMap[A, B]: Default[Map[A, B]] = new Default[Map[A, B]](Map[A, B]())
implicit def defaultOption[A]: Default[Option[A]] = new Default[Option[A]](None)
def value[A](implicit value: Default[A]): A = value.default
}
I'm running Scala 2.13
.
Could you give me a hand? Thanks.
def this() = this(col = 4, row = 4, elems = List.fill(4 * 4)(Default.value[A]))
because there is no constraint on A
, you're telling the compiler that this function should work for every A
possible, which means A
here must be the maximal type in scala - AnyRef
. Now that A is solved to be AnyRef
, Default.value[A]
always resolves to defaultNull
regardless of what you put at the call site. And that makes sense, because you're saying that it should work with every type possible.
The solution is simply putting a constraint in A so that compiler doesn't infer that to be maximal type:
class Matrix[A: Default] {
def this() = this(col = 4, row = 4, elems = List.fill(4 * 4)(Default.value[A]))
}
// equivalent to this form
def apply[A: Default]: Matrix[A] = new Matrix(col = 4, row = 4, elems = List.fill(4 * 4)(Default.value[A]))
// which is syntactic sugar for
def apply[A](implicit default: Default[A]): Matrix[A] = new Matrix(col = 4, row = 4, elems = ???
by putting the constraint there, you defer solving for A
to the actual usage (calling new Matrix[Int]
) because now compiler needs to know the actual A
you have so that it can search for implicit parameter.