Skip to content
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

Fix up javaslang FlavourImpl, modify some derivators to generate slightly more idiomatic code #29

Merged
merged 1 commit into from
Feb 18, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Fix up javaslang FlavourImpl, modify some derivators to generate slig…
…htly more idiomatic code
  • Loading branch information
rdegnan committed Feb 13, 2016
commit 2eb44fa9cf071cfdea8c621847094ede02e94280
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
Expand Down Expand Up @@ -85,39 +86,41 @@ private DeriveResult<DerivedCodeSpec> functionDispatchImpl(List<DataConstructor>
TypeElement f = FlavourImpl.findF(context.flavour(), utils.elements());
TypeName returnType = TypeName.get(utils.types().getDeclaredType(f,
adt.typeConstructor().declaredType(), adt.matchMethod().returnTypeVariable()));
ExecutableElement abstractMethod = Utils.getAbstractMethods(f.getEnclosedElements()).get(0);

TypeSpec wrapper = TypeSpec.anonymousClassBuilder("")
.addField(FieldSpec.builder(returnType, nameAllocator.get("cata"))
.initializer(CodeBlock.builder().addStatement("$L -> $L.$L($L)",
nameAllocator.get("adt var"), nameAllocator.get("adt var"),
.addSuperinterface(returnType)
.addMethod(MethodSpec.methodBuilder(abstractMethod.getSimpleName().toString())
.addAnnotation(Override.class)
.addModifiers(abstractMethod.getModifiers().stream().filter(m -> m != Modifier.ABSTRACT).collect(Collectors.toList()))
.returns(TypeName.get(adt.matchMethod().returnTypeVariable()))
.addParameter(TypeName.get(adt.typeConstructor().declaredType()), nameAllocator.get("adt var"))
.addStatement("return $L.$L($L)",
nameAllocator.get("adt var"),
adt.matchMethod().element().getSimpleName(),
Utils.joinStringsAsArguments(constructors.stream().map(
constructor ->
constructor.arguments().stream()
.map(DataArguments::getType)
.noneMatch(tm -> utils.types().isSameType(tm, adt.typeConstructor().declaredType()))
Utils.joinStringsAsArguments(constructors.stream().map(constructor ->
constructor.arguments().stream()
.map(DataArguments::getType)
.noneMatch(tm -> utils.types().isSameType(tm, adt.typeConstructor().declaredType()))
? "\n" + constructor.name()
: CodeBlock.builder().add("\n($L) -> $L.$L($L)", Utils.joinStringsAsArguments(Stream.concat(
constructor.arguments().stream().map(DataArgument::fieldName)
.map(fn -> nameAllocator.clone().newName(fn, fn + " field")),
constructor.typeRestrictions().stream().map(TypeRestriction::idFunction).map(DataArgument::fieldName)
.map(fn -> nameAllocator.clone().newName(fn, fn + " field")))),
constructor.name(),
MapperDerivator.mapperApplyMethod(utils, context, constructor),
Utils.joinStringsAsArguments(Stream.concat(
constructor.arguments().stream().map(
argument ->
utils.types().isSameType(argument.type(),
adt.typeConstructor().declaredType())
? "() -> this." + nameAllocator.get("cata")
+ "." + FlavourImpl.functionApplyMethod(utils, context)
+ "(" + nameAllocator.clone().newName(argument.fieldName(), argument.fieldName() + " field") + ")"
: argument.fieldName()),
constructor.typeRestrictions().stream()
.map(TypeRestriction::idFunction)
.map(DataArgument::fieldName)))).build().toString())

)).build()).build()).build();
constructor.arguments().stream().map(DataArgument::fieldName)
.map(fn -> nameAllocator.clone().newName(fn, fn + " field")),
constructor.typeRestrictions().stream().map(TypeRestriction::idFunction).map(DataArgument::fieldName)
.map(fn -> nameAllocator.clone().newName(fn, fn + " field")))),
constructor.name(),
MapperDerivator.mapperApplyMethod(utils, context, constructor),
Utils.joinStringsAsArguments(Stream.concat(
constructor.arguments().stream().map(argument ->
utils.types().isSameType(argument.type(),
adt.typeConstructor().declaredType())
? "() -> " + FlavourImpl.functionApplyMethod(utils, context)
+ "(" + nameAllocator.clone().newName(argument.fieldName(), argument.fieldName() + " field") + ")"
: argument.fieldName()),
constructor.typeRestrictions().stream()
.map(TypeRestriction::idFunction)
.map(DataArgument::fieldName)))).build().toString())
)).build()).build();

