Reputation: 1229
Suppose I have trait that represents something like a polymorphic function, e.g.:
trait Func[A[X, Y]] {
def apply[X, Y](a: A[X, Y]): A[X, Y]
}
Now I want to use my trait as a non-polymorphic function by passing type lambda as argument:
type single[T] = { type t[X, Y] = T }
val aInstance: Func[single[String]#t] =
new Func[single[String]#t] {
def apply[X, Y](a: String): String = ???
}
Now I have method test
which does some useful things with func
, e.g.
def test[A[X, Y]](f: Func[A]): Unit = ???
And I want to invoke test
with aInstance
without specifying type parameters by hand:
test(aInstance)
Unfortunately, this code does not compile (but test[single[String]#t](aInstance)
does) with error messages:
[error] /test.scala:16:3: no type parameters for method test: (f: Func[A])Unit exist so that it can be applied to arguments (Func[[X, Y]String])
[error] --- because ---
[error] argument expression's type is not compatible with formal parameter type;
[error] found : Func[[X, Y]String]
[error] required: Func[?A]
[error] test(aInstance)
[error] ^
[error] /test.scala:16:8: type mismatch;
[error] found : Func[[X, Y]String]
[error] required: Func[A]
[error] test(aInstance)
[error] ^
[error] two errors found
My question is: how can I modify these declarations to allow compiler to infer all required types automatically?
To those wondering why I declared Func
as having [X, Y]
but never used them in actual code there is a more real-world and less abstract example:
object GenericTest {
trait Item { def name: String }
class ItemA extends Item { def name: String = "a" }
class ItemB extends Item { def name: String = "b" }
trait MapFn[-A[X <: Item], +B[X <: Item]] {
def apply[X <: Item](data: A[X]): B[X]
}
case class ItemsCollection[C[A <: Item]](a: C[ItemA], b: C[ItemB]) {
def map[D[A <: Item]](f: MapFn[C, D]): ItemsCollection[D] =
ItemsCollection(f(a), f(b))
}
// sometimes we want to store sequences...
val itemSeq = ItemsCollection[Seq](Seq(new ItemA), Seq(new ItemB))
// or transform them:
val itemSet = itemSeq.map(new MapFn[Seq, Set] {
override def apply[X <: Item](data: Seq[X]): Set[X] = data.toSet
})
// but one day we wanted to store just objects without any item-specific types... e.g. names:
type single[X] = { type t[A] = X }
val itemNames = itemSeq.map(new MapFn[Seq, single[String]#t] {
override def apply[X <: Item](data: Seq[X]): String = data.head.name
})
/*
[error] test.scala:28:27: no type parameters for method map: (f: MapFn[Seq,D])ItemsCollection[D] exist so that it can be applied to arguments (MapFn[Seq,[A]String])
[error] --- because ---
[error] argument expression's type is not compatible with formal parameter type;
[error] found : MapFn[Seq,[A]String]
[error] required: MapFn[Seq,?D]
[error] val itemNames = itemSeq.map(new MapFn[Seq, single[String]#t] {
[error] ^
[error] test.scala:28:31: type mismatch;
[error] found : MapFn[Seq,[A]String]
[error] required: MapFn[Seq,D]
[error] val itemNames = itemSeq.map(new MapFn[Seq, single[String]#t] {
[error] ^
[error] two errors found
*/
}
Upvotes: 4
Views: 254
Reputation: 175
Referring to your GenericTest
, there is no way to get Scala to infer that shape, because of this closed-but-unfixed bug.
One thing you can do is try to adapt the technique of Unapply, using implicit resolution to determine the likely candidate for D
. This will probably entail defining your own typeclass and instances, not using the ones Scalaz supplies, and possibly changing how MapFn
is declared to be more suitable for this pattern. Make sure that the instance that gives you single
has the lowest priority, as it can always be used (every T
is an F[T]
if F = Id
).
If you control the definition of MapFn
, you may also move the B
type parameter to a type member. Then the signature of map
becomes
def map(f: MapFn[C]): ItemsCollection[f.B] =
You can add a subclass to MapFn
that moves the type member back to a parameter for ease of MapFn
creation.
Upvotes: 4