From 551c0fc182862ee8400ab6a2f6c7d6ee69afd8bd Mon Sep 17 00:00:00 2001 From: Michal Stawarski Date: Wed, 30 Sep 2026 10:09:31 +0200 Subject: [PATCH 1/7] Updates gradle configuration --- build.gradle | 47 ++++++++++++++++++++++++++++++++++++++++++----- settings.gradle | 3 +-- 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/build.gradle b/build.gradle index 2628826..8b459b2 100644 --- a/build.gradle +++ b/build.gradle @@ -1,19 +1,56 @@ plugins { - id 'java' + id 'java-library' + id 'jacoco' } group = 'io.codeswarm' -version = '1.0-SNAPSHOT' +version = '0.1.1' + +description = 'Educational implementation of the typeclass pattern in modern Java.' + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(25) + } + withSourcesJar() + withJavadocJar() +} repositories { mavenCentral() } dependencies { - testImplementation platform('org.junit:junit-bom:5.9.1') + testImplementation platform('org.junit:junit-bom:6.1.2') testImplementation 'org.junit.jupiter:junit-jupiter' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' +} + +tasks.withType(JavaCompile).configureEach { + options.encoding = 'UTF-8' + options.compilerArgs.addAll(['-Xlint:all', '-parameters']) +} + +tasks.withType(Javadoc).configureEach { + options.encoding = 'UTF-8' + options.addStringOption('Xdoclint:all,-missing', '-quiet') } -test { +tasks.withType(Test).configureEach { useJUnitPlatform() -} \ No newline at end of file +} + +jacoco { + toolVersion = '0.8.13' +} + +jacocoTestReport { + dependsOn test + reports { + xml.required = true + html.required = true + csv.required = false + } +} + +check.dependsOn jacocoTestReport diff --git a/settings.gradle b/settings.gradle index 21b02ff..133bd27 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1 @@ -rootProject.name = 'FunctionalJava' - +rootProject.name = 'java-typeclasses' From 8b240b5451ca2d2972d0c26bfb7d5bb041b3c737 Mon Sep 17 00:00:00 2001 From: Michal Stawarski Date: Wed, 30 Sep 2026 10:10:12 +0200 Subject: [PATCH 2/7] Updates gradle configuration --- gradle/wrapper/README.md | 11 +++++++++++ gradle/wrapper/gradle-wrapper.properties | 5 +++-- 2 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 gradle/wrapper/README.md diff --git a/gradle/wrapper/README.md b/gradle/wrapper/README.md new file mode 100644 index 0000000..09d070c --- /dev/null +++ b/gradle/wrapper/README.md @@ -0,0 +1,11 @@ +# Gradle Wrapper bootstrap note + +`gradle-wrapper.properties` targets Gradle 9.8.0. + +The binary `gradle-wrapper.jar` is generated by Gradle and is intentionally not recreated from source in this archive. If it is not already present after applying this milestone on top of the repository, regenerate the wrapper once with: + +```bash +gradle wrapper --gradle-version 9.8.0 +``` + +In the original repository, keep the existing wrapper scripts/JAR when applying the refactor and regenerate them with the command above so all wrapper components are consistent. diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index b847767..299848b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ -#Thu Oct 12 11:21:43 GMT+02:00 2023 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.8.0-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From b8858ef0769d68da089a406b1051b59e8a325b5a Mon Sep 17 00:00:00 2001 From: Michal Stawarski Date: Wed, 30 Sep 2026 10:15:55 +0200 Subject: [PATCH 3/7] Updates java typeclasses framework --- .../java/io/codeswarm/FunctionalRunner.java | 133 ------------------ .../patterns/applicative/Applicative.java | 10 -- .../patterns/applicative/ApplicativeType.java | 56 -------- .../io/codeswarm/patterns/apply/Apply.java | 10 -- .../codeswarm/patterns/apply/ApplyType.java | 53 ------- .../io/codeswarm/patterns/either/Either.java | 13 -- .../codeswarm/patterns/either/EitherType.java | 45 ------ .../codeswarm/patterns/foldable/Foldable.java | 10 -- .../patterns/foldable/FoldableType.java | 55 -------- .../codeswarm/patterns/functor/Functor.java | 10 -- .../patterns/functor/FunctorType.java | 44 ------ .../patterns/invariant/Invariant.java | 8 -- .../patterns/invariant/InvariantType.java | 41 ------ .../io/codeswarm/patterns/monad/Monad.java | 12 -- .../codeswarm/patterns/monad/MonadType.java | 63 --------- .../io/codeswarm/patterns/monoid/Monoid.java | 9 -- .../codeswarm/patterns/monoid/MonoidType.java | 29 ---- .../patterns/semigroup/Semigroup.java | 7 - .../patterns/semigroup/SemigroupType.java | 17 --- .../io/codeswarm/typeclasses/core/Show.java | 27 ++++ .../typeclasses/core/TypeClassFunctions.java | 32 +++++ .../typeclasses/examples/Person.java | 26 ++++ .../typeclasses/examples/PersonInstances.java | 28 ++++ .../typeclasses/examples/ShowExample.java | 33 +++++ .../instances/IntegerInstances.java | 18 +++ .../instances/LocalDateInstances.java | 27 ++++ .../instances/StringInstances.java | 18 +++ .../patterns/applicative/ApplicativeTest.java | 42 ------ .../codeswarm/patterns/apply/ApplyTest.java | 39 ----- .../codeswarm/patterns/either/EitherTest.java | 78 ---------- .../patterns/foldable/FoldableTest.java | 58 -------- .../patterns/functor/FunctorTest.java | 26 ---- .../patterns/invariant/InvariantTest.java | 35 ----- .../codeswarm/patterns/monad/MonadTest.java | 48 ------- .../codeswarm/patterns/monoid/MonoidTest.java | 31 ---- .../patterns/semigroup/SemigroupTest.java | 34 ----- .../core/TypeClassFunctionsTest.java | 45 ++++++ .../examples/PersonInstancesTest.java | 44 ++++++ .../typeclasses/examples/PersonTest.java | 40 ++++++ .../instances/StandardInstancesTest.java | 39 +++++ 40 files changed, 377 insertions(+), 1016 deletions(-) delete mode 100644 src/main/java/io/codeswarm/FunctionalRunner.java delete mode 100644 src/main/java/io/codeswarm/patterns/applicative/Applicative.java delete mode 100644 src/main/java/io/codeswarm/patterns/applicative/ApplicativeType.java delete mode 100644 src/main/java/io/codeswarm/patterns/apply/Apply.java delete mode 100644 src/main/java/io/codeswarm/patterns/apply/ApplyType.java delete mode 100644 src/main/java/io/codeswarm/patterns/either/Either.java delete mode 100644 src/main/java/io/codeswarm/patterns/either/EitherType.java delete mode 100644 src/main/java/io/codeswarm/patterns/foldable/Foldable.java delete mode 100644 src/main/java/io/codeswarm/patterns/foldable/FoldableType.java delete mode 100644 src/main/java/io/codeswarm/patterns/functor/Functor.java delete mode 100644 src/main/java/io/codeswarm/patterns/functor/FunctorType.java delete mode 100644 src/main/java/io/codeswarm/patterns/invariant/Invariant.java delete mode 100644 src/main/java/io/codeswarm/patterns/invariant/InvariantType.java delete mode 100644 src/main/java/io/codeswarm/patterns/monad/Monad.java delete mode 100644 src/main/java/io/codeswarm/patterns/monad/MonadType.java delete mode 100644 src/main/java/io/codeswarm/patterns/monoid/Monoid.java delete mode 100644 src/main/java/io/codeswarm/patterns/monoid/MonoidType.java delete mode 100644 src/main/java/io/codeswarm/patterns/semigroup/Semigroup.java delete mode 100644 src/main/java/io/codeswarm/patterns/semigroup/SemigroupType.java create mode 100644 src/main/java/io/codeswarm/typeclasses/core/Show.java create mode 100644 src/main/java/io/codeswarm/typeclasses/core/TypeClassFunctions.java create mode 100644 src/main/java/io/codeswarm/typeclasses/examples/Person.java create mode 100644 src/main/java/io/codeswarm/typeclasses/examples/PersonInstances.java create mode 100644 src/main/java/io/codeswarm/typeclasses/examples/ShowExample.java create mode 100644 src/main/java/io/codeswarm/typeclasses/instances/IntegerInstances.java create mode 100644 src/main/java/io/codeswarm/typeclasses/instances/LocalDateInstances.java create mode 100644 src/main/java/io/codeswarm/typeclasses/instances/StringInstances.java delete mode 100644 src/test/java/io/codeswarm/patterns/applicative/ApplicativeTest.java delete mode 100644 src/test/java/io/codeswarm/patterns/apply/ApplyTest.java delete mode 100644 src/test/java/io/codeswarm/patterns/either/EitherTest.java delete mode 100644 src/test/java/io/codeswarm/patterns/foldable/FoldableTest.java delete mode 100644 src/test/java/io/codeswarm/patterns/functor/FunctorTest.java delete mode 100644 src/test/java/io/codeswarm/patterns/invariant/InvariantTest.java delete mode 100644 src/test/java/io/codeswarm/patterns/monad/MonadTest.java delete mode 100644 src/test/java/io/codeswarm/patterns/monoid/MonoidTest.java delete mode 100644 src/test/java/io/codeswarm/patterns/semigroup/SemigroupTest.java create mode 100644 src/test/java/io/codeswarm/typeclasses/core/TypeClassFunctionsTest.java create mode 100644 src/test/java/io/codeswarm/typeclasses/examples/PersonInstancesTest.java create mode 100644 src/test/java/io/codeswarm/typeclasses/examples/PersonTest.java create mode 100644 src/test/java/io/codeswarm/typeclasses/instances/StandardInstancesTest.java diff --git a/src/main/java/io/codeswarm/FunctionalRunner.java b/src/main/java/io/codeswarm/FunctionalRunner.java deleted file mode 100644 index 88481f3..0000000 --- a/src/main/java/io/codeswarm/FunctionalRunner.java +++ /dev/null @@ -1,133 +0,0 @@ -package io.codeswarm; - -import io.codeswarm.patterns.applicative.Applicative; -import io.codeswarm.patterns.applicative.ApplicativeType; -import io.codeswarm.patterns.apply.Apply; -import io.codeswarm.patterns.apply.ApplyType; -import io.codeswarm.patterns.either.Either; -import io.codeswarm.patterns.either.EitherType; -import io.codeswarm.patterns.foldable.Foldable; -import io.codeswarm.patterns.foldable.FoldableType; -import io.codeswarm.patterns.functor.Functor; -import io.codeswarm.patterns.functor.FunctorType; -import io.codeswarm.patterns.monad.Monad; -import io.codeswarm.patterns.monad.MonadType; -import io.codeswarm.patterns.monoid.MonoidType; -import io.codeswarm.patterns.semigroup.SemigroupType; - -import java.util.List; -import java.util.Optional; -import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -public class FunctionalRunner { - - public static void main(String[] args) { - - semigroupCheck(); - monoidCheck(); - functorCheck(); - applyCheck(); - applicativeCheck(); - monadCheck(); - foldableCheck(); - eitherCheck(); - } - - private static void semigroupCheck() { - SemigroupType semigroupTypeInt = new SemigroupType<>(Integer::sum); - SemigroupType semigroupTypeString = new SemigroupType<>(String::concat); - System.out.println("Semigroup check:"); - System.out.println(semigroupTypeInt.combine(5,4)); - System.out.println(semigroupTypeString.combine("Semigroup functional ", "type class")); - } - - private static void monoidCheck() { - MonoidType monoidTypeInteger = new MonoidType<>(0, Integer::sum); - MonoidType monoidTypeString = new MonoidType<>("", String::concat); - System.out.printf("%nMonoid check:%n"); - System.out.println(monoidTypeInteger.combine(2,3)); - System.out.println(monoidTypeInteger.empty()); - System.out.println(monoidTypeString.combine("Monoid ", "type class.")); - System.out.println(monoidTypeString.empty()); - } - - private static void functorCheck() { - System.out.println(); - System.out.println("Functor check:"); - Functor functorTypeIntegerMap = new FunctorType<>(7).map(i -> i + 1); - Boolean functorTypeIntegerEq = new FunctorType<>(10).eq(7); - Functor functorTypeStringMap = new FunctorType<>("Java").map(s -> s.replace('a', 'e')); - Boolean functorTypeStringEq = new FunctorType<>("Java").eq("Java"); - System.out.println(functorTypeIntegerMap); - System.out.println(functorTypeIntegerEq); - System.out.println(functorTypeStringMap); - System.out.println(functorTypeStringEq); - } - - private static void applyCheck() { - System.out.println(); - System.out.println("Apply check:"); - Apply apIntMap = new ApplyType<>(6).map(i -> i * 2); - Apply apAp = new ApplyType<>(2).ap(new ApplyType<>(i -> i + 1)); - System.out.println(apIntMap);; - System.out.println(apAp); - } - - private static void applicativeCheck() { - System.out.println(); - System.out.println("Applicative check:"); - Applicative applicativeIntMap = new ApplicativeType<>(5).map(i -> i * 3); - System.out.println(applicativeIntMap); - Applicative applicativePure = new ApplicativeType<>(5).pure(2); - System.out.println(applicativePure); - } - - private static void monadCheck() { - System.out.println(); - System.out.println("Monad check:"); - Monad monadIntMap = new MonadType<>(7).map(i -> i + 10); - System.out.println(monadIntMap); - Monad monadIntFlatMap = new MonadType<>(7).flatMap(x -> (y -> x * 3 + 2)); - System.out.println(monadIntFlatMap); - } - - private static void foldableCheck() { - System.out.println(); - System.out.println("Foldable check:"); - List list = List.of(1,2,3,4,5); - Foldable, Integer> foldable = new FoldableType<>(list, 0); - var resultLeft = foldable.foldLeft(List.of(list), 0, (acc, x) -> acc + x.get(acc)); - System.out.println(resultLeft); - -// var resultRight = foldable.foldRight(List.of(list), 0, (a, x) -> appendList(a, x)); -// var r = foldable.foldLeft(List.of(list), 0, Integer::sum); -// System.out.println(foldable.foldLeft(list, 0, Integer::sum)); - } - - private static void eitherCheck() { - System.out.println(); - System.out.println("Either check:"); - - Optional optional = Optional.of(1); - Optional nextOptional = Optional.of(2); - Either either = new EitherType<>(optional, nextOptional); - System.out.println(either.left(1)); - System.out.println(either.right(2)); - System.out.println(Optional.ofNullable(either.map(x -> x + 1, y -> y + 2))); - System.out.println(Optional.of(either.mapLeft(x -> x + 1))); - System.out.println(Optional.of(either.mapRight(y -> y + 2))); - } - - @SafeVarargs - private static List appendList(List list, T... elements) { - return Stream.concat(list.stream(), Stream.of(elements)) - .collect(Collectors.toList()); - } - - private static List prependList(List list, T element) { - return Stream.concat(Stream.of(element), list.stream()) - .collect(Collectors.toList()); - } -} \ No newline at end of file diff --git a/src/main/java/io/codeswarm/patterns/applicative/Applicative.java b/src/main/java/io/codeswarm/patterns/applicative/Applicative.java deleted file mode 100644 index 80c9bb4..0000000 --- a/src/main/java/io/codeswarm/patterns/applicative/Applicative.java +++ /dev/null @@ -1,10 +0,0 @@ -package io.codeswarm.patterns.applicative; - -import io.codeswarm.patterns.functor.Functor; - -import java.util.function.Function; - -public interface Applicative extends Functor { - Applicative pure(T value); - Applicative ap(ApplicativeType> transformer); -} diff --git a/src/main/java/io/codeswarm/patterns/applicative/ApplicativeType.java b/src/main/java/io/codeswarm/patterns/applicative/ApplicativeType.java deleted file mode 100644 index eb8cb51..0000000 --- a/src/main/java/io/codeswarm/patterns/applicative/ApplicativeType.java +++ /dev/null @@ -1,56 +0,0 @@ -package io.codeswarm.patterns.applicative; - -import java.util.Optional; -import java.util.function.BinaryOperator; -import java.util.function.Function; - -public class ApplicativeType implements Applicative { - - private final T value; - - private final BinaryOperator operation; - - public ApplicativeType(T value) { - this.value = value; - this.operation = (a, b) -> a; - } - - public static Applicative of(T value) { - return new ApplicativeType<>(value); - } - - @Override - public Optional empty() { - return Optional.empty(); - } - - @Override - public T combine(T a, T b) { - return operation.apply(a, b); - } - - @Override - public Applicative pure(T value) { - return new ApplicativeType<>(value); - } - - @Override - public Applicative ap(ApplicativeType> transformer) { - return new ApplicativeType<>(transformer.value.apply(value)); - } - - @Override - public Applicative map(Function mapper) { - return new ApplicativeType<>(mapper.apply(value)); - } - - @Override - public Boolean eq(T other) { - return value.equals(other); - } - - @Override - public String toString() { - return value.toString(); - } -} diff --git a/src/main/java/io/codeswarm/patterns/apply/Apply.java b/src/main/java/io/codeswarm/patterns/apply/Apply.java deleted file mode 100644 index efe0605..0000000 --- a/src/main/java/io/codeswarm/patterns/apply/Apply.java +++ /dev/null @@ -1,10 +0,0 @@ -package io.codeswarm.patterns.apply; - -import io.codeswarm.patterns.functor.Functor; - -import java.util.function.Function; - -public interface Apply extends Functor { - Apply ap(ApplyType> transformer); - -} diff --git a/src/main/java/io/codeswarm/patterns/apply/ApplyType.java b/src/main/java/io/codeswarm/patterns/apply/ApplyType.java deleted file mode 100644 index 4bb3566..0000000 --- a/src/main/java/io/codeswarm/patterns/apply/ApplyType.java +++ /dev/null @@ -1,53 +0,0 @@ -package io.codeswarm.patterns.apply; - -import java.util.Optional; -import java.util.function.BinaryOperator; -import java.util.function.Function; - -public class ApplyType implements Apply { - - private final T value; - - private final BinaryOperator operation; - - public ApplyType(T value) { - this.value = value; - this.operation = (a, b) -> a; - } - - @Override - public Optional empty() { - return Optional.empty(); - } - - @Override - public T combine(T a, T b) { - return operation.apply(a, b); - } - - @Override - public Apply ap(ApplyType> transformer) { - return new ApplyType<>(transformer.value.apply(value)); - } - - @Override - public Apply map(Function mapper) { - return new ApplyType<>(mapper.apply(value)); - } - - @Override - public Boolean eq(T other) { - return value.equals(other); - } - - public static Apply of(T value) { - return new ApplyType<>(value); - } - - @Override - public String toString() { - return "ApplyType{" + - "value = " + value + - '}'; - } -} diff --git a/src/main/java/io/codeswarm/patterns/either/Either.java b/src/main/java/io/codeswarm/patterns/either/Either.java deleted file mode 100644 index afd8bc6..0000000 --- a/src/main/java/io/codeswarm/patterns/either/Either.java +++ /dev/null @@ -1,13 +0,0 @@ -package io.codeswarm.patterns.either; - -public interface Either { - - Either left(L value); - Either right(R value); - T map(java.util.function.Function lFunc, - java.util.function.Function rFunc); - Either mapLeft(java.util.function.Function lFunc); - Either mapRight(java.util.function.Function rFunc); - void apply(java.util.function.Consumer lFunc, - java.util.function.Consumer rFunc); -} diff --git a/src/main/java/io/codeswarm/patterns/either/EitherType.java b/src/main/java/io/codeswarm/patterns/either/EitherType.java deleted file mode 100644 index ddfcd4a..0000000 --- a/src/main/java/io/codeswarm/patterns/either/EitherType.java +++ /dev/null @@ -1,45 +0,0 @@ -package io.codeswarm.patterns.either; - -import java.util.Optional; -import java.util.function.Consumer; -import java.util.function.Function; - -public final class EitherType implements Either { - - final Optional left; - final Optional right; - - public EitherType(Optional l, Optional r) { - left=l; - right=r; - } - - public Either left(L value) { - return new EitherType<>(Optional.of(value), Optional.empty()); - } - - public Either right(R value) { - return new EitherType<>(Optional.empty(), Optional.of(value)); - } - - public T map(Function lFunc, Function rFunc){ - return left.map(lFunc).orElseGet(()->right.map(rFunc).get()); - } - - public Either mapLeft(Function lFunc){ - return new EitherType<>(left.map(lFunc),right); - } - - public Either mapRight(Function rFunc){ - return new EitherType<>(left, right.map(rFunc)); - } - - public void apply(Consumer lFunc, Consumer rFunc){ - left.ifPresent(lFunc); - right.ifPresent(rFunc); - } - - public String toString() { - return "EitherType{left = " + left + ", right = " + right + "}"; - } -} diff --git a/src/main/java/io/codeswarm/patterns/foldable/Foldable.java b/src/main/java/io/codeswarm/patterns/foldable/Foldable.java deleted file mode 100644 index 18d1160..0000000 --- a/src/main/java/io/codeswarm/patterns/foldable/Foldable.java +++ /dev/null @@ -1,10 +0,0 @@ -package io.codeswarm.patterns.foldable; - -import java.util.List; -import java.util.function.BiFunction; - -public interface Foldable { - - U foldLeft(List list, U initialValue, BiFunction accumulator); - U foldRight(List list, U initialValue, BiFunction accumulator); -} diff --git a/src/main/java/io/codeswarm/patterns/foldable/FoldableType.java b/src/main/java/io/codeswarm/patterns/foldable/FoldableType.java deleted file mode 100644 index 662d0e7..0000000 --- a/src/main/java/io/codeswarm/patterns/foldable/FoldableType.java +++ /dev/null @@ -1,55 +0,0 @@ -package io.codeswarm.patterns.foldable; - -import java.util.*; -import java.util.function.BiFunction; - -public class FoldableType implements Foldable { - - public final T firstValue; - public final U secondValue; - - public FoldableType(T firstValue, U secondValue) { - this.firstValue = firstValue; - this.secondValue = secondValue; - } - - public static T head(List list) { - return !list.isEmpty() - ? list.get(0) - : null; - } - - public static List tail(List list) { - List workList = copy(list); - workList.remove(0); - return !list.isEmpty() - ? Collections.unmodifiableList(workList) - : null; - } - - private static List copy(List list) { - return new ArrayList<>(list); - } - -// @Override -// public U foldLeft(List list, U initialValue, BiFunction accumulator) { -// U result = initialValue; -// for (T element: list) { -// result = accumulator.apply(result, element); -// } -// return result; -// } - - @Override - public U foldLeft(List list, U initialValue, BiFunction accumulator) { - return list.stream() - .reduce(initialValue, accumulator, (a, b) -> b); - } - - @Override - public U foldRight(List list, U initialValue, BiFunction accumulator) { - return list.isEmpty() - ? initialValue - : accumulator.apply(head(list), foldRight(Objects.requireNonNull(tail(list)), initialValue, accumulator)); - } -} diff --git a/src/main/java/io/codeswarm/patterns/functor/Functor.java b/src/main/java/io/codeswarm/patterns/functor/Functor.java deleted file mode 100644 index bf1e767..0000000 --- a/src/main/java/io/codeswarm/patterns/functor/Functor.java +++ /dev/null @@ -1,10 +0,0 @@ -package io.codeswarm.patterns.functor; - -import io.codeswarm.patterns.monoid.Monoid; - -import java.util.function.Function; - -public interface Functor extends Monoid { - Functor map(Function mapper); - Boolean eq(T other); -} diff --git a/src/main/java/io/codeswarm/patterns/functor/FunctorType.java b/src/main/java/io/codeswarm/patterns/functor/FunctorType.java deleted file mode 100644 index d4f7292..0000000 --- a/src/main/java/io/codeswarm/patterns/functor/FunctorType.java +++ /dev/null @@ -1,44 +0,0 @@ -package io.codeswarm.patterns.functor; - -import java.util.Optional; -import java.util.function.Function; - -public class FunctorType implements Functor { - - private final T value; - - public FunctorType(T value) { - this.value = value; - } - - @Override - public Optional empty() { - return Optional.empty(); - } - - @Override - public T combine(T a, T b) { - return null; - } - - @Override - public Functor map(Function mapper) { - return new FunctorType<>(mapper.apply(value)); - } - - @Override - public Boolean eq(T other) { - return value.equals(other); - } - - public static Functor of(T value) { - return new FunctorType<>(value); - } - - @Override - public String toString() { - return "FunctorType{" + - "value = " + value + - '}'; - } -} diff --git a/src/main/java/io/codeswarm/patterns/invariant/Invariant.java b/src/main/java/io/codeswarm/patterns/invariant/Invariant.java deleted file mode 100644 index 9ea0638..0000000 --- a/src/main/java/io/codeswarm/patterns/invariant/Invariant.java +++ /dev/null @@ -1,8 +0,0 @@ -package io.codeswarm.patterns.invariant; - -import java.io.Serializable; -import java.util.function.Function; - -public interface Invariant extends Serializable { - Invariant imap(Function mapper, Function reversedMapper); -} diff --git a/src/main/java/io/codeswarm/patterns/invariant/InvariantType.java b/src/main/java/io/codeswarm/patterns/invariant/InvariantType.java deleted file mode 100644 index 1ffa9ee..0000000 --- a/src/main/java/io/codeswarm/patterns/invariant/InvariantType.java +++ /dev/null @@ -1,41 +0,0 @@ -package io.codeswarm.patterns.invariant; - -import java.util.Optional; -import java.util.function.Function; - -@SuppressWarnings("unchecked") -public class InvariantType implements Invariant{ - - private final T value; - private final R reversedValue; - - public InvariantType(T value, R reversedValue) { - this.value = value; - this.reversedValue = reversedValue; - } - - @Override - public Invariant imap(Function mapper, Function reversedMapper) { - if (mapper != null && reversedMapper == null) { - return (Invariant) new InvariantType<>(mapper.apply(value), null); - } - if (mapper == null && reversedMapper != null) { - return (Invariant) new InvariantType<>(null, reversedMapper.apply(reversedValue)); - } - else { - return empty(); - } - } - - private Invariant empty() { - return new InvariantType<>((T) Optional.empty(), (R) Optional.empty()); - } - - @Override - public String toString() { - return "InvariantType{" + - "value=" + value + - ", reversedValue=" + reversedValue + - '}'; - } -} diff --git a/src/main/java/io/codeswarm/patterns/monad/Monad.java b/src/main/java/io/codeswarm/patterns/monad/Monad.java deleted file mode 100644 index 3a8a577..0000000 --- a/src/main/java/io/codeswarm/patterns/monad/Monad.java +++ /dev/null @@ -1,12 +0,0 @@ -package io.codeswarm.patterns.monad; - -import io.codeswarm.patterns.functor.Functor; - -import java.util.function.Function; - -public interface Monad extends Functor { - - Monad pure(T value); - Monad ap(MonadType> transformer); - Monad flatMap(Function> mapper); -} diff --git a/src/main/java/io/codeswarm/patterns/monad/MonadType.java b/src/main/java/io/codeswarm/patterns/monad/MonadType.java deleted file mode 100644 index a5d6feb..0000000 --- a/src/main/java/io/codeswarm/patterns/monad/MonadType.java +++ /dev/null @@ -1,63 +0,0 @@ -package io.codeswarm.patterns.monad; - -import java.util.Optional; -import java.util.function.BinaryOperator; -import java.util.function.Function; - -public class MonadType implements Monad{ - - private final T value; - - private final BinaryOperator operation; - - public MonadType(T value) { - this.value = value; - this.operation = (a, b) -> a; - } - - @Override - public Optional empty() { - return Optional.empty(); - } - - @Override - public T combine(T a, T b) { - return operation.apply(a, b); - } - - @Override - public Monad map(Function mapper) { - return new MonadType<>(mapper.apply(value)); - } - - @Override - public Boolean eq(T other) { - return value.equals(other); - } - - @Override - public Monad pure(T value) { - return new MonadType<>(value); - } - - @Override - public Monad ap(MonadType> transformer) { - return new MonadType<>(transformer.value.apply(value)); - } - - @Override - public Monad flatMap(Function> mapper) { - return new MonadType<>(mapper.apply(value).apply(value)); - } - - public static Monad of(T value) { - return new MonadType<>(value); - } - - @Override - public String toString() { - return "MonadType{" + - "value = " + value + - '}'; - } -} diff --git a/src/main/java/io/codeswarm/patterns/monoid/Monoid.java b/src/main/java/io/codeswarm/patterns/monoid/Monoid.java deleted file mode 100644 index 1e1ccc6..0000000 --- a/src/main/java/io/codeswarm/patterns/monoid/Monoid.java +++ /dev/null @@ -1,9 +0,0 @@ -package io.codeswarm.patterns.monoid; - -import io.codeswarm.patterns.semigroup.Semigroup; - -import java.util.Optional; - -public interface Monoid extends Semigroup { - Optional empty(); -} diff --git a/src/main/java/io/codeswarm/patterns/monoid/MonoidType.java b/src/main/java/io/codeswarm/patterns/monoid/MonoidType.java deleted file mode 100644 index cbfee68..0000000 --- a/src/main/java/io/codeswarm/patterns/monoid/MonoidType.java +++ /dev/null @@ -1,29 +0,0 @@ -package io.codeswarm.patterns.monoid; - -import java.util.Optional; -import java.util.function.BinaryOperator; - -public class MonoidType implements Monoid { - - private final T value; - private final BinaryOperator operation; - - public MonoidType(T value, BinaryOperator operation) { - this.value = value; - this.operation = operation; - } - - @Override - public Optional empty() { - return Optional.ofNullable(value); - } - - @Override - public T combine(T a, T b) { - return operation.apply(a, b); - } - - public static Monoid of(T value, BinaryOperator operation) { - return new MonoidType<>(value, operation); - } -} diff --git a/src/main/java/io/codeswarm/patterns/semigroup/Semigroup.java b/src/main/java/io/codeswarm/patterns/semigroup/Semigroup.java deleted file mode 100644 index dfb09a3..0000000 --- a/src/main/java/io/codeswarm/patterns/semigroup/Semigroup.java +++ /dev/null @@ -1,7 +0,0 @@ -package io.codeswarm.patterns.semigroup; - -@FunctionalInterface -public interface Semigroup { - - T combine(T a, T b); -} diff --git a/src/main/java/io/codeswarm/patterns/semigroup/SemigroupType.java b/src/main/java/io/codeswarm/patterns/semigroup/SemigroupType.java deleted file mode 100644 index 1e2579d..0000000 --- a/src/main/java/io/codeswarm/patterns/semigroup/SemigroupType.java +++ /dev/null @@ -1,17 +0,0 @@ -package io.codeswarm.patterns.semigroup; - -import java.util.function.BinaryOperator; - -public class SemigroupType implements Semigroup { - - private final BinaryOperator operation; - - public SemigroupType(BinaryOperator operation) { - this.operation = operation; - } - - @Override - public T combine(T a, T b) { - return operation.apply(a, b); - } -} diff --git a/src/main/java/io/codeswarm/typeclasses/core/Show.java b/src/main/java/io/codeswarm/typeclasses/core/Show.java new file mode 100644 index 0000000..0c607a8 --- /dev/null +++ b/src/main/java/io/codeswarm/typeclasses/core/Show.java @@ -0,0 +1,27 @@ +package io.codeswarm.typeclasses.core; + +/** + * Describes how a value of type {@code A} is converted to a human-readable + * textual representation. + * + *

