
Security News
The Changelog Podcast: Practical Steps to Stay Safe on npm
Learn the essential steps every developer should take to stay secure on npm and reduce exposure to supply chain attacks.
com.github.tonivade:purefun
Advanced tools
This module was developed as the core of the zeromock project. It defines all the basic classes and interfaces used in the rest of the project.
Initially the module only holds a few basic interfaces and it has grown to become an entire functional programming library (well, a humble one), and now is an independent library.
Working in this library helps me to learn and understand some important concepts of functional programming.
Finally, I have to say thanks to vavr library author, this library is largely inspired in his work, and also to Scala standard library authors. Their awesome work help me a lot.
This project is not ready to be used in production, I use it to learn functional programming concepts by my self, but, if you want to you it, use it at your own risk. Anyway if you think is useful for you, go ahead, also any feedback and PR are wellcome.
In this project I have implemented some patterns of functional programming that need Higher Kinded Types. In Java there are not such thing, but it can be simulated using a especial codification of types.
In Scala we can define a higher kinded typed just like this Monad[F[_]] but in Java it can be codified
like this Monad<F extends Kind>. Kind is a simple mark interface. Then we can define a type using a special
codification like this:
interface SomeType<T> extends Higher1<SomeType.µ, T> {
interface µ extends Kind {}
// this is a safe cast
static SomeType<T> narrowK(Higher1<SomeType.µ, T> hkt) {
return (SomeType<T>) hkt;
}
}
It can be triky but, but in the end is easy to work with. By the way, I tried to hide this details to the user of the library. Except with typeclasses because is the only way to implement them in Java.
So, there are interfaces to encode kinds of 1, 2 and 3 types. It can be defined types for 4, 5 or more types, but it wasn't necessary to implement the library.
It represent the arrow between two categories, in other words, it encapsulates a data type that can be mapped to other data type.
interface Mappable<W extends Kind, T> extends Higher1<W, T> {
<R> Mappable<W, R> map(Function1<T, R> map);
}
These interfaces define the method flatMap for the kinds 1, 2 and 3, so it can be called as Monad also, so as not to create
confusion I named FlatMap.
interface FlatMap1<W extends Kind, T> extends Higher1<W, T>, Mappable<W, T> {
<R> FlatMap1<W, R> flatMap(Function1<T, ? extends Higher1<W, R>> map);
}
It represent a type that can hold any value. It defines these methods: get and flatten.
It represent a type that can be filtered. It defines one method: filter.
All these data types implement FlatMap and Mappable base interface and implement these methods: get, map, flatMap,
filter, fold and flatten.
Is an alternative to Optional of Java standard library. It can contains two values, a some or a none
Option<String> some = Option.some("Hello world");
Option<String> none = Option.none();
Is an implementation of scala Try in Java. It can contains two values, a success or a failure.
Try<String> success = Try.success("Hello world");
Try<String> failure = Try.failure(new RuntimeException("Error"));
Is an implementation of scala Either in Java.
Either<Integer, String> right = Either.right("Hello world");
Either<Integer, String> left = Either.left(100);
This type represents two different states, valid or invalid, an also it allows to combine several
validations using map2 to map5 methods.
Validation<String, String> name = Validation.valid("John Smith");
Validation<String, String> email = Validation.valid("john.smith@example.net");
// Person has a constructor with two String parameters, name and email.
Valdation<Sequence<String>, Person> person = Validation.map2(name, email, Person::new);
These classes allow to hold some values together, as tuples. There are tuples from 1 to 5.
Tuple1<String> tuple1 = Tuple.of("Hello world");
Tuple2<String, Integer> tuple2 = Tuple.of("John Smith", 100);
Java doesn't define immutable collections, so I have implemented some of them.
Is the equivalent to java Collection interface. It defines all the common methods. Also implements Monad interface.
It represents a linked list. It has a head and a tail.
It represents a set of elements. This elements cannot be duplicated.
It represents an array. You can access to the elements by its position in the array.
This class represents a hash map.
This class represents a binary tree.
This class represents a binary tree map.
Also I have implemented some Monads that allows to combine some operations.
Is the traditional State Modad from FP languages, like Haskel or Scala. It allows to combine operations over a state. The state should be a immutable class. It recives an state and generates a tuple with the new state and an intermediate result.
State<ImmutableList<String>, Option<String>> read = State.state(list -> Tuple.of(list.tail(), list.head()));
Tuple<ImmutableList<String>, Option<String>> result = read.run(ImmutableList.of("a", "b", "c"));
assertEquals(Tuple.of(ImmutableList.of("b", "c"), Option.some("a")), result);
This is an implementation of Reader Monad. It allows to combine operations over a common input. It can be used to inject dependencies.
Reader<ImmutableList<String>, String> read2 = Reader.reader(list -> list.tail().head().orElse(""));
String result = read2.eval(ImmutableList.of("a", "b", "c"));
assertEqual("b", result);
It allow to combine operations over a common output.
Writer<ImmutableList<String>, Integer> writer = Writer.<String, Integer>listPure(5)
.flatMap(value -> listWriter("add 5", value + 5))
.flatMap(value -> listWriter("plus 2", value * 2));
assertAll(() -> assertEquals(Integer.valueOf(20), writer.getValue()),
() -> assertEquals(listOf("add 5", "plus 2"), writer.getLog()));
This is a experimental implementation of IO Monad in java. Inspired in this work.
IO<Nothing> echo = Console.print("write your name")
.andThen(Console.read())
.flatMap(name -> Console.print("Hello " + name))
.andThen(Console.print("end"));
echo.unsafeRunSync();
This is an experimental implementation of Future. Computations are executed in another thread inmediatelly.
Future<String> future = Future.success("Hello world!");
Future<String> result = future.flatMap(string -> Future.run(string::toUpperCase));
assertEquals(Try.success("HELLO WORLD!"), result.await());
Implements recursion using an iteration and is stack safe.
private Trampoline<Integer> fibLoop(Integer n) {
if (n < 2) {
return Trampoline.done(n);
}
return Trampoline.more(() -> fibLoop(n - 1)).flatMap(x -> fibLoop(n - 2).map(y -> x + y));
}
Finally, after hours of hard coding, I managed to implement a Free monad. This is a highly inestable implementation and I have implemented because it can be implemented. Inspired in this work.
Free<IOProgram.µ, Nothing> echo =
IOProgram.write("what's your name?")
.andThen(IOProgram.read())
.flatMap(text -> IOProgram.write("Hello " + text))
.andThen(IOProgram.write("end"));
Higher<IO.µ, Nothing> foldMap = echo.foldMap(new IOMonad(),
new IOProgramFunctor(),
new IOProgramInterperter());
IO.narrowK(foldMap).unsafeRunSync();
Also I implemented the Kleisli composition for functions that returns monadic values like Option, Try or Either.
Kleisli<Try.µ, String, Integer> toInt = Kleisli.lift(Try.monad(), Integer::parseInt);
Kleisli<Try.µ, Integer, Double> half = Kleisli.lift(Try.monad(), i -> i / 2.);
Higher1<Try.µ, Double> result = toInt.compose(half).run("123");
assertEquals(Try.success(61.5), result);
Monad Transformer for Option type
OptionT<IO.µ, String> some = OptionT.some(IO.monad(), "abc");
OptionT<IO.µ, String> map = some.flatMap(value -> OptionT.some(IO.monad(), value.toUpperCase()));
assertEquals("ABC", IO.narrowK(map.get()).unsafeRunSync());
Monad Transformer for Either type
EitherT<IO.µ, Nothing, String> right = EitherT.right(IO.monad(), "abc");
EitherT<IO.µ, Nothing, String> map = right.flatMap(value -> EitherT.right(IO.monad(), value.toUpperCase()));
assertEquals("ABC", IO.narrowK(map.get()).unsafeRunSync());
Monad Transformer for State type
StateT<IO.µ, ImmutableList<String>, Nothing> state =
pure("a").flatMap(append("b")).flatMap(append("c")).flatMap(end());
IO<Tuple2<ImmutableList<String>, Nothing>> result = IO.narrowK(state.run(ImmutableList.empty()));
assertEquals(Tuple.of(listOf("a", "b", "c"), nothing()), result.unsafeRunSync());
Monad Transformer for Writer type
WriterT<Id.µ, Sequence<String>, Integer> writer =
WriterT.<Id.µ, Sequence<String>, Integer>pure(monoid, monad, 5)
.flatMap(value -> lift(monoid, monad, Tuple.of(listOf("add 5"), value + 5)))
.flatMap(value -> lift(monoid, monad, Tuple.of(listOf("plus 2"), value * 2)));
assertAll(() -> assertEquals(Id.of(Integer.valueOf(20)), writer.getValue()),
() -> assertEquals(Id.of(listOf("add 5", "plus 2")), writer.getLog()));
An experimental version of a Stream like scala fs2 project.
StreamOf<IO.µ> streamOfIO = Stream.ofIO();
IO<String> readFile = streamOfIO.eval(IO.of(() -> reader(file)))
.flatMap(reader -> streamOfIO.iterate(() -> Option.of(() -> readLine(reader))))
.takeWhile(Option::isPresent)
.map(Option::get)
.foldLeft("", (a, b) -> a + "\n" + b)
.fix1(IO::narrowK)
.recoverWith(UncheckedIOException.class, cons("--- file not found ---"));
String content = readFile.unsafeRunSync();
Some type classes are implemented
SemigroupK Functor -- Comonad
| / \
MonoidK _ Applicative Traverse -- Foldable
| / | \
Alternative Monad ApplicativeError
| /
MonadError
It represents a binary operation over a type.
T combine(T t1, T t2);
There are instances for lists, strings and integers.
Extends Semigroup adding a zero operation that represent an identity.
T zero();
T combine(T t1, T t2);
There are instances for strings and integers.
It represents a Semigroup but defined for a kind, like a List, so it extends a regular Semigroup.
The same like SemigroupK but for a Monoid.
With higher kinded types simulation, we can represent a Functor in Java.
public interface Functor<F extends Kind> {
<T, R> Higher<F, R> map(Higher<F, T> value, Function1<T, R> map);
}
public interface BiFunctor<F extends Kind> {
<A, B, C, D> Higher2<F, C, D> bimap(Higher2<F, A, B> value, Function1<A, C> leftMap, Function1<B, D> rightMap);
}
Also an Applicative
public interface Applicative<F extends Kind> extends Functor<F> {
<T> Higher<F, T> pure(T value);
<T, R> Higher1<F, R> ap(Higher1<F, T> value, Higher1<F, Function1<T, R>> apply);
@Override
default <T, R> Higher1<F, R> map(Higher1<F, T> value, Function1<T, R> map) {
return ap(value, pure(map));
}
}
public interface ApplicativeError<F extends Kind, E> extends Applicative<F> {
<A> Higher1<F, A> raiseError(E error);
<A> Higher1<F, A> handleErrorWith(Higher1<F, A> value, Function1<E, ? extends Higher1<F, A>> handler);
}
Also a Monad. It is difficult to explain what a monad is, many people have tried and this is my humble attempt.
It is something that allows to combine operations, in a functional way, but simulating the imperative style.
For example, State, Reader, Writer and IO monads are ways to combine operations.
public interface Monad<F extends Kind> extends Applicative<F> {
<T, R> Higher1<F, R> flatMap(Higher1<F, T> value, Function1<T, ? extends Higher1<F, R>> map);
@Override
default <T, R> Higher1<F, R> map(Higher1<F, T> value, Function1<T, R> map) {
return flatMap(value, map.andThen(this::pure));
}
@Override
default <T, R> Higher1<F, R> ap(Higher1<F, T> value, Higher1<F, Function1<T, R>> apply) {
return flatMap(apply, map -> map(value, map));
}
}
public interface MonadError<F extends Kind, E> extends ApplicativeError<F, E>, Monad<F> {
default <A> Higher1<F, A> ensure(Higher1<F, A> value, Producer<E> error, Matcher1<A> matcher) {
return flatMap(value, a -> matcher.match(a) ? pure(a) : raiseError(error.get()));
}
}
public interface Comonad<F extends Kind> extends Functor<F> {
<A, B> Higher1<F, B> coflatMap(Higher1<F, A> value, Function1<Higher1<F, A>, B> map);
<A> A extract(Higher1<F, A> value);
default <A> Higher1<F, Higher1<F, A>> coflatten(Higher1<F, A> value) {
return coflatMap(value, identity());
}
}
public interface Foldable<F extends Kind> {
<A, B> B foldLeft(Higher1<F, A> value, B initial, Function2<B, A, B> mapper);
<A, B> Eval<B> foldRight(Higher1<F, A> value, Eval<B> initial, Function2<A, Eval<B>, Eval<B>> mapper);
}
public interface Traverse<F extends Kind> extends Functor<F>, Foldable<F> {
<G extends Kind, T, R> Higher1<G, Higher1<F, R>> traverse(Applicative<G> applicative, Higher1<F, T> value,
Function1<T, ? extends Higher1<G, R>> mapper);
}
public interface Semigroupal<F extends Kind> {
<A, B> Higher1<F, Tuple2<A, B>> product(Higher1<F, A> fa, Higher1<F, B> fb);
}
public interface Defer<F extends Kind> {
<A> Higher1<F, A> defer(Producer<Higher1<F, A>> defer);
}
It represents a natural transformation between two different kinds.
public interface Transformer<F extends Kind, G extends Kind> {
<X> Higher1<G, T> apply(Higher<F, T> from);
}
This class helps to create readable equals methods. An example:
@Override
public boolean equals(Object obj) {
return Equal.of(this)
.append(comparing(Data::getId))
.append(comparing(Data::getValue))
.applyTo(obj);
}
purefun is released under MIT license
FAQs
Functional Programming Library for Java
We found that com.github.tonivade:purefun demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Learn the essential steps every developer should take to stay secure on npm and reduce exposure to supply chain attacks.

Security News
Experts push back on new claims about AI-driven ransomware, warning that hype and sponsored research are distorting how the threat is understood.

Security News
Ruby's creator Matz assumes control of RubyGems and Bundler repositories while former maintainers agree to step back and transfer all rights to end the dispute.