I’ve recently been playing with context functions, and have been enjoying the syntactic shortcut that doesn’t require you to make the implicit parameter explicit.
For example:
def print[A](a: A)(using console: Console) =
console.print(a.toString)
type Print[A] = Console ?=> A
// No need for (_: Console) ?=> print("foo")
val test: Print[A] = print("foo")
I’ve also been playing with that in the context of polymorphic contexts. Here’s a “trivial” example of what I mean:
// Polymorphic context.
// The point is to be able to provide various interpreter,
// such as a pretty printer or an evaluator.
trait NumSym[Out[_]]:
def num(value: Int): Out[Int]
// Simple syntax helper.
def num[Out[_]](i: Int)(using sym: NumSym[Out]) = sym.num(i)
type NumRule[A] = [Out[_]] => NumSym[Out] ?=> Out[A]
// This does not compile.
val test: NumRule[Int] = num(1)
// This does compile.
val test2 = [Out[_]] => (_: NumSym[Out]) ?=> num(1)
And I wonder if there’s a particular reason the compiler can’t do the same kind of syntactic magic as in the previous example: if it knows an expression is a polymorphic context function, but the type parameter and implicit parameter are not explicit, simply insert them?
For a bit of context, this is something I stumbled on a few years ago when I was trying to write a good Scala 3 Tagless Final encoding. It was brought to mind again recently as I looked into direct style and realised that my encoding was basically polymorphic capabilities.