I created a custom annotation in order to apply currying to a method or constructor.
You can find the complete code for this here: curry.
For more information on currying check this post and for a background on annotations in Java check this one.
First we create the annotation:
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
public @interface Curry {}
We only need the annotation to be processed and then discarded (we don’t need to have it available at run time or recorded on the .class) so we use @Retention(RetentionPolicy.SOURCE)
.
This annotation will only be used on methods and constructors so we set those as the Target on @Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
.
Then we create a processor for the annotation:
@SupportedAnnotationTypes("dev.jsedano.curry.annotation.Curry")
@SupportedSourceVersion(SourceVersion.RELEASE_17)
@AutoService(Processor.class)
public class CurryProcessor extends AbstractProcessor {
Using @SupportedAnnotationTypes("dev.jsedano.curry.annotation.Curry")
we say that this processor will only look for that particular annotation.
@SupportedSourceVersion(SourceVersion.RELEASE_17)
here we are saying the Java version supported.
The last one is pretty interesting, @AutoService(Processor.class)
is from a Google library called auto-service. In order for the Java compiler to use a processor the class needs to be declared inside the jar on the META-INF/services
directory on the javax.annotation.processing.Processor
file, the auto-service library does that for you.
Then we need to implement the process
method on out custom processor.
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
Inside our class we get the processingEnv
object that provides us with functionality such as getMessager()
that we use to print a warning message on compile time when the @Curry annotation is used on a method with 1 or more than 10 parameters:
otherMethods.stream()
.forEach(
e ->
processingEnv
.getMessager()
.printMessage(
Diagnostic.Kind.MANDATORY_WARNING,
"incorrect number of parameters, allowed only between 2 and 1O, will not generate code for this one",
e));
We also have this one getFiler()
which allows us to create Java source files:
JavaFileObject builderFile = processingEnv.getFiler().createSourceFile(builderClassName);
On the tests module of the project we declare some methods with the @Curry annotation, for example this constructor:
@Curry
public AnnotatedClass(
boolean aBoolean, List<String> aStringList, int aNumber, char aChar, float aFloat) {
this.aBoolean = aBoolean;
this.aStringList = aStringList;
this.aNumber = aNumber;
this.aChar = aChar;
this.aFloat = aFloat;
}
After running mvn clean verify
on the parent module we can see the autogenerated code under target/generated-sources/annotations/dev.jsedano.curry.tests
:
public static java.util.function.Function<java.lang.Boolean,java.util.function.Function<java.util.List<java.lang.String>,java.util.function.Function<java.lang.Integer,java.util.function.Function<java.lang.Character,java.util.function.Function<java.lang.Float,dev.jsedano.curry.tests.AnnotatedClass>>>>> pentaConstructor(dev.jsedano.curry.util.function.PentaFunction<java.lang.Boolean,java.util.List<java.lang.String>,java.lang.Integer,java.lang.Character,java.lang.Float,dev.jsedano.curry.tests.AnnotatedClass> function) {
return v0->v1->v2->v3->v4-> function.apply(v0,v1,v2,v3,v4);
}
It is not pretty looking, but we can use it to then curry the five parameter constructor of the example class:
var pentaConstructor = AnnotatedClassCurryer.pentaConstructor(AnnotatedClass::new);
You can see another example here, but if you want to compile it you need to do mvn clean install
on the curryer module of curry.
@Curry
public static String wget(
int connectionTimeout,
int readTimeout,
boolean followRedirects,
String requestMethod,
String address) {
try {
URL url = new URL(address);
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod(requestMethod);
con.setConnectTimeout(connectionTimeout);
con.setReadTimeout(readTimeout);
con.setInstanceFollowRedirects(followRedirects);
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer content = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
content.append(inputLine);
}
in.close();
return address + " " + content.toString();
} catch (Exception e) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw);
String stackTrace = sw.toString();
return address + " " + stackTrace.substring(0, stackTrace.indexOf("\n"));
}
}
Then we can set the values we need in a curried way and use it:
public static void main(String[] args) {
var get =
WgetVersion2Curryer.wget(WgetVersion2::wget)
.apply(100)
.apply(100)
.apply(false)
.apply("GET");
List.of(
"https://www.google.com",
"https://www.wikipedia.org",
"asdf",
"https://docs.oracle.com/javase/10/docs/api/java/net/package-summary.html",
"https://jsedano.dev",
"https://raw.githubusercontent.com/center-key/clabe-validator/main/clabe.ts")
.parallelStream()
.map(get)
.forEach(System.out::println);
}
Download the complete code from this post here: curry.
Top comments (0)