Scala 3: find a good way to bind functions with implicit parameters using new Scala 3 features
neko-kai opened this issue · comments
Currently it's somewhat hard to bind functions with implicit parameters using ModuleDefDSL. We can't bind them like ordinary functions:
final case class Description[T](description: String)
final case class X(s: String)
def makeX()(implicit desc: Description[X]): X = new X(desc.description)
object module extends ModuleDef {
make[Description[X]].fromValue(Description("X"))
make[X].from(makeX _) // error: no implicit value Description[X]
}
Because implicit parameters are resolved eagerly the moment we refer to the function value makeX _
- they are not part of the type signature of the function value: val f: () => X = makeX _ // no Description[X] parameter
. The two ways to work around this currently are to:
- pass the implicit parameters explicitly:
make[X].from((d: Description[X]) => makeX()(d))
- And (preferred) to wrap the function into a class and use automatic class construction feature of the DSL:
object X {
final class Resource(implicit desc: Description[X])
extends Lifecycle.LiftF[Identity, X](
makeX
)
}
make[X].fromResource[X.Resource]
This is the main use case of helper classes Lifecycle.LiftF
and Lifecycle.Of
- to create class-based wrappers for functions that require implicit parameters (such as typeclass instances), to workaround the limitation of Scala 2 that implicit functions are unrepresentable - to improve ergonomics of adding them to the object graph.
Scala 3
However, Scala 3 now makes implicit functions representable which could allow us to solve this problem directly. Unfortunately the easiest way to support this - using overloading - doesn't work - lampepfl/dotty-feature-requests#150, but perhaps we could implement this using a macro - using a signature like def from[I <: T](f: Nothing ?=> Functoid[I]): AfterFrom
to obtain a tree such as (nothing: Nothing) ?=> makeX()(nothing)
and then rewriting the tree by substituting the Nothing placeholder with the correct types for arguments taken out of makeX
type signature: (desc: Description[X]) => makeX()(using desc)