This commit is contained in:
parent
d81727e0d9
commit
e27dbc102d
21
.gitea/workflows/LambdaTonic.yml
Normal file
21
.gitea/workflows/LambdaTonic.yml
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
name: LambdaTonic
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
distribution: "corretto"
|
||||||
|
java-version: "22"
|
||||||
|
- name: Execute unit tests with Maven
|
||||||
|
run: |
|
||||||
|
dnf update -y
|
||||||
|
dnf install maven -y
|
||||||
|
mvn test
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2024 Cetica Marco
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
297
README.md
Normal file
297
README.md
Normal file
@ -0,0 +1,297 @@
|
|||||||
|
# λTonic 🥃 ![](https://github.com/ceticamarco/lambdatonic/actions/workflows/LambdaTonic.yml/badge.svg)
|
||||||
|
|
||||||
|
**λTonic**(_LambdaTonic_) is functional library designed for modern Java(+21).
|
||||||
|
|
||||||
|
This library introduces a new algebraic data type called `Either<L, R>`;
|
||||||
|
that is, an immutable sum type that _discriminates_ between two values, `Left<L>` and `Right<R>`,
|
||||||
|
representing the failure and the success values, respectively.
|
||||||
|
|
||||||
|
The `Either<L, R>` data type is implemented using a **sealed interface**, while the `Left<LL>`
|
||||||
|
and the `Right<R>` are **record classes** that adopt the `Either<L, R>` protocol. Both the `Left<L>`
|
||||||
|
and the `Right<R>` data types can be used inside a `switch` statement using Java pattern matching.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
The `Either<L, R>` algebraic data type can be used orthogonally over exceptions to propagate
|
||||||
|
an error from a function. Consider the following scenario:
|
||||||
|
```java
|
||||||
|
public class Main {
|
||||||
|
public static Either<Error, Double> division(double dividend, double divisor) {
|
||||||
|
// Return an error whether the divisor is zero
|
||||||
|
if(divisor == 0) {
|
||||||
|
return new Left<>(new Error("Cannot divide by zero"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise return the result of the division
|
||||||
|
return new Right<>(dividend / divisor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
// Try to divide 15 by 3
|
||||||
|
Either<Error, Double> divResult = division(15, 3);
|
||||||
|
switch (divResult) {
|
||||||
|
case Left<Error, Double> err -> System.err.println(err.value().getMessage());
|
||||||
|
case Right<Error, Double> val -> System.out.printf("15 / 3 = %f\n", val.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to divide 2 by 0
|
||||||
|
var div2Result = division(2, 0);
|
||||||
|
switch (div2Result) {
|
||||||
|
case Left<Error, Double> err -> System.err.println(err.value().getMessage());
|
||||||
|
case Right<Error, Double> val -> System.out.printf("2 / 0 = %f\n", val.value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In this example we have defined a `division` method that takes two arguments and perform a division
|
||||||
|
on them. To handle the case when the `divisor` parameter is equal to zero, we return a new `Left<L>`
|
||||||
|
instance of the `Either<L, R>` type, while in any other case, we return a new `Right<R>` instance of the
|
||||||
|
`Either<L, R>` type. In the caller method(i.e., the `main`) we can then execute a custom statement using
|
||||||
|
Java's builtin pattern matching.
|
||||||
|
|
||||||
|
## API Usage
|
||||||
|
The `Either<L, R>` data type supports a broad spectrum of features, below there is a list of all supported
|
||||||
|
functionalities.
|
||||||
|
|
||||||
|
- `map`
|
||||||
|
|
||||||
|
### Description
|
||||||
|
```java
|
||||||
|
<T> Either<L, T> map(Function<R, T> fn);
|
||||||
|
```
|
||||||
|
The `map` method applies a function(`fn`) to the values inside the data type,
|
||||||
|
returning a new data type if and only if the `Either<L, R>` type is instantiated
|
||||||
|
to the `Right<R>` type. The `map` method adheres to the
|
||||||
|
functor laws(identity and composition of morphisms), which allows the `Either<L, R>` data type
|
||||||
|
to be classified as a functor.
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
The `map` method can be used to apply a computation to the value inside a functor:
|
||||||
|
```java
|
||||||
|
public class Main {
|
||||||
|
// ...
|
||||||
|
public static void main(String[] args) {
|
||||||
|
var resDivision = division(15, 3);
|
||||||
|
var resSquared = resDivision.map(x -> x * x);
|
||||||
|
|
||||||
|
switch (resSquared) {
|
||||||
|
case Left<Error, Double> err -> System.err.println(err.value().getMessage());
|
||||||
|
case Right<Error, Double> val -> System.out.println(val.value()); // prints 25.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `bimap`
|
||||||
|
|
||||||
|
### Description
|
||||||
|
```java
|
||||||
|
<T, K> Either<T, K> bimap(Function<L, T> onLeft, Function<R, K> onRight);
|
||||||
|
```
|
||||||
|
|
||||||
|
The `bimap` method applies the `onLeft` method to the `Left<L>` subtype or the
|
||||||
|
`onRight` to the `Right<R>`.
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
```java
|
||||||
|
public class Main {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
var sc = new Scanner(System.in);
|
||||||
|
|
||||||
|
// Read from stdin
|
||||||
|
System.out.print("Enter a divisor: ");
|
||||||
|
var input = sc.nextInt();
|
||||||
|
|
||||||
|
// Divide a fixed dividend by user input divisor
|
||||||
|
var divRes = division(15, input);
|
||||||
|
|
||||||
|
// Apply a function regardless of the type of Either
|
||||||
|
// On the left we uppercase the error message
|
||||||
|
// On the right we square the result
|
||||||
|
var bimapRes = divRes.bimap(
|
||||||
|
err -> new Error(err.toString().toUpperCase()),
|
||||||
|
val -> val * val
|
||||||
|
);
|
||||||
|
|
||||||
|
switch (bimapRes) {
|
||||||
|
case Left<Error, Double> err -> System.err.println(err.value().getMessage());
|
||||||
|
case Right<Error, Double> val -> System.out.println(val.value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `isLeft`
|
||||||
|
|
||||||
|
### Description
|
||||||
|
```java
|
||||||
|
boolean isLeft();
|
||||||
|
```
|
||||||
|
|
||||||
|
`isLeft` returns true whether `Either<L, R>` is instantiated to the `Left<L>`, false otherwise
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
```java
|
||||||
|
public class Main {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
var sc = new Scanner(System.in);
|
||||||
|
|
||||||
|
// Read from stdin
|
||||||
|
System.out.print("Enter a divisor: ");
|
||||||
|
var input = sc.nextInt();
|
||||||
|
|
||||||
|
// Divide a fixed dividend by user input divisor
|
||||||
|
var divRes = division(15, input);
|
||||||
|
|
||||||
|
if(divRes.isLeft()) {
|
||||||
|
System.out.println("Cannot divide by zero");
|
||||||
|
} else {
|
||||||
|
System.out.println(divRes.fromRight(-1.0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `isRight`
|
||||||
|
|
||||||
|
### Description
|
||||||
|
```java
|
||||||
|
boolean isRight();
|
||||||
|
```
|
||||||
|
|
||||||
|
`isRight` returns true whether `Either<L, R>` is instantiated to the `Right<L>`, false otherwise
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
```java
|
||||||
|
public class Main {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
var sc = new Scanner(System.in);
|
||||||
|
|
||||||
|
// Read from stdin
|
||||||
|
System.out.print("Enter a divisor: ");
|
||||||
|
var input = sc.nextInt();
|
||||||
|
|
||||||
|
// Divide a fixed dividend by user input divisor
|
||||||
|
var divRes = division(15, input);
|
||||||
|
|
||||||
|
if(divRes.isRight()) {
|
||||||
|
System.out.println(divRes.fromRight(-1.0));
|
||||||
|
} else {
|
||||||
|
System.out.println("Cannot divide by zero");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `fromLeft`
|
||||||
|
|
||||||
|
### Description
|
||||||
|
```java
|
||||||
|
L fromLeft(L defaultValue);
|
||||||
|
```
|
||||||
|
|
||||||
|
`fromLeft` returns the content of the `Left<L>` value or a default value
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
```java
|
||||||
|
public class Main {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
var sc = new Scanner(System.in);
|
||||||
|
|
||||||
|
// Read from stdin
|
||||||
|
System.out.print("Enter a divisor: ");
|
||||||
|
var input = sc.nextInt();
|
||||||
|
|
||||||
|
// Divide a fixed dividend by user input divisor
|
||||||
|
var divRes = division(15, input);
|
||||||
|
|
||||||
|
// Prints out the error message or nothing
|
||||||
|
System.out.println(divRes.fromLeft(new Error("")).getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `fromRight`
|
||||||
|
|
||||||
|
### Description
|
||||||
|
```java
|
||||||
|
L fromLeft(L defaultValue);
|
||||||
|
```
|
||||||
|
|
||||||
|
`fromRight` returns the content of the `Right<R>` value or a default value
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
```java
|
||||||
|
public class Main {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
var sc = new Scanner(System.in);
|
||||||
|
|
||||||
|
// Read from stdin
|
||||||
|
System.out.print("Enter a divisor: ");
|
||||||
|
var input = sc.nextInt();
|
||||||
|
|
||||||
|
// Divide a fixed dividend by user input divisor
|
||||||
|
var divRes = division(15, input);
|
||||||
|
|
||||||
|
// Prints out the actual value or nothing
|
||||||
|
System.out.println(divRes.fromRight(0.0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `toOptional`
|
||||||
|
|
||||||
|
### Description
|
||||||
|
```java
|
||||||
|
Optional<R> toOptional();
|
||||||
|
```
|
||||||
|
|
||||||
|
`toOptional` converts an `Either<L, R>` data type to a `java.util.Optional`,
|
||||||
|
where the `Right<R>` becomes a non-null `Optional<R>` and the `Left<L>`
|
||||||
|
becomes a null `Optional`.
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
```java
|
||||||
|
public class Main {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
var sc = new Scanner(System.in);
|
||||||
|
|
||||||
|
// Read from stdin
|
||||||
|
System.out.print("Enter a divisor: ");
|
||||||
|
var input = sc.nextInt();
|
||||||
|
|
||||||
|
// Divide a fixed dividend by user input divisor
|
||||||
|
var divRes = division(15, input).toOptional();
|
||||||
|
|
||||||
|
// Prints out the actual value or nothing
|
||||||
|
divRes.ifPresent(System.out::println);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `swap`
|
||||||
|
|
||||||
|
### Description
|
||||||
|
```java
|
||||||
|
Either<R, L> swap();
|
||||||
|
```
|
||||||
|
|
||||||
|
`swap` returns an `Either<R, L>` type with `Left<R>` and `Right<L>` swapped.
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
```java
|
||||||
|
public class Main {
|
||||||
|
public static void main(String[] args) {
|
||||||
|
Either<String, Integer> val = new Left<>("generic error");
|
||||||
|
Either<Integer, String> res = val.swap();
|
||||||
|
|
||||||
|
System.out.println(res.isLeft()); // Prints false
|
||||||
|
System.out.println(res.isRight()); // Prints true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
This software is released under the MIT license.
|
||||||
|
You can find a copy of the license with this repository or by visiting
|
||||||
|
the [following page](https://choosealicense.com/licenses/mit/).
|
@ -39,8 +39,8 @@ public sealed interface Either<L, R> permits Left, Right {
|
|||||||
* <br /><br />
|
* <br /><br />
|
||||||
* This method
|
* This method
|
||||||
* applies a function(<i>fn</i>) to the values inside the data type,
|
* applies a function(<i>fn</i>) to the values inside the data type,
|
||||||
* returning a new data type(i.e., a new functor) if and only if the the Either
|
* returning a new data type(i.e., a new functor) if and only if the Either
|
||||||
* type is instantiated with the <i>Right</i> subtype. Otherwise it leaves the functor
|
* type is instantiated to the <i>Right</i> subtype. Otherwise it leaves the functor
|
||||||
* unchanged.
|
* unchanged.
|
||||||
* <br /><br />
|
* <br /><br />
|
||||||
* The type of the resulting functor is the return type specified on the <i>fn</i>
|
* The type of the resulting functor is the return type specified on the <i>fn</i>
|
||||||
|
Loading…
Reference in New Issue
Block a user