Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/tonivade/purefun

Functional Programming library for Java
https://github.com/tonivade/purefun

effects experimental free-monad functional-programming higher-kinded-types immutable-collections io-monad java memoization monad monad-transformers monads mtl stream tagless-final tuples typeclasses

Last synced: 2 days ago
JSON representation

Functional Programming library for Java

Awesome Lists containing this project

README

        

# Purefun

```
____ __
| _ \ _ _ _ __ ___ / _|_ _ _ __
| |_) | | | | '__/ _ \ |_| | | | '_ \
| __/| |_| | | | __/ _| |_| | | | |
|_| \__,_|_| \___|_| \__,_|_| |_|
```

![Build Status](https://github.com/tonivade/purefun/workflows/Java%20CI%20with%20Gradle/badge.svg)
[![Codacy Badge](https://api.codacy.com/project/badge/Coverage/38422db161da48f09cd192c7e7caa7dd)](https://www.codacy.com/app/zeromock/purefun?utm_source=github.com&utm_medium=referral&utm_content=tonivade/purefun&utm_campaign=Badge_Coverage)
[![Join the chat at https://gitter.im/tonivade/purefun](https://badges.gitter.im/tonivade/purefun.svg)](https://gitter.im/tonivade/purefun?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)

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 held 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 helped me to learn and understand some important concepts
of functional programming, and over time I implemented higher kinded types and type classes
in Java. I don't know if this will be helpful for somebody, but the work is here to everyone want to use it.

Finally, I have to say thanks to [vavr](https://www.vavr.io/) library author, this library is largely inspired in his work,
and also to [Scala](https://www.scala-lang.org/) standard library authors. I don't want to forget some of the projects I've used as
reference: [Arrow](https://arrow-kt.io/) and [cats](https://typelevel.org/cats/) for type classes implementation,
[fs2](https://fs2.io/) for stream processing, and [ZIO](https://zio.dev/) to implement my own version in Java.
Their awesome work help me a lot.

## Disclaimer

**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 use it, **use it at your own risk**. Anyway if you think is useful for you, go ahead, also any
feedback and PR are very welcome.

## Higher Kinded Types

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`. Then we can define a type using a special codification like this:

```java
interface SomeType extends SomeTypeOf { }

// Boilerplate
interface SomeTypeOf implements Kind, T> {

// this is a safe cast
static SomeType toSomeType(Kind, ? extends T> hkt) {
return (SomeType) hkt;
}
}
```

It can be triky 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 type classes because is the only way to implement them correctly.

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.

## Annotation Processor

In order to simplify working with higher kinded types, in the last version I've included an annotation processor to generate
all this boilerplate code:

```java
@HigherKind
interface SomeType extends SomeTypeOf { }
```

With this annotation, all the above code, is generated automatically.

## Data types

### Option

Is an alternative to `Optional` of Java standard library. It can contains two values, a `some` or a `none`

```java
Option some = Option.some("Hello world");

Option none = Option.none();
```

### Try

Is an implementation of scala `Try` in Java. It can contains two values, a `success` or a `failure`.

```java
Try success = Try.success("Hello world");

Try failure = Try.failure(new RuntimeException("Error"));
```

### Either

Is an implementation of scala `Either` in Java.

```java
Either right = Either.right("Hello world");

Either left = Either.left(100);
```

### Validation

This type represents two different states, valid or invalid, an also it allows to combine several
validations using `mapN` methods.

```java
Validation name = Validation.valid("John Smith");
Validation email = Validation.valid("[email protected]");

// Person has a constructor with two String parameters, name and email.
Valdation, Person> person = Validation.map2(name, email, Person::new);
```

### Const

A object with a phantom parameter:

```java
Const constInt = Const.of("Hello world!");

Const constFloat = constInt.retag();

assertEquals("Hello world!", constFloat.value());
```

### Future

This is an experimental implementation of Future. Computations are executed in another thread inmediatelly (since version 5.0 de default executor is a virtual thread per task executor).

```java
Future future = Future.success("Hello world!");

Future result = future.flatMap(string -> Future.run(string::toUpperCase));

assertEquals(Try.success("HELLO WORLD!"), result.await());
```

### Trampoline

Implements recursion using an iteration and is stack safe.

```java
private Trampoline 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));
}
```

### Tuples

These classes allow to hold some values together, as tuples. There are tuples from 1 to 5.

```java
Tuple1 tuple1 = Tuple.of("Hello world");

Tuple2 tuple2 = Tuple.of("John Smith", 100);
```

## Data structures

Java doesn't define immutable collections, so I have implemented some of them.

### Sequence

Is the equivalent to java `Collection` interface. It defines all the common methods. The default implementation use persistent collections based on [pcollections](https://github.com/hrldcpr/pcollections/) library.

### ImmutableList

It represents a linked list. It has a head and a tail. Based on `ConsPStack`.

### ImmutableSet

It represents a set of elements. This elements cannot be duplicated. Based on `HashTreePSet`.

### ImmutableArray

It represents an array. You can access to the elements by its position in the array. Based on `TreePVector`.

### ImmutableMap

This class represents a hash map. Based on `HashTreePMap`.

### ImmutableTree

This class represents a binary tree. Based on `TreePSet`

### ImmutableTreeMap

This class represents a binary tree map. Based on `TreePMap`.

## Monads

Also I have implemented some Monads that allows to combine some operations.

### State Monad

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.

```java
State, Option> read = State.state(list -> Tuple.of(list.tail(), list.head()));

Tuple, Option> result = read.run(ImmutableList.of("a", "b", "c"));

assertEquals(Tuple.of(ImmutableList.of("b", "c"), Option.some("a")), result);
```

### Reader Monad

This is an implementation of Reader Monad. It allows to combine operations over a common input.
It can be used to inject dependencies.

```java
Reader, String> read2 = Reader.reader(list -> list.tail().head().orElse(""));

String result = read2.eval(ImmutableList.of("a", "b", "c"));

assertEqual("b", result);
```

### Writer Monad

It allow to combine operations over a common output.

```java
Writer, Integer> writer = Writer.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()));
```

### IO Monad

This is a experimental implementation of IO Monad in java. Inspired in this [work](https://gist.github.com/joergrathlev/f17092d3470dcf732be6).

```java
IO echo = Console.print("write your name")
.andThen(Console.read())
.flatMap(name -> Console.print("Hello " + name))
.andThen(Console.print("end"));

echo.unsafeRunSync();
```

## Free

### Free Monad

Finally, after hours of hard coding, I managed to implement a Free monad. This is a highly
unstable implementation and I have implemented because it can be implemented. Inspired
in this [work](https://github.com/xuwei-k/free-monad-java).

```java
Free echo =
IOProgram.write("what's your name?")
.andThen(IOProgram.read())
.flatMap(text -> IOProgram.write("Hello " + text))
.andThen(IOProgram.write("end"));

Kind foldMap = echo.foldMap(IOInstances.monad(), new IOProgramInterperter());

foldMap.fix(toIO()).unsafeRunSync();
```

### Free Applicative

Similar to Free monad, but allows static analysis without to run the program.

```java
FreeAp> tuple =
applicative.map5(
DSL.readInt(2),
DSL.readBoolean(false),
DSL.readDouble(2.1),
DSL.readString("hola mundo"),
DSL.readUnit(),
Tuple::of
).fix(toFreeAp());

Kind> map =
tuple.foldMap(idTransform(), IdInstances.applicative());

assertEquals(Id.of(Tuple.of(2, false, 2.1, "hola mundo", unit())), map.fix(toId()));
```

## Monad Transformers

### OptionT

Monad Transformer for `Option` type

```java
OptionT some = OptionT.some(IO.monad(), "abc");

OptionT map = some.flatMap(value -> OptionT.some(IOInstances.monad(), value.toUpperCase()));

assertEquals("ABC", map.get().fix(toIO()).unsafeRunSync());
```

### EitherT

Monad Transformer for `Either` type

```java
EitherT right = EitherT.right(IO.monad(), "abc");

EitherT map = right.flatMap(value -> EitherT.right(IOInstances.monad(), value.toUpperCase()));

assertEquals("ABC", map.get().fix(toIO()).unsafeRunSync());
```

### StateT

Monad Transformer for `State` type

```java
StateT, Unit> state =
pure("a").flatMap(append("b")).flatMap(append("c")).flatMap(end());

IO, Unit>> result = state.run(ImmutableList.empty()).fix(toIO());

assertEquals(Tuple.of(listOf("a", "b", "c"), unit()), result.unsafeRunSync());
```

### WriterT

Monad Transformer for `Writer` type

```java
WriterT, Integer> writer =
WriterT., 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()));
```

### Kleisli

Also I implemented the Kleisli composition for functions that returns monadic values like `Option`, `Try` or `Either`.

```java
Kleisli toInt = Kleisli.lift(Try.monad(), Integer::parseInt);
Kleisli half = Kleisli.lift(Try.monad(), i -> i / 2.);

Kind result = toInt.compose(half).run("123");

assertEquals(Try.success(61.5), result);
```

## Stream

An experimental version of a `Stream` like scala fs2 project.

```java
StreamOf> streamOfIO = Stream.ofIO();

IO 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)
.fix(IOOf::toIO)
.recoverWith(UncheckedIOException.class, cons("--- file not found ---"));

String content = readFile.unsafeRunSync();
```

## Effects

An experimental version of `PureIO` similar to ZIO.

```java
PureIO echoProgram =
Console.println("what's your name?")
.andThen(Console.readln())
.flatMap(name -> Console.println("Hello " + name));

interface Console {

Console.Service console();

static PureIO readln() {
return PureIO.accessM(env -> env.console().readln());
}

static PureIO println(String text) {
return PureIO.accessM(env -> env.console().println(text));
}

interface Service {
PureIO readln();

PureIO println(String text);
}
}
```

Additionally, there are aliases for some PureIO special cases:

```
UIO => PureIO
EIO => PureIO
Task => PureIO
RIO => PureIO
URIO => PureIO
```

## Type Classes

With higher kinded types simulation we can implement typeclases.

```
Invariant -- Contravariant
\
SemigroupK Functor -- Comonad
| / \
MonoidK _ Applicative Traverse -- Foldable
| / | \
Alternative Selective ApplicativeError
| |
MonadWriter Monad |
\________________| |
/ / \ /
MonadState MonadReader MonadError_____
\ \
MonadThrow Bracket
\ /
Defer -- MonadDefer -- Timer
|
Async
|
Concurrent
```

### Functor

```java
public interface Functor extends Invariant {

Kind map(Kind value, Function1 map);
}
```

### Applicative

```java
public interface Applicative extends Functor {

Kind pure(T value);

Kind ap(Kind value, Kind> apply);

@Override
default Kind map(Kind value, Function1 map) {
return ap(value, pure(map));
}
}
```

### Selective

```java
public interface Selective extends Applicative {

Kind select(Kind> value, Kind> apply);

default Kind branch(Kind> value,
Kind> applyA,
Kind> applyB) {
Kind>> abc = map(value, either -> either.map(Either::left));
Kind>> fabc = map(applyA, fb -> fb.andThen(Either::right));
return select(select(abc, fabc), applyB);
}
}
```

### Monad

```java
public interface Monad extends Selective {

Kind flatMap(Kind value, Function1> map);

@Override
default Kind map(Kind value, Function1 map) {
return flatMap(value, map.andThen(this::pure));
}

@Override
default Kind ap(Kind value, Kind> apply) {
return flatMap(apply, map -> map(value, map));
}

@Override
default
Kind select(Kind> value, Kind> apply) {
return flatMap(value, either -> either.fold(a -> map(apply, map -> map.apply(a)), this::pure));
}
}
```

### Semigroup

It represents a binary operation over a type.

```java
@FunctionalInterface
public interface Semigroup {
T combine(T t1, T t2);
}
```

There are instances for strings and integers.

### Monoid

Extends `Semigroup` adding a `zero` operation that represent an identity.

```java
public interface Monoid extends Semigroup {
T zero();
}
```

There are instances for strings and integers.

### SemigroupK

It represents a `Semigroup` but defined for a kind, like a List, so it extends a regular `Semigroup`.

### MonoidK

The same like `SemigroupK` but for a `Monoid`.

### Invariant

```java
public interface Invariant {
Kind imap(Kind value, Function1 map, Function1 comap);
}
```

### Contravariant

```java
public interface Contravariant extends Invariant {
Kind contramap(Kind value, Function1 map);
}
```

### Applicative Error

```java
public interface ApplicativeError extends Applicative {

Kind raiseError(E error);

Kind handleErrorWith(Kind value, Function1> handler);
}
```

### Monad Error

```java
public interface MonadError extends ApplicativeError, Monad {

default Kind ensure(Kind value, Producer error, Matcher1 matcher) {
return flatMap(value, a -> matcher.match(a) ? pure(a) : raiseError(error.get()));
}
}
```

### Monad Throw

```java
public interface MonadThrow extends MonadError {

}
```

### MonadReader

```java
public interface MonadReader extends Monad {

Kind ask();

default Kind reader(Function1 mapper) {
return map(ask(), mapper);
}
}
```

### MonadState

```java
public interface MonadState extends Monad {
Kind get();
Kind set(S state);

default Kind modify(Operator1 mapper) {
return flatMap(get(), s -> set(mapper.apply(s)));
}

default Kind inspect(Function1 mapper) {
return map(get(), mapper);
}

default Kind state(Function1> mapper) {
return flatMap(get(), s -> mapper.apply(s).applyTo((s1, a) -> map(set(s1), x -> a)));
}
}
```

### MonadWriter

```java
public interface MonadWriter extends Monad {

Kind writer(Tuple2 value);
Kind> listen(Kind value);
Kind pass(Kind, A>> value);

default Kind tell(W writer) {
return writer(Tuple.of(writer, unit()));
}
}
```

### Comonad

```java
public interface Comonad extends Functor {

Kind coflatMap(Kind value, Function1, B> map);

A extract(Kind value);

default Kind> coflatten(Kind value) {
return coflatMap(value, identity());
}
}
```

### Foldable

```java
public interface Foldable {

B foldLeft(Kind value, B initial, Function2 mapper);

Eval foldRight(Kind value, Eval initial, Function2, Eval> mapper);
}
```

### Traverse

```java
public interface Traverse extends Functor, Foldable {

Kind> traverse(Applicative applicative, Kind value,
Function1> mapper);
}
```

### Semigroupal

```java
public interface Semigroupal {

Kind> product(Kind fa, Kind fb);
}
```

### Defer

```java
public interface Defer {

Kind defer(Producer> defer);
}
```

### Bracket

```java
public interface Bracket extends MonadError {

Kind bracket(Kind acquire, Function1> use, Consumer1 release);
}
```

### MonadDefer

```java
public interface MonadDefer extends MonadThrow, Bracket, Defer, Timer {

default Kind later(Producer later) {
return defer(() -> Try.of(later::get).fold(this::raiseError, this::pure));
}
}
```

### Async

```java
public interface Async extends MonadDefer {

Kind async(Consumer1>> consumer);

}
```

### Timer

```java
public interface Timer {

Kind sleep(Duration duration);
}
```

### FunctionK

It represents a natural transformation between two different kinds.

```java
public interface FunctionK {
Kind apply(Kind from);
}
```

## Optics

```
__Iso__
/ \
Lens Prism
\ /
Optional
```

### Iso

An `Iso` is an optic which converts elements of some type into elements of other type without loss.
In other words, it's **isomorphic**.

```java
Point point = new Point(1, 2);

Iso> pointToTuple =
Iso.of(p -> Tuple.of(p.x, p.y),
t -> new Point(t.get1(), t.get2()));

assertEquals(point, pointToTuple.set(pointToTuple.get(point)));
```

### Lens

A `Lens` is an optic used to zoom inside a structure. In other words, it's an abstraction of a setter and a getter
but with immutable objects.

```java
Lens nameLens = Lens.of(Employee::getName, Employee::withName);

Employee pepe = new Employee("pepe");

assertEquals("pepe", nameLens.get(pepe));
assertEquals("paco", nameLens.get(nameLens.set(pepe, "paco")));
```

We can compose Lenses to get deeper inside. For example, if we add an attribute of type `Address` into `Employee`.
We can create a lens to access the city name of the address of the employee.

```java
Lens addressLens = Lens.of(Employee::getAddress, Employee::withAddress);
Lens

cityLens = Lens.of(Address::getCity, Address::withCity);
Lens cityAddressLens = addressLens.compose(cityLens);

Employee pepe = new Employee("pepe", new Address("Madrid"));

assertEquals("Madrid", cityAddressLens.get(pepe));
```

### Prism

A `Prism` is a lossless invertible optic that can see into a structure and optionally find a value.

```java
Function1> parseInt = ...; // is a method that only returns a value when the string can be parsed

Prism stringToInteger = Prism.of(parseInt, String::valueOf);

assertEquals(Option.some(5), stringToInteger.getOption("5"));
assertEquals(Option.none(), stringToInteger.getOption("a"));
assertEquals("5", stringToInteger.reverseGet(5));
```

### Optional

An `Optional` is an optic that allows to see into a structure and getting, setting like a `Lens` an optional find a value like a `Prism`.

```java
Optional addressOptional = Optional.of(
Employee::withAddress, employee -> Option.of(employee::getAddress)
);

Address madrid = new Address("Madrid");
Employee pepe = new Employee("pepe", null);

assertEquals(Option.none(), addressOptional.getOption(pepe));
assertEquals(Option.some(madrid), addressOptional.getOption(addressOptional.set(pepe, madrid)));
```

### Composition

| | Optional | Prism | Lens | Iso |
|------|------|-------|-------|-------|
| Optional | Optional | Optional | Optional | Optional |
| Prism | Optional | Prism | Optional | Prism |
| Lens | Optional | Optional | Lens | Lens |
| Iso | Optional | Prism | Lens | Iso |

## Equal

This class helps to create readable `equals` methods. An example:

```java
@Override
public boolean equals(Object obj) {
return Equal.of()
.comparing(Data::getId)
.comparing(Data::getValue)
.applyTo(this, obj);
}
```

## Stargazers over time

[![Stargazers over time](https://starchart.cc/tonivade/purefun.svg)](https://starchart.cc/tonivade/purefun)

## License

purefun is released under MIT license