I have a somewhat complex typeclass situation in the following format:
sealed trait TypeClass[S <: MyType] {
type Out <: MyType
}
sealed trait LowPriorityTypeClass {
// Case: OtherTypeClass is NOT defined for the input type S.
// The output type is the same as the input type.
implicit def default[S <: MyType]: TypeClass.Aux[S, S] = ???
}
object TypeClass extends LowPriorityTypeClass {
type Aux[S <: MyType, O <: MyType] = TypeClass[S] { type Out = O }
// Case: OtherTypeClass is defined for the input type S.
// The output type is the same as in the OtherTypeClass definition.
implicit def hasOtherTC[S <: MyType, O <: MyType](
implicit otherTC: OtherTypeClass.Aux[S, O],
): TypeClass.Aux[S, O] = ???
}
The default definition was put in the LowPriorityTypeClass trait with the intention of having a lower priority. However, an ambiguity with hasOtherTC still happens for some type S, apparently because the declaration of default is more specific than the declaration of hasOtherTC for that type S.
Is there a general way to ensure that an implicit definition will always have a higher/lower priority than other definition? (My question is not for the specific code above.)
Let me know if posting a more complete sample code would help.
Please see Why is this implicit ambiguity behaviour happening? including comments.
There is no sense in introducing trait LowPriorityTypeClass in this case because anyway implicit default is more specific than hasOtherTC.
There is no general way. You can use type classes Not (shapeless.Refute, implicitbox.Not) or shapeless.LowPriority, implicitbox.Priority or library https://github.com/milessabin/export-hook.
object TypeClass {
type Aux[S <: MyType, O <: MyType] = TypeClass[S] {type Out = O}
implicit def hasOtherTC[S <: MyType, O <: MyType](implicit
otherTC: OtherTypeClass.Aux[S, O]
): TypeClass.Aux[S, O] = ???
implicit def default[S <: MyType](implicit
noOtherTC: Refute[OtherTypeClass[S]]
): TypeClass.Aux[S, S] = ???
}
Related
The problem I'm looking at involves defining a trait which has type parameters, where it's type parameters have type parameters where I need to know these types as well. For example, consider the following code:
trait JoinData[A <: Attributes,
L <: Entity[A],
B <: Keyed
R <: Document[B],
O <: Output[A, B]] extends Something[A,L,B,R]{
def method1(a:A) : String
def method2(L, R) : O = {
...
}
...
}
When the user implements this, the types L and R do dictate what A and B have to be, however I'm not sure if it's possible to remove A and B from my API.
This problem is resulting in pretty ugly APIs, where it's pretty difficult for a user to understand why they are having to specify some of the types, or even what they should be.
Can anyone point me at some ideas of how I might resolve this (if at all possible!).
It's hard to answer your question without any details about your API and how you use it. Currently it's not even clear why monomorphic
trait JoinData extends Something {
def method1(a: Attributes): String
def method2(e: Entity, r: Document): Output = {
...
}
...
}
is insufficient.
You can try to replace (some of) type parameters with type members:
trait Attributes
trait Entity {
type A <: Attributes
}
trait Keyed
trait Document {
type B <: Keyed
}
trait Something[L <: Entity, R <: Document]
trait Output[A <: Attributes, B <: Keyed]
trait JoinData[L <: Entity, R <: Document] extends Something[L, R]{
def method1(a: L#A): String
def method2(l: L, r: R): Output[L#A, R#B] = ???
}
using type projections or
trait Attributes
trait Entity
trait Keyed
trait Document
trait Something[L <: Entity, R <: Document]
trait Output[A <: Attributes, B <: Keyed]
trait TC[L <: Entity] {
type A <: Attributes
}
trait TC1[R <: Document] {
type B <: Keyed
}
abstract class JoinData[L <: Entity, R <: Document](implicit val tc: TC[L]) extends Something[L, R]{
def method1(a: tc.A) : String
def method2(l: L, r: R)(implicit tc1: TC1[R]): Output[tc.A, tc1.B] = ???
}
using type classes.
Let's set up issue conditions
trait Bound
trait Bound2 extends Bound
trait T1[B <: Bound]
trait T2[B <: Bound2] extends T1[B]
trait WrapperT1[Tz[B2 <: Bound] <: T1[B2]]
This code compile without problem, issue comes when trying to extend WrapperT1
trait WrapperT2[Tz[B2 <: Bound2] <: T2[B2]] extends WrapperT1[T2]
// Compilator error
kinds of the type arguments (T2) do not conform to the expected kinds of the type parameters (type Tz) in trait WrapperT1.
[error] ex.T2's type parameters do not match type Tz's expected parameters:
[error] type B's bounds <: Bound2 are stricter than type B2's declared bounds <: ex.Bound
[error] trait WrapperT2[Tz[B2 <: Bound2] <: T2[B2]] extends WrapperT1[T2]
Yes B2 <: Bound2 is stricter than B2 <: Bound but i'm not understanding why the compilator complains for this reason and i would be grateful to know more about it.
Potential solution but does it has some drawbacks ?
trait Bound
trait Bound2 extends Bound
trait T1[B] {
implicit val ev: B <:< Bound
}
trait T2[B] extends T1[B] {
// this is possible thank's to covariance of class `<:<[-From, +To]` if i'm not wrong
implicit val ev: B <:< Bound2
}
trait WrapperT1[Tz[B2] <: T1[B2]]
// Compiles
trait WrapperT2[Tz[B2] <: T2[B2]] extends WrapperT1[Tz]
This looks nice and we keep the compilation checks about B2 generic type but is there any inconveniant using it ?
To understand why the compiler complains, let's look at this piece of code (which is the same as your code, but with different names)
trait Show[A <: AnyRef] {
def show(a: A): Unit
}
trait ShowString[S <: CharSequence] extends Show[S] {
def show(s: S): Unit = println(s)
}
trait Wrapper1[S[A <: AnyRef] <: Show[A]] {
def show[A <: AnyRef](a: A)(implicit show: S[A]): Unit = show.show(a)
}
trait Wrapper2[S[C <: CharSequence] <: ShowString[C]] extends Wrapper1[S]
Here, too, the compiler emits the error type C's bounds <: CharSequence are stricter than type A's declared bounds <: AnyRef, but let's pretend you somehow get it to compile here. That would mean if someone wanted to use your wrapper class to print, say, a list, they wouldn't be able to do that, meaning that your wrapper class would be basically useless.
val w2: Wrapper[ShowString] = ???
w2.show(List(1, 2))
//Error: could not find implicit value for parameter show: ShowString[List[Int]]
In this case, it's just that the implicit isn't found. However, if you have a method in wrapper that accepts a S[_] (or Tz[_], from your example) and you try to shoehorn an object of the wrong type into your show method, you could get a runtime exception.
It fails for the same reason you can't do this
trait T1[A] {
def process(a: A): Unit
}
trait T2 extends T1[Int] {
def process(i: Int): Unit = ???
}
val t2: T2 = ???
t2.process("not an int")
T2 is just not equipped to handle anything that is not an Int, and the same applies here.
I have the following problem with hierarchy of traits in Scala code:
First of all, I have a basic trait MyTrait[A] with such definition:
trait MyTrait[A] {
def v1: A
}
It is then followed by a definition of a trait Base with a type-member:
trait Base[A] {
type T <: MyTrait[A]
val baseV: T
}
And, at last, a trait Gen which overrides Base's type member.
trait Gen[A, X <: MyTrait[A]] extends Base[A] {
type T = X
}
The problem is that in the Gen trait it seems that bounds of the type-member are lost. This can be proven by following tests:
Compiles:
trait Test1 {
val x: Base[_]
println(x.baseV.v1)
}
Doesn't compile (value v1 is not a member of Test2.this.x.T):
trait Test2 {
val x: Gen[_, _]
println(x.baseV.v1)
}
I would like to know whether it's a limitation of the language or there is a workaround it. Questions on similar topics on stackowerflow (1, 2) appear to be focusing on different aspects than mine and I am genuinely at a loss because I can't find much information about such behavior in Scala.
Scala code template of this question can be found on scastie
This works:
trait Test2 {
val x: Gen[A, X] forSome { type A; type X <: MyTrait[A] }
println(x.baseV.v1)
}
I believe the issue is that
Gen[_, _]
Has to mean
Gen[_ >: Nothing <: Any, _ >: Nothing <: Any]
Which is the same as
Gen[A, X] forSome { type A; type X }
That is, even though the bounds on Gen say that X <: MyTrait[A], the wildcards do not inherit that bound. You can see a similar problem here:
trait Data { def data: String }
trait Box[A <: Data] { def data: A }
def unbox(b: Box[_]): String = b.data.data // nope; the wildcard is not <: Data
We can add the bounds to the wildcards explicitly. However, because the bound on the second wildcard depends on the first one, we are forced to use the extended forSome syntax for the existential, so we can name A and use it twice.
Gen[A, _ <: MyTrait[A]] forSome { type A }
And I opted to just put everything in the existential clause, which is equivalent:
Gen[A, X] forSome { type A; type X <: MyTrait[A] }
You can also use
Gen[_, _ <: MyTrait[_]]
but this is not equivalent, as it doesn't relate the left and right parameters. If Gen[A, _] contained an A in addition to a MyTrait[A], then using x: Gen[_, _ <: MyTrait[_]] would render the "bare" value and the "wrapped" value with incompatible types.
I have the following types defined:
trait Context
trait Attribute[C <: Context]
trait AttributeDefinition[A[_ <: Context] <: Attribute[C] forSome { type C <: Context }] {
def read[C <: Context]: A[C]
}
I want AttributeDefinition to return an Attribute parameterized by a context. Each definition applies to only one type of Attribute. Hypothetically:
class ConstantValueAttribute[C <: Context] extends Attribute[C]
object ConstantValueAttributeDefinition extends AttributeDefinition[ConstantValueAttribute] {
override def read[C <: Context]: ConstantValueAttribute[C] = ???
}
This totally compiles fine, but i have trouble crafting method signatures that accept an unbounded AttributeDefinition. As well as creating a collection of AttributeDefinitions. All the following fail:
def def1(attribute: AttributeDefinition): Unit = ???
def def2(attribute: AttributeDefinition[_]): Unit = ???
def def3(attribute: AttributeDefinition[_ <: Attribute[C] forSome { type C <: Context }]): Unit = ???
def def4(attribute: AttributeDefinition[A[_]] forSome { type A <: Attribute[C]; type C <: Context })
EDIT:
I am trying to find a method declaration like the above that will compile. I also am looking to figure out how to declare a collection of AttributeDefinitions.
val val1 = new mutable.ArrayBuffer[AttributeDefinition]
if that is not possible i would love to figure out how to simplify the traits above that can still capture the simple read method declaration.
Proposal with a type member
Essentially, what you want in both defN-declarations and Seq-collections of AttributeDeclaration is a higher kinded existential type for Attribute that is used as an argument for AttributeDeclaration. So, ideally, you would like to have:
def def1(attrDef: AttributeDeclaration[A] forSome {
type A[C <: Context] <: Attribute[C] }): Unit = ???
However, this does not work, and this is an unresolved issue since 2015, so chances are that it won't be fixed until dotty.
Instead of trying to force the compiler to work with higher kinded existentials so that you can throw away the unnecessary type parameter, I would propose that you don't add the annoying type parameter in the first place. Consider these definitions (compiles with 2.12.4):
trait Context
trait Attribute[C <: Context]
trait AttributeDefinition {
type A[C <: Context] <: Attribute[C]
def read[C <: Context]: A[C]
}
class ConstantValueAttribute[C <: Context] extends Attribute[C]
object ConstantValueAttributeDefinition extends AttributeDefinition {
type A[C <: Context] = ConstantValueAttribute[C]
def read[C <: Context]: ConstantValueAttribute[C] = ???
}
def def1(attrDef: AttributeDefinition): Unit = ???
val collDefs: Seq[AttributeDefinition] = List(ConstantValueAttributeDefinition)
These definitions have following properties:
Context and Attribute are left unchanged
A becomes a type member of AttributeDefinition, so it does not have to be forcefully erased by an existential later
read-signature is completely unchanged, it's literally the same as in your original code, character for character
declaration of the simplest and most intuitive def1 works immediately without changes (also essentially copied from your code 1:1)
declaration and definition of collections with AttributeDefinitions also works just as smoothly.
Recall that you don't even lose anything if you move something from type parameter to type member, because you can always refer to the type member by name (for example, when you want to impose an additional restriction on that type member). The following minimal example is supposed to demonstrate how to impose additional restrictions on type members (vaguely related to your code, uses Context):
trait Foo {
type Member
}
def restrictiveFoo(x: Foo { type Member <: Context }): Unit = ???
Here, you can force the argumen Foo to have type member that derives from Context. It would work with type Member = Context similarly. What I wanted to demonstrate by that: it's not like type members are forever hidden inside the trait, if you introduce them. You can do with them almost all the same things as with type parameter. To summarize:
Adding additional constraints on a (higher kinded) type member is easy
Erasing a superfluous type parameter with higher kinded existentials doesn't seem to work at all.
So, therefore, I would advise to use a type member.
Proposal with change of variance from A to +A
Another quick-fix to make it compile:
trait Context
trait Attribute[C <: Context]
trait AttributeDefinition[+A[X <: Context] <: Attribute[X]] {
def read[C <: Context]: A[C]
}
class ConstantValueAttribute[C <: Context] extends Attribute[C]
object ConstantValueAttributeDefinition
extends AttributeDefinition[ConstantValueAttribute] {
override def read[C <: Context]: ConstantValueAttribute[C] = ???
}
class FooValueAttribute[C <: Context] extends Attribute[C]
object FooValueAttributeDefinition
extends AttributeDefinition[FooValueAttribute] {
override def read[C <: Context]: FooValueAttribute[C] = ???
}
def def1(attrDef: AttributeDefinition[Attribute]): Unit = {}
def1(ConstantValueAttributeDefinition)
val listOfDefs: List[AttributeDefinition[Attribute]] = List(
ConstantValueAttributeDefinition,
FooValueAttributeDefinition
)
inspired by the answer to this question. Strangely enough, it's seems to be almost exactly the same constellation. It didn't solve the problem with higher kinded existentials though, instead the variance of A has been changed to +A, so that one could use Attribute as least upper bound of all concrete As.
The correct signature would be
def `def`[A[_ <: Context] <: Attribute[C] forSome { type C <: Context }](attribute: AttributeDefinition[A]) = ???
You need to use a type parameter for your function, which should have (at least) the same constraints as the one in your type.
Note: in my opinion this is a reason why you should avoid putting type constraints on the type arguments for your type, they should only be put on functions/operations instead.
trait Context
trait Attribute[+Context]
trait AttributeDefinition[Attribute] {
def read[Context]: Attribute
}
class ConstantValueAttribute extends Attribute[Context]
object ConstantValueAttributeDefinition extends AttributeDefinition[ConstantValueAttribute] {
def read[Context]: ConstantValueAttribute = ???
}
def def1[C](attribute: AttributeDefinition[C]) : Unit = ???
As a hint for simplification too.
Having trouble with type "kinds":
trait Sys[ S <: Sys[S]]
trait Expr[S <: Sys[S], A]
trait Attr[S <: Sys[S], A[_]]
def test[ S <: Sys[S]]: Attr[S, ({type l[x<:Sys[x]]=Expr[x,Int]})#l] = ???
This fails with
error: kinds of the type arguments (S,[x <: Sys[x]]Expr[x,Int]) do not conform
to the expected kinds of the type parameters (type S,type A) in trait Attr.
[x <: Sys[x]]Expr[x,Int]'s type parameters do not match type A's expected parameters:
type x's bounds <: Sys[x] are stricter than type _'s declared bounds >: Nothing <: Any
def test[S <: Sys[S]]: Attr[S, ({type l[x<:Sys[x]]=Expr[x,Int]})#l] = ???
^
What's the problem with the declared bounds? Do I need to carry that cr*ppy partially applied type into the type constructor of trait Attr? And why? Can I fix this without touching the definition of Attr?
I do need the bounds in function test in order for the implementation to work, but I do not want to proliferate those bounds to the public interface Attr.
Note: If I use a type member (what I don't want), it works:
trait Attr[S <: Sys[S]] { type A[_]}
def test[ S <: Sys[S]]: Attr[S] { type A[S <: Sys[S]] = Expr[S, Int]} = ???
As you observed, you can't always mismatch bounds when providing a higher-kinded type argument. Interestingly, it is actually a variance issue:
class A
class B extends A
trait NeedsNeedsA[T[S <: A]]
trait NeedsNeedsB[T[S <: B]]
trait NeedsA[S <: A]
trait NeedsB[S <: B]
def x: NeedsNeedsA[NeedsB] // fails
def y: NeedsNeedsB[NeedsA] // works
which makes sense if you think of a higher-kinded type as a function on types, contravariant in its argument's bound.
Interestingly, in a structural type, which is on the surface a lot like subtyping, Scala does not give the same error:
def t: MemberNeedsA { type T[S <: B] }
def u: MemberNeedsB { type T[S <: A] }
the reason being that a structural type is sort of like an intersection:
def s: MemberNeedsA with MemberNeedsB
and it may be that that intersection cannot actually exist in nature, but Scala doesn't check that.
OK, but that's not so relevant to your question. Back to your question: I think you have a variance issue. You want test to give the caller back an Attr, and an Attr posseses a type function (A[_]), and you want to say, this Attr has a type function that requires a more specific argument. I think you can see why you shouldn't be allowed to do that -- it's the same reason you can't substitute a function requiring a more specific argument in place of one that would require a more general argument.
At this point I'm afraid the solution will have to depend on what you want Attr to be able to accomplish. You need to figure out why you need to restrict the type argument in some cases more than others. If it makes conceptual sense in your program that "some Attrs are more restrictive than others", you could define:
trait Attr[S <: Sys[S], B[Y <: B[Y]], A[X <: B[X]]]
def test[S <: Sys[S]]: Attr[S, Sys, L] = ...
But the solution will depend on what the restriction on A[_]'s argument is intended to mean.