sealed trait Desc {
type T
}
trait Dataset[A] {
def toDS[A] = new Dataset[A] {}
}
trait DataFrame {}
sealed trait DFDesc extends Desc {
type T = Dummy
}
sealed trait DSDesc[A] extends Desc {
type T = A
}
trait JobConstruction {
def apply(desc: Desc): Job[desc.T]
}
sealed trait Job[DescType] {
def description: Desc { type T = DescType }
}
abstract class DSJob[V] extends Job[V] {
def result(con: JobConstruction): Dataset[V]
}
abstract class DFJob extends Job[Dummy] {
def result(con: JobConstruction): DataFrame
}
trait Dummy {}
case class SampleDFDesc() extends DFDesc
case class SampleDFJob(description: SampleDFDesc) extends DFJob {
override def result(con: JobConstruction) = new DataFrame {}
}
case class SampleDSDesc() extends DSDesc[Int]
case class SampleDSJob(description: SampleDSDesc) extends DSJob[Int] {
override def result(con: JobConstruction) = new Dataset[Int] {}
}
object Main {
val sampleConst = new JobConstruction {
override def apply(desc: Desc): Job[desc.T] = desc match {
case desc2: SampleDFDesc => SampleDFJob(desc2)
case desc2: SampleDSDesc => SampleDSJob(desc2)
}
}
}
Fails to compile with
/tmp/sample.scala:73: error: type mismatch;
found : this.SampleDFJob
required: this.Job[desc.T]
case desc2: SampleDFDesc => SampleDFJob(desc2)
^
/tmp/sample.scala:74: error: type mismatch;
found : this.SampleDSJob
required: this.Job[desc.T]
case desc2: SampleDSDesc => SampleDSJob(desc2)
EDIT:
I would like to get this to work in some kind:
case class SampleDepDesc(df: SampleDFDesc) extends DSDesc[Int]
case class SampleDepJob(description: SampleDepDesc) extends DSJob[Int] {
override def result(con: JobConstruction): Dataset[Int] = con(description.df).result(con).toDS[Int]
}
Analysis of the problem
The error gets formulated in a more interesting way if you write sampleConst like so:
object Main {
val sampleConst = new JobConstruction {
override def apply(desc: Desc): Job[desc.T] = {
val result = desc match {
case desc2: SampleDFDesc => SampleDFJob(desc2)
case desc2: SampleDSDesc => SampleDSJob(desc2)
}
result
}
}
The error message becomes:
type mismatch;
found : Product with Serializable with main.Job[_ >: main.Dummy with Int]{def description: Product with Serializable with main.Desc{type T >: main.Dummy with Int}}
required: main.Job[desc.T]
Note: Any >: desc.T (and Product with Serializable with main.Job[_ >: main.Dummy with Int]{def description: Product with Serializable with main.Desc{type T >: main.Dummy with Int}} <: main.Job[_ >: main.Dummy with Int]), but trait Job is invariant in type DescType. You may wish to define DescType as -DescType instead. (SLS 4.5)
This message is hard to read. The cause seems to be a problem with contravariance, as stated in the fourth line, but let's try to make this error message readable first.
The reason why this message is so long is that Scala is doing a lot of gymnastics in order to make sense of all those type castings and inheritances. We are going to (temporarily) flatten the type hierarchy a bit to see a bit clearer in all this.
Here, the intermediary classes SampleDFJob, SampleDSJob, SampleDFDesc and SampleDSDesc have been removed:
sealed trait Desc {
type T
}
sealed trait DFDesc extends Desc {
type T = Dummy
}
sealed trait DSDesc[A] extends Desc {
type T = A
}
trait JobConstruction {
def apply(desc: Desc): Job[desc.T]
}
sealed trait Job[DescType] {
def description: Desc { type T = DescType }
}
class DSJob[V] extends Job[V]
class DFJob extends Job[Dummy]
trait Dummy {}
object Main {
val sampleConst = new JobConstruction {
override def apply(desc: Desc): Job[desc.T] = {
val result = desc match {
case desc2: DFDesc => new DFJob
case desc2: DSDesc[Int] => new DSJob[Int]
}
result
}
}
}
And the error message is now :
type mismatch;
found : main.Job[_1] where type _1 >: main.Dummy with Int
required: main.Job[desc.T]
The problem seems to be that Scala cannot cast main.Job[_ >: main.Dummy with Int] into desc.T.
Note: why this weird type? Well, the generic type of result is different depending on the case of the pattern matching (in the first case, we have a Dummy, and in the second case, we have an Int). Since Scala is statically typed (at least during compilation), it will try to make up a return type which is a "common denominator" (or rather, a parent type) of all the possible types. The best thing it finds is _ >: main.Dummy with Int, which is "any type that is a parent of any of the types it found in the pattern matching" (main.Dummy and Int).
Why it does not work
I think the reason why this type cannot be cast into a desc.T is that Scala cannot confirm, at compilation time, that the returned type is always the same (since DescType is invariant) as Job[desc.T]. Indeed, desc.T comes from SampleDFDesc.T or SampleDSDesc.T, whereas the return type will be DescType, and nothing guarantees that those two types are the same (what if SampleDSJob extended DSJob[String] instead?)
Solution
I do not think that it is possible to code exactly in the way you're trying to do, but you can try to... dodge the issue:
If you are certain that the return type of each case will always be of the same type as desc.T, then you can specify an explicit cast with asInstanceOf, like so:
object Main {
val sampleConst = new JobConstruction {
override def apply(desc: Desc): Job[desc.T] = (desc match {
case desc2: SampleDFDesc => SampleDFJob(desc2)
case desc2: SampleDSDesc => SampleDSJob(desc2)
}).asInstanceOf[Job[desc.T]]
}
}
Of course, this is not type-safe.
Alternatively, if you can manage to write the Job class so that DescType can be contravariant (-DescType), you can write the Job.apply method to have the following signature instead:
def apply(desc: Desc): Job[_ <: desc.T]
It is not a real solution, but you could replace type inside trait, with type parameter, this code compiles:
sealed trait Desc[T]
trait Dataset[A]
trait DataFrame
sealed trait DFDesc extends Desc[Dummy]
sealed trait DSDesc[A] extends Desc[A]
trait JobConstruction {
def apply[A](desc: Desc[A]): Job[A]
}
sealed trait Job[A] {
def description: Desc[A]
}
abstract class DSJob[V] extends Job[V] {
def result: Dataset[V]
}
abstract class DFJob extends Job[Dummy] {
def result: DataFrame
}
trait Dummy
case class SampleDFDesc() extends DFDesc
case class SampleDFJob(description: SampleDFDesc) extends DFJob {
def result = new DataFrame {}
}
case class SampleDSDesc() extends DSDesc[Int]
case class SampleDSJob(description: SampleDSDesc) extends DSJob[Int] {
def result = new Dataset[Int] {}
}
val sampleConst = new JobConstruction {
override def apply[A](desc: Desc[A]): Job[A] = desc match {
case desc2: SampleDFDesc => SampleDFJob(desc2)
case desc2: SampleDSDesc => SampleDSJob(desc2)
}
}
As for how to make path dependent types work, I am curious myself.
Related
I'm designing type-safe code with ADT and Aux-pattern and cannot get rid of some asInstanceOf. Here is the example:
sealed trait Source
case object FileSystem extends Source
case object Network extends Source
sealed trait Data {
type S <: Source
}
object Data {
type Aux[T <: Source] = Data { type S = T }
}
case class RegularFile(path: String) extends Data { type S = FileSystem.type }
case class Directory(path: String) extends Data { type S = FileSystem.type }
case class UnixDevice(path: String) extends Data { type S = FileSystem.type }
case class Remote(ip: String) extends Data { type S = Network.type }
//Lots of asInstanceOf
def availableData[S <: Source](src: Source): List[Data.Aux[S]] = {
src match {
case FileSystem => List(
RegularFile("/tmp/test").asInstanceOf[Data.Aux[S]],
Directory("/home/somename").asInstanceOf[Data.Aux[S]],
UnixDevice("/dev/null").asInstanceOf[Data.Aux[S]],
)
case Network => List(
Remote("192.168.0.1").asInstanceOf[Data.Aux[S]]
)
}
}
In this case it is pretty obvious that asInstanceOf is correct, but is there a way to get rig of it?
I was considering S <: Source: ClassTag, but it doesn't seem to be useful here. Maybe other reflection trick?
Please see explanation why the signature
def availableData[S <: Source](src: S): List[Data.Aux[S]] = {
src match {
case FileSystem => List(
RegularFile("/tmp/test"),
Directory("/home/somename"),
UnixDevice("/dev/null"),
)
case Network => List(
Remote("192.168.0.1")
)
}
}
doesn't work and several approachs how to fix it:
Generic function where the return type depends on the input type in Scala?
Why can't I return a concrete subtype of A if a generic subtype of A is declared as return parameter?
Type mismatch on abstract type used in pattern matching
For example try a type class
trait AvailableData[S <: Source] {
def availableData(src: S): List[Data.Aux[S]]
}
object AvailableData {
implicit val fileSystem: AvailableData[FileSystem.type] = _ =>
List[Data.Aux[FileSystem.type]](
RegularFile("/tmp/test"),
Directory("/home/somename"),
UnixDevice("/dev/null"),
)
implicit val network: AvailableData[Network.type] = _ =>
List[Data.Aux[Network.type]](
Remote("192.168.0.1")
)
}
def availableData[S <: Source](src: S)(implicit
ad: AvailableData[S]
): List[Data.Aux[S]] = ad.availableData(src)
I tried to generalize a method to provide a type safe API as follows:
abstract class AbstractCommand {
type T = this.type
def shuffler(s: T => Seq[AbstractCommand])
}
class TestCommand extends AbstractCommand {
override def shuffler(s: (TestCommand) => Seq[AbstractCommand]): Unit = ??? //error
}
I wanted the expected type of the function argument to be the most specific in this hierarchy. But it didn't work.
Is there a way to do something like that in Scala without introducing some helper type parameters?
This looks like a perfect use-case for F-Bounded Polymorphism:
abstract class AbstractCommand[T <: AbstractCommand[T]] {
self: T =>
def shuffler(s: T => Seq[AbstractCommand[T]])
}
class TestCommand extends AbstractCommand[TestCommand] {
override def shuffler(s: (TestCommand) => Seq[AbstractCommand[TestCommand]]): Unit = ???
}
And with a type member instead of a type parameter (using the example provided by Attempting to model F-bounded polymorphism as a type member in Scala):
abstract class AbstractCommand { self =>
type T >: self.type <: AbstractCommand
}
class TestCommand extends AbstractCommand {
type T = TestCommand
}
class OtherCommand extends AbstractCommand {
type T = OtherCommand
}
You could avoid defining T in the abstract class :
abstract class AbstractCommand {
type T
def shuffler(s: T => Seq[AbstractCommand])
}
class TestCommand extends AbstractCommand {
type T = TestCommand
override def shuffler(s: (TestCommand) => Seq[AbstractCommand]): Unit = ??? //compiles !
}
On the downside, it's a bit more verbose, on the upside, it's even more generic !
I'm not entirely sure it fits your need, but I've been able to compile and run the following, let me know if it helps:
abstract class AbstractCommand {
def shuffler(s: this.type => Seq[AbstractCommand])
}
class TestCommand extends AbstractCommand {
override def shuffler(s: (TestCommand.this.type) => Seq[AbstractCommand]): Unit = {
s(this)
println("success")
}
}
new TestCommand().shuffler(_ => Seq.empty) // prints "success"
Updated my question per feedback from commenters (Miles and m-z):
I'm finding duplicate "values" by either Name or Age.
sealed trait DuplicateResult
case class DuplicatesByName(name: String, people: Set[String]) extends DuplicateResult
case class DuplicatesByAge(age: Int, people: Set[String]) extends DuplicateResult
And the return type must be different depending on Name or Age:
sealed trait QueryByDuplicate {
type DuplicateResultType
}
case class Name(name: String) extends QueryByDuplicate {
override type DuplicateResultType = DuplicatesByName
}
case class Age(age: Int) extends QueryByDuplicate {
override type DuplicateResultType = DuplicatesByAge
}
Then, I define a function that compiles and runs:
def findDupes(x: QueryByDuplicate): DuplicateResult = x match {
case Name(n) => DuplicatesByName(n, Set("1", "2"))
case Age(a) => DuplicatesByAge(a, Set("42"))
}
scala> findDupes(Name("kevin"))
res0: DuplicateResult = DuplicatesByName(kevin,Set(1, 2))
scala> findDupes(Age(77))
res1: DuplicateResult = DuplicatesByAge(77,Set(42))
However, the type DuplicateResultType seems weak, since I could put any type there.
Please criticize and improve my implementation.
Option 1: The result type information is lost in your version because the caller cannot influence the fixed result type of findDupes. You can use generics so that the caller regains this control:
sealed trait QueryByDuplicate[T <: DuplicateResult]
case class Name(name: String) extends QueryByDuplicate[DuplicatesByName]
case class Age(age: Int) extends QueryByDuplicate[DuplicatesByAge]
def findDupes[T <: DuplicateResult](x: QueryByDuplicate[T]): T = x match {
case Name(n) => DuplicatesByName(n, Set("1", "2"))
case Age(a) => DuplicatesByAge(a, Set("42"))
}
val dupes: DuplicatesByName = findDupes(Name("kevin"))
Option 2: Since you asked for criticism too, I don't think the way you designed things is a good practice. Having several classes defining your queries, and a list of implementations for each class at a different place is duplicity hard to maintain. You could just use good old polymorphism:
sealed trait QueryByDuplicate[T <: DuplicateResult] {
def findDupes: T
}
class Name(name: String) extends QueryByDuplicate[DuplicatesByName] {
override def findDupes: DuplicatesByName = DuplicatesByName(name, Set("1", "2"))
}
class Age(age: Int) extends QueryByDuplicate[DuplicatesByAge] {
override def findDupes: DuplicatesByAge = DuplicatesByAge(age, Set("42"))
}
val dupes: DuplicatesByName = new Name("kevin").findDupes
This is how you would probably do it in Java. Sometimes it's better to stick to good old ways even when we have new toys.
Option 3: Talking about instanceOf, this also works:
sealed trait QueryByDuplicate {
type DuplicateResultType
}
case class Name(name: String) extends QueryByDuplicate {
override type DuplicateResultType = DuplicatesByName
}
case class Age(age: Int) extends QueryByDuplicate {
override type DuplicateResultType = DuplicatesByAge
}
def findDupes(x: QueryByDuplicate): x.DuplicateResultType = x match {
case Name(n) => DuplicatesByName(n, Set("1", "2")).asInstanceOf[x.DuplicateResultType]
case Age(a) => DuplicatesByAge(a, Set("42")).asInstanceOf[x.DuplicateResultType]
}
val dupes: DuplicatesByName = findDupes(Name("kevin"))
findDupes() will have a return type of Object in the bytecode, but Scala is smart enough to infer the correct type, apparently.
I try to define a type constraint with abstract type. but unfortunately, it doesn't compile.
sealed trait MatchableValue {
type A
def value: A
def asSingleItemValue : ItemValue
}
sealed trait ItemValue {
type A
def value: A
}
case class StringValue(value: String) extends ItemValue {type A = String}
case class StringMatchableValue(value: String) extends MatchableValue{
type A = String
override def asSingleItemValue = StringValue(value)
}
Unfortunately, this one doesn't work
def asSingleItemValue[B <: ItemValue](implicit ev: A =:= B#A) : B
The aim of the type constraint is to be warned at compile-time of such an error :
case class IntValue(value: Int) extends ItemValue {type A = Int}
case class IntMatchableValue(value: Int) extends MatchableValue{
type A = Int
def asSingleItemValue = StringValue("error")
}
You can accomplish this with a type refinement (note the method's return type):
sealed trait MatchableValue { self =>
type A
def value: A
def asSingleItemValue: ItemValue { type A = self.A }
}
sealed trait ItemValue {
type A
def value: A
}
case class StringValue(value: String) extends ItemValue { type A = String }
case class IntValue(value: Int) extends ItemValue { type A = Int }
Now this compiles:
case class StringMatchableValue(value: String) extends MatchableValue {
type A = String
def asSingleItemValue = StringValue(value)
}
But this doesn't:
case class StringMatchableValue(value: String) extends MatchableValue {
type A = String
def asSingleItemValue = IntValue(1)
}
Which I believe is what you want.
It's also worth noting that the following is a common pattern when dealing with type refinements:
sealed trait MatchableValue { self =>
type A
def value: A
def asSingleItemValue: ItemValue.Aux[A]
}
sealed trait ItemValue {
type A
def value: A
}
object ItemValue {
type Aux[A0] = ItemValue { type A = A0 }
}
This does exactly the same thing, but the syntax is a nice alternative if you find yourself needing to write out the type refinement a lot.
I think you meant to delete this line in your code?
def asSingleItemValue = StringValue(value)
and the override definition has some of the signature missing and should be like this:
override def asSingleItemValue[B <: ItemValue](implicit ev: =:=[String, B#A]): B = ev
Finally, the resulting type of ev needs to be B.
I've defined some case classes that all have a field id: Id[T], where T is the type of the case class. I would like to have a trait enforcing this property to write generic code on these classes.
My first try uses a type parameter and a self type on a trait to enforce this property:
case class Id[M](value: Long)
trait EnforceIdType[T] {
this: T =>
val id: Id[T]
}
case class A(id: Id[A]) extends EnforceIdType[A]
case class B(id: Id[B]) extends EnforceIdType[B]
// case class C(id: Id[B]) extends EnforceIdType[B] < Won't compile, as expected
This is fine but I wonder if there is a way to do the same without using a type parameter on the trait. Here is my second try:
case class Id[M](value: Long)
trait EnforceIdType {
val id: Id[_ <: EnforceIdType]
}
case class A(id: Id[A]) extends EnforceIdType
case class B(id: Id[B]) extends EnforceIdType
case class C(id: Id[B]) extends EnforceIdType // Compiles :(
Concretely, I would like to use these definitions of case classes (without passing the type parameter to EnforceIdType) but still enforce the property (this C should not compile). Is there any way to do this? I feel like something like this.type could be used here but I'm not very familiar with it.
You are right, this.type need to be used, but with some limitation:
trait EnforceIdType {
def id: Id[_ >: this.type <: EnforceIdType]
}
case class A(id: Id[A]) extends EnforceIdType
case class B(id: Id[B]) extends EnforceIdType
//case class C(id: Id[B]) extends EnforceIdType // Won't compile, as expected
Updated:
About second limitation (shown by Alexey Romanov). It can be eliminated but with long way:
//Embeded polymorphism used
class Id(val value: Long) {
type M
}
// Factory method for shift type variable to type parameter field
object Id {
def apply[T](value : Long) = new Id(value) { type M = T }
}
trait EnforceIdType {
type This = this.type // store this.type for use inside Id { type M = .. }
val id: Id { type M >: This <: EnforceIdType } // need to be val
def show(x : id.M) { println (x) } // dependent method
}
case class A(id: Id { type M = A }) extends EnforceIdType
case class B(id: Id { type M = B }) extends EnforceIdType
// case class C(id: Id { type M = B }) extends EnforceIdType // Won't compile, as expected
val a = A( Id[A](10))
val b = B( Id[B](10))
a.show(a)
// a.show(b) // Won't compile, as expected