@odersky So I was really enthusiastic about this change but I’m running into a nasty problem with API tractability that I don’t think is solveable by export-clauses or really any of the proposed things.
I am trying to implement method extensions in every possible case where I need unquotation but it’s getting very, very cumbersome.
Right now Quill’s quotation mechanism universally relies on a conversion Quoted[T] => T
(let’s call this implicit-unquoting). This applies not only to Quill data-types but built in Scala data-types as well.
For example:
case class Person(name: String, age: Int)
val filterJoes: Quoted[Person => Boolean] = quote { (p: Person) => p.name == "Joe" }
// I am relying on Quoted[Person => Boolean] => (Person => Boolean) implicit-unquoting
val a = quote { filterJoes.apply(query[Person].head:Person) } // (Quoted[Person => String]).apply(Person)
In order to have a usable API, I need to allow my user to use Quoted[Person => String]
as Person => String
inside of a quoted section but that also means I need to convert the argument of Function1
from Quoted[Person]
to Person
when needed.
All four of the below possibilities (a, b, c, and d) need to be supported.
val people: Quoted[Person] = quote { query[Person].head }
val filterJoes: Quoted[Person => Boolean] = quote { (p: Person) => p.name == "Joe" }
// (Quoted[Person => Boolean]).apply(Person)
val a = quote { filterJoes.apply(query[Person].head) }
// (Quoted[Person => Boolean]).apply(Quoted[Person])
val b = quote { filterJoes.apply(people) }
// (Person => Boolean).apply(Person)
val c = quote { ((p: Person) => p.name == "Joe")(query[Person].head) }
// (Person => Boolean).apply(Quoted[Person]
val d = quote { ((p: Person) => p.name == "Joe")(people) }
Now since I don’t control the Function1.apply
method I can’t just change it to Function1.apply[A, R](a:~A):R
so I need to write extension methods for all 4 of the above cases (well, actually 3 because Function1.apply(A)
is already given:
object Forwarders:
extension [A, B](inline f: Quoted[A => B])
inline def apply(inline a: Quoted[A]) = unquote(f).apply(unquote(a))
inline def apply(inline a: A) = unquote(f).apply(a)
extension [A, B](inline f: A => B)
inline def apply(inline a: Quoted[A]) = unquote(f).apply(unquote(a))
// inline def apply(inline a: A) = unquote(f).apply(a) // Already exists
This essentially means that I need to at-least 3x the size of my API footprint to define all of these methods, but it gets worse!
Each one needs a Quoted[T]
/ T
variation for its input and output respectively. If they have multiple T
arguments then each argument needs to have a Quoted[T]
and T
method, for example:
trait Query:
def groupByMap[A, G, R](f: A=>G)(m: A => R)
// This needs to have the following defined:
extension (Query[T])
//inline def groupByMap[A, G, R](inline f: A=>G)(inline m: A => R) // already given
inline def groupByMap[A, G, R](inline f: Quoted[A=>G])(inline m: A => R)
inline def groupByMap[A, G, R](inline f: A=>G)(inline m: Quoted[A => R])
inline def groupByMap[A, G, R](inline f: Quoted[A=>G])(inline m: Quoted[A => R])
extension (Quoted[Query[T]])
inline def groupByMap[A, G, R](inline f: A=>G)(inline m: A => R)
inline def groupByMap[A, G, R](inline f: Quoted[A=>G])(inline m: A => R)
inline def groupByMap[A, G, R](inline f: A=>G)(inline m: Quoted[A => R])
inline def groupByMap[A, G, R](inline f: Quoted[A=>G])(inline m: Quoted[A => R])
This represents a literal cartesian explosion of the different possibilities!
Now I could possibly change it to inline f: ~Function1[A, G]
which would hopefully decrease the API down to 2x but that’s only for methods that I control. What about if I want to use Function2[T/Quoted[T], T/Quoted[T], R]
, I don’t have the option to use ~T
with that.
Therefore, I don’t think export-clauses would help me at all because they can’t account for the needed Quoted[T] => T
conversion in every single argument (also, I’m not sure if they can be inline).
For this reason, I think that I need to stay with implicit conversions Quoted[T] => T
(and possibly T => Quoted[T]
) somehow. The only realistic possibility that I am seeing is injecting Converter[Quoted[T], T]
(and possibly Converter[T, Quoted[T]]
) whenever I open a quote e.g:
val q = quote { // inject `implicit Converter[Quoted[T], T]` here:
...
}
Will this be possible?