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

Close over free variables in lambdas and object literals #1648

Merged
merged 11 commits into from
Mar 11, 2017

Conversation

sylvanc
Copy link
Contributor

@sylvanc sylvanc commented Mar 9, 2017

Previously, using a variable from the surrounding lexical scope in an object literal or a lambda produced a compiler error. This was surprising to the programmer.

This change allows both lambdas and object literals to use variables in the surrounding lexical scope, as the programmer would expect. In both cases, the closure is expressed under the hood as adding a field that is initialised by passing an alias of the variable to the constructor. For example:

let x = "hi"
let f = {(): String => x}

Here, when f rewrites to an object literal, it becomes:

let f =
  object
    var x: String = x
    fun apply(): String => x
  end

@sylvanc sylvanc added the changelog - added Automatically add "Added" CHANGELOG entry on merge label Mar 9, 2017
@sylvanc sylvanc requested review from SeanTAllen and jemc March 9, 2017 21:21
@sylvanc
Copy link
Contributor Author

sylvanc commented Mar 9, 2017

I didn't create an RFC for this, as I think it is a "principle of least surprise" bug.

@SeanTAllen
Copy link
Member

THIS MAKES ME SO GOD DAMN HAPPY!

}

// TODO: add as a field, add as a constructor parameter, init field from
// parameter in constructor, add at the call site
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this TODO need to be handled before merging? Should we create an issue for it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, stray TODO, already handled.

@@ -292,6 +292,11 @@ ast_result_t pass_expr(ast_t** astp, pass_opt_t* options)
case TK_ADDRESS: r = expr_addressof(options, ast); break;
case TK_DIGESTOF: r = expr_digestof(options, ast); break;

case TK_OBJECT:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for my own edificaton, what's this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's moving object literals from the sugar pass (where they used to be, as pure sugar for an anonymous type and a constructor) to the expression pass, where they live now, because they need to know about the existence and type of variables within lexical scope.

@SeanTAllen
Copy link
Member

@sylvanc should the 6 commits be squashed to one logical commit before merging?

@sylvanc
Copy link
Contributor Author

sylvanc commented Mar 9, 2017

Yes, squash before merge, definitely. I'm afraid those commits are a bit "stream of work".

@SeanTAllen
Copy link
Member

Looks good to me if CI passes and the squashing happens. But @jemc is far more qualified than I in all matters compiler.

Copy link
Member

@jemc jemc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome!

@@ -69,7 +69,7 @@
#define TREE_CLEAR_PASS(tree) \
{ \
if(ast_parent(tree) != NULL) tree = ast_dup(tree); \
ast_resetpass(tree); \
ast_resetpass(tree. 0); \
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like a typo here - should this period be a comma?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops! Yup, typo. I guess that macro is never used :)

" object ref be foo() => 4 end";

TEST_ERROR(short_form);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a lot of tests removed here that were covering some important aspects of object literals.

I understand that the object processing was moved out of the sugar pass and into the expr pass, so that these tests don't really belong in this file anymore, and that TEST_EQUIV no longer makes sense. However, adapting these tests for the expr pass and figuring out how to cover the same aspects would be ideal, so we can prevent regressions as we move forward.

Also, any other tests that can be made to reflect possible pitfalls of the capture process would be great. I saw that this PR adds a test to builtin_test, but it doesn't seem like enough to really cover this major feature, especially with all these tests having been removed.

If you don't have the time to devote to adding more tests to this PR, I'd be happy to help chip in and make sure we have some more tests.

Copy link
Member

@jemc jemc Mar 10, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One aspect coming to mind - it would be great to confirm the right behaviour and/or error message in some of the problem cases, like closing over a non-sendable local in an object iso, for example.

@jemc
Copy link
Member

jemc commented Mar 10, 2017

@sylvanc Does anything in the tutorial need to be updated to reflect this new feature?

@sylvanc
Copy link
Contributor Author

sylvanc commented Mar 10, 2017

I agree that additional tests would be good, probably in parse_expr.cc. If you're up for chipping in, that would be awesome! Feel free to push changes to this branch, or make a PR to do after this, or whatever suits you.

For the tutorial:

https://tutorial.ponylang.org/expressions/object-literals.html

That page could use a bit of editing to show closing over free variables. I don't think it needs a big explanation or anything, just showing how we can capture from the lexical scope anywhere, not just in field initialisers.

@sylvanc
Copy link
Contributor Author

sylvanc commented Mar 10, 2017

I added a few test cases.

@sylvanc
Copy link
Contributor Author

sylvanc commented Mar 10, 2017

Added a tutorial PR as well:

ponylang/pony-tutorial#172

Copy link
Member

@jemc jemc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding some more test cases!

If I think of any more to add, I can do it asynchronously in another PR.

Looks good to me - feel free to merge when CI passes.

@SeanTAllen SeanTAllen merged commit eaf1dcd into master Mar 11, 2017
ponylang-main added a commit that referenced this pull request Mar 11, 2017
@SeanTAllen SeanTAllen deleted the close-over-free branch March 11, 2017 00:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
changelog - added Automatically add "Added" CHANGELOG entry on merge
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants