Conversion from Function to sets of custom types

68 views Asked by At

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.

0

There are 0 answers