Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closures #313

Merged
merged 19 commits into from Jun 23, 2016

Conversation

@bvssvni
Copy link
Member

commented Jun 23, 2016

For design, see #314

This PR adds a simple form of closures to Dyon. It is closer to anonymous functions with current objects as dynamical scoped variables. There is no capturing of variables in the environment, instead current objects are used to simulate aspects of closures. Capturing of environment might be added at a later point, depending on how it fits the language.

  • All closures must return a value, because the type is not guaranteed and Dyon has no null
  • Works similar to normal functions, with a similar syntax to ease refactoring

How to use closures

Assume we have a function foo that applies a transformation for each item in a list, returning a new list:

fn main() {
    list := [1, 2, 3, 4]
    println(sift i { foo(list[i]) }) // prints `[2, 3, 4, 5]`
}

fn foo(x: f64) -> f64 {
    return x + 1
}

We can inline the function by creating a closure:

fn main() {
    list := [1, 2, 3, 4]
    foo := \(x: f64) = x + 1 // all closures in Dyon are anonymous functions.
    println(sift i { \foo(list[i]) }) // prints `[2, 3, 4, 5]`
}

Instead of transforming the values in the list, you can compute a value from the index by setting list as a current object (using ~ in front of it):

fn main() {
    ~ list := [1, 2, 3, 4]
    foo := \(i: f64) ~ list = list[i] + 1
    println(sift i len(list) { \foo(i) }) // prints `[2, 3, 4, 5]`
}

If you forget ~ list after the arguments you will get an error message:

 --- ERROR --- 
In `source/test.dyon`:

Could not find declaration of `list`
3,24:     foo := \(i: f64) = list[i] + 1
3,24:                        ^

Objects, arrays and named syntax

When you call a closure, you can use any item, including objects:

fn main() {
    a := {x: \(x) = x + 1}
    println(\a.x(2)) // prints `3`
}

... arrays:

fn main() {
    a := [\(x) = x + 1]
    println(\a[0](2)) // prints `3`
}

... and named syntax (Dyon uses __ to separate function name and arguments, and _ to separate arguments:

fn main() {
    foo__bar_baz := \(x, y) = x + y
    println(\foo(bar: 2, baz: 1)) // prints `3`
}

There is a trick you can do with objects by combining a property with named syntax:

fn main() {
    a := {foo__bar_baz: \(x, y) = x + y}
    println(\a.foo(bar: 2, baz: 1)) // prints `3`
}

Dyon rewrites the AST such that named arguments are appended to the item:

fn main() {
    a := {foo__bar_baz: \(x, y) = x + y}
    println(\a.foo__bar_baz(2, 1)) // prints `3`
}

You can also use the ? operator to propagate error before calling:

fn main() {
    a := {foo__bar_baz: some(\(x, y) = x + y)}
    println(foo(a)) // prints `ok(3)`
}

fn foo(a) -> res {
    r := \a.foo?(bar: 2, baz: 1) // propagate error before calling
    return ok(r)
}

A new class pattern?

Closures and current objects in Dyon gives a syntax that looks a bit like methods, but remember that is is no self keyword. Instead, one might use this pattern:

~ <system> := <components>
\<Class>.<method>(args...)

For example:

fn main() {
    Spaceship := {
        update__dt: \(dt) ~ mut spaceships
        = update_spaceships(mut spaceships, dt)
    }

    ~ spaceships := [
        {pos: (0, 0), vel: (1, 0)},
        {pos: (0, 10), vel: (1, 0)}
    ]

    dt := 0.1
    _ := \Spaceship.update(dt: dt)
}

fn update_spaceships(mut spaceships: [{}], dt: f64) -> bool {
    for i {
        spaceships[i].pos += spaceships[i].vel * dt
        println(spaceships[i].pos)
    }
    return true
}

This fits a typical ECS pattern.

How current objects differ from captured variables and globals

A current object is not stored within a closure. You can create multiple closures using the same current object and mutate it when the closure is called:

fn main() {
    ~ a := 0
    foo1 := \(x) ~ mut a = {
        a += x
        println(a)
        true
    }
    foo2 := \(x) ~ mut a = {
        a += x
        println(a)
        true
    }
    _ := \foo1(1) // prints `1`
    _ := \foo2(1) // prints `2`
}

This means that closures in Dyon at the moment does not have the nice mathematical properties of normal closures, but are actually anonymous functions with current objects as dynamic scoped variables. It might be added later, but right now the only way to change the environment is through current objects.

Current objects is similar to using first class functions with globals, but differs in the way that you can pass the closure to another function that sets up a different current object:

fn main() {
    ~ a := 0
    foo1 := \(x) ~ mut a = {
        a += x
        println(a)
        true
    }
    foo2 := \(x) ~ mut a = {
        a += x
        println(a)
        true
    }
    _ := \foo1(1) // prints `1`
    bar(foo2) // prints `5`
    _ := \foo2(1) // prints `2`
}

fn bar(f: \(any) -> any) {
    ~ a := 4
    _ := \f(1)
}

bvssvni added some commits Jun 23, 2016

Use mathematical declaration for closures
Closures must return a value because the AST converter can not resolve
whether it returns a value or not.
Argument to closure must be immutable
Since “mut" is part of the name of a function, and closures does not
have a name, the arguments must be immutable.
Do not allow lifetimes on closure arguments
Lifetimes are mostly useful when mutating variables. Since closure
arguments must be immutable, there is no point in supporting lifetimes.
Return closure from function
- Added “syntax/closure.dyon”
Fix `_` with multiple named arguments
- Added “syntax/closure_2.dyon”
- Added “syntax/closure_3.dyon”
Closures has no lifetime
- Added “syntax/lifetime_15.dyon”
Type check closures
- Fixed closure type description
- Added “typechk/closure_3.dyon”
- Added “typechk/closure_4.dyon”
- Added “typechk/closure_5.dyon”

@bvssvni bvssvni merged commit e8e6dae into PistonDevelopers:master Jun 23, 2016

@bvssvni bvssvni deleted the bvssvni:closures branch Jun 23, 2016

@bvssvni bvssvni referenced this pull request Jun 24, 2016

Closed

First class functions #293

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
1 participant
You can’t perform that action at this time.