Skip to content
This repository was archived by the owner on Aug 11, 2025. It is now read-only.

Add Lambda support through Lambdaspec class#16

Open
HliasMpGH wants to merge 2 commits intoRandgalt:masterfrom
HliasMpGH:lambdaspec-addition
Open

Add Lambda support through Lambdaspec class#16
HliasMpGH wants to merge 2 commits intoRandgalt:masterfrom
HliasMpGH:lambdaspec-addition

Conversation

@HliasMpGH
Copy link
Contributor

@HliasMpGH HliasMpGH commented Jun 7, 2024

First Part of #5

This PR supplies the user with the ability to create lambdas and integrate them into his generated code, with the usage of the LambdaSpec class, which acts as a model of an anonymous function. A demonstration of the features' usage can be seen below.

The process of creating some simple lambda functions can be performed as such:

// lambdas with no inputs
LambdaSpec lambda1 = LambdaSpec.builder("5 + 7").build();
LambdaSpec lambda2 = LambdaSpec.builder("method1(); method2();").build();

// initialize some inputs
ParameterSpec p1 = ParameterSpec.builder(TypeName.INT, "x").build();
ParameterSpec p2 = ParameterSpec.builder(TypeName.DOUBLE, "y").build();

// lambda with inputs
LambdaSpec lambda3 = LambdaSpec.builder("x + y").addInput(p1, p2).build();
or (for the same result):
LambdaSpec lambda3 = LambdaSpec.builder(List.of(p1, p2), "x + y").build();

Now that the models are ready, they can be added in generated code:

MethodSpec method = MethodSpec.methodBuilder("method")
.addCode("methodCall(")
.addLambda(lambda1).addCode(", ")
.addLambda(lambda2).addCode(", ")
.addLambda(lambda3).addCode(");\n")
.build();

which results in:

void method() {
  methodCall(() -> 5 + 7, () -> {method1(); method2();}, (x, y) -> x + y);
}

They can be added to CodeBlocks the same way.
For further configuration of the output, such as formatting and optional visibility of components:

// body of the function
CodeBlock body = CodeBlock.of("int $1N = 3; int $2N = 5; return $1N + $2N;", "x", "y");

LambdaSpec lambda4 = LambdaSpec.builder(body).bodyOnNewLine().indentedBody().build();

Which is a model of:

() -> {
  int x = 3; int y = 5; return x + y;
}

While

LambdaSpec lambda5 = LambdaSpec.builder("x + 5").addInput(p1).visibleTypes().build();

is a model of:

(int x) -> x + 5

