I've been stuck with a type inference problem and I'm not sure if I'm doing something wrong, there is a bug in the compiler, or it's a limitation on the language
I've created a dummy example to show the problem, the use case makes no sense, but trust me I have a valid use case for this
Let's say i have this code
val function: (Int, String) => String = (_, _) => ""
implicit class Function2Ops[P1, P2, R](f: (P1, P2) => R) {
def printArgs(p1: P1, p2: P2): Unit = println(p1, p2)
}
function.printArgs(1, "foo")
That works and prints (1,foo)
Now, if I change the code to be (notice the by-name argument)
val function: (Int, => String) => String = (_, _) => ""
implicit class Function2Ops[P1, P2, R](f: (P1, P2) => R) {
def printArgs(p1: P1, p2: P2): Unit = println(p1, p2)
}
function.printArgs(1, "foo")
It will print (1,MyTest$$Lambda$131/192881625@61d47554)
Now, at this point I could try to pattern match and/or use a TypeTag to extract the value in case is a by-name parameter, but, what I'm actually trying to achieve is to do something like this
trait Formatter[T] {
def format: String
}
case class ReverseStringFormat(v: String) extends Formatter[String] {
override def format: String = v.reverse
}
case class PlusFortyOneFormat(v: Int) extends Formatter[Int] {
override def format: String = (v + 41).toString
}
implicit def noOpFormatter[T](v: T): Formatter[T] = new Formatter[T] {
override def format: String = v.toString
}
val function: (Int, => String) => String = (_, _) => ""
implicit class Function2Ops[P1, P2, R](f: (P1, P2) => R) {
def printArgs(p1: Formatter[P1], p2: Formatter[P2]): Unit = println( p1.format, p2.format)
}
function.printArgs(1, ReverseStringFormat("foo"))
Notice that the main intention is that I should be able to pass either the original type of the argument or a formatter instead, that's why the signature of this extension method uses Formatter[TypeOfOriginalParam] and that's also why I have implicit def noOpFormatter[T](v: T): Formatter[T] for when I don't want any formatting
Now, here, I can't do much as the code fails to compile with this error
Error:(22, 40) type mismatch;
found : ReverseStringFormat
required: Formatter[=> String]
function.printArgs(1, ReverseStringFormat("foo"))
I can make it run if I make the second type argument of my implicit class by-name
val function: (Int, => String) => String = (_, _) => ""
implicit class Function2Ops[P1, P2, R](f: (P1, => P2) => R) {
def printArgs(p1: Formatter[P1], p2: Formatter[P2]): Unit = println( p1.format, p2.format)
}
function.printArgs(1, ReverseStringFormat("foo"))
this prints (1,oof)
Now, the main problem is that I want to do this for any function regardless if any of its arguments is by-value or by-name. And that's where I'm stuck, I can create different implicit classes for every possible combination of cases where there is by-name params or not but it wouldn't be practical as I need to do this for every function from Function1 to Function10, and the the amount of possible combinations between by-name and by-value arguments would be massive.
Any ideas? Should I really need to care about the lazyness of an argument if I'm only interested in the type? Am I trying to do something unsupported by design or is maybe a bug in the compiler?
BTW, this is what I'm trying to avoid
val function: (Int, => String) => String = (_, _) => ""
val function2: (Int, String) => String = (_, _) => ""
val function3: (=> Int, String) => String = (_, _) => ""
val function4: (=> Int, => String) => String = (_, _) => ""
implicit class Function2Ops[P1, P2, R](f: (P1, => P2) => R) {
def printArgs(p1: Formatter[P1], p2: Formatter[P2]): Unit = println("f1", p1.format, p2.format)
}
implicit class Function2Opss[P1, P2, R](f: (P1, P2) => R) {
def printArgs(p1: Formatter[P1], p2: Formatter[P2]): Unit = println("f2", p1.format, p2.format)
}
implicit class Function2Opsss[P1, P2, R](f: (=> P1, P2) => R) {
def printArgs(p1: Formatter[P1], p2: Formatter[P2]): Unit = println("f3", p1.format, p2.format)
}
implicit class Function2Opssss[P1, P2, R](f: (=> P1, => P2) => R) {
def printArgs(p1: Formatter[P1], p2: Formatter[P2]): Unit = println("f4", p1.format, p2.format)
}
function.printArgs(1, "foo")
function2.printArgs(1, ReverseStringFormat("foo"))
function3.printArgs(1, "foo")
function4.printArgs(PlusFortyOneFormat(1), "foo")
which it works (notice that I've used formatters or raw values randomly, it shouldn't matter if the original param was by-name or by-value)
(f1,1,foo)
(f2,1,oof)
(f3,1,foo)
(f4,42,foo)
but it seems super odd to have to write all of that to me