-
Notifications
You must be signed in to change notification settings - Fork 258
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
[SUGGESTION] for
loop is ambiguous with lambda syntax
#386
Comments
The syntax is supposed to make the language more regular, as a I did find it a bit weird, but only because of the association with function expressions and the knowledge they're lowered to Cpp1 lambda expressions. Otherwise, I think it's a win. The function syntax is regular. At namespace scope and type scope, they declare functions. At statement scope, they're currently banned. As expressions, they work and permit captures. And finally, they work as the |
IMO that's why it complicates the language. It just feels like another |
Consider the upcoming EDIT: It's https://wg21.link/P1306, |
That's nice, but func: <T: type...> (args: T...) = {
for... arg: T in args {
//: statements...
}
} or this one: func: <T: type...> (args: T...) = {
for... args next arg: T {
//: statements...
}
} or any other syntax without |
Sorry, that was a bad example. "Expansion statements", proposed for Cpp1, operates on values. So it'd probably be a matter of permitting |
Thanks for the feedback. The status quo is by design, because the body of a range-for loop (not a do or while loop) is already conceptually a function invoked once per element in the range. Note that capture is already not allowed in all functions. In particular, named functions already don't allow capture. Perhaps it helps if you view the body of a |
Although it's not possible to have nested functions in Cpp2, but yes, it helps if I view it as a function named |
BTW, Cpp2 will have local functions. They're pretty much there, I just ban them as a 'temporary alpha limitation' only because I haven't been motivated to prioritize the task of emitting them as a lambda and making sure they can capture. It shouldn't be hard, but I've wanted to drive other things like meta functions first. Also, they have a pretty easy workaround (as in Cpp1 today) which is in the error message you get if you try to write one:
That said, I have said the same about other features and then ended up prioritizing and implementing them anyway already because they came up in issues... squeaky wheels do get grease, and programmers asking for a feature and showing concrete use cases is new information that justifies revisiting priority. So if you want cppfront to prioritize implementing nested functions, get friends to open issues and show use cases. 😁 |
Or... propose implementation in PR. It might save some Herb time (hopefully). I can help in reviewing the code. |
Thank you. I think I have to write a conclusion for future readers if they come up to this issue. I'm reading again this comment from @JohelEGP:
And I put it next to this informative comment from @hsutter:
So I should give up to separate functions and lambdas for learning Cpp2, because they are implementation detail. To simplify the learning progress I should keep in mind that there is only functions (either named or unnamed). Thus I should change my comment from:
... to this one: ConclusionCpp2 have functions and local functions (either named or unnamed).
In this way Cpp2 language is consistent with simple rules. Thanks for your guidance from those comments. |
@filipsajdak, You're right, it's the best way to suggest features in Cpp2. But I hope I could... I don't have any experience or study in transpilers, compilers and etc... I'm going to read the source code to try my luck to understand it. |
@msadeqhe I did not have much experience with it as well. I can help. |
Preface lost in an accidentally deleted comment. Recovered:
That said, I'd prefer a description that doesn't rely on Cpp1. I'd like to think that you don't have to learn Cpp1 to learn Cpp2, before learning Cpp1, and such. Much like we say about teaching C++ and C. |
Granted, Cpp2 is grounded on Cpp1. For this particular case, I meant not having to learn the Cpp1's range-based It's not as easy to come up with a pure Cpp2 description without |
Thanks, now I see your point: Because the range- You have hit the nail on the head with your pointing out the capture difference. You are touching directly on an intended unification that has driven the current design, and I think you're pointing out that I should tweak the First, I'll summarize the general design. Then, let's talk about From the (unpublished) Cpp2 design doc: Unifying functions and blocksHere's a piece from my Cpp2 design doc that aims to unify functions and blocks, and where I've currently only implemented the first two parts... this is mostly identical to what I wrote a few years ago, tweaking a phrase or two here to touch it up as I write this:
and for visual completeness I'll now add the simplest familiar case:
(Recall that in Cpp2 The idea is to treat functions and blocks/statements uniformly, as syntactic and semantic subsets of each other:
In this model, the last (currently unimplemented) option above allow a block parameter list, which does the same work as "let" variables in other language, but without a "let" keyword. This would subsume all the Cpp1 loop/branch scope variables (and more generally because you could declare multiple parmeters easly which you can't currently do with the Cpp1 loop/branch scope variables)... quoting the next part from the design doc (note these
Last year, eagle-eyed readers will have noticed that the cppfront code had stubbed-in support for these "let" parameters, and I subsequently removed it "for now" because I wasn't quite ready to pursue the idea. In particular I worried about readability without something like "let" to introduce it, but I also wasn't yet willing to invent such a single-use keyword/concept without a compelling reason. Maybe it's time to bring this back. Implications for
|
👍 Does the above accomplish that? |
Thanks. The new syntax looks great. |
Sorry. I accidently touched close with comment on my phone 😬. |
Also, over on #382, @JohelEGP mentioned this paper proposing Which makes me think, hmm, could using
|
Parametrized statements fit the |
Thank you for explaining the disappearing I was looking for a solution to solve issues from the following code: it := blocks.begin(); while it != blocks.end() next it++ {
// ...
} I was annoyed that From what I understand the above lines after let blocks will look like the following: (it := blocks.begin()) while it != blocks.end() next it++ {
// ...
} Or with the do (it := blocks.begin()) while it != blocks.end() next it++ {
// ... //Is the similarity with do-while a problem here?
} Looks maybe a little noisy. Maybe a version with an explicit block will be better: (it := blocks.begin()) {
while it != blocks.end() next it++ {
// ...
}
} And with the do (it := blocks.begin()) {
while it != blocks.end() next it++ { // is the similarity with do-while is a problem here?
// ...
}
} I will try to port more code examples to see how it looks like and share some thought. I am very happy that there will be an easy way to limit the lifetime of the variables used in loops |
Exactly that example was something that made me hesitate, because it's not much different from the already-legal: {
it := blocks.begin();
while it != blocks.end() next it++ {
// ...
}
} and that made me hesitate about the value of the feature. |
So would "next" become a keyword for standard for loops then?
On 21 April 2023 19:27:21 Herb Sutter ***@***.***> wrote:
Thanks, now I see your point: Because the range-for loop already effectively treats its body as a function body executed once per loop iteration, I model it syntactically as such. But you are pointing out that with any other such function that appears at local scope (an unnamed expression function today, and a named local function in the future) it would have to capture to refer to locals, whereas the for loop body has direct access to locals.
You have hit the nail on the head with your pointing out the capture difference. You are touching directly on an intended unification that has driven the current design, and I think you're pointing out that I should tweak the for syntax to be consistent.
First, I'll summarize the general design. Then, let's talk about for...
From the (unpublished) Cpp2 design doc: Unifying functions and blocks
Here's a piece from my Cpp2 design doc that aims to unify functions and blocks, and where I've currently only implemented the first two parts:
A declaration within a scope, or in ( ) that precedes a scope, has the lifetime of that scope. For example, in all of the following cases x’s lifetime ends at the following } (implicit in the last case):
f:(x: int = init) = { ... } // x is a parameter to the function
f:(x: int = init) = statement; // same, { } is implicit
:(x: int = init) = { ... } // x is a parameter to the lambda
:(x: int = init) = statement; // same, { } is implicit
(x: int = init) { ... } // x is a “let parameter” to the block
(x: int = init) statement; // same, { } is implicit
(Recall that in Cpp2 : always and only means "declaring a new thing," and therefore also always has an = immediately or eventually to set the value of that new thing.)
The idea is to treat functions and blocks/statements uniformly, as syntactic and semantic subsets of each other:
* A named function has all the parts: A name, a : (and therefore =) because we're declaring a new entity and setting its value, a parameter list, and a block (possibly an implicit block in the convenient syntax for single-statement bodies).
* An unnamed function drops only the name: It's still a declared new entity so it still have : (and =), still has a parameter list, still has a block.
* (not currently implemented) A block drops only the name and : (and therefore =): We have a parameterized block. It is not a separate entity (there's no : or =), it's part of its enclosing entity, and therefore it doesn't need to capture.
* Finally, if you drop also the parameter list, you have an ordinary block.
In this model, the last (currently unimplemented) option above allow a block parameter list, which does the same work as "let" variables in other language, but without a "let" keyword. This would subsume all the Cpp1 loop/branch scope variables (and more generally because you could declare multiple parmeters easly which you can't currently do with the Cpp1 loop/branch scope variables)... quoting the next part from the design doc (note these for examples don't show declaring the loop variable, more on this below):
Note Because this works naturally for flow control statements, those no longer need a special syntax for if/switch/etc. scoped variables. For example:
(x:int = f()) if x>1 // same as C++17: if (int x = f(); x>1)
(x:int = f()) switch x // same as C++17: switch (int x = f(); x)
(x:int = f()) for x>1 next --x // same as K&R C: for (int x = f(); x>1; --x)
(x:int = f()) for range do // same as C++20: for (int x = f(); _ : range)
(x:int = f(), i:=0) for range do next ++i
// same as C++20: { int i=0; for (int x = f(); _ : range) { ++i;
(x:int = f()) while x>1 // not yet allowed for ‘while’ in C++20
(x:int = f()) do { } while x>1 // not yet allowed for ‘do’ in C++20
(x:int = f()) try // not yet allowed for ‘try’ in C++20
// note x is available inside the catch too
Last year, eagle-eyed readers will have noticed that the cppfront code had stubbed-in support for these "let" parameters, and I subsequently removed it "for now" because I wasn't quite ready to pursue the idea. In particular I worried about readability without something like "let" to introduce it, but I also wasn't yet willing to invent such a single-use keyword/concept without a compelling reason.
Maybe it's time to bring this back.
Implications for for
Coming back around now to for: Your point that capture is not needed is really evidence that the for loop body is in the third case... it really is a block with a parameter list, not really declaring a new function with a parameter list. If so, then that's an argument that for should not have a : or = (other than that the syntax is fine and consistent as above), i.e., to change the for loop syntax from this:
// right now
for items do: (item) = {
x := local + 10;
// ...
}
to this:
// alternative
for items do (item) {
x := local + 10;
// ...
}
(I think the do probably still has value. In both versions it adds nothing grammatically, only visually, but visually I think it's important because it makes it more readable.)
Then if I also supported parameters on blocks, which would allow this:
// a block with parameters
(value := local) { // an 'in' (read-only) parameter initialized from a local
x := value + 10;
// ...
}
then the for loop would be visually and semantically just like that and easy to teach and explain as "both are parameterized blocks." I'd expect that consistency to be easy to explain and teach... e.g., "local functions can capture because they're in (but not part of) the local scope, and blocks naturally don't capture because they're part of the local scope."
Thoughts?
—
Reply to this email directly, view it on GitHub<#386 (comment)>, or unsubscribe<https://github.com/notifications/unsubscribe-auth/AALUZQOMMTXD2Z63SYQEDILXCLGP7ANCNFSM6AAAAAAXFQABLQ>.
You are receiving this because you are subscribed to this thread.Message ID: ***@***.***>
|
Will this let parameters require/allows setting argument passing type? That might be something not present in the following case: {
it := blocks.begin();
while it != blocks.end() next it++ {
// ...
}
} |
Yes, that's the idea.
Do you mean something like this? This works now:
Invoked with
|
If we consider it with
Interestingly let-while-next is similar to
Both of them are parameterized statement blocks, and they can be used together, e.g. |
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
This comment was marked as off-topic.
That's right, and
|
This comment was marked as off-topic.
This comment was marked as off-topic.
Thanks everyone for all the comments, and especially for pointing out the key things that prompted me to finally bring back and implement the function/block unification I've always wanted, including block/statement scope parameter lists, and removing Now that I've implemented block/statement scope parameter lists and tried using them in a ~dozen test examples, I do find them to be as semantically useful as I had hoped, which is shown in this new test case where it feels nice to be able to directly declare intended side effects of a statement/loop/block of code. For now I'm limiting it to
Re migration: The Feedback always welcome, especially bugs (e.g., does something not work) and usability (e.g., does the migration message fire in most cases). Thanks again. |
Thanks. I like how the following is similar to
EDITThanks @ntrel about the issue with my example. This is working now: (copy i: = 0) while i < 10 next i++ {
std::cout << i << "\n";
} |
That needs (inout i := 0) while i < 10 next i++ { I think it would read a bit nicer if |
…that syntax to `for` (remove `:` and `=`), closes hsutter#386
Preface
Cpp2 has the following control structures:
While I always thought that
for
loop gets a lambda, and because of that I started to think that Cpp2 should have user-defined language constructs. But NO, hopefully the statement block offor
loop is not a lambda.All control structures of Cpp2 have statement blocks, therefore we don't need to capture anything inside the statement block of
for
loop.How to reproduce?
It's an example:
A typical programmer would think that
: (item) = { ... }
is a lambda because of its similarity with lambda syntax, but hopefully it's not. Cpp2 generates the following code:Suggestion Detail
I didn't know I should create a bug report or a suggestion for ambiguous problem of
for
loop. By the way I created a suggestion.I suggest to change the syntax of
for
loop from:to something consistent with other control structures such as one of the following syntax:
In which:
keyword
for the first option can bein
,of
,iter
or etc.keyword
for the second option can benext
,each
,loop
or etc.Any other syntax which is similar to other control structures, is good.
Why do I suggest this change?
Because:
for
loop syntax: (item) = { ... }
is ambiguous with lambda syntax. A typical programmer expects to capture variables inside it.for
loop syntax: (item) = { ... }
is inconsistent with other control structures which their statement block are simply{ ... }
.Your Questions
Will your feature suggestion eliminate X% of security vulnerabilities of a given kind in current C++ code?
No.
Will your feature suggestion automate or eliminate X% of current C++ guidance literature?
Yes absolutely. It would make control structures to be consistent with each other in addition to simplification of guidance:
{ ... }
will be for the statement block of control structures, therefore variables don't have to be captured.: (args) = { ... }
will be for lambdas, therefore variables have to be captured.for
loop breaks this rule.next
keyword inwhile
loop) won't be evaluate at first evidence, and they can be evaluated multiple times.Considered alternatives
The other alternative solution is to make the statement block of
for
loop to be actually a lambda, which is weird and inconsistent with other control structures.The text was updated successfully, but these errors were encountered: