class OptionLikeMatcher[F[_], T, U](typeName: String, toOption: F[T] => Option[U]) extends Matcher[F[T]] { ... }
case class RightMatcher[T]() extends OptionLikeMatcher[({type l[a]=Either[_, a]})#l, T, T]("Right", (_:Either[Any, T]).right.toOption)
case class LeftMatcher[T]() extends OptionLikeMatcher[({type l[a]=Either[a, _]})#l, T, T]("Left", (_:Either[T, Any]).left.toOption)
Can some one explain what are they trying to achieve here with the type parameters?
What type does the toOption: F[T] => Option[U] parameter have when used for RightMatcher and LeftMatcher?
Its from Scala Specs2 library.
Let's first address the most complicated type constructor here:
({type l[a]=Either[_, a]})#l
is the original way to encode type lambdas. It could be written more succinctly as either
Either[_, ?]
with kind-projector, or as
[X] =>> Either[_, X]
in Scala 3. It basically just selects the right parameter of Either as the relevant one, and replaces the left parameter by a wildcard.
Now, the three type parameters of OptionLikeMatcher are:
F[_] - the type constructor for the thing that's being matched; It is expected to behave "somewhat similarly" to Option, i.e. there should be some way to get an Option out of it.
T is the type that is plugged into the F type constructor; Together F[T] gives the type of what's being matched.
U can be thought as a "slightly perturbed" version of T; It captures basically the same information as T, but it allows for some controlled sloppiness when transforming an F[T] into an Option[U] (otherwise, if one required F[T] => Option[T], that would practically be the same as asking for quite a rigid structure called natural transformation F ~> Option, which might be too restrictive in this case).
Putting it all together gives for RightMatcher:
F[A] = Either[_, A] - i.e. pick right parameter of Either, ignore left parameter
The T from the OptionLikeMatcher is bound to T from RightMatcher
The U from the OptionLikeMatcher is also bound to T from RightMatcher, i.e. the possibility to "slightly perturb" T is not used here, both T and U from OptionLikeMatcher are instantiated by the same type.
Thus, the toOption in RightMatcher has the type Either[_, T] => Option[T]. The (_:Either[Any, T]).right.toOption conforms to Either[_, T] => Option[T], because the Any appears in a covariant position within Either, and Either[Any, T] appears in a contravariant position within the function type, so that Either[Any, T] => Option[T] is a subtype of Either[_, T] => Option[T], because its input type is strictly more permissive, and it can be used everywhere an Either[_, T] => Option[T] is needed.
All that being said, I couldn't find any applications of F that were not of shape F[T] (neither in OptionLikeMatcher, nor in OptionLikeChekedMatcher), so it seems that they are splitting up the input type into a type constructor F and a type parameter T, only to use them as an indivisible unit F[T] later on. Maybe this was left over after a refactoring from some more complicated construct.
What type does the toOption: F[T] => Option[U] parameter have when used for RightMatcher and LeftMatcher
Perhaps a concrete drill exercise would help. Let's consider RightMatcher[Int]() where we instantiate type parameter T to Int. This gives us
F = ({type l[a]=Either[_, a]})#l
T = Int
U = T = Int
and plugging the above type instantiations into
toOption: F[T] => Option[U]
we get
toOption: (({type l[a]=Either[_, a]})#l)[Int] => Option[Int]
which simplifies to
toOption: Either[_, Int] => Option[Int]
Let's do another drill for LeftMatcher[String](). We have
F = ({type l[a]=Either[a, _]})#l
T = String
U = T = String
and plugging the above type instantiations into
toOption: F[T] => Option[U]
we get
toOption: (({type l[a]=Either[a, _]})#l)[String] => Option[String]
which simplifies to
toOption: Either[String, _] => Option[String]
Related
I have a trait with a self-type annotation that has a type parameter. This trait is from a library and cannot be modified. I want to pass this trait to a function that will require an upper bound for the type parameter. For example, I have this code snippet:
sealed trait Job[K] { self =>
type T
}
case class Encoder[T <: Product]()
def encoder(job: Job[_])(implicit ev: job.T <:< Product): Encoder[job.T] =
new Encoder[job.T]()
This returns an error that Type argument job.T does not conform to upper bound Product and a warning that ev is never used. How should I design the encoder function?
Why it doesn't work?
Your issue has nothing to do with the generalized type constraint. You can remove it and still get the same error. A generalized type constraint is used to constrain the type of arguments the method can receive.
(implicit ev: job.T <:< Product) provides an evidence in scope that matches only if job.T <: Product, allowing only calls to the method with Job arguments where job.T <: Product. This is its purpose.
Your issue is because the Encoder class has its type parameter T <: Product. The generalized type constraint does not treat the type job.T itself as a subtype of Product, as you expected. The evidence only applies to value arguments, not to the type itself, because this is how implicit conversions work.
For example, assuming a value x of type job.T that can be passed to the method as an argument:
def encoder(job: Job[_])(x: job.T)(implicit ev: job.T <:< Product): Unit = {
val y: Product = x // expands to: ev.apply(x)
val z: Encoder[Product] = new Encoder[job.T] // does not compile
}
The first line compiles because x is expanded to ev.apply(x), but the second one cannot be expanded, regardless if Encoder is covariant or not.
First workaround
One workaround you can do is this:
def encoder[U <: Product](job: Job[_])(implicit ev: job.T <:< Product): Encoder[U] =
new Encoder[U]()
The problem with this is that while both type parameters U and T are subtypes of Product, this definition does not says much about the relation between them, and the compiler (and even Intellij) will not infer the correct resulting type, unless you specify it explicitly. For example:
val myjob = new Job[Int] {
type T = (Int, Int)
}
val myencoder: Encoder[Nothing] = encoder(myjob) // infers type Nothing
val myencoder2: Encoder[(Int, Int)] = encoder[(Int, Int)](myjob) // fix
But why use job.T <:< Product if we already have U <: Product. We can instead use the =:= evidence to make sure their types are equal.
def encoder[U <: Product](job: Job[_])(implicit ev: job.T =:= U): Encoder[U] =
new Encoder[U]()
Now the resulting type will be correctly inferred.
Second workaround
A shorter workaround is using a structural type instead:
def encoder(job: Job[_] { type T <: Product }): Encoder[job.T] =
new Encoder[job.T]()
Which is not only cleaner (doesn't require a generalized type constraint), but also avoids the earlier problem.
Both versions work on Scala 2.13.8.
Expanding on Alin's answer, you may also use a type alias to express the same thing like this:
type JobProduct[K, P <: Product] = Job[K] { type T = P }
// Here I personally prefer to use a type parameter rather than an existential
// since I have had troubles with those, but if you don't find issues you may just use
// JobProdut[_, P] instead and remove the K type parameter.
def encoder[K, P <: Product](job: JobProduct[K, P]): Encoder[P] =
new Encoder[P]()
This approach may be more readable to newcomers and allows reuse; however, is essentially the same as what Alin did.
Given:
class Invar[T]
trait ExtendsAnyref extends AnyRef
def f(a: Invar[ExtendsAnyref]) = {}
The following is erroneous
scala> val x: Function1[Invar[_ <: AnyRef], Unit] = f
<console>:13: error: type mismatch;
found : Invar[ExtendsAnyref] => Unit
required: Invar[_ <: AnyRef] => Unit
val x: Function1[Invar[_ <: AnyRef], Unit] = f
^
Why?
I understand that in Scala, generic types have by
default nonvariant subtyping. Thus, in the context of this example, instances of Invar with different type parameters would never be in a subtype relationship with each other. So an Invar[ExtendsAnyref] would not be usable as a Invar[AnyRef].
But I am confused about the meaning of _ <: AnyRef which I understood to mean "some type below AnyRef in the type hierarchy." ExtendsAnyref is some type below AnyRef in the type hierarchy, so I would expect Invar[ExtendsAnyref] to conform to Invar[_ <: AnyRef].
I understand that function objects are contravariant in their input-parameter types, but since I use Invar[_ <: AnyRef] rather than Invar[AnyRef] I understood, apparently incorrectly, the use of the upper bounds would have the meaning "Invar parameterized with Anyref or any extension thereof."
What am I missing?
When you write
val x: Function1[Invar[_ <: AnyRef], Unit] = ...
it means x must accept any Invar[_ <: AnyRef]. That is, it must accept Invar[AnyRef], Invar[String], etc. f obviously doesn't: it only accepts Invar[ExtendsAnyref].
In other words, you need to combine your last two paragraphs: because functions are contravariant in argument types, for Function1[Invar[ExtendsAnyref], Unit] to conform to Function1[Invar[_ <: AnyRef], Unit] you'd need Invar[_ <: AnyRef] to conform to Invar[ExtendsAnyref], not vice versa.
If you
want a function that takes Invar parameterized with any subclass of AnyRef
this can be written as Function1[Invar[A], Unit] forSome { type A <: AnyRef }. However, I don't believe there is anything useful you could do with an object of this type, because 1) the only thing you can do with a function is to apply it to an argument, but 2) you don't know what arguments this function accepts.
I'm reading Functional Programming in Scala, and in chapter 04 the authors implement Option on their own. Now, when defining the function getOrElse they use an upper bound to restrict the type of A to a supertype (if a understood correctly)
So, the definition goes:
sealed trait Option[+A] {
def getOrElse[B >: A](default: => B): B = this match {
case None => default
case Some(a) => a
}
}
So, when we have something like
val a = Some(4)
println(a.getOrElse(None)) => println prints a integer value
val b = None
println(b.getOrElse(Some(3)) => println prints a Option[Integer] value
a has type Option[Int], so A would be type Int. B would be type Nothing. Nothing is a subtype of every other type. That means that Option[Nothing] is a subtype of Option[Int] (because of covariance), right?
But with B >: A we said that B has to be a supertype?! So how can we get an Int back? This is a bit confusing for me...
Anyone care to try and clarify?
That means that Option[Nothing] is a subtype of Option[Int] (because of covariance), right?
Correct. Option[Nothing] is an Option[Int].
But with B >: A we said that B has to be a supertype?! So how can we get an Int back?
It doesn't have to be a super-type. It just requires A as a lower-bound. Which means you can still pass Int to getOrElse if A is Int.
But that doesn't mean you can't pass instances of a sub-class. For instance:
class A
class B extends A
class C extends B
scala> Option(new B)
res196: Option[B] = Some(B#661f82ac)
scala> res196.getOrElse(new C)
res197: B = B#661f82ac
scala> res196.getOrElse(new A)
res198: A = B#661f82ac
scala> res196.getOrElse("...")
res199: Object = B#661f82ac
I can still pass an instance of C, because C can be up-cast to B. I can also pass a type higher up the inheritance tree, and getOrElse will return that type, instead. If I pass a type that has nothing to do with the type contained in the Option, then the type with the least upper-bound will be inferred. In the above case, it's Any.
So why is the lower-bound there at all? Why not have:
def getOrElse[B <: A](default: => B): B
This won't work because getOrElse must either return the A that's contained in the Option, or the default B. But if we return the A, and A is not a B, so the type-bound is invalid. Perhaps if getOrElse returned A:
def getOrElse[B <: A](default: => B): A
This would work (if it were really defined that way), but you would be restricted by the type-bounds. So in my above example, you could only pass B or C to getOrElse on an Option[B]. In any case, this is not how it is in the standard library.
The standard library getOrElse allows you to pass anything to it. Say you have Option[A]. If we pass a sub-type of A, then it is up-cast to A. If we pass A, obviously this is okay. And if we pass some other type, then the compiler infers the least upper-bound between the two. In all cases, the type-bound B >: A is met.
Because getOrElse allows you to pass anything to it, many consider it very tricky. For example you could have:
val number = "blah"
// ... lots of code
val result = Option(1).getOrElse(number)
And this will compile. We'll just have an Option[Any] that will probably cause an error somewhere else down the line.
Assume the existence of the following types and method:
trait X[A <: X[A]]
case class C extends X[C]
def m(x: PartialFunction[X[_], Boolean])
I want to be able to create a PartialFunction to be passed into m.
A first attempt would be to write
val f: PartialFunction[X[_], Boolean] = {
case c: C => true
}
m(f)
This fails with type arguments [_$1] do not conform to trait X's type parameter bounds [A <: X[A]]. So, it seems we have to constraint X's type parameters.
A second attempt:
val f: PartialFunction[{type A <: X[A]}, Boolean] = {
case c: C => true
}
m(f)
This fails on the application of m because PartialFunction[AnyRef{type A <: X[this.A]},Boolean] <: PartialFunction[X[_],Boolean] is false.
Is there any way not involving casting that actually satisfies the compiler both on the definition of the partial function and on the application of m?
I'm not sure what exactly you want, but since you are using an existential type (in disguise of the _ syntax), this is how you can make that work:
val f: PartialFunction[X[A] forSome {type A <: X[A]}, Boolean] = {
case c : C => true
}
The _ syntax isn't good enough here, since you need to give the existential type the right upper bound. That is only possible with the more explicit forSome syntax.
What I find surprising, though, is that Scala accepts the declaration
def m(x: PartialFunction[X[_], Boolean])
in the first place. It seems weird that it even considers X[_] a well-formed type. This is short for X[A] forSome {type A <: Any}, which should not be a valid application of X, because it does not conform to the parameter bounds.
Not sure if that's what you wanted to achieve, but this is the working sequence:
trait X[A <: X[A]]
case class C extends X[C]
def m[T<:X[T]](x: PartialFunction[X[T], Boolean]) = print("yahoo!")
scala> def f[T<:X[T]]:PartialFunction[X[T], Boolean] = {
| case c: C => true
| }
f: [T <: X[T]]=> PartialFunction[X[T],Boolean]
scala> m(f)
yahoo!
I have a method that takes a Comparable and returns a Comparable and wraps another method that does the same thing:
def myMethod[T <: Comparable[T]](arg: T): T = otherMethod(arg)
def otherMethod[T <: Comparable[T]](arg: T): T = arg
This compiles, but doesn't allow me to call myMethod with an Int or any other type that requires an implicit conversion to implement Comparable. As I understand it, view bounds are meant to address this type of problem, but using the view bound
def myMethod[T <% Comparable[T]](arg: T): T = otherMethod(arg)
I get the compiler error:
inferred type arguments [T] do not conform to method otherMethod's type parameter bounds [T <: java.lang.Comparable[T]]
So far, the only workaround I've come up with is to use a second type parameter and cast between the two:
def myMethod[T <% Comparable[T], U <: Comparable[U]](arg: T): T =
otherMethod(arg.asInstanceOf[U]).asInstanceOf[T]
This works, but it's ugly. Is there a better way?
Would either of the following work?
Make the T's view bound consistent in both methods,
def otherMethod[T <% Comparable[T]](arg: T): T = arg
def myMethod[T <% Comparable[T]](arg: T): T = otherMethod(arg)
Introduce a new type parameter U <: Comparable[U] and an implicit conversion from T to U,
def otherMethod[T <: Comparable[T]](arg: T): T = arg
def myMethod[U <: Comparable[U], T <% U](arg: T): U = otherMethod(arg)
The problem with your version is that T <% Comparable[T] converts T to type Comparable[T], but this does not satisfy the recursive type T <: Comparable[T <: Comparable[T <: ...]] (pseudocode) that otherMethod expects.
Update. To use either otherMethod or myMethod with Scala's Int, you will need to help the type inferencer a little bit,
myMethod(2) // Int value types don't implement Comparable
myMethod(2: java.lang.Integer) // Apply implicit conversion (Int => java.lang.Integer)
Update 2. In the comments, you said you're willing to make myMethod a little uglier to improve type inference at the call site. Here's a way,
def myMethod[U <: Comparable[U], T](arg: T)
(implicit ev1: T => U, ev2: T => Comparable[U]): U = otherMethod(arg)
myMethod(2) // returns java.lang.Integer(2)
The trick is to use two implicit conversions: ev1 actually gets applied, and ev2 is there only to aid type inference. The latter requires Scala to search its implicits for a conversion of type Int => Comparable[U]. In this case, only one such conversion can be found, which fixes U = java.lang.Integer.
By the way, try compiling this code with scalac -Xprint:typer. You'll see that the same implicit, Predef.int2Integer, is used for both ev1 and ev2 parameters.
Side note: it's best to avoid asInstanceOf casts because those defeat the soundness of the Scala type system.