Following up on Standard Library: Now open for improvements and suggestions!, I’d like to propose a small addition to all FunctionN traits:
trait Function2[-T1, -T2, +R]:
def apply(v1: T1, v2: T2): R
def applyToContext(using v1: T1, v2: T2): R = apply(v1, v2) // this is the new thing I'm proposing
It applies an ordinary function to arguments that come from the implicit/given context.
Prototype:
Motivation
1. Bridging ordinary and contextual functions
Contextual functions (?=>) are supposed to be a hallmark of Scala 3, but there are still issues with ergonomics and interoperability. Much existing code (constructors, companion applys, library functions) takes ordinary parameters. Today, turning (A, B) => R into (A, B) ?=> R needs a hand-written lambda per arity. With applyToContext:
val f: (Config, Logger) => Service = Service.apply
val g: (Config, Logger) ?=> Service = f.applyToContext
2. Boilerplate-free, “no magic” dependency injection
Components stay plain case classes with ordinary constructors (no implicits in their signatures, so they are not “viral”). Only the wiring site uses the context:
object app:
private given ConnectionPool = ConnectionPool(10) // is private, doesn't need to "leak" out
lazy val value = (UserSubscription.apply _).applyToContext // can be in any order
private given UserDatabase = (UserDatabaseLive.apply _).applyToContext
private given EmailService = (EmailService.apply _).applyToContext
Declaration order does not matter, missing dependencies are reported by the compiler, IDE navigation works, and the components can be swapped for tests. It works on Scala 2 and 3, JVM, JS and Native. More discussion: Reddit: The simplest Dependency Injection.
Why the standard library
Everyone who needs this re-implements 23 overloads (Function0–Function22). The method is trivial, but only a generated stdlib definition can cover all arities consistently.
Open questions
- Name:
applyToContext,fromContext,toContextual,applyUsing, …? - Placement: a method on
FunctionN, or overloads inobject Function? - Venue: the prototype targets scala/scala 2.13.x; should it move to scala/scala3 now that the stdlib lives there?
(Disclosure: I’ve used the help of LLM to write up this proposal.)