Skip to content

Commit

Permalink
adding initial implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
jabrena committed Jul 21, 2024
1 parent aaf8f14 commit 2f11e67
Show file tree
Hide file tree
Showing 6 changed files with 454 additions and 0 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ A repository to review the main concepts about Functional Programming with Java.

```bash
sdk env install

./mvnw clean test -Dtest=EasyRacer1Test -pl problems
./mvnw clean test -DexcludedGroups=performance,endtoend
./mvnw clean test -DexcludedGroups=performance,endtoend -pl training
./mvnw clean test -DexcludedGroups=performance,endtoend -Dtest=EitherTest -pl training
Expand Down
11 changes: 11 additions & 0 deletions problems/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,19 @@
<name>problems</name>
<description>Euler and Latency solutions/tests</description>

<properties>
<jakarta.annotation-api.version>3.0.0</jakarta.annotation-api.version>
</properties>

<dependencies>

<!-- Not nulls -->
<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
<version>${jakarta.annotation-api.version}</version>
</dependency>

<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
Expand Down
74 changes: 74 additions & 0 deletions problems/src/main/java/info/jab/fp/easyracer/EasyRacer1.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package info.jab.fp.easyracer;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.concurrent.StructuredTaskScope;
import java.util.concurrent.Future.State;
import java.util.concurrent.StructuredTaskScope.Subtask;
import java.util.function.Function;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.stream.Stream;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Race 2 concurrent requests
* GET /1
* The winner returns a 200 response with a body containing right
*/
public class EasyRacer1 {

private static final Logger logger = LoggerFactory.getLogger(EasyRacer1.class);

enum ConnectionProblem {
INVALID_URI,
INVALID_CONNECTION,
}

Function<String, Either<ConnectionProblem, String>> toHTML = param -> {
try {
URI uri = new URI(param);
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder().uri(uri).GET().build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
return Either.right(response.body());
} catch (URISyntaxException | IllegalArgumentException ex) {
logger.warn(ex.getLocalizedMessage(), ex);
return Either.left(ConnectionProblem.INVALID_URI);
} catch (IOException | InterruptedException ex) {
logger.warn(ex.getLocalizedMessage(), ex);
return Either.left(ConnectionProblem.INVALID_CONNECTION);
}
};

public String call(String address) {
try (StructuredTaskScope scope = new StructuredTaskScope.ShutdownOnSuccess<Either>()) { // Create a StructuredTaskScope
// Define subtasks using fork()
Subtask<Either> cs1 = scope.fork(() -> toHTML.apply(address));
Subtask<Either> cs2 = scope.fork(() -> toHTML.apply(address));

var result = scope.join();

result.res

/*
return Stream.of(cs1, cs2)
.filter(s -> s.state() == Subtask.State.SUCCESS)
.map(Subtask::get)
.filter(Either::isRight)
.map(Either::get)
.map(String::valueOf)
.peek(System.out::println)
.findAny()
.get();
*/
} catch (InterruptedException ex) {
return "";
}
}

}
264 changes: 264 additions & 0 deletions problems/src/main/java/info/jab/fp/easyracer/Either.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
package info.jab.fp.easyracer;

import jakarta.annotation.Nonnull;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;

/**
* A generic sealed interface representing a value of one of two possible types (a disjoint union).
* Instances of Either are either an instance of Left or Right.
*
* Inspired by the implemenation from Either (Scala) and Either (ArrowKt)
*
* @param <L> the type of the Left value
* @param <R> the type of the Right value
*
* @author Juan Antonio Breña Moral
* @author ChatGPT-40
*/
public sealed interface Either<L, R> permits Either.Left, Either.Right {
/**
* Applies either the leftMapper function to the value if this is a Left, or the rightMapper function if this is a Right.
*
* @param leftMapper the function to apply if this is a Left
* @param rightMapper the function to apply if this is a Right
* @param <T> the type of the result
* @return the result of applying the appropriate function
*/
<T> T fold(Function<? super L, ? extends T> leftMapper, Function<? super R, ? extends T> rightMapper);

/**
* Checks if this is an instance of Left.
*
* @return true if this is a Left, false otherwise
*/
boolean isLeft();

/**
* Checks if this is an instance of Right.
*
* @return true if this is a Right, false otherwise
*/
boolean isRight();

/**
* Creates an instance of Left.
*
* @param value the value to be wrapped in Left
* @param <L> the type of the Left value
* @param <R> the type of the Right value
* @return a Left containing the given value
*/
static <L, R> Either<L, R> left(@Nonnull L value) {
Objects.requireNonNull(value, "Left value cannot be null");
return new Left<>(value);
}

/**
* Creates an instance of Right.
*
* @param value the value to be wrapped in Right
* @param <L> the type of the Left value
* @param <R> the type of the Right value
* @return a Right containing the given value
*/
static <L, R> Either<L, R> right(@Nonnull R value) {
Objects.requireNonNull(value, "Right value cannot be null");
return new Right<>(value);
}

/**
* Transforms the value in Right using the given function if this is a Right, otherwise returns the current Left.
*
* @param mapper the function to apply to the Right value
* @param <U> the type of the new Right value
* @return a new Either instance
*/
default <U> Either<L, U> map(Function<? super R, ? extends U> mapper) {
if (isRight()) {
return Either.right(mapper.apply(((Right<L, R>) this).value()));
} else {
return Either.left(((Left<L, R>) this).value());
}
}

/**
* Transforms the value in Right using the given function that returns another Either, if this is a Right.
* Otherwise, returns the current Left.
*
* @param mapper the function to apply to the Right value
* @param <U> the type of the new Right value
* @return a new Either instance
*/
default <U> Either<L, U> flatMap(Function<? super R, ? extends Either<L, U>> mapper) {
if (isRight()) {
return mapper.apply(((Right<L, R>) this).value());
} else {
return Either.left(((Left<L, R>) this).value());
}
}

/**
* Swaps the Left and Right types. Converts a Left to a Right and vice versa.
*
* @return a new Either instance with the Left and Right types swapped
*/
default Either<R, L> swap() {
if (isRight()) {
return Either.left(((Right<L, R>) this).value());
} else {
return Either.right(((Left<L, R>) this).value());
}
}

/**
* Returns the Right value if this is a Right, otherwise returns the result of the given Supplier.
*
* @param other a Supplier whose result is returned if this is a Left
* @return the Right value or the result of the Supplier
*/
default R getOrElse(Supplier<? extends R> other) {
if (isRight()) {
return ((Right<L, R>) this).value();
} else {
return other.get();
}
}

/**
* Returns this Either if it is a Right, otherwise returns the result of the given Supplier.
*
* @param other a Supplier whose result is returned if this is a Left
* @return this Either or the result of the Supplier
*/
default Either<L, R> orElse(Supplier<? extends Either<L, R>> other) {
if (isRight()) {
return this;
} else {
return other.get();
}
}

/**
* Combines two Either instances using the given combiner function if both are Right.
* Otherwise, returns the first Right instance encountered, or the last Left if both are Left.
*
* @param other another Either instance to combine with
* @param combiner a BiFunction to combine the Right values
* @return a new Either instance resulting from the combination
*/
default Either<L, R> combine(Either<L, R> other, BiFunction<? super R, ? super R, ? extends R> combiner) {
if (this.isRight() && other.isRight()) {
return Either.right(combiner.apply(((Right<L, R>) this).value(), ((Right<L, R>) other).value()));
} else if (this.isRight()) {
return this;
} else {
return other;
}
}

/**
* Converts this Either to an Optional.
* If this is a Right, the Optional will contain the value.
* If this is a Left, the Optional will be empty.
*
* @return an Optional containing the Right value if present, otherwise an empty Optional
*/
default Optional<R> toOptional() {
if (isRight()) {
return Optional.of(((Right<L, R>) this).value());
} else {
return Optional.empty();
}
}

/**
* Returns the value if this is a Right, otherwise throws an exception.
*
* @return the Right value
* @throws NoSuchElementException if this is a Left
*/
R get();

/**
* A record representing the Left variant of an Either.
*
* @param <L> the type of the Left value
* @param <R> the type of the Right value
* @param value the value
*/
record Left<L, R>(@Nonnull L value) implements Either<L, R> {
/**
* Constructs a new {@code Left} with the given value.
*
* @param value the value to be contained in this {@code Left}
* @throws NullPointerException if {@code value} is {@code null}
*/
public Left {
Objects.requireNonNull(value, "Left value cannot be null");
}

@Override
public <T> T fold(Function<? super L, ? extends T> leftMapper, Function<? super R, ? extends T> rightMapper) {
return leftMapper.apply(value);
}

@Override
public boolean isLeft() {
return true;
}

@Override
public boolean isRight() {
return false;
}

@Override
public R get() {
throw new NoSuchElementException("No value present in Left");
}
}

/**
* A record representing the Right variant of an Either.
*
* @param <L> the type of the Left value
* @param <R> the type of the Right value
* @param value the value
*/
record Right<L, R>(@Nonnull R value) implements Either<L, R> {
/**
* Constructs a new {@code Right} with the given value.
*
* @param value the value to be contained in this {@code Right}
* @throws NullPointerException if {@code value} is {@code null}
*/
public Right {
Objects.requireNonNull(value, "Right value cannot be null");
}

@Override
public <T> T fold(Function<? super L, ? extends T> leftMapper, Function<? super R, ? extends T> rightMapper) {
return rightMapper.apply(value);
}

@Override
public boolean isLeft() {
return false;
}

@Override
public boolean isRight() {
return true;
}

@Override
public R get() {
return value;
}
}
}
Loading

0 comments on commit 2f11e67

Please sign in to comment.