I have a situation where I want to use a bounded generic type as a constraint on what class can be produced. The problem is that I need to
abstract class SomeAbstractClass
trait Foo[A <: SomeAbstractClass]
trait Bar[A] extends Foo[A]
// Fails error: type arguments [A] do not conform to trait Foo's type parameter bounds [A <: SomeAbstractClass]
// Need to write it like this, for every single subclass of Foo
trait Bar[A <: SomeAbstractClass] extends Foo[A]
Is there an easier way to promote that through the system without having to retype the bounds every time?
Constraints on type parameters are constraints. They don't propagate transitively via inheritance as you would like them to.
Perhaps this is applicable or produces some new ideas at least:
abstract class SomeAbstractClass
trait Foo { // propagated by abstract type member
type A <: SomeAbstractClass
}
trait Bar extends Foo // no generic type parameter needed here
trait BAR[SAC <: SomeAbstractClass] extends Bar { type A = SAC } // introduce type parameter
trait Baz[SAC <: SomeAbstractClass] extends BAR[SAC] // generic type parameter needed here
Related
I believe my understanding on this is correct but I'd like to check. When creating typeclasses, it feels neater to have them take a single type parameter, like TypeClass[A]. If the typeclass needs to be parameterized in other ways, abstract types can be used, and there is a comparison of the two approaches here:
Abstract types versus type parameters
So far as I have been able to figure out, one thing which is not mentioned in the link is that if using a type parameter, you can witness that the parameter implements a (different) typeclass, likeso:
trait IsValidForTC[A]
abstract class TCWithTypeParam[A, B] (implicit ev: IsValidForTC[B]) {}
If I use an abstract type, I cannot be sure that it implements IsValidForTC:
abstract class TCWithAbstractType[A] (implicit ev: IsValidForTC[B]) {
type B
} //not found: Type B
If so then this makes sense, but this difference isn't mentioned in the link above so I'd like to check.
Thanks!
It's your choice whether to put implicit constraints on class level or method level. This makes impact on when the implicits are resolved.
In a type-parameter type class with implicit parameter you don't constrain the type of type class (applied to type parameters), i.e. type TCWithTypeParam[A, B] can be used even if there is no implicit IsValidForTC[B] in a scope. What you do constrain is the constructor of type class. You can emulate this behavior for type-member type class in the following way. Make the constructor private and define apply method (or instance as it's called sometimes) in companion object with desired implicit constraint
abstract class TCWithAbstractType[A] private {
type B
}
object TCWithAbstractType {
def apply[A, _B: IsValidForTC]: TCWithAbstractType[A] { type B = _B } =
new TCWithAbstractType[A] { type B = _B }
}
You can add the witness, but it needs to be inside the class scope so it has access to B:
abstract class TCWithAbstractType[A] {
type B
implicit val ev: IsValidForTC[B]
}
But in practice this is often less convenient than the type parameter, because it has to be implemented explicitly, something like
new TCWithAbstractType[A] {
type B = ...
implicit val ev: IsValidForTC[B] = ...
}
while a constructor parameter just gets the implicit value from outer scope.
Note: this is a partial duplicate of my answer to your follow-up question, but left here in case someone stumbles on this question first.
Considering the following example:
trait Supe {
type Out <: Supe
def out: Out
}
class Reif1 extends Supe {
type Out = Reif1
override def out: Out = this
}
class Reif2 extends Supe {
type Out >: this.type <: Reif2
override def out: Out = this
}
class Reif1 should obviously work. as type Out is reified and becomes a type alias
class Reif2 also works but seriously? type Out only has upper/lower bound defined and the bound was not even tight enough: this.type is a singleton type, and Reif2 is a class type. So what exactly would Out look like if Reif2 got instantiated? Is it going to be this.type? Or Reif2? But the bigger question should be: why scalac 2.12/2.13 allows it be compiled?
In Scala (or in DOT calculus 1 2) all types are intervals.
type Out = Reif1 is (or is supposed to be) type Out >: Reif1 <: Reif1.
Abstract type without bounds type Out is type Out >: Nothing <: Any.
So what exactly would Out look like if Reif2 got instantiated?
It will remain exactly type Out >: this.type <: Reif2
val r = new Reif2
import scala.reflect.runtime.universe._
typeOf[r.Out] // App.r.Out
showRaw(typeOf[r.Out]) // TypeRef(SingleType(ThisType(App), TermName("r")), TypeName("Out"), List())
typeOf[r.Out].typeSymbol.isAbstract // true
typeOf[r.Out].typeSymbol.typeSignature // >: Reif2.this.type <: App.Reif2
If you replace type Out >: this.type <: Reif2 in Reif2 with type Out = this.type (or type Out = Reif2 or type Out = Supe) then isAbstract will return false.
Abstract type member of a singleton object (see application of abstract type)
What is the meaning of a type declaration without definition in an object? (see why it's not easy to check that type T >: L <: U is not abstract)
Concrete classes can have abstract type members #1753
SI-8217 allow abstract type members in objects #4024
Abstract type members are incorrectly forbidden in objects (unless inherited) #8217
Use of abstract type in a concrete class?
Concrete classes with abstract type members
What's different between "def apply[T](c:T)" and "type T;def apply(c:T)"
I am trying to replace a type defined with 2 type parameters and a self-type, with a simpler version based on a single path-dependent type.
My problem is that I can't express the same constraints. I think a full example will be easier to understand.
class Attribute
// Test 1: Using a 2nd higher-kinded parameter and a self-type
// Advantage: Subclasses are constrained by the self-type to specify the "This" parameter correctly
// Drawback: Container1 has a 2nd type parameter and "AnyContainer1" type is really ugly
sealed trait Container1[+A <: Attribute, +This[+X <: Attribute] <: Container1[X, This]] { self: This[A] => }
class Foo[+A <: Attribute] extends Container1[A, Foo]
//class Bar[+A <: Attribute] extends Container1[A, Foo] // Won't compile :-)
object Container1 {
// This is ugly...
type AnyContainer1[+X <: Attribute] = F[X] forSome { type F[+Y <: Attribute] <: Container1[X, F] }
// type AnyContainer1[+X <: Attribute] = Container1[X, AnyContainer1] // This doesn't compile :-(
}
// Test 2: Using path-dependent types
// Advantage: Container2 has a single type parameter and AnyContainer2 is easily expressed
// Drawback: The path-dependent parameter can be overridden in a bad way
sealed trait Container2[+A <: Attribute] {
protected type This[+B <: Attribute] <: Container2[B]
// Note that I need `This` to take a generic parameter in
// order to declare methods like `def foo[T]: This[T]` in subclasses.
}
class Bar[+A <: Attribute] extends Container2[A] {
override protected type This[+X <: Attribute] = Bar[X]
}
class Baz[+A <: Attribute] extends Container2[A] {
// Will compile even though `Bar != Baz`, I'd like to enforce This = Baz somehow...
override protected type This[+X <: Attribute] = Bar[X]
}
object Container2 {
type AnyContainer2[+X <: Attribute] = Container2[Attribute] // Easy :-)
}
Is there a better way to constrain the This type while using path-dependent types to keep things clean? With the next version of Scala (Dotty for now), existential types will disappear, and should be replaced with path-dependent ones, so how do I implement this without existential types?
I want to limit a parameter of union type of A and B types, where B is some general type, that will be subtyped. I want to put the objects in this method:
def accept[A](a:A)(implicit ev:FooOrBaish[A]){ /* do something */}
This is, how do I specify the implicits:
case class Foo(i:Int)
trait Baish
case object Bar extends Baish
case class Baz(x:String) extends Baish
class FooOrBaish[A]
object FooOrBaish{
implicit object FooWit extends FooOrBaish[Foo]
implicit object BaishWit extends FooOrBaish[Baish]
}
Now, I can put in accept Foo(5), but cannot put there Baz("a") neither Bar, the compiler screams: error: could not find implicit value for parameter ev: FooOrBaish[Baz].
Where can I specify the subtype relation?
Change FooOrBaish's type to be contravariant and it works
class FooOrBaish[-A]
I have defined the following trait:
trait Felem[T <: Felem[T]] {
def mul(that: T): T
def square: T = this.mul(this.asInstanceOf[T])
}
I also define a class based on this trait:
class F2elem(val coef: Boolean) extends Felem[F2elem] {
override def square: F2elem = this.mul(this)
...
}
My questions are about the need of "asInstanceOf" in the definition of the "square" method in the trait. If I remove it, I get the following error:
error: type mismatch;
found : Felem.this.type (with underlying type Felem[T])
required: T
def square: T = this.mul(this)
Why is it needed in the trait ?
Why it is not needed in the class ?
Does it cost anything in term of execution time or memory ?
The parameter of mult must be of type T.
When calling mul(this), the this parameter is of type Felem[T], which is not and does not conform to T. There is the additional constraint that T conforms to Felem[T]. But this is not what you want, you would need the opposite, Felem[T] to conform to T.
On the other hand, in F2elem, T is exactly F2elem, so it typechecks (completley unrelated to one being a trait and the other one a class)
Here is example to show that the definition in Felem must indeed not typecheck, and that it is possible to have implementors where Felem[T] does not conform to T.
class F3elem extends Felem[F2elem] // this is 2, not 3
This declaration is correct, F2elem which is given for T satisfies T <: Felem[T].
However, an inherited this.mul(this) in square would be invalid, mult expect a T, that is F2elem, and this is F3elem. And they are unrelated.
What you probably want is that every Felem must be like F2elem, that is that T must be the type of the actual class. You can enforce this with a self type.
trait Felem [T <: Felem[T]] { this: T => /* your code */ }
When you write that, you state that in every implementation, the type of the implementation must conform to T. Doing that, it will typecheck, and you will not be allowed to instanciate F3elem above :
error: illegal inheritance; self-type F3elem does not conform to
Felem[F2elem]'s selftype F2elem
class F3elem extends Felem[F2elem] {
1) In your trait this is not an instance of T:
scala> trait Felem[T <: Felem[T]] {
| def mul(that: T): T = that
| def square: T = this.mul(this.asInstanceOf[T])
| }
defined trait Felem
scala> class F2elem extends Felem[F2elem]
defined class F2elem
scala> class F3elem extends Felem[F2elem]
defined class F3elem
scala> new F3elem()
res1: F3elem = F3elem#2e0b08f1
scala> res1.square
java.lang.ClassCastException: F3elem cannot be cast to F2elem
2) In your class this is F2elem and T == F2elem, so this is T.
You're using T as your method's parameter type. This means that whatever the type of T is will be the type that's required in the method (hence not needing the cast in the class, since its type is what is signified by T originally).
If you change the type of that to Felem[T], you will not need the cast.