From 3d5e994c68fe503cdac092e7b676ca281e7b8d50 Mon Sep 17 00:00:00 2001 From: Colin DAMON <cdamon@ippon.fr> Date: Thu, 13 May 2021 09:40:26 +0200 Subject: [PATCH] Coding monades in Java --- .gitlab-ci.yml | 1 + java-monades/.gitlab-ci.yml | 11 +++++ java-monades/pom.xml | 29 +++++++++++++ java-monades/readme.md | 9 ++++ .../src/main/java/fr/ippon/monad/Maybe.java | 35 ++++++++++++++++ .../src/main/java/fr/ippon/monad/State.java | 41 +++++++++++++++++++ .../test/java/fr/ippon/monad/MaybeTest.java | 30 ++++++++++++++ .../test/java/fr/ippon/monad/StateTest.java | 28 +++++++++++++ readme.md | 4 ++ 9 files changed, 188 insertions(+) create mode 100644 java-monades/.gitlab-ci.yml create mode 100644 java-monades/pom.xml create mode 100644 java-monades/readme.md create mode 100644 java-monades/src/main/java/fr/ippon/monad/Maybe.java create mode 100644 java-monades/src/main/java/fr/ippon/monad/State.java create mode 100644 java-monades/src/test/java/fr/ippon/monad/MaybeTest.java create mode 100644 java-monades/src/test/java/fr/ippon/monad/StateTest.java diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a3769253..6be72a49 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 00000000..632450ec --- /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 00000000..9d851f40 --- /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 00000000..55ca076f --- /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 00000000..d4ac5234 --- /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 00000000..d0117112 --- /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 00000000..56f6dca8 --- /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 00000000..2854544f --- /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 d83bded7..bdbf2f26 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) -- GitLab