{@code Show} demonstrates the central idea of the typeclass pattern: + * behaviour is defined independently from the domain type. A domain class does + * not have to implement this interface and more than one {@code Show} instance + * may exist for the same type.

+ * + * @param the type of value that can be rendered + */ +@FunctionalInterface +public interface Show { + + /** + * Produces a textual representation of the supplied value. + * + *

The treatment of {@code null} is intentionally left to the concrete + * instance. The typeclass itself does not impose a global null policy.

+ * + * @param value the value to render + * @return the textual representation + */ + String show(A value); +} diff --git a/src/main/java/io/codeswarm/typeclasses/core/TypeClassFunctions.java b/src/main/java/io/codeswarm/typeclasses/core/TypeClassFunctions.java new file mode 100644 index 0000000..18ca640 --- /dev/null +++ b/src/main/java/io/codeswarm/typeclasses/core/TypeClassFunctions.java @@ -0,0 +1,32 @@ +package io.codeswarm.typeclasses.core; + +import java.util.Objects; + +/** + * Contains generic algorithms that consume typeclass instances explicitly. + * + *

Milestone 0.1.1 intentionally uses explicit dictionary passing instead + * of reflection, a registry, dependency injection, or automatic instance + * resolution. This keeps the mechanism visible and makes dependencies easy to + * reason about.

+ */ +public final class TypeClassFunctions { + + private TypeClassFunctions() { + throw new AssertionError("Utility class must not be instantiated"); + } + + /** + * Renders a value using the supplied {@link Show} instance. + * + * @param value the value to render + * @param show the typeclass instance that defines the rendering behaviour + * @param
the value type + * @return the representation produced by {@code show} + * @throws NullPointerException if {@code show} is {@code null} + */ + public static String show(final A value, final Show show) { + Objects.requireNonNull(show, "show must not be null"); + return show.show(value); + } +} diff --git a/src/main/java/io/codeswarm/typeclasses/examples/Person.java b/src/main/java/io/codeswarm/typeclasses/examples/Person.java new file mode 100644 index 0000000..b7928e8 --- /dev/null +++ b/src/main/java/io/codeswarm/typeclasses/examples/Person.java @@ -0,0 +1,26 @@ +package io.codeswarm.typeclasses.examples; + +import java.util.Objects; + +/** + * Example immutable domain value used to demonstrate that a model does not + * need to implement its typeclasses. + * + * @param name the person's non-null name + * @param age the person's age; must not be negative + */ +public record Person(String name, int age) { + + /** + * Creates a validated person value. + * + * @throws NullPointerException if {@code name} is {@code null} + * @throws IllegalArgumentException if {@code age} is negative + */ + public Person { + Objects.requireNonNull(name, "name must not be null"); + if (age < 0) { + throw new IllegalArgumentException("age must not be negative"); + } + } +} diff --git a/src/main/java/io/codeswarm/typeclasses/examples/PersonInstances.java b/src/main/java/io/codeswarm/typeclasses/examples/PersonInstances.java new file mode 100644 index 0000000..83830c8 --- /dev/null +++ b/src/main/java/io/codeswarm/typeclasses/examples/PersonInstances.java @@ -0,0 +1,28 @@ +package io.codeswarm.typeclasses.examples; + +import io.codeswarm.typeclasses.core.Show; + +/** + * Typeclass instances associated with the example {@link Person} domain type. + * + *

The instances are deliberately kept outside {@code Person}. This makes + * the domain model independent from presentation concerns and permits several + * valid representations to coexist.

+ */ +public final class PersonInstances { + + /** + * Compact representation containing only the person's name. + */ + public static final Show COMPACT_SHOW = Person::name; + + /** + * Verbose representation containing both the person's name and age. + */ + public static final Show VERBOSE_SHOW = + person -> "%s (%d)".formatted(person.name(), person.age()); + + private PersonInstances() { + throw new AssertionError("Instances holder must not be instantiated"); + } +} diff --git a/src/main/java/io/codeswarm/typeclasses/examples/ShowExample.java b/src/main/java/io/codeswarm/typeclasses/examples/ShowExample.java new file mode 100644 index 0000000..d02bfc6 --- /dev/null +++ b/src/main/java/io/codeswarm/typeclasses/examples/ShowExample.java @@ -0,0 +1,33 @@ +package io.codeswarm.typeclasses.examples; + +import io.codeswarm.typeclasses.core.TypeClassFunctions; +import io.codeswarm.typeclasses.instances.IntegerInstances; +import io.codeswarm.typeclasses.instances.LocalDateInstances; + +import java.time.LocalDate; + +/** + * Small executable demonstration of explicit typeclass instance passing. + */ +public final class ShowExample { + + private ShowExample() { + throw new AssertionError("Example class must not be instantiated"); + } + + /** + * Runs the milestone 0.1.1 example. + * + * @param args command-line arguments; ignored + */ + public static void main(final String[] args) { + final var person = new Person("Alice", 30); + + System.out.println(TypeClassFunctions.show(person, PersonInstances.COMPACT_SHOW)); + System.out.println(TypeClassFunctions.show(person, PersonInstances.VERBOSE_SHOW)); + System.out.println(TypeClassFunctions.show(42, IntegerInstances.SHOW)); + System.out.println(TypeClassFunctions.show( + LocalDate.of(2026, 9, 30), + LocalDateInstances.ISO_SHOW)); + } +} diff --git a/src/main/java/io/codeswarm/typeclasses/instances/IntegerInstances.java b/src/main/java/io/codeswarm/typeclasses/instances/IntegerInstances.java new file mode 100644 index 0000000..07e0566 --- /dev/null +++ b/src/main/java/io/codeswarm/typeclasses/instances/IntegerInstances.java @@ -0,0 +1,18 @@ +package io.codeswarm.typeclasses.instances; + +import io.codeswarm.typeclasses.core.Show; + +/** + * Standard typeclass instances for {@link Integer} values. + */ +public final class IntegerInstances { + + /** + * Renders an integer using its standard decimal representation. + */ + public static final Show SHOW = String::valueOf; + + private IntegerInstances() { + throw new AssertionError("Instances holder must not be instantiated"); + } +} diff --git a/src/main/java/io/codeswarm/typeclasses/instances/LocalDateInstances.java b/src/main/java/io/codeswarm/typeclasses/instances/LocalDateInstances.java new file mode 100644 index 0000000..c52cc55 --- /dev/null +++ b/src/main/java/io/codeswarm/typeclasses/instances/LocalDateInstances.java @@ -0,0 +1,27 @@ +package io.codeswarm.typeclasses.instances; + +import io.codeswarm.typeclasses.core.Show; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; + +/** + * Standard typeclass instances for {@link LocalDate} values. + * + *

This class also demonstrates an important advantage of the typeclass + * pattern: behaviour can be added to a type from the JDK without modifying or + * extending that type.

+ */ +public final class LocalDateInstances { + + /** + * Renders a date in ISO-8601 local-date format, for example + * {@code 2026-09-30}. + */ + public static final Show ISO_SHOW = + date -> DateTimeFormatter.ISO_LOCAL_DATE.format(date); + + private LocalDateInstances() { + throw new AssertionError("Instances holder must not be instantiated"); + } +} diff --git a/src/main/java/io/codeswarm/typeclasses/instances/StringInstances.java b/src/main/java/io/codeswarm/typeclasses/instances/StringInstances.java new file mode 100644 index 0000000..5fd4ad1 --- /dev/null +++ b/src/main/java/io/codeswarm/typeclasses/instances/StringInstances.java @@ -0,0 +1,18 @@ +package io.codeswarm.typeclasses.instances; + +import io.codeswarm.typeclasses.core.Show; + +/** + * Standard typeclass instances for {@link String} values. + */ +public final class StringInstances { + + /** + * Renders a string without adding formatting or quotation marks. + */ + public static final Show SHOW = value -> value; + + private StringInstances() { + throw new AssertionError("Instances holder must not be instantiated"); + } +} diff --git a/src/test/java/io/codeswarm/patterns/applicative/ApplicativeTest.java b/src/test/java/io/codeswarm/patterns/applicative/ApplicativeTest.java deleted file mode 100644 index 5d0aa5a..0000000 --- a/src/test/java/io/codeswarm/patterns/applicative/ApplicativeTest.java +++ /dev/null @@ -1,42 +0,0 @@ -package io.codeswarm.patterns.applicative; - -import io.codeswarm.patterns.functor.Functor; -import org.junit.jupiter.api.Test; -import java.util.function.Function; - -import static org.junit.jupiter.api.Assertions.*; - -public class ApplicativeTest { - - @Test - public void testPure() { - Integer value = 2; - Applicative applicative = new ApplicativeType<>(value); - assertEquals(value, Integer.valueOf(applicative.pure(value).toString())); - } - - @Test - public void testAp() { - Applicative applicative = new ApplicativeType<>(5); - ApplicativeType> transformer = new ApplicativeType<>(x -> "Result: " + x); - Applicative result = applicative.ap(transformer); - assertEquals("Result: 5", result.toString()); - } - - @Test - public void testMap() { - Integer value = 42; - Applicative applicative = new ApplicativeType<>(value); - Function mapper = String::valueOf; - Functor result = applicative.map(mapper); - assertEquals("42", result.toString()); - } - - @Test - public void testEq() { - Integer value = 42; - Applicative applicative = new ApplicativeType<>(value); - assertTrue(applicative.eq(42)); - assertFalse(applicative.eq(43)); - } -} diff --git a/src/test/java/io/codeswarm/patterns/apply/ApplyTest.java b/src/test/java/io/codeswarm/patterns/apply/ApplyTest.java deleted file mode 100644 index a6f7639..0000000 --- a/src/test/java/io/codeswarm/patterns/apply/ApplyTest.java +++ /dev/null @@ -1,39 +0,0 @@ -package io.codeswarm.patterns.apply; - -import org.junit.jupiter.api.Test; - -import java.util.function.Function; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -public class ApplyTest { - - @Test - public void applyApTest() { - ApplyType applyType = new ApplyType<>(5); - ApplyType> transformer = new ApplyType<>(x -> "Result: " + x); - - Apply result = applyType.ap(transformer); - assertEquals("ApplyType{value = Result: 5}", result.toString()); - } - - @Test - public void applyMapTest() { - ApplyType applyType = new ApplyType<>(10); - - Function mapper = x -> "Value: " + x; - Apply result = applyType.map(mapper); - - assertEquals("ApplyType{value = Value: 10}", result.toString()); - } - - @Test - public void applyEqTest() { - ApplyType applyType = new ApplyType<>(15); - Boolean isEqual = applyType.eq(15); - - assertTrue(isEqual); - } - -} diff --git a/src/test/java/io/codeswarm/patterns/either/EitherTest.java b/src/test/java/io/codeswarm/patterns/either/EitherTest.java deleted file mode 100644 index db4e5a4..0000000 --- a/src/test/java/io/codeswarm/patterns/either/EitherTest.java +++ /dev/null @@ -1,78 +0,0 @@ -package io.codeswarm.patterns.either; - -import org.junit.jupiter.api.Test; - -import java.util.Optional; - -import static org.junit.jupiter.api.Assertions.*; - -public class EitherTest { - - @Test - void testLeft() { - EitherType either = new EitherType<>(Optional.empty(), Optional.empty()); - Either left = either.left("LeftValue"); - - assertInstanceOf(EitherType.class, left); - assertEquals("LeftValue", ((EitherType) left).map(l -> l, r -> null)); - assertFalse(((EitherType) left).right.isPresent()); - } - - @Test - void testRight() { - EitherType either = new EitherType<>(Optional.empty(), Optional.empty()); - Either right = either.right(42); - - assertInstanceOf(EitherType.class, right); - assertEquals(Optional.of(42), Optional.ofNullable(right.map(l -> null, r -> r))); - assertFalse(((EitherType) right).left.isPresent()); - } - - @Test - void testMap() { - EitherType leftEither = new EitherType<>(Optional.of("LeftValue"), Optional.empty()); - EitherType rightEither = new EitherType<>(Optional.empty(), Optional.of(42)); - - // Test mapping left value - String leftResult = leftEither.map(l -> "Left: " + l, r -> "Right: " + r); - assertEquals("Left: LeftValue", leftResult); - - // Test mapping right value - String rightResult = rightEither.map(l -> "Left: " + l, r -> "Right: " + r); - assertEquals("Right: 42", rightResult); - } - - @Test - void testMapLeft() { - EitherType either = new EitherType<>(Optional.of("LeftValue"), Optional.of(42)); - - Either mappedLeft = either.mapLeft(l -> l + "Mapped"); - - assertEquals("LeftValueMapped", mappedLeft.map(l -> l, r -> null)); - } - - @Test - void testMapRight() { - EitherType either = new EitherType<>(Optional.of("LeftValue"), Optional.of(42)); - - Either mappedRight = either.mapRight(r -> r + 10); - - assertEquals(Optional.of(52), Optional.ofNullable(mappedRight.map(l -> null, r -> r))); - } - - @Test - void testApply() { - EitherType leftEither = new EitherType<>(Optional.of("LeftValue"), Optional.empty()); - EitherType rightEither = new EitherType<>(Optional.empty(), Optional.of(42)); - - // Test applying consumers to left value - StringBuilder resultLeft = new StringBuilder(); - leftEither.apply(l -> resultLeft.append("Left: ").append(l), r -> resultLeft.append("Right: ").append(r)); - assertEquals("Left: LeftValue", resultLeft.toString()); - - // Test applying consumers to right value - StringBuilder resultRight = new StringBuilder(); - rightEither.apply(l -> resultRight.append("Left: ").append(l), r -> resultRight.append("Right: ").append(r)); - assertEquals("Right: 42", resultRight.toString()); - } -} diff --git a/src/test/java/io/codeswarm/patterns/foldable/FoldableTest.java b/src/test/java/io/codeswarm/patterns/foldable/FoldableTest.java deleted file mode 100644 index 3abb1d4..0000000 --- a/src/test/java/io/codeswarm/patterns/foldable/FoldableTest.java +++ /dev/null @@ -1,58 +0,0 @@ -package io.codeswarm.patterns.foldable; - -import static org.junit.jupiter.api.Assertions.*; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import java.util.List; -import java.util.function.BiFunction; - -class FoldableTest { - - List intList; - FoldableType foldable; - - @BeforeEach - public void setUp() { - intList = List.of(1, 2, 3, 4, 5); - foldable = new FoldableType<>(0, 0); - } - - @Test - public void testFoldLeft() { - - // Define an accumulator that adds two integers - BiFunction accumulator = Integer::sum; - - // Test the foldLeft method - int result = foldable.foldLeft(intList, 0, accumulator); - assertEquals(15, result); - } - - @Test - public void testFoldRight() { - - // Define an accumulator that adds two integers - BiFunction accumulator = Integer::sum; - - // Test the foldRight method - int result = foldable.foldRight(intList, 0, accumulator); - assertEquals(15, result); - } - - @Test - public void testHead() { - - // Test the head method - Integer result = FoldableType.head(intList); - assertEquals(1, result); - } - - @Test - public void testTail() { - - // Test the tail method - List result = FoldableType.tail(intList); - assertEquals(List.of(2, 3, 4, 5), result); - } -} \ No newline at end of file diff --git a/src/test/java/io/codeswarm/patterns/functor/FunctorTest.java b/src/test/java/io/codeswarm/patterns/functor/FunctorTest.java deleted file mode 100644 index 3dbde04..0000000 --- a/src/test/java/io/codeswarm/patterns/functor/FunctorTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package io.codeswarm.patterns.functor; - -import org.junit.jupiter.api.Test; - -import java.util.Objects; -import java.util.function.Function; - -import static org.junit.jupiter.api.Assertions.*; - -public class FunctorTest { - - @Test - public void functorMapToStringTest() { - Functor functor = new FunctorType<>(35); - Function mapper = Objects::toString; - String result = functor.map(mapper).toString(); - assertEquals("FunctorType{value = 35}", result); - } - - @Test - public void functorEqTEst() { - Functor functor = new FunctorType<>("Functional design pattern"); - assertTrue(functor.eq("Functional design pattern")); - assertFalse(functor.eq("Functor")); - } -} diff --git a/src/test/java/io/codeswarm/patterns/invariant/InvariantTest.java b/src/test/java/io/codeswarm/patterns/invariant/InvariantTest.java deleted file mode 100644 index 12a8c53..0000000 --- a/src/test/java/io/codeswarm/patterns/invariant/InvariantTest.java +++ /dev/null @@ -1,35 +0,0 @@ -package io.codeswarm.patterns.invariant; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.*; - -public class InvariantTest { - - @Test - public void testImapWithMapper() { - InvariantType invariant = new InvariantType<>(42, null); - Invariant mappedInvariant = invariant.imap(Object::toString, null); - - assertNotNull(mappedInvariant); - assertEquals("InvariantType{value=42, reversedValue=null}", mappedInvariant.toString()); - } - - @Test - public void testImapWithReversedMapper() { - InvariantType invariant = new InvariantType<>(null, "42"); - Invariant mappedInvariant = invariant.imap(null, Integer::parseInt); - - assertNotNull(mappedInvariant); - assertEquals("InvariantType{value=null, reversedValue=42}", mappedInvariant.toString()); - } - - @Test - public void testImapWithBothMappersNull() { - InvariantType invariant = new InvariantType<>(42, "42"); - Invariant mappedInvariant = invariant.imap(null, null); - - assertNotNull(mappedInvariant); - assertNotNull(mappedInvariant.imap(null, null)); - } -} diff --git a/src/test/java/io/codeswarm/patterns/monad/MonadTest.java b/src/test/java/io/codeswarm/patterns/monad/MonadTest.java deleted file mode 100644 index d4dbd8a..0000000 --- a/src/test/java/io/codeswarm/patterns/monad/MonadTest.java +++ /dev/null @@ -1,48 +0,0 @@ -package io.codeswarm.patterns.monad; - -import org.junit.jupiter.api.Test; - -import java.util.Objects; -import java.util.function.Function; - -import static org.junit.jupiter.api.Assertions.*; - -public class MonadTest { - - @Test - public void testMap() { - Monad monad = new MonadType<>(5); - Function mapper = Objects::toString; - String result = monad.map(mapper).toString(); - assertEquals("MonadType{value = 5}", result); - } - - @Test - public void testEq() { - Monad monad = new MonadType<>(5); - assertTrue(monad.eq(5)); - assertFalse(monad.eq(10)); - } - - @Test - public void testPure() { - Monad monad = new MonadType<>(5); - Monad result = monad.pure(10); - assertEquals("MonadType{value = 10}", result.toString()); - } - -// @Test -// public void testAp() { -// Monad monad = new MonadType<>(5); -// Monad> transformer = new MonadType<>(x -> "Result: " + x); -// Monad result = monad.ap(transformer); -// assertEquals("Result: 5", result.toString()); -// } -// -// @Test -// public void testFlatMap() { -// Monad monad = new MonadType<>(5); -// Monad result = monad.flatMap(x -> new MonadType<>(x * 2)); -// assertEquals("MonadType{value = 10}", result.toString()); -// } -} diff --git a/src/test/java/io/codeswarm/patterns/monoid/MonoidTest.java b/src/test/java/io/codeswarm/patterns/monoid/MonoidTest.java deleted file mode 100644 index c073eda..0000000 --- a/src/test/java/io/codeswarm/patterns/monoid/MonoidTest.java +++ /dev/null @@ -1,31 +0,0 @@ -package io.codeswarm.patterns.monoid; - -import org.junit.jupiter.api.Test; - -import java.util.Optional; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -public class MonoidTest { - - @Test - public void monoidEmptyTest() { - Monoid monoid = new MonoidType<>(0, Integer::sum); - Optional result = monoid.empty(); - assertEquals(0, result.orElseThrow()); - } - - @Test - public void monoidCombineTest() { - Monoid monoid = new MonoidType<>(0, Integer::sum); - int result = monoid.combine(3, 4); - assertEquals(7, result); - } - - @Test - public void MonoidOfTest() { - Monoid monoid = MonoidType.of(0, Integer::sum); - Optional result = monoid.empty(); - assertEquals(0, result.orElseThrow()); - } -} diff --git a/src/test/java/io/codeswarm/patterns/semigroup/SemigroupTest.java b/src/test/java/io/codeswarm/patterns/semigroup/SemigroupTest.java deleted file mode 100644 index f87822f..0000000 --- a/src/test/java/io/codeswarm/patterns/semigroup/SemigroupTest.java +++ /dev/null @@ -1,34 +0,0 @@ -package io.codeswarm.patterns.semigroup; - -import org.junit.jupiter.api.Test; - -import java.util.function.BinaryOperator; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -public class SemigroupTest { - - @Test - public void semigroupIntegerTest() { - BinaryOperator additionOfInts = Integer::sum; - Semigroup semigroup = new SemigroupType<>(additionOfInts); - var result = semigroup.combine(2, 3); - assertEquals(5, result); - } - - @Test - public void semigroupDoubleTest() { - BinaryOperator multiplicationOfDoubles = (a,b) -> a * b; - Semigroup semigroup = new SemigroupType<>(multiplicationOfDoubles); - var result = semigroup.combine(4.0, 5.0); - assertEquals(20, result); - } - - @Test - public void semigroupStringTest() { - BinaryOperator concatenation = String::concat; - Semigroup semigroup = new SemigroupType<>(concatenation); - var result = semigroup.combine("Type ", "class"); - assertEquals("Type class", result); - } -} diff --git a/src/test/java/io/codeswarm/typeclasses/core/TypeClassFunctionsTest.java b/src/test/java/io/codeswarm/typeclasses/core/TypeClassFunctionsTest.java new file mode 100644 index 0000000..e85491f --- /dev/null +++ b/src/test/java/io/codeswarm/typeclasses/core/TypeClassFunctionsTest.java @@ -0,0 +1,45 @@ +package io.codeswarm.typeclasses.core; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * Tests for {@link TypeClassFunctions}. + */ +class TypeClassFunctionsTest { + + /** + * Verifies that a generic algorithm delegates rendering to the supplied + * dictionary rather than relying on the runtime type. + */ + @Test + void shouldUseProvidedShowInstance() { + final Show hexadecimal = value -> Integer.toHexString(value); + + assertEquals("ff", TypeClassFunctions.show(255, hexadecimal)); + } + + /** + * Verifies that a missing typeclass dictionary fails immediately with a + * clear error rather than producing a later, less useful failure. + */ + @Test + void shouldRejectNullShowInstance() { + assertThrows( + NullPointerException.class, + () -> TypeClassFunctions.show(1, null)); + } + + /** + * Verifies that null handling remains the responsibility of an individual + * typeclass instance. + */ + @Test + void shouldAllowInstanceSpecificNullPolicy() { + final Show nullAware = value -> value == null ? "" : value; + + assertEquals("", TypeClassFunctions.show(null, nullAware)); + } +} diff --git a/src/test/java/io/codeswarm/typeclasses/examples/PersonInstancesTest.java b/src/test/java/io/codeswarm/typeclasses/examples/PersonInstancesTest.java new file mode 100644 index 0000000..2ae9d82 --- /dev/null +++ b/src/test/java/io/codeswarm/typeclasses/examples/PersonInstancesTest.java @@ -0,0 +1,44 @@ +package io.codeswarm.typeclasses.examples; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Tests demonstrating that several typeclass instances may coexist for one + * domain type. + */ +class PersonInstancesTest { + + /** + * Verifies the compact person representation. + */ + @Test + void shouldUseCompactRepresentation() { + final var person = new Person("Alice", 30); + + assertEquals("Alice", PersonInstances.COMPACT_SHOW.show(person)); + } + + /** + * Verifies the verbose person representation. + */ + @Test + void shouldUseVerboseRepresentation() { + final var person = new Person("Alice", 30); + + assertEquals("Alice (30)", PersonInstances.VERBOSE_SHOW.show(person)); + } + + /** + * Demonstrates that choosing behaviour is a call-site decision rather than + * a responsibility permanently attached to the domain class. + */ + @Test + void shouldAllowMultipleInstancesForSameType() { + final var person = new Person("Alice", 30); + + assertEquals("Alice", PersonInstances.COMPACT_SHOW.show(person)); + assertEquals("Alice (30)", PersonInstances.VERBOSE_SHOW.show(person)); + } +} diff --git a/src/test/java/io/codeswarm/typeclasses/examples/PersonTest.java b/src/test/java/io/codeswarm/typeclasses/examples/PersonTest.java new file mode 100644 index 0000000..ac2c5c5 --- /dev/null +++ b/src/test/java/io/codeswarm/typeclasses/examples/PersonTest.java @@ -0,0 +1,40 @@ +package io.codeswarm.typeclasses.examples; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * Tests for the example {@link Person} domain value. + */ +class PersonTest { + + /** + * Verifies that valid data is retained by the record. + */ + @Test + void shouldCreateValidPerson() { + final var person = new Person("Alice", 30); + + assertEquals("Alice", person.name()); + assertEquals(30, person.age()); + } + + /** + * Verifies that invalid negative age values are rejected at the domain + * boundary. + */ + @Test + void shouldRejectNegativeAge() { + assertThrows(IllegalArgumentException.class, () -> new Person("Alice", -1)); + } + + /** + * Verifies that a person always has a name. + */ + @Test + void shouldRejectNullName() { + assertThrows(NullPointerException.class, () -> new Person(null, 30)); + } +} diff --git a/src/test/java/io/codeswarm/typeclasses/instances/StandardInstancesTest.java b/src/test/java/io/codeswarm/typeclasses/instances/StandardInstancesTest.java new file mode 100644 index 0000000..6a44df5 --- /dev/null +++ b/src/test/java/io/codeswarm/typeclasses/instances/StandardInstancesTest.java @@ -0,0 +1,39 @@ +package io.codeswarm.typeclasses.instances; + +import org.junit.jupiter.api.Test; + +import java.time.LocalDate; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Tests for the standard typeclass instances shipped in milestone 0.1.1. + */ +class StandardInstancesTest { + + /** + * Verifies standard decimal integer rendering. + */ + @Test + void shouldShowInteger() { + assertEquals("42", IntegerInstances.SHOW.show(42)); + } + + /** + * Verifies identity-like string rendering. + */ + @Test + void shouldShowString() { + assertEquals("typeclasses", StringInstances.SHOW.show("typeclasses")); + } + + /** + * Verifies that behaviour can be supplied for an unmodified JDK type. + */ + @Test + void shouldShowLocalDateInIsoFormat() { + assertEquals( + "2026-09-30", + LocalDateInstances.ISO_SHOW.show(LocalDate.of(2026, 9, 30))); + } +} From d22f4531aa6bca6716ece83c07f69708fd9f23a1 Mon Sep 17 00:00:00 2001 From: Michal Stawarski Date: Wed, 30 Sep 2026 10:16:44 +0200 Subject: [PATCH 4/7] Updates github configuration --- .github/workflows/ci.yml | 38 ++++++++++++++++++++++++++++++++++++++ .gitignore | 36 +++++++++++------------------------- 2 files changed, 49 insertions(+), 25 deletions(-) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..53ae31e --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,38 @@ +name: CI + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +permissions: + contents: read + +jobs: + build: + name: Java ${{ matrix.java }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + java: [ '21', '25', '27' ] + + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Set up Java + uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: ${{ matrix.java }} + cache: gradle + + - name: Set up Gradle + uses: gradle/actions/setup-gradle@v5 + with: + gradle-version: '9.8.0' + + - name: Verify + run: gradle clean check javadoc diff --git a/.gitignore b/.gitignore index 6594e67..944480e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,43 +1,29 @@ -.gradle +.gradle/ build/ -!gradle/wrapper/gradle-wrapper.jar -!**/src/main/**/build/ -!**/src/test/**/build/ +out/ -### IntelliJ IDEA ### -.idea/** -.idea/modules.xml -.idea/jarRepositories.xml -.idea/compiler.xml -.idea/libraries/ +# IntelliJ IDEA +.idea/ *.iws *.iml *.ipr -out/ -!**/src/main/**/out/ -!**/src/test/**/out/ -### Eclipse ### -.apt_generated +# Eclipse .classpath -.factorypath .project -.settings -.springBeans -.sts4-cache +.settings/ bin/ -!**/src/main/**/bin/ -!**/src/test/**/bin/ -### NetBeans ### +# NetBeans /nbproject/private/ /nbbuild/ /dist/ /nbdist/ /.nb-gradle/ -### VS Code ### +# VS Code .vscode/ -### Mac OS ### -.DS_Store \ No newline at end of file +# OS files +.DS_Store +Thumbs.db From bef36cbe2d6c3e63668a38b6bec6c996f1ef6a2a Mon Sep 17 00:00:00 2001 From: Michal Stawarski Date: Wed, 30 Sep 2026 10:17:03 +0200 Subject: [PATCH 5/7] Updates documentation --- CHANGELOG.md | 49 +++++++++ LICENSE | 201 +++++++++++++++++++++++++++++++++++ README.md | 191 +++++++++++++++++++++++---------- docs/ARCHITECTURE.md | 116 ++++++++++++++++++++ docs/IMPLEMENTATION_TASKS.md | 139 ++++++++++++++++++++++++ docs/ROADMAP.md | 78 ++++++++++++++ docs/TYPECLASSES.md | 79 ++++++++++++++ 7 files changed, 798 insertions(+), 55 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 LICENSE create mode 100644 docs/ARCHITECTURE.md create mode 100644 docs/IMPLEMENTATION_TASKS.md create mode 100644 docs/ROADMAP.md create mode 100644 docs/TYPECLASSES.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..a66b004 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,49 @@ +# Changelog + +All notable changes to this project are documented in this file. + +The project follows an incremental milestone model. Version 0.1 is treated as the original proof of concept; 0.1.1 is its first hardened release. + +## [0.1.1] - 2026-09-30 + +### Added + +- Java 21 toolchain baseline. +- `Show
` as the minimal first-order typeclass contract. +- `TypeClassFunctions.show(...)` demonstrating explicit dictionary passing. +- `StringInstances`, `IntegerInstances`, and `LocalDateInstances`. +- `Person` example record with domain validation. +- compact and verbose `Show` instances. +- executable `ShowExample`. +- JUnit tests for the core API, standard instances, domain validation, and multiple-instance behaviour. +- JaCoCo report generation. +- sources JAR and Javadoc JAR generation. +- GitHub Actions build matrix for Java 21, 25, and 27. +- `docs/ARCHITECTURE.md`. +- `docs/TYPECLASSES.md`. +- `docs/ROADMAP.md`. +- Apache License 2.0. + +### Changed + +- Replaced the original GitLab template README with project-specific documentation. +- Renamed the Gradle root project to `java-typeclasses`. +- Changed the Gradle plugin from `java` to `java-library`. +- Set project version to `0.1.1`. +- Updated the Gradle wrapper configuration target from Gradle 8.2 to 9.8.0. +- Standardised source packages under `io.codeswarm.typeclasses`. +- Separated reusable core abstractions, platform instances, and educational examples. +- Enabled compiler linting and UTF-8 consistently. + +### Removed + +- Placeholder GitLab onboarding documentation. +- Legacy/unused source structure from the proof-of-concept milestone. +- Unused IDE/build artefacts and obsolete generated files from the milestone source tree. +- Premature abstractions not required to explain milestone 0.1.1. + +### Design notes + +- Typeclass instances remain independent from domain classes. +- Instance dependencies are passed explicitly. +- No reflection, service locator, registry, dependency-injection framework, HKT encoding, or automatic derivation is introduced in this release. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index 6344b4f..8e46ebb 100644 --- a/README.md +++ b/README.md @@ -1,93 +1,174 @@ -# java_typeclasses +# Java Typeclasses +An educational implementation of the **typeclass pattern in modern Java**. +Milestone **0.1.1** is a foundation-hardening release. It keeps the code deliberately small and explicit while establishing the architecture, documentation, tests, and build conventions used by later milestones. -## Getting started +## Goals -To make it easy for you to get started with GitLab, here's a list of recommended next steps. +The project demonstrates how behaviour can be defined independently from domain types and passed explicitly to generic algorithms. -Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)! +Milestone 0.1.1 focuses on: -## Add your files +- explicit dictionary passing; +- separation of domain data from typeclass instances; +- multiple instances for the same domain type; +- instances for types that cannot be modified, such as JDK classes; +- a clean Java 21 baseline; +- tests, Javadoc, JaCoCo, and CI; +- a stable foundation for future algebraic typeclasses. -- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files -- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command: +It intentionally does **not** yet implement `Eq`, `Ord`, `Semigroup`, `Monoid`, higher-kinded type encodings, `Functor`, `Applicative`, `Monad`, instance registries, reflection-based resolution, or automatic derivation. Those belong to later milestones. +## What is a typeclass here? + +Java does not provide native typeclasses. This project models the pattern using ordinary generic interfaces whose implementations are values passed to generic algorithms. + +```java +@FunctionalInterface +public interface Show { + String show(A value); +} +``` + +A domain model does not implement `Show`: + +```java +public record Person(String name, int age) { +} +``` + +Instead, behaviour is defined separately: + +```java +public static final Show COMPACT_SHOW = Person::name; + +public static final Show VERBOSE_SHOW = + person -> "%s (%d)".formatted(person.name(), person.age()); +``` + +Both instances can coexist: + +```java +var person = new Person("Alice", 30); + +COMPACT_SHOW.show(person); // Alice +VERBOSE_SHOW.show(person); // Alice (30) +``` + +This separation is the key design decision of the project. + +## Explicit dictionary passing + +Generic code receives the behaviour it needs explicitly: + +```java +var result = TypeClassFunctions.show(person, PersonInstances.VERBOSE_SHOW); ``` -cd existing_repo -git remote add origin https://gitlab.com/stamich/java_typeclasses.git -git branch -M main -git push -uf origin main + +No reflection, global registry, dependency-injection framework, or hidden instance lookup is required. + +## Why not inheritance? + +With inheritance, behaviour is usually attached to the model itself. That makes it difficult or impossible to: + +- define a typeclass for a class you cannot modify; +- define several equally valid instances for one type; +- keep domain data independent from unrelated behaviour. + +For example, this project defines `Show` even though `LocalDate` is a JDK class. + +## Project structure + +```text +src/main/java/io/codeswarm/typeclasses/ +├── core/ +│ ├── Show.java +│ └── TypeClassFunctions.java +├── instances/ +│ ├── IntegerInstances.java +│ ├── LocalDateInstances.java +│ └── StringInstances.java +└── examples/ + ├── Person.java + ├── PersonInstances.java + └── ShowExample.java ``` -## Integrate with your tools +Supporting documentation is stored in `docs/`. -- [ ] [Set up project integrations](https://gitlab.com/stamich/java_typeclasses/-/settings/integrations) +## Requirements -## Collaborate with your team +- Java 21 or newer +- Gradle 9.8.0 -- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/) -- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html) -- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically) -- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/) -- [ ] [Set auto-merge](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html) +The project uses Java 21 as its source/toolchain baseline. CI verifies the project on Java 21, 25, and 27. -## Test and Deploy +## Build -Use the built-in continuous integration in GitLab. +```bash +gradle clean build +``` -- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html) -- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing (SAST)](https://docs.gitlab.com/ee/user/application_security/sast/) -- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html) -- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/) -- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html) +When this milestone is applied to the original repository, regenerate its existing Gradle Wrapper for 9.8.0 with `gradle wrapper --gradle-version 9.8.0`. The source archive includes the target wrapper properties, but not a regenerated binary wrapper JAR. -*** +## Tests and quality checks -# Editing this README +```bash +gradle clean check javadoc +``` + +JaCoCo reports are generated under: + +```text +build/reports/jacoco/test/html/ +``` -When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thanks to [makeareadme.com](https://www.makeareadme.com/) for this template. +Javadoc is generated under: -## Suggestions for a good README +```text +build/docs/javadoc/ +``` + +## Running the example -Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information. +Compile the project and run: -## Name -Choose a self-explaining name for your project. +```text +io.codeswarm.typeclasses.examples.ShowExample +``` -## Description -Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors. +Expected output: -## Badges -On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge. +```text +Alice +Alice (30) +42 +2026-09-30 +``` -## Visuals -Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method. +## Design principles -## Installation -Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection. +The milestone follows: -## Usage -Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README. +- **SOLID** — especially SRP and dependency inversion through explicit behavioural interfaces; +- **KISS** — no registry, reflection, DI framework, or HKT encoding yet; +- **DRY** — shared generic behaviour lives in small reusable abstractions; +- **YAGNI** — only abstractions required to explain the current milestone are implemented; +- **composition over inheritance** — behaviour is supplied as values rather than embedded into domain hierarchies. -## Support -Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc. +See [Architecture](docs/ARCHITECTURE.md) for details. ## Roadmap -If you have ideas for releases in the future, it is a good idea to list them in the README. -## Contributing -State if you are open to contributions and what your requirements are for accepting them. +The planned evolution is documented in [ROADMAP.md](docs/ROADMAP.md). -For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self. +The next milestone, **0.2**, is expected to introduce the first algebraic typeclasses such as `Eq`, `Ord`, `Semigroup`, and `Monoid` while keeping the explicit instance model established here. -You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser. +## Changelog -## Authors and acknowledgment -Show your appreciation to those who have contributed to the project. +See [CHANGELOG.md](CHANGELOG.md). ## License -For open source projects, say how it is licensed. -## Project status -If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers. +Apache License 2.0. See [LICENSE](LICENSE). diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..5f0ec79 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,116 @@ +# Architecture — milestone 0.1.1 + +## Purpose + +Milestone 0.1.1 establishes the smallest architecture that can explain and safely evolve the typeclass pattern in Java. + +## Dependency direction + +```text +Domain data Typeclass contract + Person Show + │ │ + │ │ + └──────────┐ ┌────────┘ + ▼ ▼ + Typeclass instance + Show + │ + ▼ + Generic algorithm + TypeClassFunctions.show +``` + +The domain model does not depend on the typeclass contract or on a concrete instance. + +## Packages + +### `io.codeswarm.typeclasses.core` + +Contains the minimal reusable API of the milestone: + +- `Show` — the typeclass contract; +- `TypeClassFunctions` — generic algorithms consuming typeclass instances. + +### `io.codeswarm.typeclasses.instances` + +Contains reusable instances for JDK/platform types. Instances are grouped by target type to avoid a global god-class. + +### `io.codeswarm.typeclasses.examples` + +Contains educational domain examples. Example-only code does not leak into the core package. + +## Architectural rules + +1. **Domain types do not implement typeclasses.** + A typeclass instance is separate from the represented model. + +2. **Dependencies are explicit.** + Generic algorithms receive instances as method arguments. + +3. **No hidden global resolution in 0.1.1.** + There is no service locator, registry, reflection, annotation processing, or dependency-injection container. + +4. **Instances are immutable values.** + Stateless instances are exposed as `public static final` values. + +5. **Core stays independent.** + The core package has no dependency on examples and no external runtime dependency. + +6. **Examples do not define architecture.** + `Person` exists to demonstrate the pattern, not as part of the reusable public abstraction. + +## SOLID / KISS / DRY / YAGNI + +### Single Responsibility Principle + +- `Show` defines one capability. +- instance holders define concrete behaviour. +- `Person` represents domain data. +- `TypeClassFunctions` hosts generic algorithms. + +### Open/Closed Principle + +New `Show` instances can be added without changing `A`, `Show`, or existing algorithms. + +### Liskov Substitution Principle + +Every `Show` implementation obeys the same minimal contract: it produces a string representation for an `A` according to its documented policy. + +### Interface Segregation Principle + +`Show` contains a single operation. Consumers are not forced to depend on unrelated capabilities. + +### Dependency Inversion Principle + +Generic algorithms depend on the `Show` abstraction and receive concrete dictionaries from callers. + +### KISS + +No automatic lookup mechanism is introduced. The central mechanism can be understood from one method call. + +### DRY + +Generic rendering logic is expressed once and reused with different instances. + +### YAGNI + +Abstractions planned for later milestones are not prematurely encoded in the core. + +## Null policy + +`Show` does not impose a global null policy. A particular instance may reject, support, or specially format null values. Generic infrastructure validates only its own required dependency: the `Show` dictionary itself. + +## Future evolution + +The architecture is intentionally prepared for additional first-order typeclasses without requiring a redesign: + +```text +Show +Eq +Ord +Semigroup +Monoid +``` + +Higher-kinded encodings and automatic instance resolution are postponed until their dedicated milestones. diff --git a/docs/IMPLEMENTATION_TASKS.md b/docs/IMPLEMENTATION_TASKS.md new file mode 100644 index 0000000..641db9d --- /dev/null +++ b/docs/IMPLEMENTATION_TASKS.md @@ -0,0 +1,139 @@ +# Milestone 0.1.1 — implementation tasks + +## Task 1 — Freeze the 0.1 baseline + +1. Tag or branch the existing repository as milestone 0.1. +2. Record the current build command and source tree. +3. Keep the refactor behaviour-focused: remove legacy files only after confirming they are not referenced. + +**Done when:** the original state can be recovered and compared with 0.1.1. + +## Task 2 — Clean repository metadata and build configuration + +1. Rename the Gradle root project from the generic `FunctionalJava` name to `java-typeclasses`. +2. Set `group` to `io.codeswarm` and version to `0.1.1`. +3. Replace the `java` plugin with `java-library`. +4. Configure the Java 21 toolchain. +5. Enable source and Javadoc JAR generation. +6. Upgrade the wrapper target to Gradle 9.8.0. +7. Replace the old JUnit BOM with JUnit 6.1.2. +8. Enable `-Xlint:all`, UTF-8, JaCoCo, and Javadoc verification. +9. Simplify `.gitignore` and remove obsolete generated/IDE files from version control. + +**Done when:** `clean`, `compileJava`, `test`, `jacocoTestReport`, and `javadoc` are defined by the build. + +## Task 3 — Establish package boundaries + +1. Create `io.codeswarm.typeclasses.core` for reusable abstractions. +2. Create `io.codeswarm.typeclasses.instances` for reusable platform/JDK instances. +3. Create `io.codeswarm.typeclasses.examples` for educational models and demos. +4. Mirror the package structure in `src/test/java`. +5. Remove old default-package or legacy package source files once their behaviour is represented in the new structure. + +**Done when:** no production code remains in the default package and dependency direction is core <- instances/examples. + +## Task 4 — Implement the minimal typeclass contract + +1. Add `Show` as a `@FunctionalInterface`. +2. Document its role and its deliberately instance-specific null policy. +3. Do not add a marker `TypeClass` parent interface; it would provide no behaviour in this milestone. + +**Done when:** `Show` contains exactly one abstract operation and no dependency on examples. + +## Task 5 — Implement explicit dictionary passing + +1. Add `TypeClassFunctions` as a non-instantiable utility class. +2. Add `show(A, Show)`. +3. Validate only the required dictionary dependency, not the represented value. +4. Do not add reflection, registries, `ServiceLoader`, DI, annotations, or hidden lookup. + +**Done when:** generic rendering depends only on `Show` and a caller-supplied instance. + +## Task 6 — Add standard instances + +1. Add `StringInstances.SHOW`. +2. Add `IntegerInstances.SHOW`. +3. Add `LocalDateInstances.ISO_SHOW` to prove that third-party/JDK types can receive external behaviour. +4. Keep each holder focused on a single represented type. + +**Done when:** standard instances are stateless immutable values and contain no unrelated algorithms. + +## Task 7 — Add an immutable domain example + +1. Implement `Person` as a record. +2. Validate `name` and `age` in the canonical constructor. +3. Keep `Person` independent from `Show`. +4. Add `PersonInstances.COMPACT_SHOW`. +5. Add `PersonInstances.VERBOSE_SHOW`. + +**Done when:** two `Show` implementations coexist without changing `Person`. + +## Task 8 — Add an executable demonstration + +1. Add `ShowExample`. +2. Demonstrate compact and verbose person rendering. +3. Demonstrate integer rendering. +4. Demonstrate `LocalDate` rendering. +5. Keep the example deterministic and free of external I/O other than stdout. + +**Done when:** the example produces the four documented output lines. + +## Task 9 — Add unit tests + +1. Test generic delegation in `TypeClassFunctions`. +2. Test null-dictionary rejection. +3. Test instance-specific null handling. +4. Test each standard instance. +5. Test `Person` invariants. +6. Test both `Person` instances and their coexistence. + +**Done when:** all public milestone behaviour has focused tests and examples are not used as substitutes for assertions. + +## Task 10 — Add documentation + +1. Replace the GitLab template README. +2. Add `ARCHITECTURE.md` with package boundaries, dependency direction, principles, and null policy. +3. Add `TYPECLASSES.md` explaining the Java encoding and dictionary passing. +4. Add `ROADMAP.md` covering 0.1 through 1.0. +5. Add `IMPLEMENTATION_TASKS.md`. +6. Add Javadoc to every public type and public member, plus useful documentation on test classes/methods for educational clarity. + +**Done when:** a new developer can understand the milestone without reading commit history. + +## Task 11 — Add CI + +1. Add a GitHub Actions workflow. +2. Test Java 21, 25, and 27. +3. Run `clean check javadoc`. +4. Keep workflow permissions read-only unless a future release job needs more. + +**Done when:** pull requests verify compilation, tests, coverage report generation, and Javadoc. + +## Task 12 — Record changes and remove obsolete files + +1. Add Apache-2.0 `LICENSE`. +2. Add `CHANGELOG.md` entry for 0.1.1. +3. Remove the placeholder GitLab README. +4. Remove unreferenced scripts and generated files from 0.1. +5. Remove legacy sources superseded by the new package structure. +6. Do not remove Gradle wrapper scripts/JAR when applying the milestone to the actual repository; regenerate them consistently for 9.8.0. + +**Done when:** repository root contains only build, source, documentation, CI, wrapper, and license files needed by the current project. + +## Task 13 — Final verification + +Run: + +```bash +gradle clean check javadoc +``` + +Then verify: + +- no compiler warnings introduced by project sources; +- all tests pass; +- JaCoCo HTML/XML reports exist; +- Javadoc is generated; +- no stale package/source files remain; +- README examples match actual APIs; +- version is exactly `0.1.1`. diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md new file mode 100644 index 0000000..2dcf3f4 --- /dev/null +++ b/docs/ROADMAP.md @@ -0,0 +1,78 @@ +# Roadmap + +The roadmap is incremental. Each milestone should remain buildable, documented, and understandable on its own. + +## 0.1 — Original proof of concept + +Initial experiments with typeclass-oriented functional programming in Java. + +## 0.1.1 — Foundation hardening + +- Java 21 baseline; +- modern Gradle build; +- clear package structure; +- explicit `Show` typeclass; +- explicit dictionary passing; +- standard and domain-specific instances; +- multiple instances for one domain type; +- JUnit tests; +- JaCoCo and Javadoc; +- GitHub Actions CI; +- architecture, typeclass, roadmap, and changelog documentation. + +## 0.2 — Basic algebraic typeclasses + +Planned: + +- `Eq`; +- `Ord`; +- `Semigroup`; +- `Monoid`; +- standard instances for selected JDK types; +- reusable generic algorithms consuming those instances. + +## 0.3 — Typeclass laws + +Planned: + +- reusable law definitions; +- property-based testing; +- associativity, identity, equality, and ordering laws; +- jqwik-based test support. + +## 0.4 — Algebraic data types + +Planned: + +- `Option`; +- `Either`; +- `Validated`; +- Java records, sealed interfaces, and pattern matching. + +## 0.5 — Higher-kinded type encoding + +Planned: + +- `Kind` or equivalent witness encoding; +- safe inject/project boundaries; +- documentation of Java type-system limitations. + +## 0.6 — Functor / Applicative / Monad + +Planned higher-order typeclasses based on the 0.5 encoding. + +## 0.7 — Foldable / Traverse + +Planned structural typeclasses and instances for supported ADTs. + +## 0.8 — Instance resolution experiments + +Planned investigation of explicit registries, type tokens, scoping, ambiguity detection, and a `summon`-style API. Explicit dictionary passing remains the reference semantics. + +## 0.9 — Automatic derivation + +Planned annotation-processing or source-generation experiments for selected typeclasses such as `Eq` and `Show`. + +## 1.0 — Stable educational release + +Planned stable public API, complete documentation, examples, compatibility policy, publishing metadata, and release automation. diff --git a/docs/TYPECLASSES.md b/docs/TYPECLASSES.md new file mode 100644 index 0000000..2813d98 --- /dev/null +++ b/docs/TYPECLASSES.md @@ -0,0 +1,79 @@ +# Typeclasses in Java + +## 1. Motivation + +A typeclass describes behaviour for a type without requiring that type to inherit from a particular interface. + +In milestone 0.1.1 the pattern has three pieces: + +1. a generic behavioural contract; +2. one or more instances of that contract for a concrete type; +3. generic algorithms that receive an instance explicitly. + +## 2. Contract + +```java +@FunctionalInterface +public interface Show { + String show(A value); +} +``` + +`Show` says only that values of `A` can be represented as text. It says nothing about how `A` is implemented. + +## 3. Instance + +```java +Show decimalShow = String::valueOf; +``` + +The instance is an ordinary Java value. It can be stored in a constant, passed as an argument, selected dynamically, or created locally. + +## 4. Dictionary passing + +A typeclass instance can be understood as a dictionary containing the operations required by a generic algorithm. + +```java +static String show(A value, Show dictionary) { + return dictionary.show(value); +} +``` + +The dependency is explicit and compile-time typed. + +## 5. Multiple instances + +Because behaviour is not attached to the model, several representations can coexist: + +```java +Show compact = Person::name; +Show verbose = person -> + "%s (%d)".formatted(person.name(), person.age()); +``` + +The caller chooses the behaviour appropriate to its context. + +## 6. Existing types + +Typeclasses can describe types that cannot be modified. The project defines an ISO rendering instance for `java.time.LocalDate` without subclassing or wrapping `LocalDate`. + +## 7. Typeclass vs inheritance + +Inheritance is useful when a capability is an intrinsic part of a model's public contract. A typeclass-like approach is useful when behaviour should be: + +- externally defined; +- independently evolvable; +- replaceable at the call site; +- available in several variants for one type. + +The approaches are complementary rather than universally interchangeable. + +## 8. Typeclass vs Strategy + +In Java, the runtime mechanics are close to the Strategy pattern: behaviour is represented by an interface and passed to a consumer. The typeclass interpretation adds a type-oriented viewpoint: the interface represents an operation family parameterised by a target type, and generic algorithms are written in terms of that capability. + +## 9. Java limitations + +Java has no native syntax corresponding to Scala `given`/`using`, Haskell typeclass instance search, or higher-kinded types. Milestone 0.1.1 intentionally does not hide those limitations. + +Later milestones may explore explicit encodings for higher-kinded types and instance resolution, but only after the basic first-order model is stable. From fa1a9f376c5188507520aa12c82f95a5fa84bc5c Mon Sep 17 00:00:00 2001 From: Michal Stawarski Date: Wed, 30 Sep 2026 10:23:27 +0200 Subject: [PATCH 6/7] Updates java code --- build.gradle | 2 +- src/main/java/io/codeswarm/typeclasses/core/Show.java | 2 +- .../io/codeswarm/typeclasses/core/TypeClassFunctions.java | 2 +- .../io/codeswarm/typeclasses/examples/PersonInstances.java | 2 +- .../codeswarm/typeclasses/instances/LocalDateInstances.java | 4 ++-- .../io/codeswarm/typeclasses/core/TypeClassFunctionsTest.java | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/build.gradle b/build.gradle index 8b459b2..ff7b2d7 100644 --- a/build.gradle +++ b/build.gradle @@ -10,7 +10,7 @@ description = 'Educational implementation of the typeclass pattern in modern Jav java { toolchain { - languageVersion = JavaLanguageVersion.of(25) + languageVersion = JavaLanguageVersion.of(21) } withSourcesJar() withJavadocJar() diff --git a/src/main/java/io/codeswarm/typeclasses/core/Show.java b/src/main/java/io/codeswarm/typeclasses/core/Show.java index 0c607a8..5e7e3cb 100644 --- a/src/main/java/io/codeswarm/typeclasses/core/Show.java +++ b/src/main/java/io/codeswarm/typeclasses/core/Show.java @@ -5,7 +5,7 @@ * textual representation. * *

{@code Show} demonstrates the central idea of the typeclass pattern: - * behaviour is defined independently from the domain type. A domain class does + * behavior is defined independently of the domain type. A domain class does * not have to implement this interface and more than one {@code Show} instance * may exist for the same type.

* diff --git a/src/main/java/io/codeswarm/typeclasses/core/TypeClassFunctions.java b/src/main/java/io/codeswarm/typeclasses/core/TypeClassFunctions.java index 18ca640..b9d4ad8 100644 --- a/src/main/java/io/codeswarm/typeclasses/core/TypeClassFunctions.java +++ b/src/main/java/io/codeswarm/typeclasses/core/TypeClassFunctions.java @@ -20,7 +20,7 @@ private TypeClassFunctions() { * Renders a value using the supplied {@link Show} instance. * * @param value the value to render - * @param show the typeclass instance that defines the rendering behaviour + * @param show the typeclass instance that defines the rendering behavior * @param
the value type * @return the representation produced by {@code show} * @throws NullPointerException if {@code show} is {@code null} diff --git a/src/main/java/io/codeswarm/typeclasses/examples/PersonInstances.java b/src/main/java/io/codeswarm/typeclasses/examples/PersonInstances.java index 83830c8..6158f5f 100644 --- a/src/main/java/io/codeswarm/typeclasses/examples/PersonInstances.java +++ b/src/main/java/io/codeswarm/typeclasses/examples/PersonInstances.java @@ -6,7 +6,7 @@ * Typeclass instances associated with the example {@link Person} domain type. * *

The instances are deliberately kept outside {@code Person}. This makes - * the domain model independent from presentation concerns and permits several + * the domain model independent of presentation concerns and permits several * valid representations to coexist.

*/ public final class PersonInstances { diff --git a/src/main/java/io/codeswarm/typeclasses/instances/LocalDateInstances.java b/src/main/java/io/codeswarm/typeclasses/instances/LocalDateInstances.java index c52cc55..fc9e25c 100644 --- a/src/main/java/io/codeswarm/typeclasses/instances/LocalDateInstances.java +++ b/src/main/java/io/codeswarm/typeclasses/instances/LocalDateInstances.java @@ -9,7 +9,7 @@ * Standard typeclass instances for {@link LocalDate} values. * *

This class also demonstrates an important advantage of the typeclass - * pattern: behaviour can be added to a type from the JDK without modifying or + * pattern: behavior can be added to a type from the JDK without modifying or * extending that type.

*/ public final class LocalDateInstances { @@ -19,7 +19,7 @@ public final class LocalDateInstances { * {@code 2026-09-30}. */ public static final Show ISO_SHOW = - date -> DateTimeFormatter.ISO_LOCAL_DATE.format(date); + DateTimeFormatter.ISO_LOCAL_DATE::format; private LocalDateInstances() { throw new AssertionError("Instances holder must not be instantiated"); diff --git a/src/test/java/io/codeswarm/typeclasses/core/TypeClassFunctionsTest.java b/src/test/java/io/codeswarm/typeclasses/core/TypeClassFunctionsTest.java index e85491f..89eadfe 100644 --- a/src/test/java/io/codeswarm/typeclasses/core/TypeClassFunctionsTest.java +++ b/src/test/java/io/codeswarm/typeclasses/core/TypeClassFunctionsTest.java @@ -16,7 +16,7 @@ class TypeClassFunctionsTest { */ @Test void shouldUseProvidedShowInstance() { - final Show hexadecimal = value -> Integer.toHexString(value); + final Show hexadecimal = Integer::toHexString; assertEquals("ff", TypeClassFunctions.show(255, hexadecimal)); } From c35db918a7ab38ab744fb5b55927cd914d7d993a Mon Sep 17 00:00:00 2001 From: Michal Stawarski Date: Wed, 30 Sep 2026 10:24:12 +0200 Subject: [PATCH 7/7] Updates java code --- .../java/io/codeswarm/typeclasses/examples/ShowExample.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/io/codeswarm/typeclasses/examples/ShowExample.java b/src/main/java/io/codeswarm/typeclasses/examples/ShowExample.java index d02bfc6..60dd9b1 100644 --- a/src/main/java/io/codeswarm/typeclasses/examples/ShowExample.java +++ b/src/main/java/io/codeswarm/typeclasses/examples/ShowExample.java @@ -18,9 +18,8 @@ private ShowExample() { /** * Runs the milestone 0.1.1 example. * - * @param args command-line arguments; ignored */ - public static void main(final String[] args) { + static void main() { final var person = new Person("Alice", 30); System.out.println(TypeClassFunctions.show(person, PersonInstances.COMPACT_SHOW));