Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mixing generic types

I would like to mix generic types in my myTest

case class CC(i:Int, s:String, s2:String)

case class Value[V](value: V)

def myTest[F](values: (CC ⇒ Value[F])*) = Unit

class QF[M](x: M) {
  def eqs(v: M) = Value(x)
}

implicit def conversion[V](v: V): QF[V] = new QF(v)

myTest(_.i eqs 1)
myTest(_.s eqs "lol")
myTest(_.s eqs "lol", _.s2 eqs "lol2")

It works until that point but I think I'm getting a covariance error when I try to do mix types.

When I do :

myTest(_.i eqs 1, _.s eqs "lol")

I get the following error :

Error:(16, 13) type mismatch;
 found   : A$A135.this.CC => A$A135.this.Value[Int]
 required: A$A135.this.CC => A$A135.this.Value[Any]
myTest(_.i eqs 1, _.s eqs "lol");}
           ^
like image 954
Roch Avatar asked Feb 10 '23 07:02

Roch


2 Answers

Yes that is correct. Because Value is defined as invariant in V, it means that Value[Int] and Value[String] are not sub-types of Value[Any], but Any is the nearest common super-type of Int and String, i.e. F in myTest. Because V is in covariant position, you can simply amend this:

case class Value[+V](value: V)
like image 152
0__ Avatar answered Feb 16 '23 04:02

0__


Not co-variance but existential types. In your function

def myTest[F](values: (CC ⇒ Value[F])*) = Unit

F can vary from a call to myTest to another one but is fixed inside the argument list (CC ⇒ Value[F])*. The types of the arguments in your examples are

scala> val i_eqs = (cc : CC) => cc.i eqs 1
i_eqs: CC => Value[Int] = <function1>

where F is then Int.

scala> val s_eqs = (cc : CC) => cc.s eqs "lol"
s_eqs: CC => Value[String] = <function1>

where F is String. It works for all 3 examples because F is the same for all the elements in the argument list. But when you call

myTest(_.i eqs 1, _.s eqs "lol")

the compiler tries to unify CC => Value[Int] (the type of the first argument) to CC => Value[String] (second argument) which reduces to unifying Value[Int] with Value[String] . Indeed you can define Value as covariant and it's probably a good idea. But i guess that what you really want here is to express that the arguments of myTest is "a list of CC => Value[F] for some type F which may differ from an argument to another in the list. Which exactly is existential types :) The long version is probably clearer:

scala> def myTest(values: (CC ⇒ Value[F] forSome { type F } )*) = Unit
myTest: (values: CC => Value[_]*)Unit.type

The added forSome { type F } states that there exists an F for which the argument is of type CC => Value[F] but "forget" about which F. So Value[Int] become Value[F] for an unknown F and so does Value[String]. From the outside they both become the same type so it works. This syntax though being very clear can be shortened most of the time by an underscore:

scala> def myTest(values: (CC ⇒ Value[_])*) = Unit
myTest: (values: CC => Value[_]*)Unit.type
like image 38
Christophe Calvès Avatar answered Feb 16 '23 04:02

Christophe Calvès