hsutter / cppfront

A personal experimental C++ Syntax 2 -> Syntax 1 compiler
Other
5.43k stars 236 forks source link

[SUGGESTION] Implement local functions (as CPP1 lambda-s) #1238

Open jamadagni opened 3 weeks ago

jamadagni commented 3 weeks ago

Background:

This request stems from #1234. I am not sure if #714 addresses this too but since @hsutter said he would like to do it, I assume this is separate and file this just to keep track of the matter.

Current situation:

Currently it is required to declare local functions (which include captures) as:

func := :() = { std::cout << "Price = " << price$ << "\n"; };

And this is rewritten into the following CPP1:

auto func {[_0 = price]() mutable -> void{
    std::cout << "Price = " << _0 << "\n";
}}; 

Current difficulty:

However, :=:()= is quite abstruse to decipher, which is why I had to ask #1234. IIANM CPP2 wants to cut out such abstruseness in the language as far as possible.

So I tried to just write a local function, but:

func : () = std::cout << "Price = " << price$ << "\n";

gives:

test4b.cpp2(4,41): error: $ (capture) cannot appear here - it must appear in an anonymous expression function, a postcondition, or an interpolated string literal (at '$')

and even without the capture:

greet : () = std::cout << "Hello\n";

gives:

test4c.cpp2(2,5): error: (temporary alpha limitation) local functions like 'greet: (/*params*/) = {/*body*/}' are not currently supported - write a local variable initialized with an unnamed function like 'greet := :(/*params*/) = {/*body*/};' instead (add '=' and ';')

Request:

I am not an expert but I am not sure there is any effective difference between a local function and a named lambda.

Given that CPP2 already rewrites code to overcome CPP1 limitations, I hence feel it should just automatically rewrite the local function as a named lambda. Hence the recommendation to “add = and ;” (which seems to be missing the :) should no longer be made and CppFront should silently do this rewrite for us instead.

This would also adhere to the one declaration syntax principle, rather than having the user to write :=:()= io the normal :()= for effectively the same purpose.

IIANM this would mean that the final ; should also not be there, as it is there only because this is currently treated as a lambda variable definition rather than a function definition.

DyXel commented 3 weeks ago

I agree with the baby step of simply lowering a regular function to a lambda in C++ so we can write things close to where they are used, it helps a lot with refactoring and evolving the code, something that comes up naturally while you design a solution to your problem.

Word of caution though: Supporting writing functions like this might seem controversial to others. At work, where we use Python mostly today, I have heard that inline functions (not lambda) are the devil and should have never been supported, the perceived problem, which to be honest I have seen the worst of in a legacy project so I can understand where they come from, is that they encourage inlining all the logic within a single massive function, hurting readability, and also encourage duplication of logic (since you can't access a function within another function from anywhere else). In my experience, this is a organizational/architectural problem, more than a tooling, language or developer issue.

On the other hand, I know functional programming folks like to write functions within another function since this naturally provides encapsulation, I can appreciate that, but to be honest never thought about it too hard since C++ provides more explicit mechanisms for this, as I said before, I want to look at it from the perspective of consistency and simplicity, and disallowing writing regular functions seems like something artificial rather than a legitimate technical issue.

Finally, I have the feeling that allowing captures in regular functions should also maybe be pursued, a combination of a @pure metafunction (or equivalent) that enforces only accessing explicitly named arguments and/or captures should help with refactoring, catching hard to track bugs in multi-threaded code, and allow to more easily reason about what you wrote.

hsutter commented 3 weeks ago

Thanks! Very good thoughtful notes here.

Very brief ack:

DyXel commented 3 weeks ago

I would need evidence to convince me that capturing the current state of a global would many any sense at any time, never mind the concurrency implications of doing that.)

I don't really have any as there isn't much out there unfortunately 😅. Its just an itch, but it might be worth experimenting on it and see if it leads anywhere.