Proposal: Simple scope injection via import/export parameters

Motivation

For motivation see: PRE SIP: ThisFunction | scope injection (similar to kotlin receiver function)

Proposal

I propose that we can annotate an argument with the keywords import / export to cause an implied import/export of the annotated argument’s namespace inside the body of the method/class (arguments can be given or explicit).

Classes Interaction

The following code will be simply rewritten.

Before Rewrite

has import/export arguments

class Scope {
  val a : Int = 0
}

class ExtraScope extends Scope {
  val b : Int = 0
}

class Importer[S <: Scope](given import scope : S) {
  val aAccess = a
}

class Exporter[S <: Scope](given export scope : S) {
  val aAccess = a
}

given ExtraScope

val im = new Importer[ExtraScope] {
  val bAccess = b
}

val ex = new Exporter[ExtraScope] {
  val bAccess = b
}
val aAccess = ex.a
val bAccess = ex.b

After Rewrite

import/export arguments changed to import/export statements

class Scope {
  val a : Int = 0
}

class ExtraScope extends Scope {
  val b : Int = 0
}

class Importer[S <: Scope](given val scope : S) {
  import scope._
  val aAccess = a
}

class Exporter[S <: Scope](given val scope : S) {
  export scope._
  val aAccess = a
}

given ExtraScope

val im = new Importer[ExtraScope] {
  import scope._
  val bAccess = b
}

val ex = new Exporter[ExtraScope] {
  export scope._
  val bAccess = b
}
val aAccess = ex.a
val bAccess = ex.b

Import Function Arguments

import function arguments are special-cased and are treated differently than value arguments. The import statement will take effect inside

Before Rewrite

class Scope {
  val a : Int = 0
}
class ExtraScope extends Scope {
  val b : Int = 0
}
def importer[S <: Scope, T](import block : S => T)(given s : S) : T = block(s)
given ExtraScope
val bAccess = importer{s : ExtraScope => 
  val aAccess = a
  b  
}

After Rewrite

class Scope {
  val a : Int = 0
}
class ExtraScope extends Scope {
  val b : Int = 0
}
def importer[S <: Scope, T](block : S => T)(given s : S) : T = block(s)
given ExtraScope
val bAccess = importer{s : ExtraScope => 
  import s._
  val aAccess = a
  b  
}

Rules

  1. import/export arguments can be anonymous. This helps us avoid polluting the namespace with the owner of the scope.
class Importer[S <: Scope](given import S) {
  val aAccess = a
}

class Foo(import Scope)
  1. import/export value arguments are automatically inferred to be public values (not function arguments).
  2. defs do not allow export arguments (it doesn’t really matter, but I don’t think we should allow both keywords to do the same thing in defs).
  3. import/export annotations are not allowed for by-name values.
  4. import/export function arguments can be high-order functions. All their arguments will be imported.
1 Like