Trouble with type variance - scala

So, let's say I have a class with a contravariant type parameter:
trait Storage[-T] {
def list(path: String): Seq[String]
def getObject[R <: T](path: String): Future[R]
}
The idea of the type parameter is to constrain the implementation to the upper boundary of types that it can return. So, Storage[Any] can read anything, while Storage[avro.SpecificRecord] can read avro records, but not other classes:
def storage: Storage[avro.SpecificRecord]
storage.getObject[MyAvroDoc]("foo") // works
storage.getObject[String]("bar") // fails
Now, I have a utility class that can be used to iterate through objects in a given location:
class StorageIterator[+T](
val storage: Storage[_ >: T],
location: String
)(filter: String => Boolean) extends AbstractIterator[Future[T]] {
val it = storage.list(location).filter(filter)
def hasNext = it.hasNext
def next = storage.getObject[T](it.next)
}
This works, but sometimes I need to access the underlying storage from the iterator downstream, to read another type of object from an aux location:
def storage: Storage[avro.SpecificRecord]
val iter = new StorageIterator[MyAvroDoc]("foo")
iter.storage.getObject[AuxAvroDoc](aux)
This does not work, of course, because storage type parameter is a wildcard, and there is no proof that it can be used to read AuxAvroDoc
I try to fix it like this:
class StorageIterator2[P, +T <: P](storage: Storage[P])
extends StorageIterator[T](storage)
This works, but now I have to specify two type params when creating it, and that sucks :(
I tried to work around it by adding a method to the Storage itself:
trait Storage[-T] {
...
def iterate[R <: T](path: String) =
new StorageIterator2[T, R](this, path)
}
But this doesn't compile because it puts T into an invariant position :(
And if I make P contravariant, then StorageIterator2[-P, +T <: P] fails, because it thinks that P occurs in covariant position in type P of value T.
This last error I don't understand. Why exactly cannot P be contravariant here? If this position is really covariant (why is it?) then why does it allow me to specify an invariant parameter there?
Finally, does anyone have an idea how I can work around this?
Basically, the idea is to be able to
Do storage.iterate[MyAvroDoc] without having to give it the upper boundary again, and
Do iterator.storage.getObject[AnotherAvroDoc] without having to cast the storage to prove that it can read this type of object.
Any ideas are appreciated.

StorageIterator2[-P, +T <: P] fails because it is nonsensical. If you have a StorageIterator2[Foo, Bar], and Bar <: Foo, then because it is contravariant in the first parameter, it is also a StorageIterator[Nothing, Bar], but Nothing has no subtypes, so it is logically impossible that Bar <: Nothing, yet this is what must be true to have a StorageIterator2. Therefore, StorageIterator2 cannot exist.
The root problem is that Storage should not be contravariant. Think of what a Storage[T] is, in terms of the contract it gives to its users. A Storage[T] is an object that you give paths to, and will output Ts. It makes perfect sense for Storage to be covariant: something that knows how to output Strings, for example, is also outputting Anys, so it makes logical sense that Storage[String] <: Storage[Any]. You say that it should be the other way around, that a Storage[T] should know how to output any subtype of T, but how would that work? What if someone adds a subtype to T after the fact? T can even be final and still have this problem, because of singleton types. That is unnecessarily complicated, and is reflected in your problem. That is, Storage should be
trait Storage[+T] {
def list(path: String]: Seq[String]
def get(path: String): T
}
This does not open you up to the example mistake you gave in your question:
val x: Storage[avro.SpecificRecord] = ???
x.get(???): avro.SpecificRecord // ok
x.get(???): String // nope
(x: Storage[String]).get(???) // nope
Now, your issue is that you can't do something like storage.getObject[T] and have the cast be implicit. You can instead a match:
storage.getObject(path) match {
case value: CorrectType => ...
case _ => // Oops, something else. Error?
}
a plain asInstanceOf (undesirable), or you can add a helper method to Storage, like the one you had before:
def getCast[U <: T](path: String)(implicit tag: ClassTag[U]): Option[U] = tag.unapply(get(path))

Related

Is there a way to derive implicits for each member of an HList from Scala Shapeless?

I've tried the following:
type Params = String :: Int :: HNil
implicit val params: Params = "hello" :: 5 :: HNil
// Supposed to create an implicit for string and int if needed
implicit def meberImplicit[A](
implicit
params: Params,
selector: Selector[Params, A]
): A = params.select[A]
// Summoning a string
implicitly[String] // compile-time error
However, I'm getting a diverging implicit error:
diverging implicit expansion for type String
Am I missing something here? And maybe there already is a built-in or better way to achieve this?
The problem is that you are way too generic:
implicit def memberImplicit[A](
implicit // what you put here is irrelevant
): A = ...
With that you basically provided implicit for any value. This clashed with any other implicit you defined, as well as with any implicit parameter that you need to fetch.
But let's ask why compiler cannot prove that you just cannot provide the implicits that you pass into memberImplicit for bad cases, and so it won't consider it a viable alternative, ans so it would be able to prove that this branch of derivation should be cut (where you don't intent it), ambiguity is resolved, then cake.
Thing is, that the type you are returning is A. Which means that even if you added some constraint there like e.g. A =:!= Params - while normally it would work... you just provided all these implicits, so type constraints stopped working, and suddenly derivation for things like e.g. Selector[Params, String] have more that one way of being instantiated. In that situation virtually any implementation you'll try - as long as it returns A - will fail.
In order for things to work you HAVE TO constrain the output to be something that won't match everything - as a matter of the fact, the less it match the better. For instance create a separate type-class for extracting values from the HLists:
trait Extractable[A] { def extract(): A }
object Extractable {
implicit def extractHList[H <: HList, A](
implicit
h: H,
selector: Selector[H, A]
): Extractable[A] = () => selector(h)
}
def extract[A](implicit extractable: Extractable[A]): A = extractable.extract()
and then
extract[String] // "hello"

Circumventing variance checks with extension methods

This doesn't compile:
class MyClass[+A] {
def myMethod(a: A): A = a
}
//error: covariant type A occurs in contravariant position in type A of value a
Alright, fair enough. But this does compile:
class MyClass[+A]
implicit class MyImplicitClass[A](mc: MyClass[A]) {
def myMethod(a: A): A = a
}
Which lets us circumvent whatever problems the variance checks are giving us:
class MyClass[+A] {
def myMethod[B >: A](b: B): B = b //B >: A => B
}
implicit class MyImplicitClass[A](mc: MyClass[A]) {
def myExtensionMethod(a: A): A = mc.myMethod(a) //A => A!!
}
val foo = new MyClass[String]
//foo: MyClass[String] = MyClass#4c273e6c
foo.myExtensionMethod("Welp.")
//res0: String = Welp.
foo.myExtensionMethod(new Object())
//error: type mismatch
This feels like cheating. Should it be avoided? Or is there some legitimate reason why the compiler lets it slide?
Update:
Consider this for example:
class CovariantSet[+A] {
private def contains_[B >: A](b: B): Boolean = ???
}
object CovariantSet {
implicit class ImpCovSet[A](cs: CovariantSet[A]) {
def contains(a: A): Boolean = cs.contains_(a)
}
}
It certainly appears we've managed to achieve the impossible: a covariant "set" that still satisfies A => Boolean. But if this is impossible, shouldn't the compiler disallow it?
I don't think it's cheating any more than the version after desugaring is:
val foo: MyClass[String] = ...
new MyImplicitClass(foo).myExtensionMethod("Welp.") // compiles
new MyImplicitClass(foo).myExtensionMethod(new Object()) // doesn't
The reason is that the type parameter on MyImplicitClass constructor gets inferred before myExtensionMethod is considered.
Initially I wanted to say it doesn't let you "circumvent whatever problems the variance checks are giving us", because the extension method needs to be expressed in terms of variance-legal methods, but this is wrong: it can be defined in the companion object and use private state.
The only problem I see is that it might be confusing for people modifying the code (not even reading it, since those won't see non-compiling code). I wouldn't expect it to be a big problem, but without trying in practice it's hard to be sure.
You did not achieve the impossible. You just chose a trade-off that is different from that in the standard library.
What you lost
The signature
def contains[B >: A](b: B): Boolean
forces you to implement your covariant Set in a way that works for Any, because B is completely unconstrained. That means:
No BitSets for Ints only
No Orderings
No custom hashing functions.
This signature forces you to implement essentially a Set[Any].
What you gained
An easily circumventable facade:
val x: CovariantSet[Int] = ???
(x: CovariantSet[Any]).contains("stuff it cannot possibly contain")
compiles just fine. It means that your set x, which has been constructed as a set of integers, and can therefore contain only integers, will be forced to invoke the method contains at runtime to determine whether it contains a String or not, despite the fact that it cannot possibly contain any Strings. So again, the type system doesn't help you in any way to eliminate such nonsensical queries which will always yield a false.

Howto create a generic type class function using functors

Having this type class for converting a Map into a case class:
/**
* Type class for transforming a map of values into a case class instance.
*
* #tparam T Wanted case class type.
*/
#implicitNotFound("Missing ToCaseClassMapper implementation for type ${T}")
trait ToCaseClassMapper[T <: Product] {
def toCaseClass(map: Map[String, Any]): T
}
And this function to implicitly get the correct mapper
def toCaseClass[T <: Product](map: Map[String, Any])(implicit mapper: ToCaseClassMapper[T]): T = {
mapper.toCaseClass(map)
}
It can be used as toCaseClass[User](aUserMap) // returns User
But I also would like to be able to use this function with an Option[Map[]] or Future[Map[]] or List[Map[]].
So I implemented a generic function using a functor like this:
def toCaseClass[T <: Product, F[_]: cats.Functor](fOfMaps: F[Map[String, Any]])(implicit mapper: ToCaseClassMapper[T]): F[T] = {
cats.Functor[F].map(fOfMaps)(map => toCaseClass(map)(mapper))
}
But now this function has to be used as toCaseClass[User,List](listOfUserMaps) // returns List[User].
However, I'd would like to be able to use the function as
toCaseClass[User](listOfMaps)
toCaseClass[User](futureOfMap)
toCaseClass[User](optionOfMap)
without the need to specify the functor type.
Is this somehow possible?
Could Shapeless's Lazy be used to solve this?
Edit: solution
Thanks to #Jasper-m and #dk14 for their answers.
So the 'trick' to solve this is to capture the type 'T' first in a class before the Functor type. I liked #Jasper-m solution with the 'apply' method since that would keep the syntax almost similar to what it was before.
I made a few adjustments though. Since there was already the 'ToCaseClassMapper' class which also captures the type 'T', I decided to combine it with the 'ToCaseClass' class. Also, with #Jasper-m's approach, when using the 'toCaseClass' function when mapping over some value like Option(value).map(toCaseClass) the usage of toCaseClass had to be different for when the value was a Map or a List[Map].
My solution is now as follows:
#implicitNotFound("Missing ToCaseClassMapper implementation for type ${T}")
trait ToCaseClassMapper[T <: Product] {
def toCaseClass(map: Map[String, Any]): T
import scala.language.higherKinds
def toCaseClass[F[_]: cats.Functor, A](fOfMaps: F[Map[String, A]]): F[T] = {
cats.Functor[F].map(fOfMaps)(toCaseClass)
}
}
Since the ToCaseClassMapper instance was already implicitly available where the toCaseClass function was used, I decided to throw away that function and just replace it with mapper.toCaseClass(_). This cleaned up some unneeded code and now the syntax for using the mapper is the same regardless whether the value is a Map or Option, List, Future (or any other Functor).
Currently it's not possible in Scala to have one type parameter provided explicitly and another one in the same type parameter list be inferred, nor is it currently possible to have multiple type parameter lists for a method. A workaround is to create a helper class and split your method call in two stages: first create an instance of the helper class, then call the apply method on that object.
class ToCaseClass[T <: Product] {
def apply[F[_]: cats.Functor, A](fOfMaps: F[Map[String, A]])(implicit mapper: ToCaseClassMapper[T]): F[T] = {
cats.Functor[F].map(fOfMaps)(map => toCaseClass(map)(mapper))
}
}
def toCaseClass[T <: Product] = new ToCaseClass[T]
def toCaseClass[T <: Product](map: Map[String, Any])(implicit mapper: ToCaseClassMapper[T]): T = {
mapper.toCaseClass(map)
}
toCaseClass[User](listOfMaps)
toCaseClass[User](futureOfMap)
toCaseClass[User](optionOfMap)
Edit: As pointed out by dk14, there is still a type inference problem here, where F is inferred as Any. I don't know what causes it, but I think it is a separate orthogonal problem from the one being solved by this pattern.
Edit 2: I figured it out. It's because F is invariant in its type parameter. F[Map[String, String]] is not a subtype of F[Map[String, Any]], so the compiler does something strange and infers F as Any. A solution is to put a type parameter A instead of Any, or use an existential type Map[String,_].
This works:
class Mapper[T <: Product](implicit val mapper: ToCaseClassMapper[T]){
def toCaseClass[F[_]: cats.Functor, Z <: Map[String, Any]](fOfMaps: F[Z]): F[T] = {
cats.Functor[F].map(fOfMaps)(map => mapper.toCaseClass(map))
}
}
object Mapper{
def apply[T <: Product: ToCaseClassMapper] = new Mapper[T]{}
}
import cats.implicits._
Mapper[User].toCaseClass(List(Map("aaa" -> 0)))
Few tricks besides obvious introducing class (to split type parameters) were used as well:
1) move mapper to constructor so it could be resolved first (not sure it helped)
2) what definitely helped is to introduce Z <: Map[String, Any], otherwise scala (at least my old version 2.11.8) would infer F[_] as Any for some reason
P.S. You can use apply instead of toCaseClass as well - it would shorten the syntax

Resolution of implicit within class body

At a high-level, I'm curious to know how implicits defined inside a class body are resolved? I assumed they would be considered part of the current scope (as per the awesome answer here), but I am wrong.
For a specific example, why doesn't the following work:
trait Convert[T, U] {
final def value(record: T)(implicit f: T => U): U = f(record)
}
case class ConvertMap(key: String)
extends Convert[Map[String, String], Boolean] {
implicit def a2b(m: Map[String, String]): Boolean = m.contains(key)
}
I can instantiate the class ConvertMap, but when I call the value method I get an error stating that the view from Map[String, String] => Boolean can't be found.
scala> val c = ConvertMap("key")
c: ConvertMap = ConvertMap(key)
scala> c.value(Map("key" -> "value"))
<console>:13: error: No implicit view available from Map[String,String] => Boolean.
If you re-read the answer you provided for implicit resolution scope, you see that there are a couple of things which happen here causing the implicit you've defined not to be found.
First, if the implicit isn't found in the local scope of the call-site, we defer to "category 2" lookup for implicits which involves the types underlying the implicit. We're searching for an implicit conversion of type Map[String, String] => Boolean. According to the implicit scoping rules, the following is applicable to our use case:
If T is a parameterized type S[T1, ..., Tn], the union of the parts of S and T1, ..., Tn.
Our S[T1.. Tn] is a Map[String, String] (and it's base classes), so we have both Map and String as a candidate type for implicit lookup, but neither of them hold the conversion. Further, the return type is also considered, meaning Boolean is also in scope, but again, it doesn't hold the implicit and hence why the compiler complains.
The simplest thing that can be done to help the compiler find it is to place the implicit inside the companion object of ConvertMap, but that means we can no longer accept a value key in the constructor, which makes your example a bit contrived.
It needs to be resolvable at the call site (where c.value is called).
At that time, the only thing you have in scope c. I am not sure why you would think it makes sense for something defined inside a class to be considered in scope at that point.
BTW, your example doesn't really seem like a good use for implicits to begin with. Why not just make f a member method in the trait?

Scala type erasure issue with path dependent/nested types

Let's assume the following:
class Wrapper1 {
case class Condition(test: String)
}
object Wrapper1 extends Wrapper1
class Wrapper2 {
case class Condition[A](test: String)
}
object Wrapper2 extends Wrapper2
class Test
type T = // whatever
def test(fn: T => Wrapper1.Condition): X
def test[R](fn: T => Wrapper2.Condition[R]): X
}
The problem is that because of type erasure those methods have the exact same type after erasure. It's easy to change the signature of the second with say:
def test[R](fn: T => Wrapper2.Condition[R])(implicit ev: R =:= R): X
But that confuses the compiler and using the test method in other places is impossible. For a number of design reasons I'm trying to keep the name of this method consistent. Is there any way to successfully do this?
Seems this is NOT a duplicate of Scala double definition (2 methods have the same type erasure)
My suggestion... Just have a single method
def test[Cond: TypeTag](fn: T => Cond): X = {
if(typeOf[T] <:< typeOf[Wrapper1.Condition]) ...
else if(typeOf[T] <:< typeOf[Wrapper2.Condition[_]) ...
else ...
}
Finally able to work it out. What I was after was a way to conditionally append to an HList. Basically multiple overloads of the same method had to co-exist because they would return a different kind of HList.
To only have one method, arguments needed to dictate what the final signature of the method looked like.
So let's assume the following, the test method had to return
def test[RR](arg: Type1[RR]): Wrapper[RR :: ExistingHList]
Or respectively:
def test(arg: Type2): Wrapper[ExistingList]
The same method would have to append to an Hlist depending on whether or not the argument satisfied certain conditions. The fix was as trivial as using shapeless.ops.hlist.Prepend, which will by default returning the existing HList in the event we try to append HNil to an hlist.
Making the argument of Type2 return an HNil or implicitly convert to something that would end up being HNil was the approach that worked.
Specifically:
class Wrapper[PS <: HList] {
def test[
RR,
HL <: HList,
Out <: HList
](
condition: T => Wrapper[HL]
)(implicit prepend: Prepend.Aux[HL, PS, Out]): Wrapper[Out[
}
This approach works if the condition function will return a Wrapper[HNil] for situations where the function doesn't need to modify the end return type. Functions who do modify are simply free to build up their own HList independently.