I think I may have a found an encoding that works with context functions.
trait Box[Typeclass[_]]:
type Self
protected def self: Self
protected def inst: Typeclass[Self]
inline def apply[U](f: Typeclass[Self] ?=> Self => U): U = f(using inst)(self)
class BoxImpl[Self0, Typeclass[_]](val self: Self0)(using val inst: Typeclass[Self0]) extends Box[Typeclass]:
type Self = Self0
object Box:
def apply[Self, Typeclass[_]](self: Self)(using Typeclass[Self]): Box[Typeclass] = BoxImpl(self)
def unapply[Typeclass[_]](box: Box[Typeclass]): Some[Any] = Some(box.self)
And then:
trait Foo[A]:
extension (a: A) def foo(i: Int): Int
given Foo[Int] with
extension (a: Int) def foo(i: Int): Int = a * i
given Foo[String] with
extension (a: String) def foo(i: Int): Int = a.length * i
def branch(switch: Boolean): Box[Foo] =
if switch then Box(4) else Box("foo")
val box: Box[Foo] = branch(true)
box(_.foo(5)) // 20