diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a3769253ae48088095e118e6e7dffb5c43facc51..6be72a4941e93c64fda2dc631ef5860e9eed6ea5 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -36,3 +36,4 @@ include: - local: "/mastermind/.gitlab-ci.yml" - local: "/try-monade/.gitlab-ci.yml" - local: "/java-puzzles/.gitlab-ci.yml" + - local: "/java-monades/.gitlab-ci.yml" diff --git a/java-monades/.gitlab-ci.yml b/java-monades/.gitlab-ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..632450ec49a31a04ba95c3f0d2dd1a72b3cafcc1 --- /dev/null +++ b/java-monades/.gitlab-ci.yml @@ -0,0 +1,11 @@ +package-java-monades: + variables: + PROJECT_FOLDER: "java-monades" + extends: .java + only: + refs: + - master + - merge_requests + changes: + - ".gitlab-common-ci.yml" + - "java-monades/**/*" diff --git a/java-monades/pom.xml b/java-monades/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..9d851f40dc8c47fd1307fcd76b638146259bd79c --- /dev/null +++ b/java-monades/pom.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <version>1.0.0</version> + <groupId>fr.ippon.kata</groupId> + <artifactId>java-parent</artifactId> + <relativePath>../java-parent</relativePath> + </parent> + + <version>1.0.0-SNAPSHOT</version> + <artifactId>java-monades</artifactId> + + <name>JavaMonades</name> + + <developers> + <developer> + <email>gautier.difolco@gmail.com</email> + <name>Gautier DI FOLCO</name> + </developer> + <developer> + <email>cdamon@ippon.fr</email> + <name>Colin DAMON</name> + </developer> + </developers> +</project> diff --git a/java-monades/readme.md b/java-monades/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..55ca076fdc12a7b5ad8cf0c9386bd7f3ba75704d --- /dev/null +++ b/java-monades/readme.md @@ -0,0 +1,9 @@ +# Java monades + +Implémentation de différents types de monades en Java + +- **Auteurs** : Gautier DI FOLCO et Colin DAMON +- **Date** : 13/05/2021 +- **Langage** : Java +- **Niveau** : Bon Chance +- **Replay** : [Twitch](https://www.twitch.tv/videos/1020950472) diff --git a/java-monades/src/main/java/fr/ippon/monad/Maybe.java b/java-monades/src/main/java/fr/ippon/monad/Maybe.java new file mode 100644 index 0000000000000000000000000000000000000000..d4ac523420d1683f5e6a69d7515774b443faa08f --- /dev/null +++ b/java-monades/src/main/java/fr/ippon/monad/Maybe.java @@ -0,0 +1,35 @@ +package fr.ippon.monad; + +import java.util.Optional; +import java.util.function.Function; + +public class Maybe<T> { + + private final Optional<T> value; + + private Maybe(Optional<T> value) { + this.value = value; + } + + public static <T> Maybe<T> pure(T value) { + return new Maybe<>(Optional.of(value)); + } + + public static <T> Maybe<T> fail() { + return new Maybe<>(Optional.empty()); + } + + public <U> Maybe<U> map(Function<T, U> mapper) { + return new Maybe<>(value.map(mapper)); + } + + public <U> Maybe<U> bind(Function<T, Maybe<U>> other) { + return value.map(other) + .orElse(Maybe.fail()); + } + + @Override + public String toString() { + return value.toString(); + } +} diff --git a/java-monades/src/main/java/fr/ippon/monad/State.java b/java-monades/src/main/java/fr/ippon/monad/State.java new file mode 100644 index 0000000000000000000000000000000000000000..d0117112620206be0ca83b12eb0f8e075c060762 --- /dev/null +++ b/java-monades/src/main/java/fr/ippon/monad/State.java @@ -0,0 +1,41 @@ +package fr.ippon.monad; + +import java.util.function.Function; + +public class State<S, T> { + + private final Function<S, Result<S, T>> runner; + + private State(Function<S, Result<S, T>> runner) { + this.runner = runner; + } + + public static <S, T> State<S, T> pure(T value) { + return new State<>(state -> new Result<>(state, value)); + } + + public static <S> State<S, Void> set(S value) { + return new State<>(state -> new Result<>(value, null)); + } + + public static <S> State<S, S> get() { + return new State<>(state -> new Result<>(state, state)); + } + + public <U> State<S, U> bind( + Function<T, State<S, U>> binder) { + return new State<>(initial -> { + Result<S, T> result = runner.apply(initial); + + return binder.apply(result.result()) + .run(result.updated()); + }); + } + + public Result<S, T> run(S initial) { + return runner.apply(initial); + } + + public static record Result<S, T> (S updated, T result) { + } +} diff --git a/java-monades/src/test/java/fr/ippon/monad/MaybeTest.java b/java-monades/src/test/java/fr/ippon/monad/MaybeTest.java new file mode 100644 index 0000000000000000000000000000000000000000..56f6dca81d5d4d974909c514c7c12371d72ea726 --- /dev/null +++ b/java-monades/src/test/java/fr/ippon/monad/MaybeTest.java @@ -0,0 +1,30 @@ +package fr.ippon.monad; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class MaybeTest { + + @Test + void shouldMapFromIntegerToString() { + assertThat(Maybe.pure(42) + .map(String::valueOf)).usingRecursiveComparison() + .isEqualTo(Maybe.pure("42")); + } + + @Test + void shouldNotMapFailedMaybe() { + assertThat(Maybe.fail() + .map(String::valueOf)).usingRecursiveComparison() + .isEqualTo(Maybe.fail()); + } + + @Test + void shouldBindPureToPure() { + assertThat(Maybe.pure(42) + .bind(value -> Maybe.pure(String.valueOf(value)))) + .usingRecursiveComparison() + .isEqualTo(Maybe.pure("42")); + } +} diff --git a/java-monades/src/test/java/fr/ippon/monad/StateTest.java b/java-monades/src/test/java/fr/ippon/monad/StateTest.java new file mode 100644 index 0000000000000000000000000000000000000000..2854544fc8743281d646fcbe22cb1b3731b05d06 --- /dev/null +++ b/java-monades/src/test/java/fr/ippon/monad/StateTest.java @@ -0,0 +1,28 @@ +package fr.ippon.monad; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class StateTest { + + @Test + void shouldWriteSyracuseSequence() { + State<Integer, Integer> syracuse = intState() + .bind((previous) -> { + Integer next = previous % 2 == 0 ? previous / 2 + : previous * 3 + 1; + + return State.set(next) + .bind(dummy -> State.pure(next)); + }); + + assertThat(syracuse.run(5)) + .isEqualTo(new State.Result<>(16, 16)); + } + + private State<Integer, Integer> intState() { + return State.get(); + } + +} diff --git a/readme.md b/readme.md index d83bded7351ce34ff93ee8928c2fffa938b3ff53..bdbf2f26c9b6955f4584873ad7e78351d50d3c95 100644 --- a/readme.md +++ b/readme.md @@ -86,6 +86,10 @@ Un kata de code est un petit exercice pensé pour s'entrainer jusqu'à maitriser - [Concurrence en Java](/java-concurrence) - [Try monade](/try-monade) +### Bon Chance + +- [Monades en java](/java-monades) + # Discussions et présentations - [C'est une bonne situation ça techlead ?](https://www.youtube.com/watch?v=9tOoXfOE12o)