Syntax sugar for context, polymorphic functions

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.

1 Like

This seems to be related to polymorphic eta-expansion, which is not yet working:

def foo[T](x: T) = x

val bar: [U] => U => U = foo // error:
// Found:    Any => Any
// Required: [U] => (x$1: U) => U

https://docs.scala-lang.org/sips/polymorphic-eta-expansion.html

I had made a proof of concept implementation, and @smarter was working on a more robust implementation, but I do not know where it is at right now

2 Likes

That’s not just related, that’s exactly it. SIP-49 contains this explicit sample, which unless I’m mistaken is exactly what I’m talking about:

def uf2[A]: A = ???
val vuf2: [B] => B ?=> B = uf2 // uf2 becomes [B'] => (y: B') ?=> uf2[B']

Thanks for pointing this out!

2 Likes