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

`grab` expressions #318

Merged
merged 20 commits into from Jun 26, 2016

Conversation

@bvssvni
Copy link
Member

commented Jun 24, 2016

This PR adds grab expressions to Dyon. For design, see #316.

What is a grab expression?

A grab expression is a partial evaluation operator that evaluates expressions in the closure environment. This can be used to capture variables read-only.

fn main() {
    a := \(x) = \(y) = \(z) = z + (grab y) + (grab '2 x)
    // prints `\(x: any) = \(y: any) = \(z: any) = z + (grab y) + (grab '2 x)`
    println(a)
    b := \a(1)
    // prints `\(y: any) = \(z: any) = z + (grab y) + 1`
    println(b)
    c := \b(2)
    // prints `\(z: any) = z + 2 + 1`
    println(c)
    d := \c(3)
    println(d)
}

Higher order functions

The grab expression allows you compute with higher order functions, such as function currying, composition of traversing operators, etc.

fn main() {
    f := \(x) = x + 1

    a := fmap(f, for_each())
    list := [1, 2, 3]
    println(\a(list)) // prints `[2, 3, 4]`

    a := fmap(f, maybe())
    b := some(2)
    println(\a(b)) // prints `some(3)`
    println(\a(none())) // prints `none()`

    a := fmap(f, tree())
    b := {left: 1, right: {left: 2, right: 3}}
    println(\a(b)) // prints `{right: {right: 4, left: 3}, left: 2}`

    // Combine two functors.
    a := fmap(f, fjoin(for_each(), maybe()))
    b := [some(1), some(2), some(3)]
    // prints `[some(2), some(3), some(4)]`
    println(\a(b))

    // Combine a list of functors.
    a := fmap(f, fjoins([for_each(), maybe(), tree()]))
    b := [some({left: 1, right: 2}), some({left: 2, right: 3}), some({left: 3, right: 4})]
    // prints `[some([2, 3]), some([3, 4]), some([4, 5])]`
    println(\a(b))

    println(fjoins([for_each(), maybe(), tree()]))
}

for_each() = \(f, v) = sift i {\f(v[i])}
maybe() = \(f, v) = if v == none() { none() } else { some(\f(unwrap(v))) }
tree() = \(f, v) = {
    if typeof(v) == "object" {
        t := tree()
        {left: \t(f, v.left), right: \t(f, v.right)}
    } else {
        \f(v)
    }
}

fn fmap(f: \(any) -> any, g: \(\(any) -> any, any) -> any) -> \(any) -> any {
    return \(x) = {
        g := grab g
        \g(grab f, x)
    }
}

fn fjoin(
    a: \(\(any) -> any, any) -> any,
    b: \(\(any) -> any, any) -> any
) -> \(\(any) -> any, any) -> any {
    return \(f, v) = {
        a := grab a
        \a(\(v) = {
            b := grab '2 b
            \b(grab f, v)
        }, v)
    }
}

fn fjoins(fs: [\(\(any) -> any, any) -> any])
-> \(\(any) -> any, any) -> any {
    a := fs[0]
    for i [1, len(fs)) {
        a = fjoin(a, fs[i])
    }
    return clone(a)
}

Code generation

grab expressions can be used as form of run-time code generation:

fn main() {
    a := 5
    b := sift i a { \(x) = x / grab prod j i + 1 { j + 1 } }
    // [
    //   \(x: any) = x / 1,
    //   \(x: any) = x / 2,
    //   \(x: any) = x / 6,
    //   \(x: any) = x / 24,
    //   \(x: any) = x / 120
    // ]
    println(b)
}

This has the following benefits:

  • The generated code is already lifetime/type checked
  • No need to write strings together and output them to a file, and then load it as a dynamic module
  • Can be used to optimize some expressions that are used frequently but not known before running

Motivation

Before, closures in Dyon could not capture variables from their envionment. With grab expressions they can, and gives you more precise control over how this happens.

Adds a non-state capture ability of variables in the environment of closures. It is more powerful in the way that it works as a partial evaluation operator, but less flexible than mutable captured variables.

Dyon uses current objects for mutating the environment, so there is less need for capturing mutatable variables. A non-state capture is easier to reason about, requires no lifetime, and does not add overhead when calling closures.

bvssvni added some commits Jun 24, 2016

Started on `grab` expressions
- Added closure_stack when resolving locals since `grab` expressions
use closure environment.
- Added `grab` expressions to syntax
- Added new “grab” module for evaluating `grab` expressions
- Added “syntax/closure_4.dyon”
Added more support for `grab`
- Added “syntax/closure_5.dyon”
- Added “syntax/closure_6.dyon”
Check return lifetime on closures
- Added “syntax/lifetime_16.dyon”
Infer type of closure calls
- Added “typechk/closure_6.dyon”
Infer more cases when calling closure
- Added “typechk/closure_7.dyon”
- Added “typechk/closure_8.dyon”
Write return statement
- Take closure into account when type checking return statement
- Added “typechk/closure_9.dyon”
Report error if `grab` returns `void`
- Added “typechk/grab.dyon”
Added higher level grabs
Evaluate grab at higher environment level.

- Added “typechk/grab_3.dyon”
- Report error when grab level is less than 1
Write parenthesis around `grab` expressions
This makes sure the right side gets evaluated properly when using the
written closure as code.

@bvssvni bvssvni merged commit 69ba217 into PistonDevelopers:master Jun 26, 2016

@bvssvni bvssvni deleted the bvssvni:capture branch Jun 26, 2016

@bvssvni bvssvni referenced this pull request Jun 26, 2016

Closed

Print out closure as code #317

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.