Ideas for this kind of tighter scoping of contextual identifiers were brought up quite some time ago by @lihaoyi and @stanch on the scala-debate mailing list. Short recap: They used scala-async as an example, which provides two main operations: async
and await
. await
is defined to be only meaningful within the context of an async
block. However, as scala-async behaves today, await
is simply sitting in the global scope cluttering the namespace:
import scala.async.Async.{async, await}
val future = async {
val f1 = async { ...; true }
val f2 = async { ...; 42 }
if (await(f1)) await(f2) else 0
}
With Scope Injection, you could define async
as:
object Holder {
val await = ???
}
def async[T](thunk: import Holder => T): Future[T] = ???
Which would give you a call-site syntax:
import scala.async.Async.async
val future = async {
val f1 = async { ...; true }
val f2 = async { ...; 42 }
if (await(f1)) await(f2) else 0
}
There were also some other ideas for boilerplate-free implicit and scope injection, especially useful for writing DSLs in Scala (among them one idea which is basically Dotty’s implicit function types). You can find more in the current README.
DSL Paradise: Current State
The initial discussion resulted in an early prototype: http://github.com/dsl-paradise/dsl-paradise
I took the liberty to continue the work on this prototype. The current state is a Scala compiler plugin that supports the proposed implicit context propagation (i.e., implicit functions for Scala 2) and scope injection: GitHub - pweisenburger/dslparadise: Scala compiler plugin for boilerplate-free context propagation and scope injection
It supports the following three use cases:
-
Implicit Context Propagation
def f(a: Int `implicit =>` String) = println(a(5)) def g(implicit x: Int) = x.toString > f("Hi, " + g) // desugaring > f { implicit imparg$1 => "Hi, " + g } Hi, 5
Note: This issue has already been addressed in Dotty by implicit function types. The original DSL Paradise proposal is more restricted in the sense that that it only defines implicit function types with a single function argument (however, implicit functions can be curried). The proposal also does not specify that arguments to implicit functions should be resolved from the implicit scope on the call-site. The current implementation, however, supports this by now.
-
Scope Injection
class Thingy { val u = 6 val v = 7 } def f(a: Thingy `import =>` Int) = println(a(new Thingy)) > f(4 + u - v) // desugaring > f { imparg$1 => import imparg$1._; 4 + u - v } 3
-
Static Scope Injection
object Thingy { val u = 6 } def f(a: Int `import` Thingy.type) = println(a) > f(u + 1) // desugaring > f { import Thingy._; u + 1 } 7
The current implementation defines the implicit and import functions simply as type aliases for the standard function type (and thus, retains compatibility with standard Scala):
type `implicit =>`[-T, +R] = T => R
type `implicit import =>`[-T, +R] = T => R
type `import =>`[-T, +R] = T => R
type `import`[T, I] = T
Nested Contexts: When nesting implicit functions, using a fresh name for each compiler-generated implicit argument can result in ambiguous implicit values (this problem is solved in Dotty by refining the precedence rules for implicit resolution).
This compiler plugin allows to specify a fixed name to be used for the implicit argument to enable implicit argument resolution for nested contexts by shadowing the implicit argument of the outer context (the README gives a more detailed example).