Method generalization in Scala - scala

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"

Related

Defining a generic to be a case class

In this example, I want the generic T to be a case class and a DAOEntity with id, so in the abstract implementation, I can use the copy method.
How to define it?
trait DAOEntity {
def id: String
}
// How to define this generic to force the use of a `case class` to have access to `copy`?
abstract class DAO[T <: DAOEntity] {
def storeInUppercase(entity: T): T = entity.copy(id = entity.id)
}
case class MyEntity(id: String) extends DAOEntity
class MyEntityDAO extends DAO[MyEntity] {
// Other stuff
}
There is no way to know if a type is a case class or not.
And even if there was, you won't get the copy method. The language doesn't provide a way to abstract over constructor; thus neither copy and factories (apply on companions) by extension. Which makes sense, what would be the type signature of such function?
What you can do instead is create a factory-like typeclass and ask for that:
trait DAOFactory[T <: DAOEntity] {
def copy(oldEntity: T, newId: String): T
}
object DAOFactory {
def instance[T <: DAOEntity](f: (T, String) => T): DAOFactory[T] =
new DAOFactory[T] {
override final def copy(oldEntity: T, newId: String): T =
f(oldEntity, newId)
}
}
Which can be used like this:
abstract class DAO[T <: DAOEntity](implicit factory: DAOFactory[T]) {
def storeInUppercase(entity: T): T =
factory.copy(
oldEntity = entity,
newId = entity.id.toUpperCase
)
}
And entities would provide the instance like this:
final case class MyEntity(id: String, age: Int) extends DAOEntity
object MyEntity {
implicit final val MyEntityFactory: DAOFactory[MyEntity] =
DAOFactory.instance {
case (oldEntity, newId) =>
oldEntity.copy(id = newId)
}
}
// This compile thanks to the instance in the companion object.
object MyEntityDAO extends DAO[MyEntity]
You can see the code running here.

Extract parameterised type from self in scala

I have class with parameterised type
abstract class Worker[T] {
def conf1: ...
def conf2: ...
def doWork ...
}
abstract class SpecializedWorker[T: TypeTag] extends Worker[T] {
//some behavior overriden (used fields from Trait that i want create)
}
I want to create trait that can be mixed to Worker.
trait Extension {
self: Worker[_] =>
def someParameter: ... // only several workers can have that. thats why i need trait
def produceSpecializedWorker = new SpecializedWorker[???]() {}
}
How to extract type information from self to replace ???
Here is a way to extract a type parameter:
trait Extension {
self: Worker[_] =>
def mkSpWorker[T](implicit ev: this.type <:< Worker[T]) = new SpecializedWorker[T]() {}
}
But I wouldn't recommend that :) You could define a type member in Worker that can be used in Extension.
abstract class Worker[T] {
type TT = T
}
trait Extension {
self: Worker[_] =>
def mkSpWorker = new SpecializedWorker[TT]() {}
}
Or you could just consider giving Extension a type parameter. It's not such a big deal, I think.

Scala trait method restricted to implementing type

So I'm having some trouble with what I think is a pretty simple situation in trait implementation, and I'm hoping there is some simple solution that I'm missing. I'd like to have a method on a trait that accepts as a parameter (and returns as a value only the type of the concrete implementation that it is being called on. Specifically:
trait Foo {
type ConcreteFoo // what to put here?
def combine(that:ConcreteFoo):ConcreteFoo
}
class FooImpl1 extends Foo {
def combine(that:FooImpl1):FooImpl1 = {
// implementation
}
}
class FooImpl2 extends Foo {
def combine(that:FooImpl2):FooImpl2 = {
// implementation
}
}
Right now I have a type Self = FooImpl on the implementing classes, but I'd rather have something on the trait that takes care of it if possible.
This is exactly F-Bounded Polymorphism:
trait Foo[T <: Foo[T]]
def combine(that: T): T
}
class FooImpl1 extends Foo[FooImpl1] {
def combine(that: FooImpl1): FooImpl1 = {
???
}
}
class FooImpl2 extends Foo[FooImpl2] {
def combine(that: FooImpl2): FooImpl2 = {
???
}
}
You can add a type parameter to your trait like this:
trait Foo[A] {
def combine(that: A): A
}
class FooImpl1 extends Foo[FooImpl1] {
override def combine(that: FooImpl1): FooImpl1 = ???
}
class FooImpl2 extends Foo[FooImpl2] {
override def combine(that: FooImpl2): FooImpl2 = ???
}

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.

How can I implement concrete class which extends trait defining a method with type by the type parameter's type alias

I would like ask for some help for advanced scala developers. My problem is that I would like to access a type alias belonging to a type parameters of a class' parent.
case class MyModel(foo: String = "bar")
case class MyDispatcher()
trait Module[M, D] {
type Dispatcher = D
type Model = M
}
trait MySpecificModule[A <: Module[_, _]] {
def dispatcher(): A#Dispatcher
}
class ModuleClass extends Module[MyModel, MyDispatcher] {
//...
}
class MySpecificModuleClass extends MySpecificModule[ModuleClass] {
override def dispatcher(): MyDispatcher = MyDispatcher()
}
So basically MySpecificModule extends a generic trait, and should know the type of the dispatcher method. In this case of MySpecificModuleClass it should be MyDispatcher. But when I try to compile this code I am getting compilation error because the type of the method, is not the same as defined: A#Dispatcher, however in the reality it is.
Error:(21, 18) overriding method dispatcher in trait MySpecificModule of type ()_$2;
method dispatcher has incompatible type
override def dispatcher(): MyDispatcher = MyDispatcher()
^
I would appreciate any advice you suggest. Thanks in advance,
Gabor
Resolved
case class MyModel(foo: String = "bar")
case class MyDispatcher()
trait AbstractModule {
type Dispatcher
type Model
}
trait Module[M, D] extends AbstractModule {
type Dispatcher = D
type Model = M
}
trait MySpecificModule[A <: AbstractModule] {
def dispatcher(): A#Dispatcher
}
class ModuleClass extends Module[MyModel, MyDispatcher] {
//...
}
class MySpecificModuleClass extends MySpecificModule[ModuleClass] {
override def dispatcher(): MyDispatcher = MyDispatcher()
}
I don't fully understand Scala's reasoning here, but if you get rid of type parameters, things start to work:
case class MyModel(foo: String = "bar")
case class MyDispatcher()
trait Module {
type Dispatcher
type Model
}
trait MySpecificModule[A <: Module] {
def dispatcher(): A#Dispatcher
}
class ModuleClass extends Module {
type Model = MyModel
type Dispatcher = MyDispatcher
//...
}
class MySpecificModuleClass extends MySpecificModule[ModuleClass] {
override def dispatcher(): MyDispatcher = MyDispatcher()
}
And if you really want to have those type params, you can introduce a helper trait:
trait AbstractModule[M, D] extends Module {
type Model = M
type Dispatcher = D
}
class ModuleClass extends AbstractModule[MyModel,MyDispatcher]