7mind / izumi

Productivity-oriented collection of lightweight fancy stuff for Scala toolchain

Home Page:https://izumi.7mind.io

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Scala 3: find a good way to bind functions with implicit parameters using new Scala 3 features

neko-kai opened this issue · comments

commented

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:

  1. pass the implicit parameters explicitly:
make[X].from((d: Description[X]) => makeX()(d))
  1. 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)