From 51aa69e279ce0cfc9a326065c2b8432f5547dfcc Mon Sep 17 00:00:00 2001 From: dnl232 Date: Fri, 20 Feb 2026 20:20:01 +0100 Subject: [PATCH 1/8] 1. List overhaul 2. Fill CORE_CONCEPTS.md --- CHANGELOG.md | 5 ++- README.md | 19 ++------ docs/CORE_CONCEPTS.md | 37 ++++++++++++++++ .../dan323/functional/data/either/Left.java | 8 +++- .../com/dan323/functional/data/list/Cons.java | 10 ++++- .../functional/data/list/FiniteList.java | 5 +++ .../functional/data/list/Generating.java | 15 +++---- .../functional/data/list/InfiniteList.java | 14 ++++++ .../com/dan323/functional/data/list/List.java | 19 +++----- .../dan323/functional/data/list/Repeat.java | 8 +--- .../functional/data/list/ZipApplicative.java | 44 +++++++++++++------ .../dan323/functional/data/list/Zipped.java | 20 ++++----- .../dan323/functional/data/optional/Just.java | 2 +- .../functional/data/optional/Nothing.java | 3 +- .../dan323/functional/ListFunctorTest.java | 2 +- .../java/com/dan323/functional/ListTest.java | 2 +- 16 files changed, 133 insertions(+), 80 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index be61d3de..eb11902a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ ## CHANGELOG ## [Unreleased] +- Future notes. +### Features +- Overhaul List: Zipping Cons(head,tail) less lazy. Lazy only in infinite sublist. ### Docs - Documentation: Complete 16-week v2.0 production readiness plan (docs/PLAN.md) with detailed weekly breakdowns, parallel work streams, risk mitigation, and success metrics - Documentation: Update README.md to properly link and describe the v2.0 production readiness plan @@ -24,9 +27,9 @@ - Documentation: Add cross-references between BREAKING_CHANGES.md and API_AUDIT_v2.0.md for improved navigation - Documentation: Update README.md with v2.0 release planning section and migration guides - Documentation: Remove registry-related items from the v2.0 plan to align with annotation-processor-driven code generation. +- Documentation: Add initial documentation for core concepts in docs/CORE_CONCEPTS.md, covering functional and algebraic structures, annotations, type hierarchy, and minimal implementations. ### Infrastructure - Pipeline compiles in java 17, 21 and 24 -- Future notes. ### Code smells - Some sonar code smells removed by refactoring and code cleanup, but many remain due to the nature of the generated code and reflection utilities. Focus is on critical issues and maintainable code rather than eliminating all smells. diff --git a/README.md b/README.md index 5f894b8b..7e803bd5 100644 --- a/README.md +++ b/README.md @@ -60,22 +60,9 @@ Welcome to the complete documentation for **Functional Java by Annotations**. Th **Understanding the library** -- **[Core Concepts](docs/CORE_CONCEPTS.md)** - - Type hierarchy and structure - - Functional vs Algebraic structures - - Minimal implementations - - Type constructors and variance - -- **[Functional Structures](docs/FUNCTIONAL_STRUCTURES.md)** - - Functor, Applicative, Monad - - Foldable, Traversal - - Alternative - - Laws and requirements - -- **[Algebraic Structures](docs/ALGEBRAIC_STRUCTURES.md)** - - Semigroup, Monoid - - Ring - - Laws and examples +- **[Core Concepts](docs/CORE_CONCEPTS.md)**: Overview of functional and algebraic structures, annotations, type hierarchy, and minimal implementations. +- **[Functional Structures](docs/FUNCTIONAL_STRUCTURES.md)**: Details on Functor, Applicative, Monad, Foldable, Traversal, Alternative, and their laws. +- **[Algebraic Structures](docs/ALGEBRAIC_STRUCTURES.md)**: Details on Semigroup, Monoid, Ring, and their laws. ### 📖 Practical Guides diff --git a/docs/CORE_CONCEPTS.md b/docs/CORE_CONCEPTS.md index e69de29b..ae4990f3 100644 --- a/docs/CORE_CONCEPTS.md +++ b/docs/CORE_CONCEPTS.md @@ -0,0 +1,37 @@ +# Core Concepts + +## Introduction +This project leverages Java annotations to define and implement functional and algebraic structures. It enables a declarative approach to programming, making code more modular, composable, and easier to reason about. + +## Functional Structures +Functional structures are abstractions that allow operations on data in a context. Common examples include: +- **Functor**: Enables mapping functions over values in a context. +- **Applicative**: Allows applying functions wrapped in a context to values in a context. +- **Monad**: Supports chaining computations in a context. +- **Foldable**: Reduces structures to a single value. +- **Traversal**: Maps with effects across structures. +- **Alternative**: Provides choice operations. + +## Algebraic Structures +Algebraic structures define mathematical operations and laws: +- **Semigroup**: Associative operation (`op`). +- **Monoid**: Semigroup with an identity element (`unit`). +- **Ring**: Supports addition and multiplication operations. + +## Annotations +Annotations such as `@Functor`, `@Monad`, `@Semigroup`, etc., are used to mark types and specify minimal required methods. The annotation processor generates boilerplate code and enforces structure laws. + +## Type Hierarchy and Structure +Types are organized hierarchically, with interfaces like `Algebraic` and `Structure` serving as base types. Each structure requires minimal implementations (e.g., `map` for Functor, `op` for Semigroup). + +## Minimal Implementations +Each annotation specifies the minimal set of methods required. For example: +- `@Functor`: Requires `map`. +- `@Monoid`: Requires `op` and `unit`. +- `@Ring`: Requires both addition and multiplication operations. + +## Type Constructors and Variance +The library supports generic types and type constructors, enabling flexible and reusable abstractions. + +--- +For more details, see [Functional Structures](FUNCTIONAL_STRUCTURES.md) and [Algebraic Structures](ALGEBRAIC_STRUCTURES.md). diff --git a/example-functional/src/main/java/com/dan323/functional/data/either/Left.java b/example-functional/src/main/java/com/dan323/functional/data/either/Left.java index c629056a..da8cf70f 100644 --- a/example-functional/src/main/java/com/dan323/functional/data/either/Left.java +++ b/example-functional/src/main/java/com/dan323/functional/data/either/Left.java @@ -22,8 +22,12 @@ public C either(Function aToC, Function bToC) { @Override public boolean equals(Object obj) { if (obj == this) return true; - if (obj == null || obj.getClass() != getClass()) return false; - return Objects.equals(a, ((Left) obj).a); + if (obj == null) return false; + if (obj instanceof Left left) { + return Objects.equals(a, left.a); + } else { + return false; + } } @Override diff --git a/example-functional/src/main/java/com/dan323/functional/data/list/Cons.java b/example-functional/src/main/java/com/dan323/functional/data/list/Cons.java index bbf8122e..cc2f23eb 100644 --- a/example-functional/src/main/java/com/dan323/functional/data/list/Cons.java +++ b/example-functional/src/main/java/com/dan323/functional/data/list/Cons.java @@ -2,6 +2,7 @@ import com.dan323.functional.data.optional.Maybe; +import java.util.function.BiFunction; import java.util.function.Function; /** @@ -23,8 +24,8 @@ final class Cons extends InfiniteList { } @Override - public Maybe head() { - return Maybe.of(head); + public A getHead() { + return head; } @Override @@ -37,6 +38,11 @@ public InfiniteList map(Function mapping) { return new Cons<>(mapping.apply(head), tail().map(mapping)); } + public InfiniteList zipBy(BiFunction mapper, InfiniteList list) { + var newHead = mapper.apply(head,list.getHead()); + return new Cons<>(newHead, (InfiniteList) ZipApplicative.liftA2(mapper, tail, list.tail())); + } + @Override public String toString() { return "[" + head + "," + tail + "]"; diff --git a/example-functional/src/main/java/com/dan323/functional/data/list/FiniteList.java b/example-functional/src/main/java/com/dan323/functional/data/list/FiniteList.java index 9ace6606..7bdfac9e 100644 --- a/example-functional/src/main/java/com/dan323/functional/data/list/FiniteList.java +++ b/example-functional/src/main/java/com/dan323/functional/data/list/FiniteList.java @@ -43,6 +43,11 @@ static FiniteList of(A... a) { } } + @Override + default FiniteList cons(A head) { + return cons(head, this); + } + @SafeVarargs private static FiniteList of(int n, A... a) { if (n >= a.length) { diff --git a/example-functional/src/main/java/com/dan323/functional/data/list/Generating.java b/example-functional/src/main/java/com/dan323/functional/data/list/Generating.java index edc5a95b..7f109660 100644 --- a/example-functional/src/main/java/com/dan323/functional/data/list/Generating.java +++ b/example-functional/src/main/java/com/dan323/functional/data/list/Generating.java @@ -1,8 +1,5 @@ package com.dan323.functional.data.list; -import com.dan323.functional.data.optional.Maybe; - -import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.UnaryOperator; @@ -20,8 +17,8 @@ final class Generating extends InfiniteList { } @Override - public Maybe head() { - return Maybe.of(head); + public A getHead() { + return head; } @Override @@ -36,17 +33,17 @@ public InfiniteList map(Function mapping) { protected static final class GeneratingMapped extends InfiniteList { - private final List originalList; + private final InfiniteList originalList; private final Function mapping; - GeneratingMapped(List originalList, Function mapping){ + GeneratingMapped(InfiniteList originalList, Function mapping){ this.mapping = mapping; this.originalList = originalList; } @Override - public Maybe head() { - return originalList.head().maybe(h -> Maybe.of(mapping.apply(h)), Maybe.of()); + public B getHead() { + return mapping.apply(originalList.getHead()); } @Override diff --git a/example-functional/src/main/java/com/dan323/functional/data/list/InfiniteList.java b/example-functional/src/main/java/com/dan323/functional/data/list/InfiniteList.java index c8838844..87453ba8 100644 --- a/example-functional/src/main/java/com/dan323/functional/data/list/InfiniteList.java +++ b/example-functional/src/main/java/com/dan323/functional/data/list/InfiniteList.java @@ -1,5 +1,7 @@ package com.dan323.functional.data.list; +import com.dan323.functional.data.optional.Maybe; + import java.util.function.Function; /** @@ -15,6 +17,18 @@ public sealed abstract class InfiniteList implements List permits Cons, Ge @Override abstract public InfiniteList map(Function mapping); + abstract public A getHead(); + + @Override + public Maybe head() { + return Maybe.of(getHead()); + } + + @Override + public InfiniteList cons(A head) { + return new Cons<>(head, this); + } + /** * Infinite lists are incomparable in a finite amount of time * diff --git a/example-functional/src/main/java/com/dan323/functional/data/list/List.java b/example-functional/src/main/java/com/dan323/functional/data/list/List.java index 10c1eecc..e7ecfabd 100644 --- a/example-functional/src/main/java/com/dan323/functional/data/list/List.java +++ b/example-functional/src/main/java/com/dan323/functional/data/list/List.java @@ -11,23 +11,14 @@ public sealed interface List permits FiniteList, InfiniteList { List tail(); + List cons(A head); + List map(Function mapping); - static List generate(A first, UnaryOperator generator){ + static InfiniteList generate(A first, UnaryOperator generator){ return new Generating<>(first, generator); } - static List cons(A first, List tail) { - if (first == null || tail == null) { - throw new IllegalArgumentException("No input can be null"); - } - if (tail instanceof FiniteList finiteTail){ - return new FinCons<>(first, finiteTail); - } else { - return new Cons<>(first, (InfiniteList) tail); - } - } - default FiniteList limit(int k){ return head().maybe(h -> limitWithHead(h, k), FiniteList.nil()); } @@ -40,11 +31,11 @@ private FiniteList limitWithHead(A h, int k){ } } - static List nil() { + static FiniteList nil() { return (FiniteList) Nil.NIL; } - static List repeat(A a){ + static InfiniteList repeat(A a){ return new Repeat<>(a); } } \ No newline at end of file diff --git a/example-functional/src/main/java/com/dan323/functional/data/list/Repeat.java b/example-functional/src/main/java/com/dan323/functional/data/list/Repeat.java index 79176c22..a46bbbdd 100644 --- a/example-functional/src/main/java/com/dan323/functional/data/list/Repeat.java +++ b/example-functional/src/main/java/com/dan323/functional/data/list/Repeat.java @@ -1,9 +1,5 @@ package com.dan323.functional.data.list; -import com.dan323.functional.data.optional.Maybe; - -import java.util.Objects; -import java.util.function.BiFunction; import java.util.function.Function; /** @@ -21,8 +17,8 @@ final class Repeat extends InfiniteList { } @Override - public Maybe head() { - return Maybe.of(element); + public A getHead() { + return element; } @Override diff --git a/example-functional/src/main/java/com/dan323/functional/data/list/ZipApplicative.java b/example-functional/src/main/java/com/dan323/functional/data/list/ZipApplicative.java index a22f2164..688c6317 100644 --- a/example-functional/src/main/java/com/dan323/functional/data/list/ZipApplicative.java +++ b/example-functional/src/main/java/com/dan323/functional/data/list/ZipApplicative.java @@ -2,8 +2,6 @@ import com.dan323.functional.annotation.Applicative; import com.dan323.functional.annotation.funcs.IApplicative; -import com.dan323.functional.data.optional.Maybe; -import com.dan323.functional.data.optional.MaybeMonad; import java.util.function.BiFunction; import java.util.function.Function; @@ -15,19 +13,39 @@ public static List pure(A a) { return List.repeat(a); } + /** + * Depending on the type of the lists, we can have different implementations of liftA2. If one of the lists is finite, we can stop when we reach the end of it. If one of the lists is infinite, we can stop when we reach the end of the other list. If both lists are infinite, we can zip them together. + *
+ * If one of the lists is finite, we apply the bifunction sequentially until the shortest list is empty. + *
+ * If one of the lists is constantly repeating an element, we can create a mapped list by fixing + * in the bifunction the element of the repeating list and applying it to the other list. + * + * @param fun bifunction to apply to the elements of the lists + * @param lstA first list + * @param lstB second list + * @return a list containing the results of applying the bifunction to the elements of the lists + * @param
type of elements in the first list + * @param type of elements in the second list + * @param type of elements in the resulting list + */ public static List liftA2(BiFunction fun, List lstA, List lstB) { - if (lstA instanceof FiniteList finiteList) { - return finiteList.head().maybe(a -> lstB.head().maybe(b -> FiniteList.cons(fun.apply(a, b), (FiniteList) liftA2(fun, lstA.tail(), lstB.tail())), FiniteList.nil()), FiniteList.nil()); - } else if (lstB instanceof FiniteList finiteList) { - return finiteList.head().maybe(a -> lstA.head().maybe(b -> FiniteList.cons(fun.apply(b, a), (FiniteList) liftA2(fun, lstA.tail(), lstB.tail())), FiniteList.nil()), FiniteList.nil()); - } else if (lstA instanceof Repeat repeat) { - Maybe> aux = MaybeMonad.map(repeat.head(), a -> ((B b) -> fun.apply(a, b))); - return aux.maybe(lstB::map, List.nil()); - } else if (lstB instanceof Repeat repeat) { - Maybe> aux = MaybeMonad.map(repeat.head(), a -> ((A b) -> fun.apply(b, a))); - return aux.maybe(lstA::map, List.nil()); + if (lstA instanceof FiniteList) { + return lstA.head().maybe(a -> lstB.head().maybe(b -> FiniteList.cons(fun.apply(a, b), (FiniteList) liftA2(fun, lstA.tail(), lstB.tail())), FiniteList.nil()), FiniteList.nil()); + } else if (lstB instanceof FiniteList) { + return lstB.head().maybe(a -> lstA.head().maybe(b -> FiniteList.cons(fun.apply(b, a), (FiniteList) liftA2(fun, lstA.tail(), lstB.tail())), FiniteList.nil()), FiniteList.nil()); + } else if (lstA instanceof Repeat ra) { + Function auxFun = b -> fun.apply(ra.getHead(), b); + return lstB.map(auxFun); + } else if (lstB instanceof Repeat rb) { + Function auxFun = b -> fun.apply(b, rb.getHead()); + return lstA.map(auxFun); + } else if (lstA instanceof Cons ca) { + return ca.zipBy(fun, (InfiniteList) lstB); + } else if (lstB instanceof Cons cb) { + return cb.zipBy((a,b) -> fun.apply(b,a), (InfiniteList) lstA); } else { - return new Zipped<>(lstA, fun, lstB); + return new Zipped<>((InfiniteList) lstA, fun, (InfiniteList) lstB); } } diff --git a/example-functional/src/main/java/com/dan323/functional/data/list/Zipped.java b/example-functional/src/main/java/com/dan323/functional/data/list/Zipped.java index 8fb9ee66..ba0291c9 100644 --- a/example-functional/src/main/java/com/dan323/functional/data/list/Zipped.java +++ b/example-functional/src/main/java/com/dan323/functional/data/list/Zipped.java @@ -1,29 +1,20 @@ package com.dan323.functional.data.list; -import com.dan323.functional.data.optional.Maybe; -import com.dan323.functional.data.optional.MaybeMonad; - import java.util.function.BiFunction; import java.util.function.Function; final class Zipped extends InfiniteList { - private final List first; - private final List second; + private final InfiniteList first; + private final InfiniteList second; private final BiFunction zipper; - Zipped(List first, BiFunction zipper, List second) { + Zipped(InfiniteList first, BiFunction zipper, InfiniteList second) { this.first = first; this.zipper = zipper; this.second = second; } - @Override - public Maybe head() { - var aux = MaybeMonad.map(first.head(), x -> (Function) ((B b) -> zipper.apply(x, b))); - return MaybeMonad.fapply(aux, second.head()); - } - @Override public InfiniteList tail() { return new Zipped<>(first.tail(), zipper, second.tail()); @@ -34,4 +25,9 @@ public InfiniteList map(Function mapping) { return new Zipped<>(first, zipper.andThen(mapping), second); } + @Override + public C getHead() { + return zipper.apply(first.getHead(), second.getHead()); + } + } diff --git a/example-functional/src/main/java/com/dan323/functional/data/optional/Just.java b/example-functional/src/main/java/com/dan323/functional/data/optional/Just.java index 374aa8a5..70a14ae4 100644 --- a/example-functional/src/main/java/com/dan323/functional/data/optional/Just.java +++ b/example-functional/src/main/java/com/dan323/functional/data/optional/Just.java @@ -29,7 +29,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash(element); + return Objects.hash(element) * 5 + 7; } @Override diff --git a/example-functional/src/main/java/com/dan323/functional/data/optional/Nothing.java b/example-functional/src/main/java/com/dan323/functional/data/optional/Nothing.java index d2057883..77087ce0 100644 --- a/example-functional/src/main/java/com/dan323/functional/data/optional/Nothing.java +++ b/example-functional/src/main/java/com/dan323/functional/data/optional/Nothing.java @@ -1,6 +1,5 @@ package com.dan323.functional.data.optional; -import java.util.Objects; import java.util.function.Function; public final class Nothing implements Maybe { @@ -22,7 +21,7 @@ public boolean equals(Object obj) { @Override public int hashCode(){ - return 90; + return 7; } @Override diff --git a/example-functional/src/test/java/com/dan323/functional/ListFunctorTest.java b/example-functional/src/test/java/com/dan323/functional/ListFunctorTest.java index 13ca5c7f..4b8d7a13 100644 --- a/example-functional/src/test/java/com/dan323/functional/ListFunctorTest.java +++ b/example-functional/src/test/java/com/dan323/functional/ListFunctorTest.java @@ -11,7 +11,7 @@ public class ListFunctorTest { @Test public void constInfiniteMap() { - var sol = ZipApplicative.map(List.cons(5, List.repeat(6)), x -> x * 2); + var sol = ZipApplicative.map(List.repeat(6).cons(5), x -> x * 2); assertEquals(FiniteList.of(10, 12, 12), sol.limit(3)); } diff --git a/example-functional/src/test/java/com/dan323/functional/ListTest.java b/example-functional/src/test/java/com/dan323/functional/ListTest.java index 622cf26f..7a25aca9 100644 --- a/example-functional/src/test/java/com/dan323/functional/ListTest.java +++ b/example-functional/src/test/java/com/dan323/functional/ListTest.java @@ -17,7 +17,7 @@ public void nilTail() { @Test public void consToFiniteList() { - var sol = List.cons(5, FiniteList.of(1, 2)); + var sol = FiniteList.of(1, 2).cons(5); assertEquals(FiniteList.of(5, 1, 2), sol); } } From 4be18195b2e64d290af3f223d9be031670691c10 Mon Sep 17 00:00:00 2001 From: dnl232 Date: Fri, 20 Feb 2026 20:23:40 +0100 Subject: [PATCH 2/8] zip cons --- .../java/com/dan323/functional/ZipApplicativeTest.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/example-functional/src/test/java/com/dan323/functional/ZipApplicativeTest.java b/example-functional/src/test/java/com/dan323/functional/ZipApplicativeTest.java index f0b6567c..5d4248de 100644 --- a/example-functional/src/test/java/com/dan323/functional/ZipApplicativeTest.java +++ b/example-functional/src/test/java/com/dan323/functional/ZipApplicativeTest.java @@ -27,6 +27,15 @@ public void zipSum() { assertEquals(FiniteList.of(6, 7, 8), lst); } + @Test + public void zipCons() { + ZipApplicative zipApplicative = new ZipApplicative(); + List lst = (List) ApplicativeUtil.liftA2(zipApplicative, Integer::sum, List.repeat(5).cons(7), List.generate(1, x -> x + 1)); + assertEquals(FiniteList.of(8, 7, 8, 9), lst.limit(4)); + lst = (List) ApplicativeUtil.liftA2(zipApplicative, Integer::sum, List.generate(1, x -> x + 1).cons(3).cons(5), List.repeat(5)); + assertEquals(FiniteList.of(10, 8, 6, 7, 8), lst.limit(5)); + } + @Test public void zipGenerating() { ZipApplicative zipApplicative = new ZipApplicative(); From ebb7e8ffdc0dde2bfa3f593a5a38652961d5a3fa Mon Sep 17 00:00:00 2001 From: dnl232 Date: Fri, 20 Feb 2026 20:26:56 +0100 Subject: [PATCH 3/8] sonar --- .../src/main/java/com/dan323/functional/data/list/Cons.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/example-functional/src/main/java/com/dan323/functional/data/list/Cons.java b/example-functional/src/main/java/com/dan323/functional/data/list/Cons.java index cc2f23eb..2535a3de 100644 --- a/example-functional/src/main/java/com/dan323/functional/data/list/Cons.java +++ b/example-functional/src/main/java/com/dan323/functional/data/list/Cons.java @@ -1,7 +1,5 @@ package com.dan323.functional.data.list; -import com.dan323.functional.data.optional.Maybe; - import java.util.function.BiFunction; import java.util.function.Function; From 97e02fbdf620e4b03a594864cdb5b93b61236db6 Mon Sep 17 00:00:00 2001 From: dnl232 Date: Fri, 20 Feb 2026 20:27:24 +0100 Subject: [PATCH 4/8] sonar --- .../java/com/dan323/functional/data/list/InfiniteList.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/example-functional/src/main/java/com/dan323/functional/data/list/InfiniteList.java b/example-functional/src/main/java/com/dan323/functional/data/list/InfiniteList.java index 87453ba8..7743299a 100644 --- a/example-functional/src/main/java/com/dan323/functional/data/list/InfiniteList.java +++ b/example-functional/src/main/java/com/dan323/functional/data/list/InfiniteList.java @@ -12,12 +12,12 @@ public sealed abstract class InfiniteList implements List permits Cons, Generating, Generating.GeneratingMapped, Repeat, Zipped { @Override - abstract public InfiniteList tail(); + public abstract InfiniteList tail(); @Override - abstract public InfiniteList map(Function mapping); + public abstract InfiniteList map(Function mapping); - abstract public A getHead(); + public abstract A getHead(); @Override public Maybe head() { From 0d692be5b194f6cae71768136dc4e271db406a44 Mon Sep 17 00:00:00 2001 From: dnl232 Date: Fri, 20 Feb 2026 23:54:24 +0100 Subject: [PATCH 5/8] cycled lists --- .../dan323/functional/data/list/Cycle.java | 31 +++++++++++++++++++ .../data/list/FiniteListFunctional.java | 2 +- .../functional/data/list/InfiniteList.java | 2 +- .../com/dan323/functional/data/list/List.java | 10 ++++++ .../functional/data/list/ListUtils.java | 6 +++- .../dan323/functional/ListFunctorTest.java | 6 ++++ 6 files changed, 54 insertions(+), 3 deletions(-) create mode 100644 example-functional/src/main/java/com/dan323/functional/data/list/Cycle.java diff --git a/example-functional/src/main/java/com/dan323/functional/data/list/Cycle.java b/example-functional/src/main/java/com/dan323/functional/data/list/Cycle.java new file mode 100644 index 00000000..65699a18 --- /dev/null +++ b/example-functional/src/main/java/com/dan323/functional/data/list/Cycle.java @@ -0,0 +1,31 @@ +package com.dan323.functional.data.list; + +import java.util.function.Function; + +public final class Cycle extends InfiniteList { + + private final FiniteList cycle; + + Cycle(FiniteList cycle) { + if (cycle == null || cycle.length() == 0) { + throw new IllegalArgumentException("The list to be cycled must not be null or empty."); + } + this.cycle = cycle; + } + + @Override + public A getHead() { + return cycle.head().maybe(Function.identity(), null); + } + + @Override + public InfiniteList tail() { + var init = cycle.tail(); + return ListUtils.concat(init, this); + } + + @Override + public Cycle map(Function mapping) { + return new Cycle<>(cycle.map(mapping)); + } +} diff --git a/example-functional/src/main/java/com/dan323/functional/data/list/FiniteListFunctional.java b/example-functional/src/main/java/com/dan323/functional/data/list/FiniteListFunctional.java index 652126a1..281d582f 100644 --- a/example-functional/src/main/java/com/dan323/functional/data/list/FiniteListFunctional.java +++ b/example-functional/src/main/java/com/dan323/functional/data/list/FiniteListFunctional.java @@ -43,7 +43,7 @@ public static AlternativeMonoid, FiniteList> getAlternative } public static FiniteList map(FiniteList finiteList, Function mapping) { - return finiteList.head().maybe(h -> FiniteList.cons(mapping.apply(h), map(finiteList.tail(), mapping)), FiniteList.nil()); + return finiteList.map(mapping); } public static FiniteList disjunction(FiniteList op1, FiniteList op2) { diff --git a/example-functional/src/main/java/com/dan323/functional/data/list/InfiniteList.java b/example-functional/src/main/java/com/dan323/functional/data/list/InfiniteList.java index 7743299a..b0745ded 100644 --- a/example-functional/src/main/java/com/dan323/functional/data/list/InfiniteList.java +++ b/example-functional/src/main/java/com/dan323/functional/data/list/InfiniteList.java @@ -9,7 +9,7 @@ * * @param type of elements in the list */ -public sealed abstract class InfiniteList implements List permits Cons, Generating, Generating.GeneratingMapped, Repeat, Zipped { +public sealed abstract class InfiniteList implements List permits Cons, Cycle, Generating, Generating.GeneratingMapped, Repeat, Zipped { @Override public abstract InfiniteList tail(); diff --git a/example-functional/src/main/java/com/dan323/functional/data/list/List.java b/example-functional/src/main/java/com/dan323/functional/data/list/List.java index e7ecfabd..de720f7c 100644 --- a/example-functional/src/main/java/com/dan323/functional/data/list/List.java +++ b/example-functional/src/main/java/com/dan323/functional/data/list/List.java @@ -31,6 +31,16 @@ private FiniteList limitWithHead(A h, int k){ } } + static List cycle(FiniteList lst){ + if (lst.length() == 0) { + return nil(); + } else if (lst.length() == 1) { + return lst.head().maybe(List::repeat,nil()); + } else { + return new Cycle<>(lst); + } + } + static FiniteList nil() { return (FiniteList) Nil.NIL; } diff --git a/example-functional/src/main/java/com/dan323/functional/data/list/ListUtils.java b/example-functional/src/main/java/com/dan323/functional/data/list/ListUtils.java index 74ec41e5..3709f14b 100644 --- a/example-functional/src/main/java/com/dan323/functional/data/list/ListUtils.java +++ b/example-functional/src/main/java/com/dan323/functional/data/list/ListUtils.java @@ -7,6 +7,10 @@ public static FiniteList reverse(FiniteList lst) { } public static FiniteList concat(FiniteList a, FiniteList b){ - return a.head().maybe(h -> FiniteList.cons(h, concat(a.tail(), b)), b); + return a.head().maybe(h -> concat(a.tail(), b).cons(h), b); + } + + public static InfiniteList concat(FiniteList a, InfiniteList b){ + return a.head().maybe(h -> concat(a.tail(), b).cons(h), b); } } diff --git a/example-functional/src/test/java/com/dan323/functional/ListFunctorTest.java b/example-functional/src/test/java/com/dan323/functional/ListFunctorTest.java index 4b8d7a13..129eb839 100644 --- a/example-functional/src/test/java/com/dan323/functional/ListFunctorTest.java +++ b/example-functional/src/test/java/com/dan323/functional/ListFunctorTest.java @@ -33,6 +33,12 @@ public void repeatListFunctor() { assertEquals(List.repeat(15).limit(10), sol.limit(10)); } + @Test + public void cycleListFunctor() { + var sol = ZipApplicative.map(List.cycle(FiniteList.of(1,2,3)), x -> x * 3); + assertEquals(List.cycle(FiniteList.of(3,6,9)).limit(10), sol.limit(10)); + } + @Test public void generatingListFunctor() { // Since this type of lists cannot be checked in all their entries, we will test the first 10 elements From 60114930917eae2520274c445da77a8a192a4099 Mon Sep 17 00:00:00 2001 From: Daniel <32780500+dan323@users.noreply.github.com> Date: Fri, 20 Feb 2026 23:56:27 +0100 Subject: [PATCH 6/8] Update docs/CORE_CONCEPTS.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/CORE_CONCEPTS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/CORE_CONCEPTS.md b/docs/CORE_CONCEPTS.md index ae4990f3..6d515e1a 100644 --- a/docs/CORE_CONCEPTS.md +++ b/docs/CORE_CONCEPTS.md @@ -19,7 +19,7 @@ Algebraic structures define mathematical operations and laws: - **Ring**: Supports addition and multiplication operations. ## Annotations -Annotations such as `@Functor`, `@Monad`, `@Semigroup`, etc., are used to mark types and specify minimal required methods. The annotation processor generates boilerplate code and enforces structure laws. +Annotations such as `@Functor`, `@Monad`, `@Semigroup`, etc., are used to mark types and specify minimal required methods. The annotation processor generates boilerplate code and validates that the required methods are present. ## Type Hierarchy and Structure Types are organized hierarchically, with interfaces like `Algebraic` and `Structure` serving as base types. Each structure requires minimal implementations (e.g., `map` for Functor, `op` for Semigroup). From 04ef4dbc0dd9dc7f5b102ff12853859a294587b2 Mon Sep 17 00:00:00 2001 From: Daniel <32780500+dan323@users.noreply.github.com> Date: Fri, 20 Feb 2026 23:56:36 +0100 Subject: [PATCH 7/8] Update CHANGELOG.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb11902a..2a5853cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ## [Unreleased] - Future notes. ### Features -- Overhaul List: Zipping Cons(head,tail) less lazy. Lazy only in infinite sublist. +- Overhaul list zipping: make Cons(head, tail) less lazy, preserving laziness only for infinite sublists. ### Docs - Documentation: Complete 16-week v2.0 production readiness plan (docs/PLAN.md) with detailed weekly breakdowns, parallel work streams, risk mitigation, and success metrics - Documentation: Update README.md to properly link and describe the v2.0 production readiness plan From 17355afd84871c1329031239d83001e696529453 Mon Sep 17 00:00:00 2001 From: dnl232 Date: Fri, 20 Feb 2026 23:59:45 +0100 Subject: [PATCH 8/8] cycled lists --- .../java/com/dan323/functional/data/list/Cycle.java | 10 +++++----- .../com/dan323/functional/data/list/InfiniteList.java | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/example-functional/src/main/java/com/dan323/functional/data/list/Cycle.java b/example-functional/src/main/java/com/dan323/functional/data/list/Cycle.java index 65699a18..2dedeead 100644 --- a/example-functional/src/main/java/com/dan323/functional/data/list/Cycle.java +++ b/example-functional/src/main/java/com/dan323/functional/data/list/Cycle.java @@ -4,28 +4,28 @@ public final class Cycle extends InfiniteList { - private final FiniteList cycle; + private final FiniteList cycled; Cycle(FiniteList cycle) { if (cycle == null || cycle.length() == 0) { throw new IllegalArgumentException("The list to be cycled must not be null or empty."); } - this.cycle = cycle; + this.cycled = cycle; } @Override public A getHead() { - return cycle.head().maybe(Function.identity(), null); + return cycled.head().maybe(Function.identity(), null); } @Override public InfiniteList tail() { - var init = cycle.tail(); + var init = cycled.tail(); return ListUtils.concat(init, this); } @Override public Cycle map(Function mapping) { - return new Cycle<>(cycle.map(mapping)); + return new Cycle<>(cycled.map(mapping)); } } diff --git a/example-functional/src/main/java/com/dan323/functional/data/list/InfiniteList.java b/example-functional/src/main/java/com/dan323/functional/data/list/InfiniteList.java index b0745ded..a6e723ab 100644 --- a/example-functional/src/main/java/com/dan323/functional/data/list/InfiniteList.java +++ b/example-functional/src/main/java/com/dan323/functional/data/list/InfiniteList.java @@ -9,7 +9,7 @@ * * @param type of elements in the list */ -public sealed abstract class InfiniteList implements List permits Cons, Cycle, Generating, Generating.GeneratingMapped, Repeat, Zipped { +public abstract sealed class InfiniteList implements List permits Cons, Cycle, Generating, Generating.GeneratingMapped, Repeat, Zipped { @Override public abstract InfiniteList tail();