Terser function syntax? #714
Replies: 41 comments 154 replies
-
In case someone might suggest doing away with the arrow
|
Beta Was this translation helpful? Give feedback.
-
The first is due to not making the second's |
Beta Was this translation helpful? Give feedback.
-
My only concern was about symmetry between named and unnamed functions. #257 If the syntax will be the same for both I will be happy not to write additional characters 🙂 |
Beta Was this translation helpful? Give feedback.
-
💡💡 👍 And so the |
Beta Was this translation helpful? Give feedback.
-
OK, that hits the sweet spot, thanks @JohelEGP ... as soon as I did it without the Thanks for the feedback, everyone! This is an experiment worth trying... |
Beta Was this translation helpful? Give feedback.
-
Also : (a) -> a
(a) -> a; In this way, the meaning of // -> is after : so it's a return type.
: (a) -> int = a
// -> is not after : so it's a return value.
(a) -> a For example: call(param, (a) -> a);
x: = (a) -> a; |
Beta Was this translation helpful? Give feedback.
-
Data point: I decided to add test cases by simply searching for A good sign is that when I changed the test code, they all worked perfectly on the first try. So that's some data point that it's easy to spell correctly. The very first one was kind of fun because it actually had two such nested one-liners(!) plus a capture, and I felt a little anxious as I hit Compile for it, but it Just Worked on the first try:
It's a little terse, but I don't think it's too much less readable than the "previously" version. And now that I see code like |
Beta Was this translation helpful? Give feedback.
-
That's true, but I'm trying very hard to stick with " Thanks for this and all the input! |
Beta Was this translation helpful? Give feedback.
-
I am a bit late here, however here's my intuition about this (which I concede could be totally wrong):
Has this terser form been considered? (with or without the nested semicolon)
I'm not very familiar with the parsing priority of existing punctuation nor the potential ambiguities this could create. I have not seen anyone mention this here, so I wanted to see how much this is worth. Maybe it's been considered in a different thread, I might have missed it in this case. |
Beta Was this translation helpful? Give feedback.
-
This is what I originally had. The push-back I got on this in issues/comments here was that it required making the default return for a function be I don't think that it's bad for a default to default to exactly one common case. You can always write the general case out longhand. For example, the terse syntax also doesn't allow writing contracts, which is fine... to write contracts just use those additional parts of the syntax where the contracts go. For completeness: I'm aware of the counterargument that a default that defaults to exactly one common case can be considered undesirable. Notably, Bjarne has long opposed a terse lambda parameter syntax where In this case, I think a default that defaults to not-constexpr, especially at expression scope, is reasonable. It's a good tradeoff to keep in mind though, and be ready to reevaluate if we find disconfirming usage experience! Thanks! |
Beta Was this translation helpful? Give feedback.
-
Here's some feedback on https://github.com/hsutter/cppfront/wiki/Design-note%3A-Defaults-are-one-way-to-say-the-same-thing. Resolved
Only the third one currently permits a default argument.
That doesn't work: https://cpp2.godbolt.org/z/Ye5znPaYf. ResolvedLastly, |
Beta Was this translation helpful? Give feedback.
-
This could be one unpopular opinion but one experiment could be to have a consistent, verbose as needed syntax for functions and an another very terse syntax. Like from this paper P0573 or this blogpost. |
Beta Was this translation helpful? Give feedback.
-
From a CppCon ranges talk.
Cpp2 with this compact syntax, I think:
Looks good. |
Beta Was this translation helpful? Give feedback.
-
Another option is to make the default return type dependent to the right-side of So in these cases, the return type would be a: (x) = { print(x); } // -> void
call(: (x) = { print(x); }); // -> void But in these cases, the return type would be a: (x) = x; // -> _
call(: (x) = x); // -> _ It's natural that the programmer expect to return something from the function when it's defined with expression. On the other hand, naturally we expect to explicitly declare a return type for functions which are defined with statement, otherwise they are expected to return Also the behavior and syntax would be the same for both functions and lambdas. |
Beta Was this translation helpful? Give feedback.
-
So how many ways do we have to spell a function now? Functions isEven: (in x: int) -> bool = { return x % 2; }
isEven: (x: int) -> bool = { return x % 2; }
isEven: (x: int) -> _ = { return x % 2; }
isEven: (x: int) -> x % 2;
isEven: (x: int) -> { x % 2; } // ??? Expressions n: int = rand();
foo( :(in x: int) -> bool = { return x % 2; }(n) );
foo( :(x: int) -> bool = { return x % 2; }(n) );
foo( :(x: _) -> bool = { return x % 2; }(n) );
foo( :(x) -> bool = { return x % 2; }(n) );
foo( :(x) -> _ = { return x % 2; }(n) );
foo( :(x) -> x % 2;(n) ); // ??? Are there any more? (Or fewer because I've made mistakes?) |
Beta Was this translation helpful? Give feedback.
-
My basic idea about syntax designing: |
Beta Was this translation helpful? Give feedback.
-
Came back to this discussion after reading this: Not a big fan of having a named declaration that seemingly looks forward declared, but actually has a "value" associated with it (the body of a function), I think the A little bit ranty but I hope I have never to teach that declaring a name like this, without the |
Beta Was this translation helpful? Give feedback.
-
The only issue I can imagine is that, how does the IDE provide a hint with type info. |
Beta Was this translation helpful? Give feedback.
-
A wild suggestion I haven't seen in this thread yet: What if you combine them?
I don't imagine this will be a popular idea, but I thought it was kinda fun. |
Beta Was this translation helpful? Give feedback.
-
I like this as long as a function or lambda requires either one of -> or => for really clear signposting at a glance.
Your idea also frees up "(x, y, z) = thing" for unambiguous structured binding/assignment.
On another note, to keep banging my drum in this thread...
You don't read code, you parse it, and the more signposts you have, the easier it is to parse at a glance.
In my opinion, the overuse of () as signposts is already making cpp2 harder to parse mentally, as they are contextual, even if it doesn't take that long, it should still be something that could be achieved at a glance without thinking. I know that cpp2 has context free parsing, but there's a difference between knowing there is a way to figure it out, and being able to do that as a human at speed, repeatedly & reliably.
On 29 January 2024 01:53:40 Walter ***@***.***> wrote:
A wild suggestion I haven't seen in this thread yet: What if you combine them??
-> denotes "This function has this returned type"
= denotes "This function will execute this expression/block"
=> denotes "The returned type (and value) of this function is the result of this expression/block"
I don't imagine this will be a popular idea, but I thought it was kinda fun.
—
Reply to this email directly, view it on GitHub<#714 (comment)>, or unsubscribe<https://github.com/notifications/unsubscribe-auth/AALUZQOS6MLIWSVORRN4PR3YQ36KDAVCNFSM6AAAAAA5N5GVZCVHI2DSMVQWIX3LMV43SRDJONRXK43TNFXW4Q3PNVWWK3TUHM4DENZVHE2TG>.
You are receiving this because you commented.Message ID: ***@***.***>
|
Beta Was this translation helpful? Give feedback.
-
After the recent midsummer update, I've been thinking about the current terse syntax again, and I wanted to write down a few thoughts I have about the current format:
Please let me know if I'm off base Having no
|
Beta Was this translation helpful? Give feedback.
-
All good points IMO
On 1 August 2024 05:51:13 Walter ***@***.***> wrote:
After the recent midsummer update, I've been thinking about the current terse syntax again, and I wanted to write down a few thoughts I have about the current format:
:(x, y) x > y
Please let me know if I'm off base
Having no = is a special case
Like mentioned before by @DyXel<https://github.com/DyXel>, the fact that this has no = at all is a deviation from the standard way of defining things. We talk about how cool it is that we can decompose type definitions and function definitions to omit parts that aren't necessary, such as this:
x: int = 5;
x: _ = 5;
x := 5; // Wow neat!
But the decomposition being proposed allows one to omit the assignment, when you have a type (function type) defined:
fn: (x, y) -> _ = x > y;
fn: (x, y) x > y; // No assignment?
If we're able to omit the assignment, shouldn't we be able to write:
x: int 5; // This?
x: 5; // Or even this?
fn(:int 5, :int 7); // Bonus thought, should this be allowed?
The : and = combo seems very powerful to me. It makes sense that you should declare with :, and define with = in every case. Sometimes we redefine by only using =, and sometimes we declare prior to defining by only using :. But why have these special situations where it's possible to define without using =?
Why does adding = necessarily create a special case?
There was some discussion<#257> about the terse format of :(x, y) = x > y, and how it means that these two functions implicitly have different return types:
fn: (x) = std::cout << x; // Implicitly returns nothing, -> void
:(x) = std::cout << x; // Implicitly returns the stream, -> _
Which does create an undesired situation where the act of naming this function changes its return type. But I have to admit, I don't see the reason why we can't say that the { } block is part of the type deduction:
fn: (x) = { std::cout << x; } // Returns nothing, -> void
fn: (x) = std::cout << x; // Returns the stream, -> _
:(x) = { std::cout << x; } // Returns nothing, -> void
:(x) = std::cout << x; // Returns the stream, -> _
Is there a reason this wouldn't make sense?
—
Reply to this email directly, view it on GitHub<#714 (comment)>, or unsubscribe<https://github.com/notifications/unsubscribe-auth/AALUZQPD62KU3CFPBWSIXOLZPG5D3AVCNFSM6AAAAAA5N5GVZCVHI2DSMVQWIX3LMV43URDJONRXK43TNFXW4Q3PNVWWK3TUHMYTAMRQG44TKMA>.
You are receiving this because you were mentioned.Message ID: ***@***.***>
|
Beta Was this translation helpful? Give feedback.
-
Here and on Reddit and other places I keep seeing resistance especially to the lack of At some point (which I think is now), that amounts to a reason to reconsider. Here's a VERY DRAFT table of major cases, what they mean today, and a possible alternative that mirrors some of the suggestions. Disclaimer: It's late at night and I've been rereading all the cells enough times to see what I expect to see, so there may be errors (please point them out!), but I think it's pretty close. Notes:
What do you think? |
Beta Was this translation helpful? Give feedback.
-
@hsutter It took me a while to nail down what was bugging me about your image. There is an error in reference return row 3. The table says
|
Beta Was this translation helpful? Give feedback.
-
Thanks for the comments. Here is an updated table with (I think?) improvements: |
Beta Was this translation helpful? Give feedback.
-
Merging replies... based on the feedback (thanks!), I think that the two new rules I suggested are best combined into one: "omitting braces implies a default body of This states things more clearly to show that those are the defaults you get when omitting And then the summary just comes down to saying: "Changed the tersest syntax to add I think we may have a winner... here's the updated table: |
Beta Was this translation helpful? Give feedback.
-
Recording another example that just came up in #1203: // Today's tersest syntax
in: (a, b) :(x) a$ <= x <= b$; BTW I think this would be clearer with // With this issue's current proposal, add '=' twice
in: (a, b) = :(x) = a$ <= x <= b$; |
Beta Was this translation helpful? Give feedback.
-
Cpp2's function definition syntax:
looks like you're constructing the 2-tuple I'm curious as to why cpp2 doesn't adopt Rust's
Which gets rid of the |
Beta Was this translation helpful? Give feedback.
-
Herb finally implemented the discussed changes to add |
Beta Was this translation helpful? Give feedback.
-
Thanks everyone for your feedback! |
Beta Was this translation helpful? Give feedback.
-
A Reddit discussion made me think of the following, and I thought I'd try it out .Currently Cpp2 allows defaulting this:
to this, omitting the parts not being customized:
Note that the
in
and: _
on parameters can be defaulted away, so a function parameter listf: (in x: _)
is the same asf: (x)
. So my question is, what would you think if the same was done for the return type too, so the above could be spelled as just this, again omitting the parts not being customized:That would make lambdas, which have the identical syntax just without the introducing name, even simpler, for example this:
could be written as this:
WDYT?
Here's an example that works with the latest checkin:
Notes:
The equivalent in today's C++ is:
std::transform(in1, in2, out1, [](auto x){return x+1;})
This isn't motivated by C# envy, but it's now awfully close to C#'s convenient x => x+1; just by defaulting things.
Things I don't like: Having to not support top-level
=
/==
to avoid ambiguity.Things I like: My initial reaction to
-> expr;
is that just reads nicely as "returnsexpr
".But I'm definitely open to disconfirming opinions and experience, hence a Discussion... I would especially welcome examples that are badly surprising/misleading or would be actual pitfalls in this syntax. The spirit of science is "experiments are good, disconfirmation is good." Thanks in advance for your feedback, pro or con.
Beta Was this translation helpful? Give feedback.
All reactions