Repository navigation
[SUGGESTION] Parameterized Meta Functions; A replacement for Attributes, Contracts, Coroutines and User-defined Language Constructs #523
Description
Activity
- changed the title
[-][SUGGESTION] Parameterized *Meta Functions*; A replacement for Attributes, Contracts and etc[/-][+][SUGGESTION] Parameterized Meta Functions; A replacement for Attributes, Contracts, Coroutines and User-defined Language Constructs[/+]on Jun 22, 2023 This suggestion will bypass the following problems which are mentioned in @hsutter's P0707 paper.
- Fragmented local dialects
- Using meta functions, is like using a regular function or type. They are defined in a library without mutating the language.
- Nonportable code
- A library writer can extract a piece of code and reuse it in a different environment, because the language is not mutated.
- Noncomposable code
- They may be inside namespaces. They are like regular functions but they generate code instead of function call.
- Unreviewable/unmaintainable “write-only” code
- They are prefixed with
@for readability. It would mean a code generation is happening in place of@name ....
- They are prefixed with
On the other hand:
- No mutable language
- They don't change any language feature.
- No mutable meta functions
- Similar to Metaclasses, they would only generate new code. They cannot change already defined types, variables, functions or other meta functions.
- Their effects are local, not global
- For contracts and attributes which are within declarations, they would satisfy this rule.
- But for coroutines and user-defined language constructs, they would not satisfy this rule.
Reacted by Jorge Canizales- Fragmented local dialects
How to declare user-defined language constructs?
User-defined language constructs are meta functions which may be applied before a null statement, block statement or parameterized block statement:
// null statement ; // block statement { /*statements...*/ } // parameterized block statement name (args) { /*statements...*/ }
This is an example of what the declaration syntax may be look like.
forr: (src: statement, arg: expr<every_op>) -> statement = { d: every_op = arg.data(); // It's the new generated code. -> { (copy __t: timer = ()) while d.get_condition().to_string()$ { src.to_string()$ __t.delay(d.get_time_interval().to_string()$); } } } every_op: type = { condition: expr<bool>; time_interval: expr<duration>; operator=: (out this, arg1: expr<bool>, arg2: expr<duration>) = { condition = arg1; time_interval = arg2; } get_condition: (this) -> expr<bool> = condition; get_time_interval: (this) -> expr<duration> = time_interval; } every: type = { condition: expr<bool>; operator=: (out this, arg: expr<bool>) = { condition = arg; } operator(): (inout this, time_interval: expr<duration>) -> every_op = { return (: every_op = (condition, time_interval)); } }The above example, would be used like this:
main: () = { x: = random(1, 100); // Its parameter is `(x < 10) every (1min)`. @forr (x < 10) every (1min) { // Its block statement print(x); x = random(1, 100); } }The parameter
(x < 10) every (1min)is a valid syntax without meta function,@forrwould generate new code from its parameter and its block statement. It would generate this code:main: () = { x: = random(1, 100); (copy __t: timer = ()) while x < 10 { print(x); x = random(1, 100); __t.delay(1min); } }When we call
forrfunction with prefix@, the compiler would pass information about the statement to it. The code generation happens withinforrmeta function which has a parameter of typeexpr<every_op>. Because of@, also the compiler would createexpr<...>and pass it to function calls for us.expr<...>is a type which contains information about the expression itself.This example is a possible implementation, it may be like anything else. Its implementation syntax depends on Reflection which is not yet available in C++1.
Reacted by Jorge CanizalesMeta Function Overloading
It would be possible to overload meta functions. They are regular functions, therefore they can be overloaded:
// first signature forr: (src: statement, arg: expr<every_op>) -> statement = { ... } // Here is the declaration of `every_op` and `every` type, similar to the previous example. every_op: type = { /*...*/ } every: type = { /*...*/ } // second signature forr: (src: statement, arg: expr<on_released_op>) -> statement = { ... } // Here is the declaration of `on_released_op` and `on_released` type, similar to the previous example. on_released_op: type = { /*...*/ } on_released: type = { /*...*/ } // It would call the first signature. @forr (x < 10) every (1min) { //statements... } // It would call the second signature. @forr (x < 10) on_released (resource) { //statements... }
- Repository owner locked and limited conversation to collaborators
on Aug 30, 2023
1. Preface
I'm looking for a feature to pass an expression itself like macro parameters instead of immediately calculate the result of an expression to pass its result to functions. To have this feature in Cpp2, the best condidate is Metaclass Functions with
@name, because it generates code. So@name exprwill generate new code fromexpr, and it can be applied to declarations, null statements, block statements or parameterized block statements. It's all about Reflections and Generations.2. Suggestion Detail
This suggestion heavely depends on @hsutter's P0707 paper about Metaclasses.
The idea is to allow Meta Functions to get a parameter, in addition to make its usage general. This suggestion requires chaining
(args)classsyntax for object construction as described in this suggestion (e.g.(args)type1(args)type2(args)type3(args)...). It would have the following use cases:func: () @noreturn = { exit(); }co_await,co_yieldandco_return):NOTE 1:
This suggestion is just a feature to explore, and I know that it isn't going to be implemented anytime soon as Reflection isn't ready yet in Cpp2.
The rules of the suggestion are that:
(x) less (y)is an expression which creates an object with(x) lessand callsoperator()on it with(y). It is the only one parameter.std::metais applied to a null statement:@std::meta "text";Multiple parameters are not needed if we use reflections to parse that one parameter. After that we would generate new code based on that one parameter. Literally any expression are allowed to be that one parameter. Types look like they are keywords (althouth they are not) within chained object construction with
(args)classsyntax (as described in this suggestion):The point is that a parameterized meta function would generate new code from its parameter.
For example:
The above code would generate this new code:
Use Cases
2.1. User-defined Language Constructs
They are similar to macros in C and Cpp1, except:
@prefix.User-defined Language Constructs are Meta Functions which would be applied to null statements, block statements or parameterized block statements. Cpp1 attributes in which they apply to null statements, are somehow language constructs. For example,
[[assume(expr)]];and[[fallthrough]];attributes in Cpp1, would be like this with the suggestion:Also when the resource have to be released, we can have a user-defined language construct for it:
2.2. Attributes
Parameterized meta functions are similar to attributes in Cpp1, except they are user-defined as library features.
This is how to use them to change the compiler behaviour or to inform the compiler to how generate code. For example, this is equal to
[[noreturn]]in Cpp1:Compilers can implement their own attributes in Cpp2 as library features instead of how they have implementation-defined attributes in Cpp1. For example,
aliasextension attribute in GCC:In this case, they do not generate any statement, they only change the compiler behaviour.
The compiler could provide its options in
compilerobject:Meta functions can change the compiler behaviour with
compilerobject (reflections API).2.3 Contracts
Contracts
preandpostare meta functions. They would add test conditions to the before and the after of function body. They generate new function body if that's necessary. For example:The above declaration will generate this declaration:
2.4 Coroutines
Coroutines could be a library feature in Cpp1 too (via
co_await,co_yieldandco_return). For example:tcp_echo_server: () -> task<> = { //statements... while true { n: std::size_t = @co_await socket.async_read_some(buffer(data)); @co_await async_write(socket, buffer(data, n)); } } iota: (n: int = 0) -> generator<int> = { while true { @co_yield n++; } } func: () -> lazy<int> = { @co_return 7; }The above examples are modified Cpp2 version from cppreference.com examples.
3. Your Questions
This suggestion won't eliminate X% of security vulnerabilities of a given kind in current C++ code.
But this suggestion will automate or eliminate X% of current C++ guidance literature. It would unify Contracts, Attributes, Coroutines and User-defined Language Constructs to a single concept Parameterized Meta Functions.
Also this suggestion would help compiler and library writers:
4. Considered Alternatives
I've considered to use named arguments instead of
(args)class1(args)class2(args)..., but the problem of named arguments is that they don't look like built-in language constructs, also they need comma: