Scala: return a parametrized instance - scala

I cannot make work the following code:
object Factory {
def apply[U <: Cda](type: MyType.Value): MyUtilTrait[U] = {
type match {
case MyType.Value.one => MyOneUtilCustom
case MyType.Value.two => MyTwoUtilCustom
}
}
}
=> Expression of type Factory.MyType doesn't conform to expected type MyUtilTrait[U]
trait MyUtilTrait[T <: Cda] {}
object MyOneUtilCustom extends MyUtilTrait[CdaOneCustom] { }
object MyTwoUtilCustom extends MyUtilTrait[CdaTwoCustom] { }
case class CdaOneCustom(...) extends Cda {}
case class CdaTwoCustom(...) extends Cda {}
abstract class Cda(...) {}
object MyType extends Enumeration {
val one, two = Value
}
With the apply, I am supposed to return a MyUtilTrait parametrized with a subtype of Cda, so what's wrong?

It's almost completely impossible to implement this apply method with such a signature [1], because someone could come along, define
class Unobtanium extends Cda {
// implement all `Cda` methods by `???`
}
and then invoke
Factory.apply[Unobtanium](MyType.one)
How is a factory supposed to create a MyUtilTrait[Unobtanium], if it knows nothing about Unobtanium, and it is the first time it sees this strange type?
Use existential type instead:
abstract class Cda {}
case class CdaOneCustom() extends Cda {}
case class CdaTwoCustom() extends Cda {}
trait MyUtilTrait[T <: Cda] {}
object MyOneUtilCustom extends MyUtilTrait[CdaOneCustom] { }
object MyTwoUtilCustom extends MyUtilTrait[CdaTwoCustom] { }
object MyType extends Enumeration {
val one, two = Value
}
object Factory {
def apply(typ: MyType.Value): MyUtilTrait[_] = {
import MyType._
typ match {
case `one` => MyOneUtilCustom
case `two` => MyTwoUtilCustom
}
}
}
[1] Unless your MyUtilTrait[X] is something trivial, like Nil (which is a List[X] for any X), or Consumer[Any] of some sort, which does not actually care about the type parameter.

Related

Scala generics - simplify number of generic arguments

I have this class defined as follows:
abstract class MyHelper[T, E <: BaseHelper[T]](implicit clsTag: ClassTag[E]) {
val all: Array[E]
def parse(t: T): Try[E] = { ... }
}
and this is how I am using it:
trait IntHelper extends BaseHelper[Int] {}
object MyIntHelper extends MyHelper[Int, IntHelper] { }
How do I simplify MyHelper class definition to accept only the inner generic type T instead of passing two types, E and T?
There's no need for you to directly enforce T in MyHelper since you're only using E itself.
So something like this should be fine.
abstract class MyHelper[E <: BaseHelper[_]](implicit clsTag: ClassTag[E]) {
val all: Array[E]
}
A more fleshed out example looks like:
trait BaseHelper[T] {}
trait IntHelper extends BaseHelper[Int]
abstract class MyHelper[E <: BaseHelper[_]](implicit clsTag: ClassTag[E]) {
val all: Array[E]
}
object MyIntHelper extends MyHelper[IntHelper] {
override val all = Array(new IntHelper{}, new IntHelper{})
}
// just to ensure it works
MyIntHelper.all

Default type parameter in inheritence

Say I have the following classes:
class A[T] { ... }
abstract class B[T1,T2](t: T1)(implicit ev: A[T2]) {
...
}
On some instances, when I inherit from B, the type for T2 is the same as that for T1. Is there I way I can define my class to avoid having to specify this explicitly?
So rather than having to do:
class C extends B[String, String]("Some string") {
...
}
Can I have the compiler some how infer this so I only need to write:
class C extends B("Some string") {
...
}
I think a type alias should work:
type B1[T] = B[T, T]
class C extends B1("Some string") {
...
}

Scala: Return type of abstract method

I cannot figure out how to specify the return type of a method an abstract class A, if the same method of the concrete classes have different return (sub)types:
abstract class A {
def method: List[Common (?)] // I want to force all subclasses to define this method
}
class B1 extends A {
def method(a,b,c): List[Sub1] = {...}
}
class B2 extends A {
def method(a,b,c): List[Sub2] = {...}
}
I tried to define a common trait of Sub1 and Sub2:
abstract class Common // or abstract class
case class Sub1 extends Common
case class Sub2 extends Common
but I keep getting this:
Compilation error[class B1 needs to be abstract,
since method "method" in class A of type => List[Common] is not defined]
If I don't define the return type in class A, I get the same error with ... type => Unit ... instead.
How can I solve that?
def method: List[Common]
Is not the same as
// returns `List[Common]` to simplify things, but it would be the same if we returned a sub-type
def method(a: ?, b: ?, c: ?): List[Common] = {...}
The first is a parameterless method that returns a List[Common], and the second is a method with three parameters that returns a List[Common]. The compiler sees these as two completely different methods. The fact that they have the same name means nothing.
The compiler is complaining because def method: List[Common] is not defined in the subclasses of A.
This compiles:
abstract class Common // or abstract class
case class Sub1() extends Common
case class Sub2() extends Common
abstract class A {
def method(): List[Common]
}
class B1 extends A {
def method(): List[Sub1] = ???
}
class B2 extends A {
def method(): List[Sub2] = ???
}
All I have done is:
add the () to Sub1() and Sub2()
return List[Common] from A
EDIT
As #m-z mentioned, it works because every def method() has the same signature now.

Path-dependent typing over match/case

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.

programmatically setting the `type` of an abstract type

class MyModel(var username:String, var password:String) extends FrameworkModel
object MyModelQuery extends FrameworkQuery {
type T = MyModel
}
trait FrameworkQuery {
type T
//do something with that type
}
So I get a class and an object where the latter is mixing in a trait which is defined as an abstract type. Is there a way I could programmatically set the type to the type of MyModel class, so the client would not need to? ie "object MyModelQuery extends FrameworkQuery" would take care of it
Could you achieve a similar effect by just nesting the query in the model?
trait FrameworkModel {
val model = this
trait FrameworkQuery {
type T = model.type
def getModel: T = model
}
}
class UserModel extends FrameworkModel {
// model stuff...
object UserQuery extends FrameworkQuery {
// query stuff...
}
}
trait T{
type X = this.type
def x: X = this
}
object A extends T{
def b = "Yep"
}
scala> A.x.b
res0: java.lang.String = Yep