Suppose I have a function-like type e.g.
trait Parser[-Context, +Out]
and I want to be able to combine multiple parsers such that the combined Context
will be the most-specific type among the combined parsers' contexts. For example:
Parser[Any, Int] + Parser[String, Long] = Parser[String, (Int, Long)]
Parser[String, Int] + Parser[Any, Long] = Parser[String, (Int, Long)]
Parser[Option[Int], Foo] + Parser[Some[Int], Bar] = Parser[Some[Int], (Foo, Bar)]
Parser[String, Foo] + Parser[Int, Bar] = <should be a compile error>
To put the example in more concrete terms, suppose I have a function combiner like
def zipFuncs[A, B1, B2](f1: A => B1, f2: A => B2): A => (B1, B2) = {
a => (f1(a), f2(a))
}
and some functions like
val f1 = { a: Any => 123 }
val f2 = { a: String => 123 }
val f3 = { a: Option[Int] => 123 }
Now I can do
> zipFuncs(f1, f2)
res1: String => (Int, Int) = <function>
> zipFuncs(f1, f3)
res2: Option[Int] => (Int, Int) = <function>
> zipFuncs(f2, f3)
res3: Option[Int] with String => (Int, Int) = <function1>
But what I want is for zipFuncs(f2, f3)
to not compile at all. Since String
is not a subtype of Option[Int]
, and Option[Int]
is not a subtype of String
, there's no way to construct an input value for res3
.
I did create a typeclass:
// this says type `T` is the most specific type between `T1` and `T2`
sealed trait MostSpecificType[T, T1, T2] extends (T => (T1, T2))
// implementation of `object MostSpecificType` omitted
def zipFuncs[A, A1, A2, B1, B2](f1: A1 => B1, f2: A2 => B2)(
implicit mst: MostSpecificType[A, A1, A2]
): A => (B1, B2) = { a: A =>
val (a1, a2) = mst(a)
f1(a1) -> f2(a2)
}
This accomplishes the goal described above, but with a really annoying problem. IntelliJ will highlight valid combinations as errors, inferring that the "most specific type (A
)" is actually Nothing
when it is in fact a real value. Here's the actual issue in practice.
The highlighting issue is surely a bug in IntelliJ, and google searching seems to imply that various resets/cache wipes/etc should fix it (it didn't). Regardless of the blame, I'm hoping to find an alternate approach that both satisfies my original requirement, and doesn't confuse IntelliJ.