SkillAgentSearch skills...

LambdaOmega

A simple wrapper API to make usage of Java collections, lambdas and CompletableFuture more simple, concise and enjoyable.

Install / Use

/learn @codebulb/LambdaOmega
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

LambdaOmega

A simple wrapper API to make usage of Java collections, lambdas and CompletableFuture more simple, concise and enjoyable.

Build Status codecov.io

What’s in the box?

LambdaOmega consists of only a few classes. For brevity reasons, most of their names consist of a single letter.

  • L (“List”) is a wrapper for List.
  • S (“Set”) is a wrapper for Set.
  • M (“Map”) is a wrapper for Map.
  • R (“Range”) is a wrapper for an IntStream range.
  • V2 (“Vector 2D”) represents a 2D vector (= a 2-ary tuple). It can be converted into a Map.Entry.
  • F (“Function”) is a wrapper for functional interfaces (lambda expressions) which also provides helper methods to convert functions.
  • C (“Collection”) is the base class for L, S and M and provides additional helper methods to convert collections.
  • U (“Utils”) provides additional miscellaneous helper methods.
  • Promise is a wrapper and a drop-in replacement for CompletableFuture, providing several simplifications and fixes for the API. It can be used independently of all the other classes. It’s discussed in a separate section.

Why you should use it

  • Although Java’s Collections / lambdas are everywhere, you feel like their API is really cumbersome.
  • You feel even more so if you know and love Groovy or JavaScript / lodash.
  • It perfectly fits unit tests where fluid, maintainable code is key.

Other benefits:

  • Small footprint (JAR < 130KB), no other dependencies.
  • Thoroughly tested (coverage >= 90%).
  • Human-readable documentation (here and in the API docs).
  • Free & Open source (New BSD license).

How to use it

Use JitPack to add its dependency to your Maven project:

<dependency>
    <groupId>com.github.codebulb</groupId>
    <artifactId>LambdaOmega</artifactId>
    <version>0.3</version>
</dependency>
...
<repository>
    <id>jitpack.io</id>
    <url>https://jitpack.io</url>
</repository>

Replace the version by the tag / commit hash of your choice or -SNAPSHOT to get the newest SNAPSHOT.

Not using Maven? You can download the JAR directly from JitPack’s servers.

Visit JitPack’s docs for more information.

Getting started with Collections

The heart of LambdaOmega are the wrapper classes L, S and M which wrap around vanilla Java Collection List, Set or Map, respectively (decorator pattern) to provide a more concise and more enjoyable API to the underlying collection. You can wrap an L around everything which can be turned into a List: a List, a Stream, varargs:

import static ch.codebulb.lambdaomega.L.*;

L<Integer> myL = l(0, 1, 2);

The wrapper function invokes the L constructor and returns the L. You can’t invoke that constructor explicitly, always use the convenience wrapper function instead.

It’s best practice to statically import the l() method so you can write just l() instead of L.l(). The same goes for the other collections, e.g. S.s():

S<Integer> myS = s(0, 1, 2);

In the most simple case, you just want to return the underlying Collection:

List<Integer> mylist = l(0, 1, 2).l;

There’s a shorthand method for this simple case:

List<Integer> list = list(0, 1, 2);

Note that l(Collection) returns a one-element L with the collection provided being the one element. To create an L containing all the elements of the collection, use

L<Integer> flatList = L(list(0, 1, 2));

Some wrapper methods return another wrapper (e.g. of type L) or this; these are “intermediate” functions. You can chain another function call after them (builder pattern). Other wrapper methods return the underlying Java collection (e.g. of type List) or any other type; these are “terminal” functions. The wrapper API adheres to a simple method naming schema:

  • methods with names starting with an UpperCase letter are intermediate functions
  • methods with names consisting of a single letter are intermediate functions as well. These are typically shorthand aliases for another intermediate function.
  • methods with names starting with a lowerCase letter are terminal functions

There are no exceptions to these rules. (Although they don’t apply to the special Promise class.)

With these rules in mind, we can start working with a L, e.g. add a new element:

List<Integer> outcome = l(0, 1, 2).add(3);

This is a terminal function. Using the equivalent intermediate function, we can chain method calls:

L<Integer> modifiedList = l(0, 1, 2).Add(3).Add(4, 5);

There’s a shorthand for the Add() method:

L<Integer> modifiedList2 = l(0, 1, 2).a(3).a(4, 5);

When constructing a Map, you make intense use of method chaining:

Map<String, Integer> map = m("a", 0).Insert("b", 1).i("c", 2).m;

