I have a set of classes that looks something like this, which are (annoyingly) not in a single inheritance hierarchy:
trait Mxy[A,B] { def apply(a: A): B }
trait Mzz[ C ] { def apply(c: C): C }
trait Mix[ B] { def apply(i: Int): B }
trait Mxi[A ] { def apply(a: A): Int }
trait Mii { def apply(i: Int): Int }
The challenge is to map Function1
into these classes using converters (so you call .m
to switch over); direct implicit conversion makes it too hard to figure out what the code is really doing.
As long as the most specific M
is always okay, it's doable. You can write implicit conversions that look something like
abstract class MxyMaker[A, B] { def m: Mxy[A, B] }
abstract class MiiMaker { def m: Mii }
trait Priority2 {
implicit def f2mxy[A, B](f: Function1[A, B]) =
new MxyMaker[A, B] { def m = new Mxy[A, B] { def apply(a: A) = f(a) } }
}
// Bunch of missing priorities here in the full case
object Convert extends Priority2 {
implicit def f2mii[A](f: Function1[A, Int])(implicit evA: Int =:= A) =
new MiiMaker { def m = new Mii{
def apply(i: Int) = (f.asInstanceOf[Function1[Int,Int]]).apply(i)
} }
}
Now whenever you have an Int => Int
you get a Mii
, and for anything else you get a Mxy
. (If you don't ask for evA
then ((a: Any) => 5).m
comes up as a Mii
because of the contravariance of Function1 in its input argument parameter.)
So far so good. But what if you are in a context where you need not a Mii
but a Mxy
?
def muse[A, B](m: Mxy[A,B]) = ???
Now inferring the most specific type isn't what you want. But, alas,
scala> muse( ((i: Int) => 5).m )
<console>:18: error: type mismatch;
found : Mii
required: Mxy[?,?]
muse( ((i: Int) => 5).m )
^
Now what? If Mxy
were a superclass of Mii
it would be fine, but it's not and I can't change it. I could parameterize m
with an output type and have a set of typeclass-like things that do the correct conversion depending on output type and then ask the user to type .m[Mxy]
manually. But I want to skip the manual part and somehow pick up the type context from the call to muse
without messing up things like val mippy = ((i: Int) = 5).m
.
The astute may be able to think of a case like this in the wild, but it's easier to work with this "minimization" to get the principles right.