parapluu / encore

The Encore compiler.
BSD 3-Clause "New" or "Revised" License
43 stars 26 forks source link

RFC: Local Functions and Closures #585

Open TobiasWrigstad opened 8 years ago

TobiasWrigstad commented 8 years ago

(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:

The following capability modes are OK to be captured regardless of attached/detatched status:

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.

EliasC commented 8 years ago

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.

EliasC commented 7 years ago

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