Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
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
- Host: GitHub
- URL: https://github.com/tonivade/purefun
- Owner: tonivade
- License: mit
- Created: 2018-07-24T18:11:55.000Z (over 6 years ago)
- Default Branch: master
- Last Pushed: 2024-11-08T06:38:05.000Z (7 days ago)
- Last Synced: 2024-11-08T07:31:56.369Z (7 days ago)
- Topics: 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
- Language: Java
- Homepage:
- Size: 5.2 MB
- Stars: 120
- Watchers: 8
- Forks: 4
- Open Issues: 12
-
Metadata Files:
- Readme: README.md
- License: LICENSE
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);
### 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 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);
}### 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);
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
cityLens = Lens.of(Address::getCity, Address::withCity);
Lens addressLens = Lens.of(Employee::getAddress, Employee::withAddress);
Lens
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 parsedPrism 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