Chaining implicit conversions of function to a generic class

266 views Asked by At

I have the following code which was supposed to take a function A => Boolean (generic on the input type) and convert it to a generic trait Y[A] through chained implicit conversions:

val f: Int => Boolean = ???

trait X[A] {
  def m1: Unit
}
implicit def toX[A](f: A => Boolean): X[A] = ???
f.m1  // Works

trait Y[A] {
  def m2: Unit
}
implicit def toY[T, A](x: T)(implicit toX: T => X[A]): Y[A] = ???
f.m2  // Won't compile

Unfortunately, the last line won't compile.

Doing any one of the following changes is enough to make the code compile:

  • Turning X non-generic
  • Turning Y non-generic
  • Replacing the source type (which is a function generic on the input type) with a function generic on the output type (Int => A)
  • Replacing the source type with other generic types such as Option[A], Seq[A] or Array[A]

Based on this, my conclusion is that the chain of implicit conversions won't work because the source type (function generic on the input type) is generic and contravariant and the intermediate and target types (X[A] and Y[A]) are generic.

Any ideas on how to solve this?

UPDATE: The final code is indented to handle not only a function as a source type, by other types as well (Option[A], Seq[A], A, etc.). To achieve this, the idea is to have versions of toX function that convert each of these types to X[A]. Then only one version of toY is desired.

1

There are 1 answers

2
L.Lampart On BEST ANSWER

I think i have a solution for you problem, check out the following code:

val f: Int => Boolean = _ => true

trait X[A] {
  def m1: Unit
}
implicit def funcToX[A](f: A => Boolean): X[A] = new X[A] {
  override def m1: Unit = println("Hello x")
}
f.m1  // Works

trait Y[A] {
  def m2: Unit
}
implicit def toY[T[_,_], A](x: T[A, Boolean])(implicit toX: T[A, Boolean] => X[A]): Y[A] = new Y[A] {
  override def m2: Unit = {
    x.m1
    println("Hello y")
  }
}
f.m2 // now works

I am using here higher kinded type syntax. It's advanced feature of scala language and i am not expirienced enough to explain it properly. Conversion toY should work for any type that takes exactly two type parameters, (and has defined conversion "toX").

Question is do you really need conversion toY to be generic on parameter T? Maybe it is enough for it to accept functions as arguments:

implicit def toY[A](x: A => Boolean)(implicit toX: (A => Boolean) => X[A]) = ???

You can read more about higher kinded types, for example this post:

Scala: Types of a higher kind

Update

Following question author demands i came up with the following solution:

type SingleGenericFun[T] = T => _

val f: SingleGenericFun[Int] = _ > 42
val g: Int = 42

trait X[A] {
  def m1: Unit
}
implicit def toX(f: SingleGenericFun[Int]): X[Int] = ???
implicit def toX(x: Int): X[Int] = new X[Int] {
  override def m1: Unit = println(x)
}
f.m1  // Works

trait Y[A] {
  def m2: Unit
}
implicit def toY2[T[_, _], A](x: T[A, _])(implicit toX: T[A, Boolean] => X[A]): Y[A] = new Y[A] {
  override def m2: Unit = {
    x.m1
    println("Hello y!")
  }
}
implicit def toY0[A](x: A)(implicit toX: A => X[A]): Y[A] = new Y[A] {
  override def m2: Unit = {
    x.m1
    println("Hello y!")
  }
}
implicit def toY1[T[_], A](x: T[A])(implicit toX: T[A] => X[A]): Y[A] = new Y[A] {
  override def m2: Unit = {
    x.m1
    println("Hello y")
  }
}
g.m2
f.m2  // Compile

It is still not the best solution as it is needed to provide 3 (or even more) methods that technically do the the same thing, i don't know how to generify it, nor if it is possible in the first place.