Reputation: 5020
Suppose I want every instance of an Item
to be a SourceOf
that Item
, and every SourceOf
a subtype of Item
to be a SourceOf
all instances of that subtype. This seems like a natural way to do it:
object Demo {
trait Item extends SourceOf[this.type]
trait SourceOf[+A <: Item]
}
Obviously this won't work, since this
refers to the Demo
object, not to each Item
instance. (I've tested it.)
How can I tell the Scala compiler that every Item
is a source of itself? It should be possible to get the compiler to deduce stuff like:
"Every source of (all) farming implements is a source of the specific farming implement you're looking for."
"If you're looking for the source of a farming implement, and you already have the farming implement, then you need look no further."
Here's an example (written to ignore type erasure for brevity):
trait FarmingImplement extends Item
object TheRototiller extends FarmingImplement
def findSource(item: Item, seq: Seq[SourceOf[_]]): Option[SourceOf[item.type]] =
seq.collectFirst {
case src: SourceOf[item.type] => src
}
val sources = Seq( // example: possible sources of TheRototiller
new SourceOf[Nothing] { override def toString = "Wrong source" },
new SourceOf[FarmingImplement] { override def toString = "Right" },
TheRototiller /* also right */ )
val got = findSource(TheRototiller, sources).get
println(got) // Should print "Right", since the second source in `sources`
// is the first match.
I want the type of got
be SourceOf[TheRototiller.type]
, not SourceOf[Item]
. But mainly I want Scala's type system to do the work of determining whether a SourceOf
matches a given Item
or category of Items
.
Upvotes: 2
Views: 163
Reputation: 22374
At least, this gonna solve your problem with passing this
:
object Demo {
trait C[+A]
trait Item extends SourceOf {
type T = C[this.type]
}
trait SourceOf {
type T <: C[Item]
}
}
Example (you'll have to compare T
s instead of SourceOf
s here):
scala> val i = new Item{}
i: Demo.Item = $anon$1@c70cb4c
scala> implicitly[i.T =:= i.T] //compile-time type equality check
res4: =:=[i.T,i.T] = <function1>
scala> val i2 = new Item{}
i2: Demo.Item = $anon$1@1f26ac06
scala> implicitly[i.T =:= i2.T]
<console>:18: error: Cannot prove that i.T =:= i2.T.
implicitly[i.T =:= i2.T]
If you need to check types in runtime (as you've shown in example with pattern matching) - I would recomend to use TypeTag
:
import scala.reflect.runtime.universe._
object Demo {
abstract class C[+A: TypeTag]
trait Item extends SourceOf {
type T = C[this.type]
}
trait SourceOf {
val tag = typeTag[this.type] //just to access
}
}
Usage:
scala> val i = new Item{}
i: Demo.Item = $anon$1@3b2111b0
scala> val i2 = new Item{}
i2: Demo.Item = $anon$1@4e7bb48a
scala> typeTag[i.T].tpe =:= typeTag[i2.T].tpe //runtime type-equality check
res9: Boolean = false
scala> typeTag[i.T].tpe =:= typeTag[i.T].tpe
res10: Boolean = true
Finally, implementation of findSource
:
import scala.reflect.runtime.universe._
object Demo {
trait Item extends SourceOf
trait SourceOf {
type I = this.type
val tag = typeTag[I]
}
def findSource(item: Item, seq: Seq[SourceOf]): Option[item.I] =
seq.find(_.tag.tpe =:= item.tag.tpe).asInstanceOf[Option[item.I]]
}
Examples:
scala> val i = new Item{}
i: Demo.Item = $anon$1@1fc2899d
scala> val i2 = new Item{}
i2: Demo.Item = $anon$1@5f308abb
scala> val ri = findSource(i, Seq(i, i2)).get
ri: i.I = $anon$1@1fc2899d
scala> implicitly[ri.I =:= i.I]
res2: =:=[ri.I,i.I] = <function1>
asInstanceOf
is used (it's safe to do here) because Seq
looses path-dependent type in compile-time and we used runtime-check to match types, so no way to find it for compiler. However, Shapeless2's HList
+ Selector
based implementation might be more pure.
Upvotes: 1