Implicit Function Types

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).

2 Likes