Proposed Changes and Restrictions For Implicit Conversions

@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?