encore
encore copied to clipboard
RFC: Local Functions and Closures
(EDITED 2017-02-03 to reflect syntax changes due to #666)
Local Functions
Local functions in Encore are declared using the where clause:
def some_method() : t
method_body
where
local_functions
end
The local functions come in two syntactic forms:
fun id(parameters) : type
function_body
end
fun id(parameters) : type => single_expression
We will first implement support for the first one, then the second one given time. Given time, we will also implement type inference for local functions, which might be trivial if we can implement them through desugaring into closures.
A local function does not have access to the body of the method,
and it does not have access to the current this
. It may call
other local functions declared in the same where clause,
irrespective of order.
Local functions can be captured in closures.
Closures
Like local functions, closures come in two syntactic forms. The first is simple:
fun (parameters) => single_expression
The simple form may not capture any variables in the enclosing state. This requires the second form of closure:
fun (parameters)
id = expr -- 0 or more
do -- possibly optional if no id = expr above
body
end
where id = expr
adds a local variable id
whose value is
expr
, evaluated in the enclosing environment.
This can be explained by pretending that we supported currying.
Let parameters'
be a list of id:type
pairs that matches
names and types of the list of id = expr
. Now, the expression
above can be desugared into the following
let f = fun (parameters',parameters) => single_expression
in f(args)
where f(args)
returns a function where the parameters'
have
been bound to args
.
Note that { expr ; expr }
is a single expression.
Like a local function, a closure does not have access to the
body of the method, and it does not have access to the current
this
. Also, like a local function, it may call local
functions in the method it is defined.
Attached and Detached Closures
A closure which captures subordinate or local state of the enclosing actor is an attached closure and as such must be (logically) evaluated by the enclosing actor.
According to the Kappa type system, there are
When a closure captures values of the following capability modes, they must be attached:
- subordinate
- thread (to be renamed)
The following capability modes are OK to be captured regardless of attached/detatched status:
- locked
- read
- immutable
- linear
- actor
- lockfree
Alternatives
We may support capturing of local variables including this
in a
Spore-like fashion, e.g., capture(this)
instead of this
. This
will void the need for the |args
part of the closure, and let
local functions access local variables "silently". In both these
cases, capture(x)
is an rvalue, and cannot be assigned.
I have a related RFC in #616 that discusses closures more closely in the context of Kappa. Note that we have a slight disagreement on attached/detached closures. Also note that my RFC does not consider the syntax nor the difference between explicit and implicit capture lists.
It might make sense to use the where
syntax for captured variables. I think
fun (parameters)
body
where
id = expr
end
reads nicer than
fun (parameters)
id = expr
do
body
end