Luwak is a project to make it easier to write functional code in the Java Language. It provides aliases to write the code a lot shorter and extends the language with extra objects. Unlike libraries like Functional Java and Vavr, this project does not have the intention to replace native objects like the Java Stream.
With Luwak you could write this:
Ø<String> getFirstMatch(List<Ø<String>> list, ℙ<String> p) {
return Ø.of(list.stream()
.flatMap(Ø::stream)
.filter(p)
.findFirst());
}
Aliases
Java added a lot of new classes with version 8 to grant some possibility of writing code in a functional way. To work with them without writing a lot of code, this library provides aliases of these classes. The aliases are created by subclassing the original interface / class.
Supplier
Use the $
symbol:
Supplier<String> old = () -> "x";
$<String> now = () -> "x";
Function
Use the ƒ
symbol:
Function<String, String> old = String::toUpperCase;
ƒ<String, String> now = String::toUpperCase;
Consumer
Use the ₵
symbol:
Consumer<String> old = x -> {};
₵<String> now = x -> {};
Predicate
Use the ℙ
symbol:
Predicate<String> old = s -> s.contains("x");
ℙ<String> now = s -> s.contains("x");
Runnable
Use the ℝ
symbol:
Runnable old = () -> {};
ℝ now = () -> {};
Optional
Use the Ø
symbol:
Optional<String> old = Optional.of("x");
Ø<String> now = Ø.of("x");
The Ø
is implemented as wrapper class of Optional due the Optional class being defined as final. This makes the use of this alias rather clumsy, thus this alias is defined as experimental.
lambdas
When you write lambdas, you have to take some quirks into account. Luwak provide some options to workaround some problems.
Checked functions
The Java compiler does not let you write lambdas with checked exceptions. Therefore the library provides checked functions to bypass this problem. All above functional interfaces do have a checked variant; to keep it short and consise, the same symbols are used with a _
prefix. With other words, the CheckedFunction does look like _ƒ
. All the default functional interfaces do have a __
function to wrap a checked variant.
Example:
List.of("items?price=gte:10&price=lte:100").stream()
.map(ƒ.__(s -> URLEncoder.encode(s, "UTF-8"))) // or `__(...)` with a static import
.collect(toList());
Recursion
Java makes it impossible to use lambdas with a reference to itself. With a small helper¹ it is possible to go around this though:
ƒ<Integer, Integer> fact = Recursable.recurse((i, f) -> 0 == i ? 1 : i * f.apply(i - 1, f));
Recursion can lead to a StackOverflowException when the input is big. With build in tail-call optimization² it is safe to do big computations:
ƒ<BigDecimal, BigDecimal> fact = Recursable.tailRecurse((i, acc) ->
0 == acc.intValue() ? ret(ONE) :
1 == acc.intValue() ? ret(i) :
next(() -> i.multiply(acc.subtract(ONE)), () -> acc.subtract(ONE)));
Extension
To create even less verbose code, the following objects provide extra functionality.
Result
The result object can have two states, either successful or failure. Use the Œ
symbol:
ƒ<String, HttpRequest> get = url -> HttpRequest.newBuilder().uri(URI.create(url)).GET().build();
HttpClient client = HttpClient.newHttpClient();
Œ<HttpResponse<String>> result = doTry(() -> client.send(get.apply("http://openjdk.java.net/"), ofString()));
Utility
Some actions like mapping, filtering and finding an element in a List are so common, it is a pity you need a lot of streaming code to write it down. Luwak provides a Do
class to shorten these actions:
List<String> lo
rCaseString = List.of("Example 1", "Example 2").stream().map(String::toLowerCase).collect(Collectors.toList());
List<String> lowerCaseString = Do.map(List.of("Example 1", "Example 2"), String::toLowerCase);
List<String> onlyFirstExample = List.of("Example 1", "Example 2").stream().filter(s -> s.equals("Example 1")).collect(Collectors.toList());
List<String> onlyFirstExample = Do.filter(List.of("Example 1", "Example 2"), s -> s.equals("Example 1"));
Optional<String> possibleFirstExample = List.of("Example 1", "Example 2").stream().filter(s -> s.equals("Example 1")).findAny();
Ø<String> possibleFirstExample = Do.findAny(List.of("Example 1", "Example 2"), s -> s.equals("Example 1"));
boolean matched = List.of("Example 1", "Example 2").stream().anyMatch(s -> s.equals("Example 1"));
boolean matched = anyMatch(List.of("Example 1", "Example 2"), s -> s.equals("Example 1")); // by importing `Do` statically
By using Project Lombok’s extension methods, the utility methods could written like native syntax:
@ExtensionMethod({ Do.class })
public class Example {
public static void main(String[] args) {
List.of("Example 1", "Example 2").filter(s -> s.equals("Example 1"));
}
}
Why Luwak?
Kopi luwak is a coffee that consists of partially digested coffee cherries, which have been eaten and defecated by the Asian palm civet. In Indonesia this animal is called luwak. The process of creation can be written elegantly functional:
ƒ<CoffeeCherry, CoffeeCherry> eat = Luwak::eat;
ƒ<CoffeeCherry, Feces> digest = Intestines::digest;
ƒ<Feces, Feces> defecate = Body::leave;
ƒ<Feces, Core> cleanup = f -> CoffeeTeam.search(f).clean();
ƒ<Core, Bean> roast = CoffeeRoasterMachine:go;
ƒ<CoffeeCherry, Bean> processCoffee = eat.andThen(digest).andThen(defecate).andThen(cleanup).andThen(roast).apply(new CoffeeCherry());
As the Java programming language is called after the Java coffee, everything comes beautifully together.
Credits
The used favicon is a free vector image by VectorStock. You can find it here.
¹ The helper is posted by Ian Robertson at Stack Overflow. Stack Overflow uses the Creative Commons Attribution-ShareAlike licence, click here if you want to know more.
² Examples of runtime tail-call optimization for Java can be found in Functional Programming in Java by Venkat Subramaniam and Functional Programming in Java by Pierre-Yves Saumont.