The scala.language.dynamics feature can be “accessed” through the Dynamic trait.
It would be very nice to also have the alternative of a type class which allows you to do this, as it would allow existing types to have dynamic invocations. For example you could create swizzling for tuples which would let you do something like
(1, 2, 3).zyx ==> (3, 2, 1)
The type class would contain extension methods such as selectDynamic, applyDynamic, and so on.
It would look something like:
trait Dyn[T]:
// allows you to define the following methods
extension (t: T)
def selectDynamic(...): ...
def applyDynamic(...)(...): ...
extension [T <: Tuple & Dynamic](t: T)
<dynamic methods here>
given [T <: Tuple]: Conversion[T, T & Dynamic] =
(t: T) => t.asInstanceOf // Dynamic is a marker trait, so this is fine
The compiler checks at compiletime that Dynamic is a supertype, but it doesn’t have to come from an extends clause !
I stand corrected, I was able to make it work for the related (and more powerful) Selectable:
import scala.languageFeature.dynamics
import scala.language.implicitConversions
extension [T <: Tuple & Selectable](t: T)
def selectDynamic(methodName: String) = methodName match
case "first" => (t: Tuple)(0)
given [T <: Tuple]: Conversion[T, T & Selectable {def first: Int}] =
(t: T) => t.asInstanceOf[T & Selectable {def first: Int}] // Selectable is a marker trait, so this is fine
(1, 2, 3).first // 1: Int
But not with Dynamic:
import scala.languageFeature.dynamics
import scala.language.implicitConversions
extension [T <: Tuple & Dynamic](t: T)
def selectDynamic(methodName: String) = methodName match
case "first" => (t: Tuple)(0)
given [T <: Tuple]: Conversion[T, T & Dynamic] =
(t: T) => t.asInstanceOf[T & Dynamic]
val t: (Int, Int, Int) & Dynamic = (1, 2, 3)
t.first
// java.lang.ExceptionInInitializerError
// Caused by: java.lang.ClassCastException: class scala.Tuple3 cannot be cast to class scala.Dynamic
So it seems either a smarter conversion is required, or you do really need Dynamic as an extends
As for making it into a typeclass, here is my (unsuccessful) attempt:
import scala.languageFeature.dynamics
import scala.language.implicitConversions
trait Dyn[T, Shape]:
given [A <: T]: Conversion[A, A & Selectable & Shape] = _.asInstanceOf
type Firstable[T <: Tuple] = Dyn[T, {def first: Int}]
given [T <: Tuple]: Firstable[T] with
extension [T <: Tuple & Selectable](t: T)
def selectDynamic(methodName: String) = methodName match
case "first" => (t: T)(0)
def foo[T <: Tuple : Firstable](x: T) = x.first // value first is not a member of T
// The above means the implicit conversion is not being applied, as my first example works
val t: (Int, Int, Int) = (1, 2, 3)
foo(t)