(we cover Map’s insert() method in a bit.)

There’s a two-arg constructor for empty Maps of an explicit key / value type. If the “key” class is omitted, it’s assumed to be String:

M<String, Integer> emptyM = m(Integer.class);

Collection methods

None of the LambdaOmega collections implements Java’s Collection or Map interface, for two reasons:

  • We don’t want to inherit this bloated, flawed API.
  • It encourages us to use LambdaOmega collections locally only and unwrap them into Java collections for external method calls.

However, L, S and M implement a set of methods closely inspired by Collection and Map; most methods are actually the same, others are augmented to e.g. accept varargs or to provide a more reasonable return type.

Some of these methods exist both as intermediate and terminal function. For the most common methods, a shorthand one-letter variant exists.

We’ve already met the add / Add / a method on L and S. There’s also addAll / AddAll / A; all of these also works with varargs:

List<Integer> zeroToSix = l(0, 1, 2).A(list(3, 4)).addAll(l(5, 6));

For M, there’s an add method variant named insert / Insert / i / insertAll / InsertAll / I which will invoke put(), but only after a check preventing you from inserting the same key twice; otherwise, an IndexAlreadyPresentException is thrown and the map is not modified. This method comes in handy e.g. in unit tests when you explicitly build a map and you want to make sure that you don’t accidentally insert the same key twice. Note that this check costs significantly more performance than just performing put().

m("a", 0).i("b", 1).I(m("c", 2).i("d", 3), m("c", 9)); // exception because of "c"!

For L or M, you can set a single element with set / Set / s / put / Put / p or set all elements included in a Map data structure with setAll / SetAll / S / putAll / PutAll / P.

m("a", 0).p("b", 1).p("c", 2).P(m("d", 3).i("e", 4));

For L or M, you can get a single element or multiple elements with get / Get / g:

String a = l("a", "b", "c").g(0);
L<String> aAndC = l("a", "b", "c").g(0, 2);

For L or S, you can remove a single element or a collection with remove / Remove / r / removeAll / RemoveAll / R:

L<String> aAndB = l("a", "b", "c", "d", "e").r("c").R(list("d", "e"));

For M, you can remove a single element or a collection with deleteKey / DeleteKey / d / deleteAllKeys / DeleteAllKeys / D or deleteValue / DeleteValue / deleteAllValues / DeleteAllValues (it’s called “delete” instead of “remove” to avoid naming clashes for the one-letter abbreviation):

M<String, Integer> bAndC = m("a", 0).i("b", 1).i("c", 2).d("a");

Also, you can convert an L / S / M into almost any collection with a corresponding to…(…) method:

Set<Integer> set = l(0, 1, 2).toSet();

These conversion methods internally use the C.to…(…) static helper methods. You can call them directly to convert collection without the need to create intermediate L / S / M instances.

There are a lot of additional methods for L, S and M. For more information, visit the API docs.

A List is a Map and a Map is a List

Now comes the fun part. Because the LambdaOmega API lives independently of vanilla Java Collection / Map API, it features its own API which is more simple, consistent and powerful at the same time, whilst keeping it as close to the original Java APIs as possible.

Most importantly, a List is also a Map from int to T, and a Map is also a List of entries. More precisely, both L and M implement the SequentialI (Collection-like access) interface as well as the IndexedI (Map-like access) interface. Note that similar to its plain Java Set counterpart, S only implements the SequentialI interface. Thus, you can call L methods such as remove(M.E) on a M:

m("a", 0).i("b", 1).i("c", 2).remove(e("b", 1));

and M methods such as insert(Integer, T) on a L:

L<String> abcd = l("a", "b").I(m(2, "c").i(3, "d"));

Finally, indexed access using get() is actually provided by the I interface which is implemented by L, M, and F, thus you can use “one interface to rule them all”:

I<Integer, String> indexed = m(0, "a").i(1, "b");
String b = indexed.g(1);
indexed = l("a", "b");
b = indexed.g(1);
indexed = f((Integer it) -> it == 0 ? "a" : "b");
b = indexed.g(1);

Collection methods with Lambdas

One of the main reasons to use LambdaOmega are the simplifications to use collections with lambda expressions in functional programming. When compared with vanilla Java lambda use, the API is much more simple and concise. Many of these modifications are inspired by the equivalent Groovy semantics.

Most importantly, functions are invoked on the coll

Related Skills

View on GitHub
GitHub Stars7
CategoryDevelopment
Updated1y ago
Forks3

Languages

Java

Security Score

70/100

Audited on Sep 25, 2024

No findings