MethodSpec cataMethod = MethodSpec.methodBuilder("cata")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
Expand All @@ -127,7 +130,7 @@ private DeriveResult<DerivedCodeSpec> functionDispatchImpl(List<DataConstructor>
.addParameters(constructors.stream().map(dc
-> ParameterSpec.builder(cataMapperTypeName(dc), MapperDerivator.mapperFieldName(dc)).build())
.collect(toList()))
.addStatement("return $L.$L", wrapper, nameAllocator.get("cata"))
.addStatement("return $L", wrapper)
.build();

return result(methodSpec(cataMethod));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public static TypeElement findF0(Flavour flavour, Elements elements) {
.Fj(() -> "fj.F0")
.Fugue(() -> Supplier.class.getName())
.Fugue2(() -> "com.google.common.base.Supplier")
.Javaslang(() -> Supplier.class.getName())
.Javaslang(() -> "javaslang.Function0")
.apply(flavour)
);
}
Expand All @@ -54,7 +54,7 @@ public static TypeElement findF(Flavour flavour, Elements elements) {
.Fj(() -> "fj.F")
.Fugue(() -> Function.class.getName())
.Fugue2(() -> "com.google.common.base.Function")
.Javaslang(() -> Function.class.getName())
.Javaslang(() -> "javaslang.Function1")
.apply(flavour)
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ public static DeriveResult<DerivedCodeSpec> derive(AlgebraicDataType adt, Derive
TypeSpec.Builder typeSpecBuilder = TypeSpec.classBuilder(className)
.addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
.addTypeVariables(typeVariableNames)
.addField(FieldSpec.builder(TypeName.get(Object.class), "lock", Modifier.PRIVATE, Modifier.FINAL).initializer("new Object()").build())
.addField(FieldSpec.builder(lazyArgTypeName, "expression", Modifier.PRIVATE).build())
.addField(FieldSpec.builder(typeName, "evaluation", Modifier.PRIVATE, Modifier.VOLATILE).build())
.addField(FieldSpec.builder(TypeName.BOOLEAN, "initialized", Modifier.PRIVATE, Modifier.VOLATILE).build())
.addField(FieldSpec.builder(lazyArgTypeName, "expression", Modifier.PRIVATE, Modifier.FINAL).build())
.addField(FieldSpec.builder(typeName, "evaluation", Modifier.PRIVATE).build())
.addMethod(MethodSpec.constructorBuilder()
.addParameter(ParameterSpec.builder(lazyArgTypeName, lazyArgName).build())
.addStatement("this.expression = $N", lazyArgName).build())
Expand All @@ -73,20 +73,20 @@ public static DeriveResult<DerivedCodeSpec> derive(AlgebraicDataType adt, Derive
.addModifiers(Modifier.PRIVATE)
.returns(typeName)
.addCode(CodeBlock.builder()
.addStatement("$T _evaluation = this.evaluation", typeName)
.beginControlFlow("if (_evaluation == null)")
.beginControlFlow("synchronized (this.lock)")
.addStatement("_evaluation = this.evaluation")
.beginControlFlow("if (_evaluation == null)")
.addStatement("this.evaluation = _evaluation = expression.$L()", Utils.getAbstractMethods(lazyTypeElement.getEnclosedElements()).get(0).getSimpleName())
.addStatement("this.expression = null")
.endControlFlow().endControlFlow().endControlFlow()
.beginControlFlow("if (!initialized)")
.beginControlFlow("synchronized (this)")
.beginControlFlow("if (!initialized)")
Copy link
Member

Choose a reason for hiding this comment

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

I did not synchronizing on this to avoid potential synchronization issues as the lazy value could potentially be used as a monitor. But then this would probably be bad practice. As a reference, I think Guava Suppliers.memoize does synchronize on this. I need to benchmark if avoiding the creation of the lock Object result in significative performance improvement.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point, I was basing this on Suppliers.memoize, but I did not run any benchmarks — they seem to be trying to avoid making evaluation a volatile but I have no idea if that makes a difference in practice. I will see if I can come up with a benchmark to compare the different implementations.

On Feb 13, 2016, at 10:51 AM, JB Giraudeau [email protected] wrote:

In processor/src/main/java/org/derive4j/processor/derivator/LazyConstructorDerivator.java #29 (comment):

@@ -73,20 +73,20 @@
.addModifiers(Modifier.PRIVATE)
.returns(typeName)
.addCode(CodeBlock.builder()

  •                .addStatement("$T _evaluation = this.evaluation", typeName)
    
  •                .beginControlFlow("if (_evaluation == null)")
    
  •                .beginControlFlow("synchronized (this.lock)")
    
    I did not synchronizing on this to avoid potential synchronization issues as the lazy value could potentially be used as a monitor. But then this would probably be bad practice. As a reference, I think Guava Suppliers.memoize does synchronize on this. I need to benchmark if avoiding the creation of the lock Object result in significative performance improvement.


Reply to this email directly or view it on GitHub https://github.com/derive4j/derive4j/pull/29/files#r52831894.

Copy link
Member

Choose a reason for hiding this comment

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

Suppliers.memoize also implements Serializable (without serializing the cached value) which limit the possible implementation much more than if we don't implement Serializable. I think using an initialized field is therefore not necessary if we don't implement Serializable. Benchmarking synchronization on this is worth it, though.

.addStatement("$T _evaluation = expression.$L()", typeName, Utils.getAbstractMethods(lazyTypeElement.getEnclosedElements()).get(0).getSimpleName())
.addStatement("evaluation = _evaluation")
.addStatement("initialized = true")
.addStatement("return _evaluation")
Copy link
Member

Choose a reason for hiding this comment

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

the expression was set to null also to free-up memory.

.endControlFlow().endControlFlow().endControlFlow()
.addStatement("return evaluation")
.build())
.build())
.addMethod(
Utils.overrideMethodBuilder(adt.matchMethod().element())
.addStatement("return this.eval().$L($L)", adt.matchMethod().element().getSimpleName(), Utils.asArgumentsStringOld(adt.matchMethod().element().getParameters()))
.addStatement("return eval().$L($L)", adt.matchMethod().element().getSimpleName(), Utils.asArgumentsStringOld(adt.matchMethod().element().getParameters()))
.build());

if (adt.typeConstructor().declaredType().asElement().getKind() == ElementKind.INTERFACE) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import com.squareup.javapoet.TypeVariableName;
import com.squareup.javapoet.WildcardTypeName;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -65,27 +66,27 @@ public static DeriveResult<DerivedCodeSpec> derive(AlgebraicDataType adt, Derive
TypeName firstMatchBuilderTypeName = Utils.typeName(totalMatchBuilderClassName,
adt.typeConstructor().typeVariables().stream().map(TypeName::get));

TypeName firstMatchBuilderObjectifiedTypeName = Utils.typeName(totalMatchBuilderClassName,
adt.typeConstructor().typeVariables().stream().map(__ -> ClassName.get(Object.class)));
TypeName firstMatchBuilderWildcardTypeName = Utils.typeName(totalMatchBuilderClassName,
adt.typeConstructor().typeVariables().stream().map(__ -> WildcardTypeName.subtypeOf(Object.class)));

String initialCasesStepFieldName = Utils.uncapitalize(TotalMatchingStepDerivator.totalMatchBuilderClassName(firstConstructor));

FieldSpec.Builder initialCasesStepField = FieldSpec.builder(firstMatchBuilderObjectifiedTypeName, initialCasesStepFieldName)
.addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
.initializer("new $T()",
firstMatchBuilderObjectifiedTypeName);
FieldSpec.Builder initialCasesStepField = FieldSpec.builder(firstMatchBuilderWildcardTypeName, initialCasesStepFieldName)
.addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL);

MethodSpec.Builder matchFactory = MethodSpec.methodBuilder("cases")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.addTypeVariables(adt.typeConstructor().typeVariables().stream().map(TypeVariableName::get).collect(Collectors.toList()))
.returns(firstMatchBuilderTypeName);

if (adt.typeConstructor().typeVariables().isEmpty()) {
initialCasesStepField
.initializer("new $L()", totalMatchBuilderClassName.simpleName());

matchFactory.addStatement("return $L", initialCasesStepFieldName);
} else {
initialCasesStepField
.addAnnotation(AnnotationSpec.builder(SuppressWarnings.class).addMember("value", "$S", "rawtypes").build());
.initializer("new $L<>()", totalMatchBuilderClassName.simpleName());

matchFactory.addAnnotation(AnnotationSpec.builder(SuppressWarnings.class).addMember("value", "$S", "unchecked").build())
.addStatement("return ($T) $L", firstMatchBuilderTypeName, initialCasesStepFieldName);
Expand Down