Further configuration (for example indents, new lines and spaces inside the body) can be done by the user though the CodeBlock methods, when creating said CodeBlock). In either case, more configuration types can be easily added and implemented with the help of the LambdaMode enum of the same class. Regarding the spaces between the ->, i left them unconfigurable due to the fact that their placement is a formatting standard (such as a space before the { in a for loop), just like other methods perform in the codebase, when placing similar unspecified spaces (for example beginControlFlow() of CodeBlock class). Here's what's specified in the Google Java Style Guide:
https://google.github.io/styleguide/javaguide.html#s4.6.2-horizontal-whitespace

@Randgalt
Copy link
Owner

Randgalt commented Jun 7, 2024

Thanks for this. I think this is a lot better and will pay benefits later on. I'll review when I can.

@HliasMpGH
Copy link
Contributor Author

Thanks for this. I think this is a lot better and will pay benefits later on. I'll review when I can.

I believe so too. Let me know about any additions/ changes needed, when you can of course.

@Randgalt
Copy link
Owner

FYI - referring to Here's what's specified in the Google Java Style Guide I think is a mistake. This library is style agnostic. We should attempt to support whatever non-standard styles users wat.

// true if the function has more than 1 statement in its body
boolean multiStatementBody = bodySide.contains(";");

codeWriter.emit(multiStatementBody ? "{" : "");
Copy link
Owner

Choose a reason for hiding this comment

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

What if someone wants braces to start on newlines ala:

x -> 
{
   //
}

We should support that

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree. I can add more modes that support said format. Thanks for the suggestion.

String bodySide = this.body.toString();

// true if the function has more than 1 statement in its body
boolean multiStatementBody = bodySide.contains(";");
Copy link
Owner

Choose a reason for hiding this comment

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

This will break if ; is in a string, etc. This seems like a fragile way of checking for multi statements. Can something be added to CodeWriter to signal more than 1 statement?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thats an interesting thought. I will look into that

Copy link
Contributor Author

@HliasMpGH HliasMpGH Sep 10, 2024

Choose a reason for hiding this comment

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

The "issue" with CodeWriter is that the components can always use the generic .emit() method, to append code. In that case, the CodeWriter is not aware of how many lines of code its about to append (it just knows that its about to append a String, its not like it will append it line-by-line). The same is true for CodeBlocks (with the .add() method). So essentialy, the problem we are facing is still, given a some code in a string, count how many valid ';' it has. We can probably get over it by the utilization of a parser library. What do you think?

EDIT: A contender for the parser can be the javaparser library.

Here is a quick example of how it can be performed:

public boolean hasMultipleStatements(String code) {
        try {
            StaticJavaParser.parseExpression(code);
            // the string is an Expression
            return false;
        } catch (ParseProblemException e) {
            // the string code requires { }
            return true;
        }
}

@Randgalt
Copy link
Owner

@HliasMpGH do you know anyone else who can help review this?

@HliasMpGH
Copy link
Contributor Author

@HliasMpGH do you know anyone else who can help review this?

Not at the moment, i will look into it.

@HliasMpGH
Copy link
Contributor Author

FYI - referring to Here's what's specified in the Google Java Style Guide I think is a mistake. This library is style agnostic. We should attempt to support whatever non-standard styles users wat.

Is my case different than methods like beginControlFlow(), nextControlFlow(), endControlFlow() of CodeBlock, that perfrom that way by adding said spaces? In the instance that it is different, do you think by adding a mode like ARROW_SPACES (or ARROW_LEFT_SPACE and ARROW_RIGHT_SPACE for further customization (although that sounds like something a user would quickly start to despise)) we would solve said issue? if we keep the methods that manipulate the modes set and have something like addRightArrowSpace() and addLeftArrowSpace() would probably be a better solution in my opinion. Wdyt?

@HliasMpGH
Copy link
Contributor Author

HliasMpGH commented Jun 20, 2024

A different way we can approach the problem, and achieve (almost) complete configuration flexibility, is to have a second CodeBlock (that the user can modify) in the LambdaSpec (in addition to the body the user supplies) that gets placed right before the lambda body. That way, the user can add all the spaces/ indents/ new lines they want and they will get placed right before the body they provided. This works perfectly on single statement lambdas without the use of lambda modes, since they are simple. Its not the same when it comes to multistatement bodies, though, because of the braces placement. To achieve similar flexibility on multistatement lambdas we still need to use the modes, in addition to the new CodeBlock. We can modify the enum accordingly:

  • remove BODY_NEWLINE (since the user can add new lines by himself now)
  • add BRACES_DIFF_LINES, that gives the user the ability to have the lambda body and the braces on different lines

The above changes will result in something like the following:

CodeBlock lambdaBody = CodeBlock.of("int z = 3;\nreturn x + z;");

ParameterSpec p1 = ParameterSpec.builder(TypeName.INT, "x").build();

CodeBlock code1 = CodeBlock.of("");
CodeBlock code2 = CodeBlock.of(" ");
CodeBlock code3 = CodeBlock.of("\n");
CodeBlock code4 = CodeBlock.builder().add("\n").indent().build();

LambdaSpec lambda1 = LambdaSpec.builder(List.of(p1), lambdaBody).beforeBody(code1).bracesDiffLines().build();
LambdaSpec lambda2 = LambdaSpec.builder(List.of(p1), lambdaBody).beforeBody(code2).bracesDiffLines().build();
LambdaSpec lambda3 = LambdaSpec.builder(List.of(p1), lambdaBody).beforeBody(code3).bracesDiffLines().indentedBody().build();
LambdaSpec lambda4 = LambdaSpec.builder(List.of(p1), lambdaBody).beforeBody(code4).bracesDiffLines().indentedBody().build();
LambdaSpec lambda5 = LambdaSpec.builder(List.of(p1), lambdaBody).beforeBody(code4).build();
LambdaSpec lambda6 = LambdaSpec.builder(List.of(p1), lambdaBody).beforeBody(code3).bracesDiffLines().build();

With an output of:

x ->{
int z = 3;
return x + z;
}

x -> {
int z = 3;
return x + z;
}

x ->
{
  int z = 3;
  return x + z;
}

x ->
  {
    int z = 3;
    return x + z;
  }

x ->
  {int z = 3;
  return x + z;}

x ->
{
int z = 3;
return x + z;
}

The CodeBlock before the body will be modified directly on the Lambda object, the above example is a simplification to present the logic. What do you think of this approach? It will definitely save us the issue of having way to many LambdaModes to account for different formats.

@Randgalt
Copy link
Owner

I like the idea of using CodeBlocks. Going even further, we can treat the lambda spec builder as similar to a MethodSpec as MethodSpec handles CodeBlocks already. The bracing involved is handled by codeWriter.emit(" {\n"); we should do the same for lambdas

@HliasMpGH
Copy link
Contributor Author

HliasMpGH commented Aug 2, 2024

Excuse my delay, i was caught up with some other work for the past two weeks. I will look into pushing some changes as soon as i can. Thank you for your patience!

The models of lambdas are fully configurable through the builder class.
The different modes / attributes of a lambda are handled through the
LambdaMode enum, and the user can configure them with the addModes()
method. Lambdas can be added in CodeBlocks, MethodSpecs and initialized
in FieldSpecs.
@HliasMpGH HliasMpGH force-pushed the lambdaspec-addition branch from 0e60e1b to 727a2ac Compare August 9, 2024 08:16
Users can now add characters such as new lines, indents and spaces before the arrow, body and after the last curly bracket.
@HliasMpGH
Copy link
Contributor Author

What do you think of the latest updates? Its a starting point for the logic we have been discussing about the lambdas and CodeBlocks. Let me know about your thoughts whenever possible.

@Randgalt
Copy link
Owner

What do you think of the latest updates? Its a starting point for the logic we have been discussing about the lambdas and CodeBlocks. Let me know about your thoughts whenever possible.

I apologize. I started this project because I couldn't get responses from JavaPoet devs and here I am unable to respond on this. I've been slammed with work but will try to get to it soon. I like what I've seen so far I just need a block of time to look further. If you could find someone else knowledgable to review it that would help a lot.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants