diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1b87ca1..4747676 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -4,9 +4,7 @@ on: push: branches: - main - pull_request: - workflow_dispatch: inputs: dryRun: @@ -15,35 +13,60 @@ on: required: false env: - # Currently no way to detect automatically DEFAULT_BRANCH: main - GO_VERSION: 1.21.6 # renovate: datasource=golang-version depName=golang + LATEST_GO_VERSION: 1.21.6 # renovate: datasource=golang-version depName=golang NODE_VERSION: 22 DRY_RUN: true jobs: - build: + build-v1: + name: Build v1 (Go ${{ matrix.go-version }}) runs-on: ubuntu-latest strategy: matrix: - go-version: [ '1.20.x', '1.21.x', '1.22.x', '1.23.x'] + go-version: ['1.20.x', '1.21.x', '1.22.x', '1.23.x'] + fail-fast: false # Continue with other versions if one fails steps: # full checkout for semantic-release - uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0 with: fetch-depth: 0 - - name: Set up go ${{ matrix.go-version }} + - name: Set up Go ${{ matrix.go-version }} uses: actions/setup-go@v5 with: go-version: ${{ matrix.go-version }} - - - name: Tests + cache: true # Enable Go module caching + - name: Run tests run: | go mod tidy go test -v ./... + build-v2: + name: Build v2 (Go ${{ matrix.go-version }}) + runs-on: ubuntu-latest + strategy: + matrix: + go-version: ['1.24.x'] + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + - name: Set up Go ${{ matrix.go-version }} + uses: actions/setup-go@v5 + with: + go-version: ${{ matrix.go-version }} + cache: true # Enable Go module caching + - name: Run tests + run: | + cd v2 + go mod tidy + go test -v ./... + release: - needs: [build] + name: Release + needs: + - build-v1 + - build-v2 if: github.repository == 'IBM/fp-go' && github.event_name != 'pull_request' runs-on: ubuntu-latest timeout-minutes: 15 @@ -51,7 +74,6 @@ jobs: contents: write issues: write pull-requests: write - steps: # full checkout for semantic-release - name: Full checkout @@ -63,26 +85,27 @@ jobs: uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: node-version: ${{ env.NODE_VERSION }} + cache: 'npm' # Enable npm caching - - name: Set up go ${{env.GO_VERSION}} + - name: Set up Go uses: actions/setup-go@v5 with: - go-version: ${{env.GO_VERSION}} + go-version: ${{ env.LATEST_GO_VERSION }} + cache: true # Enable Go module caching - # The dry-run evaluation is only made for non PR events. Manual trigger w/dryRun true, main branch and any tagged branches will set DRY run to false - - name: Check dry run + - name: Determine release mode + id: release-mode run: | - if [[ "${{github.event_name}}" == "workflow_dispatch" && "${{ github.event.inputs.dryRun }}" != "true" ]]; then - echo "DRY_RUN=false" >> $GITHUB_ENV - elif [[ "${{github.ref}}" == "refs/heads/${{env.DEFAULT_BRANCH}}" ]]; then + if [[ "${{ github.event_name }}" == "workflow_dispatch" && "${{ github.event.inputs.dryRun }}" != "true" ]]; then echo "DRY_RUN=false" >> $GITHUB_ENV - elif [[ "${{github.ref}}" =~ ^refs/heads/v[0-9]+(\.[0-9]+)?$ ]]; then + elif [[ "${{ github.ref }}" == "refs/heads/${{ env.DEFAULT_BRANCH }}" ]]; then + echo "DRY_RUN=false" >> $GITHUB_ENV + elif [[ "${{ github.ref }}" =~ ^refs/heads/v[0-9]+(\.[0-9]+)?$ ]]; then echo "DRY_RUN=false" >> $GITHUB_ENV fi - - name: Semantic Release + - name: Run semantic release run: | - npx -p conventional-changelog-conventionalcommits -p semantic-release semantic-release --dry-run ${{env.DRY_RUN}} + npx -p conventional-changelog-conventionalcommits -p semantic-release semantic-release --dry-run ${{ env.DRY_RUN }} env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/internal/bracket/bracket.go b/internal/bracket/bracket.go index c8663a0..f90cb50 100644 --- a/internal/bracket/bracket.go +++ b/internal/bracket/bracket.go @@ -13,7 +13,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package file +package bracket import ( F "github.com/IBM/fp-go/function" diff --git a/v2/README.md b/v2/README.md new file mode 100644 index 0000000..b1a0899 --- /dev/null +++ b/v2/README.md @@ -0,0 +1,43 @@ +# Functional programming library for golang V2 + +Go 1.24 introduces [generic type aliases](https://github.com/golang/go/issues/46477) which are leveraged by V2. + +## ⚠️ Breaking Changes + +- use of [generic type aliases](https://github.com/golang/go/issues/46477) which requires [go1.24](https://tip.golang.org/doc/go1.24) +- order of generic type arguments adjusted such that types that _cannot_ be inferred by the method argument come first, e.g. in the `Ap` methods +- monadic operations for `Pair` operate on the second argument, to be compatible with the [Haskell](https://hackage.haskell.org/package/TypeCompose-0.9.14/docs/Data-Pair.html) definition + +## Simplifications + +- use type aliases to get rid of namespace imports for type declarations, e.g. instead of + +```go +import ( + ET "github.com/IBM/fp-go/v2/either" +) + +func doSth() ET.Either[error, string] { + ... +} +``` + +you can declare your type once + +```go +import ( + "github.com/IBM/fp-go/v2/either" +) + +type Either[A any] = either.Either[error, A] +``` + +and then use it across your codebase + +```go +func doSth() Either[string] { + ... +} +``` + +- library implementation does no longer need to use the `generic` subpackage, this simplifies reading and understanding of the code \ No newline at end of file diff --git a/v2/array/any.go b/v2/array/any.go new file mode 100644 index 0000000..ce653ab --- /dev/null +++ b/v2/array/any.go @@ -0,0 +1,46 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package array + +import ( + G "github.com/IBM/fp-go/v2/array/generic" +) + +// AnyWithIndex tests if any of the elements in the array matches the predicate. +// The predicate receives both the index and the element. +// Returns true if at least one element satisfies the predicate, false otherwise. +// +// Example: +// +// hasEvenAtEvenIndex := array.AnyWithIndex(func(i, x int) bool { +// return i%2 == 0 && x%2 == 0 +// }) +// result := hasEvenAtEvenIndex([]int{1, 3, 4, 5}) // true (4 is at index 2) +func AnyWithIndex[A any](pred func(int, A) bool) func([]A) bool { + return G.AnyWithIndex[[]A](pred) +} + +// Any tests if any of the elements in the array matches the predicate. +// Returns true if at least one element satisfies the predicate, false otherwise. +// Returns false for an empty array. +// +// Example: +// +// hasEven := array.Any(func(x int) bool { return x%2 == 0 }) +// result := hasEven([]int{1, 3, 4, 5}) // true +func Any[A any](pred func(A) bool) func([]A) bool { + return G.Any[[]A](pred) +} diff --git a/v2/array/any_test.go b/v2/array/any_test.go new file mode 100644 index 0000000..5a15fa3 --- /dev/null +++ b/v2/array/any_test.go @@ -0,0 +1,30 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package array + +import ( + "testing" + + F "github.com/IBM/fp-go/v2/function" + "github.com/stretchr/testify/assert" +) + +func TestAny(t *testing.T) { + anyBool := Any(F.Identity[bool]) + + assert.True(t, anyBool(From(false, true, false))) + assert.False(t, anyBool(From(false, false, false))) +} diff --git a/v2/array/array.go b/v2/array/array.go new file mode 100644 index 0000000..3da48a6 --- /dev/null +++ b/v2/array/array.go @@ -0,0 +1,439 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package array + +import ( + G "github.com/IBM/fp-go/v2/array/generic" + EM "github.com/IBM/fp-go/v2/endomorphism" + F "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/internal/array" + M "github.com/IBM/fp-go/v2/monoid" + O "github.com/IBM/fp-go/v2/option" + "github.com/IBM/fp-go/v2/tuple" +) + +// From constructs an array from a set of variadic arguments +func From[A any](data ...A) []A { + return G.From[[]A](data...) +} + +// MakeBy returns a `Array` of length `n` with element `i` initialized with `f(i)`. +func MakeBy[F ~func(int) A, A any](n int, f F) []A { + return G.MakeBy[[]A](n, f) +} + +// Replicate creates a `Array` containing a value repeated the specified number of times. +func Replicate[A any](n int, a A) []A { + return G.Replicate[[]A](n, a) +} + +// MonadMap applies a function to each element of an array, returning a new array with the results. +// This is the monadic version of Map that takes the array as the first parameter. +func MonadMap[A, B any](as []A, f func(a A) B) []B { + return G.MonadMap[[]A, []B](as, f) +} + +// MonadMapRef applies a function to a pointer to each element of an array, returning a new array with the results. +// This is useful when you need to access elements by reference without copying. +func MonadMapRef[A, B any](as []A, f func(a *A) B) []B { + count := len(as) + bs := make([]B, count) + for i := count - 1; i >= 0; i-- { + bs[i] = f(&as[i]) + } + return bs +} + +// MapWithIndex applies a function to each element and its index in an array, returning a new array with the results. +func MapWithIndex[A, B any](f func(int, A) B) func([]A) []B { + return G.MapWithIndex[[]A, []B](f) +} + +// Map applies a function to each element of an array, returning a new array with the results. +// This is the curried version that returns a function. +// +// Example: +// +// double := array.Map(func(x int) int { return x * 2 }) +// result := double([]int{1, 2, 3}) // [2, 4, 6] +func Map[A, B any](f func(a A) B) func([]A) []B { + return G.Map[[]A, []B, A, B](f) +} + +// MapRef applies a function to a pointer to each element of an array, returning a new array with the results. +// This is the curried version that returns a function. +func MapRef[A, B any](f func(a *A) B) func([]A) []B { + return F.Bind2nd(MonadMapRef[A, B], f) +} + +func filterRef[A any](fa []A, pred func(a *A) bool) []A { + var result []A + count := len(fa) + for i := 0; i < count; i++ { + a := fa[i] + if pred(&a) { + result = append(result, a) + } + } + return result +} + +func filterMapRef[A, B any](fa []A, pred func(a *A) bool, f func(a *A) B) []B { + var result []B + count := len(fa) + for i := 0; i < count; i++ { + a := fa[i] + if pred(&a) { + result = append(result, f(&a)) + } + } + return result +} + +// Filter returns a new array with all elements from the original array that match a predicate +func Filter[A any](pred func(A) bool) EM.Endomorphism[[]A] { + return G.Filter[[]A](pred) +} + +// FilterWithIndex returns a new array with all elements from the original array that match a predicate +func FilterWithIndex[A any](pred func(int, A) bool) EM.Endomorphism[[]A] { + return G.FilterWithIndex[[]A](pred) +} + +// FilterRef returns a new array with all elements from the original array that match a predicate operating on pointers. +func FilterRef[A any](pred func(*A) bool) EM.Endomorphism[[]A] { + return F.Bind2nd(filterRef[A], pred) +} + +// MonadFilterMap maps an array with a function that returns an Option and keeps only the Some values. +// This is the monadic version that takes the array as the first parameter. +func MonadFilterMap[A, B any](fa []A, f func(A) O.Option[B]) []B { + return G.MonadFilterMap[[]A, []B](fa, f) +} + +// MonadFilterMapWithIndex maps an array with a function that takes an index and returns an Option, +// keeping only the Some values. This is the monadic version that takes the array as the first parameter. +func MonadFilterMapWithIndex[A, B any](fa []A, f func(int, A) O.Option[B]) []B { + return G.MonadFilterMapWithIndex[[]A, []B](fa, f) +} + +// FilterMap maps an array with an iterating function that returns an [O.Option] and it keeps only the Some values discarding the Nones. +func FilterMap[A, B any](f func(A) O.Option[B]) func([]A) []B { + return G.FilterMap[[]A, []B](f) +} + +// FilterMapWithIndex maps an array with an iterating function that returns an [O.Option] and it keeps only the Some values discarding the Nones. +func FilterMapWithIndex[A, B any](f func(int, A) O.Option[B]) func([]A) []B { + return G.FilterMapWithIndex[[]A, []B](f) +} + +// FilterChain maps an array with an iterating function that returns an [O.Option] of an array. It keeps only the Some values discarding the Nones and then flattens the result. +func FilterChain[A, B any](f func(A) O.Option[[]B]) func([]A) []B { + return G.FilterChain[[]A](f) +} + +// FilterMapRef filters an array using a predicate on pointers and maps the matching elements using a function on pointers. +func FilterMapRef[A, B any](pred func(a *A) bool, f func(a *A) B) func([]A) []B { + return func(fa []A) []B { + return filterMapRef(fa, pred, f) + } +} + +func reduceRef[A, B any](fa []A, f func(B, *A) B, initial B) B { + current := initial + count := len(fa) + for i := 0; i < count; i++ { + current = f(current, &fa[i]) + } + return current +} + +// Reduce folds an array from left to right, applying a function to accumulate a result. +// +// Example: +// +// sum := array.Reduce(func(acc, x int) int { return acc + x }, 0) +// result := sum([]int{1, 2, 3, 4, 5}) // 15 +func Reduce[A, B any](f func(B, A) B, initial B) func([]A) B { + return G.Reduce[[]A](f, initial) +} + +// ReduceWithIndex folds an array from left to right with access to the index, +// applying a function to accumulate a result. +func ReduceWithIndex[A, B any](f func(int, B, A) B, initial B) func([]A) B { + return G.ReduceWithIndex[[]A](f, initial) +} + +// ReduceRight folds an array from right to left, applying a function to accumulate a result. +func ReduceRight[A, B any](f func(A, B) B, initial B) func([]A) B { + return G.ReduceRight[[]A](f, initial) +} + +// ReduceRightWithIndex folds an array from right to left with access to the index, +// applying a function to accumulate a result. +func ReduceRightWithIndex[A, B any](f func(int, A, B) B, initial B) func([]A) B { + return G.ReduceRightWithIndex[[]A](f, initial) +} + +// ReduceRef folds an array from left to right using pointers to elements, +// applying a function to accumulate a result. +func ReduceRef[A, B any](f func(B, *A) B, initial B) func([]A) B { + return func(as []A) B { + return reduceRef(as, f, initial) + } +} + +// Append adds an element to the end of an array, returning a new array. +func Append[A any](as []A, a A) []A { + return G.Append(as, a) +} + +// IsEmpty checks if an array has no elements. +func IsEmpty[A any](as []A) bool { + return G.IsEmpty(as) +} + +// IsNonEmpty checks if an array has at least one element. +func IsNonEmpty[A any](as []A) bool { + return len(as) > 0 +} + +// Empty returns an empty array of type A. +func Empty[A any]() []A { + return G.Empty[[]A]() +} + +// Zero returns an empty array of type A (alias for Empty). +func Zero[A any]() []A { + return Empty[A]() +} + +// Of constructs a single element array +func Of[A any](a A) []A { + return G.Of[[]A](a) +} + +// MonadChain applies a function that returns an array to each element and flattens the results. +// This is the monadic version that takes the array as the first parameter (also known as FlatMap). +func MonadChain[A, B any](fa []A, f func(a A) []B) []B { + return G.MonadChain[[]A, []B](fa, f) +} + +// Chain applies a function that returns an array to each element and flattens the results. +// This is the curried version (also known as FlatMap). +// +// Example: +// +// duplicate := array.Chain(func(x int) []int { return []int{x, x} }) +// result := duplicate([]int{1, 2, 3}) // [1, 1, 2, 2, 3, 3] +func Chain[A, B any](f func(A) []B) func([]A) []B { + return G.Chain[[]A, []B](f) +} + +// MonadAp applies an array of functions to an array of values, producing all combinations. +// This is the monadic version that takes both arrays as parameters. +func MonadAp[B, A any](fab []func(A) B, fa []A) []B { + return G.MonadAp[[]B](fab, fa) +} + +// Ap applies an array of functions to an array of values, producing all combinations. +// This is the curried version. +func Ap[B, A any](fa []A) func([]func(A) B) []B { + return G.Ap[[]B, []func(A) B](fa) +} + +// Match performs pattern matching on an array, calling onEmpty if empty or onNonEmpty if not. +func Match[A, B any](onEmpty func() B, onNonEmpty func([]A) B) func([]A) B { + return G.Match[[]A](onEmpty, onNonEmpty) +} + +// MatchLeft performs pattern matching on an array, calling onEmpty if empty or onNonEmpty with head and tail if not. +func MatchLeft[A, B any](onEmpty func() B, onNonEmpty func(A, []A) B) func([]A) B { + return G.MatchLeft[[]A](onEmpty, onNonEmpty) +} + +// Tail returns all elements except the first, wrapped in an Option. +// Returns None if the array is empty. +func Tail[A any](as []A) O.Option[[]A] { + return G.Tail(as) +} + +// Head returns the first element of an array, wrapped in an Option. +// Returns None if the array is empty. +func Head[A any](as []A) O.Option[A] { + return G.Head(as) +} + +// First returns the first element of an array, wrapped in an Option (alias for Head). +// Returns None if the array is empty. +func First[A any](as []A) O.Option[A] { + return G.First(as) +} + +// Last returns the last element of an array, wrapped in an Option. +// Returns None if the array is empty. +func Last[A any](as []A) O.Option[A] { + return G.Last(as) +} + +// PrependAll inserts a separator before each element of an array. +func PrependAll[A any](middle A) EM.Endomorphism[[]A] { + return func(as []A) []A { + count := len(as) + dst := count * 2 + result := make([]A, dst) + for i := count - 1; i >= 0; i-- { + dst-- + result[dst] = as[i] + dst-- + result[dst] = middle + } + return result + } +} + +// Intersperse inserts a separator between each element of an array. +// +// Example: +// +// result := array.Intersperse(0)([]int{1, 2, 3}) // [1, 0, 2, 0, 3] +func Intersperse[A any](middle A) EM.Endomorphism[[]A] { + prepend := PrependAll(middle) + return func(as []A) []A { + if IsEmpty(as) { + return as + } + return prepend(as)[1:] + } +} + +// Intercalate inserts a separator between elements and concatenates them using a Monoid. +func Intercalate[A any](m M.Monoid[A]) func(A) func([]A) A { + return func(middle A) func([]A) A { + return Match(m.Empty, F.Flow2(Intersperse(middle), ConcatAll[A](m))) + } +} + +// Flatten converts a nested array into a flat array by concatenating all inner arrays. +// +// Example: +// +// result := array.Flatten([][]int{{1, 2}, {3, 4}, {5}}) // [1, 2, 3, 4, 5] +func Flatten[A any](mma [][]A) []A { + return G.Flatten(mma) +} + +// Slice extracts a subarray from index low (inclusive) to high (exclusive). +func Slice[A any](low, high int) func(as []A) []A { + return array.Slice[[]A](low, high) +} + +// Lookup returns the element at the specified index, wrapped in an Option. +// Returns None if the index is out of bounds. +func Lookup[A any](idx int) func([]A) O.Option[A] { + return G.Lookup[[]A](idx) +} + +// UpsertAt returns a function that inserts or updates an element at a specific index. +// If the index is out of bounds, the element is appended. +func UpsertAt[A any](a A) EM.Endomorphism[[]A] { + return G.UpsertAt[[]A](a) +} + +// Size returns the number of elements in an array. +func Size[A any](as []A) int { + return G.Size(as) +} + +// MonadPartition splits an array into two arrays based on a predicate. +// The first array contains elements for which the predicate returns false, +// the second contains elements for which it returns true. +func MonadPartition[A any](as []A, pred func(A) bool) tuple.Tuple2[[]A, []A] { + return G.MonadPartition(as, pred) +} + +// Partition creates two new arrays out of one, the left result contains the elements +// for which the predicate returns false, the right one those for which the predicate returns true +func Partition[A any](pred func(A) bool) func([]A) tuple.Tuple2[[]A, []A] { + return G.Partition[[]A](pred) +} + +// IsNil checks if the array is set to nil +func IsNil[A any](as []A) bool { + return array.IsNil(as) +} + +// IsNonNil checks if the array is set to nil +func IsNonNil[A any](as []A) bool { + return array.IsNonNil(as) +} + +// ConstNil returns a nil array +func ConstNil[A any]() []A { + return array.ConstNil[[]A]() +} + +// SliceRight extracts a subarray from the specified start index to the end. +func SliceRight[A any](start int) EM.Endomorphism[[]A] { + return G.SliceRight[[]A](start) +} + +// Copy creates a shallow copy of the array +func Copy[A any](b []A) []A { + return G.Copy(b) +} + +// Clone creates a deep copy of the array using the provided endomorphism to clone the values +func Clone[A any](f func(A) A) func(as []A) []A { + return G.Clone[[]A](f) +} + +// FoldMap maps and folds an array. Map the Array passing each value to the iterating function. Then fold the results using the provided Monoid. +func FoldMap[A, B any](m M.Monoid[B]) func(func(A) B) func([]A) B { + return G.FoldMap[[]A](m) +} + +// FoldMapWithIndex maps and folds an array. Map the Array passing each value to the iterating function. Then fold the results using the provided Monoid. +func FoldMapWithIndex[A, B any](m M.Monoid[B]) func(func(int, A) B) func([]A) B { + return G.FoldMapWithIndex[[]A](m) +} + +// Fold folds the array using the provided Monoid. +func Fold[A any](m M.Monoid[A]) func([]A) A { + return G.Fold[[]A](m) +} + +// Push adds an element to the end of an array (alias for Append). +func Push[A any](a A) EM.Endomorphism[[]A] { + return G.Push[EM.Endomorphism[[]A]](a) +} + +// MonadFlap applies a value to an array of functions, producing an array of results. +// This is the monadic version that takes both parameters. +func MonadFlap[B, A any](fab []func(A) B, a A) []B { + return G.MonadFlap[func(A) B, []func(A) B, []B, A, B](fab, a) +} + +// Flap applies a value to an array of functions, producing an array of results. +// This is the curried version. +func Flap[B, A any](a A) func([]func(A) B) []B { + return G.Flap[func(A) B, []func(A) B, []B, A, B](a) +} + +// Prepend adds an element to the beginning of an array, returning a new array. +func Prepend[A any](head A) EM.Endomorphism[[]A] { + return G.Prepend[EM.Endomorphism[[]A]](head) +} diff --git a/v2/array/array_extended_test.go b/v2/array/array_extended_test.go new file mode 100644 index 0000000..a0b38a9 --- /dev/null +++ b/v2/array/array_extended_test.go @@ -0,0 +1,323 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package array + +import ( + "fmt" + "testing" + + N "github.com/IBM/fp-go/v2/number" + O "github.com/IBM/fp-go/v2/option" + S "github.com/IBM/fp-go/v2/string" + "github.com/stretchr/testify/assert" +) + +func TestReplicate(t *testing.T) { + result := Replicate(3, "a") + assert.Equal(t, []string{"a", "a", "a"}, result) + + empty := Replicate(0, 42) + assert.Equal(t, []int{}, empty) +} + +func TestMonadMap(t *testing.T) { + src := []int{1, 2, 3} + result := MonadMap(src, func(x int) int { return x * 2 }) + assert.Equal(t, []int{2, 4, 6}, result) +} + +func TestMonadMapRef(t *testing.T) { + src := []int{1, 2, 3} + result := MonadMapRef(src, func(x *int) int { return *x * 2 }) + assert.Equal(t, []int{2, 4, 6}, result) +} + +func TestMapWithIndex(t *testing.T) { + src := []string{"a", "b", "c"} + mapper := MapWithIndex(func(i int, s string) string { + return fmt.Sprintf("%d:%s", i, s) + }) + result := mapper(src) + assert.Equal(t, []string{"0:a", "1:b", "2:c"}, result) +} + +func TestMapRef(t *testing.T) { + src := []int{1, 2, 3} + mapper := MapRef(func(x *int) int { return *x * 2 }) + result := mapper(src) + assert.Equal(t, []int{2, 4, 6}, result) +} + +func TestFilterWithIndex(t *testing.T) { + src := []int{1, 2, 3, 4, 5} + filter := FilterWithIndex(func(i, x int) bool { + return i%2 == 0 && x > 2 + }) + result := filter(src) + assert.Equal(t, []int{3, 5}, result) +} + +func TestFilterRef(t *testing.T) { + src := []int{1, 2, 3, 4, 5} + filter := FilterRef(func(x *int) bool { return *x > 2 }) + result := filter(src) + assert.Equal(t, []int{3, 4, 5}, result) +} + +func TestMonadFilterMap(t *testing.T) { + src := []int{1, 2, 3, 4} + result := MonadFilterMap(src, func(x int) O.Option[string] { + if x%2 == 0 { + return O.Some(fmt.Sprintf("even:%d", x)) + } + return O.None[string]() + }) + assert.Equal(t, []string{"even:2", "even:4"}, result) +} + +func TestMonadFilterMapWithIndex(t *testing.T) { + src := []int{1, 2, 3, 4} + result := MonadFilterMapWithIndex(src, func(i, x int) O.Option[string] { + if i%2 == 0 { + return O.Some(fmt.Sprintf("%d:%d", i, x)) + } + return O.None[string]() + }) + assert.Equal(t, []string{"0:1", "2:3"}, result) +} + +func TestFilterMapWithIndex(t *testing.T) { + src := []int{1, 2, 3, 4} + filter := FilterMapWithIndex(func(i, x int) O.Option[string] { + if i%2 == 0 { + return O.Some(fmt.Sprintf("%d:%d", i, x)) + } + return O.None[string]() + }) + result := filter(src) + assert.Equal(t, []string{"0:1", "2:3"}, result) +} + +func TestFilterMapRef(t *testing.T) { + src := []int{1, 2, 3, 4, 5} + filter := FilterMapRef( + func(x *int) bool { return *x > 2 }, + func(x *int) string { return fmt.Sprintf("val:%d", *x) }, + ) + result := filter(src) + assert.Equal(t, []string{"val:3", "val:4", "val:5"}, result) +} + +func TestReduceWithIndex(t *testing.T) { + src := []int{1, 2, 3} + reducer := ReduceWithIndex(func(i, acc, x int) int { + return acc + i + x + }, 0) + result := reducer(src) + assert.Equal(t, 9, result) // 0 + (0+1) + (1+2) + (2+3) = 9 +} + +func TestReduceRightWithIndex(t *testing.T) { + src := []string{"a", "b", "c"} + reducer := ReduceRightWithIndex(func(i int, x, acc string) string { + return fmt.Sprintf("%s%d:%s", acc, i, x) + }, "") + result := reducer(src) + assert.Equal(t, "2:c1:b0:a", result) +} + +func TestReduceRef(t *testing.T) { + src := []int{1, 2, 3} + reducer := ReduceRef(func(acc int, x *int) int { + return acc + *x + }, 0) + result := reducer(src) + assert.Equal(t, 6, result) +} + +func TestZero(t *testing.T) { + result := Zero[int]() + assert.Equal(t, []int{}, result) + assert.True(t, IsEmpty(result)) +} + +func TestMonadChain(t *testing.T) { + src := []int{1, 2, 3} + result := MonadChain(src, func(x int) []int { + return []int{x, x * 10} + }) + assert.Equal(t, []int{1, 10, 2, 20, 3, 30}, result) +} + +func TestChain(t *testing.T) { + src := []int{1, 2, 3} + chain := Chain(func(x int) []int { + return []int{x, x * 10} + }) + result := chain(src) + assert.Equal(t, []int{1, 10, 2, 20, 3, 30}, result) +} + +func TestMonadAp(t *testing.T) { + fns := []func(int) int{ + func(x int) int { return x * 2 }, + func(x int) int { return x + 10 }, + } + values := []int{1, 2} + result := MonadAp(fns, values) + assert.Equal(t, []int{2, 4, 11, 12}, result) +} + +func TestMatchLeft(t *testing.T) { + matcher := MatchLeft( + func() string { return "empty" }, + func(head int, tail []int) string { + return fmt.Sprintf("head:%d,tail:%v", head, tail) + }, + ) + + assert.Equal(t, "empty", matcher([]int{})) + assert.Equal(t, "head:1,tail:[2 3]", matcher([]int{1, 2, 3})) +} + +func TestTail(t *testing.T) { + assert.Equal(t, O.None[[]int](), Tail([]int{})) + assert.Equal(t, O.Some([]int{2, 3}), Tail([]int{1, 2, 3})) + assert.Equal(t, O.Some([]int{}), Tail([]int{1})) +} + +func TestFirst(t *testing.T) { + assert.Equal(t, O.None[int](), First([]int{})) + assert.Equal(t, O.Some(1), First([]int{1, 2, 3})) +} + +func TestLast(t *testing.T) { + assert.Equal(t, O.None[int](), Last([]int{})) + assert.Equal(t, O.Some(3), Last([]int{1, 2, 3})) + assert.Equal(t, O.Some(1), Last([]int{1})) +} + +func TestUpsertAt(t *testing.T) { + src := []int{1, 2, 3} + upsert := UpsertAt(99) + + result1 := upsert(src) + assert.Equal(t, []int{1, 2, 3, 99}, result1) +} + +func TestSize(t *testing.T) { + assert.Equal(t, 0, Size([]int{})) + assert.Equal(t, 3, Size([]int{1, 2, 3})) +} + +func TestMonadPartition(t *testing.T) { + src := []int{1, 2, 3, 4, 5} + result := MonadPartition(src, func(x int) bool { return x > 2 }) + assert.Equal(t, []int{1, 2}, result.F1) + assert.Equal(t, []int{3, 4, 5}, result.F2) +} + +func TestIsNil(t *testing.T) { + var nilSlice []int + assert.True(t, IsNil(nilSlice)) + assert.False(t, IsNil([]int{})) + assert.False(t, IsNil([]int{1})) +} + +func TestIsNonNil(t *testing.T) { + var nilSlice []int + assert.False(t, IsNonNil(nilSlice)) + assert.True(t, IsNonNil([]int{})) + assert.True(t, IsNonNil([]int{1})) +} + +func TestConstNil(t *testing.T) { + result := ConstNil[int]() + assert.True(t, IsNil(result)) +} + +func TestSliceRight(t *testing.T) { + src := []int{1, 2, 3, 4, 5} + slicer := SliceRight[int](2) + result := slicer(src) + assert.Equal(t, []int{3, 4, 5}, result) +} + +func TestCopy(t *testing.T) { + src := []int{1, 2, 3} + copied := Copy(src) + assert.Equal(t, src, copied) + // Verify it's a different slice + copied[0] = 99 + assert.Equal(t, 1, src[0]) + assert.Equal(t, 99, copied[0]) +} + +func TestClone(t *testing.T) { + src := []int{1, 2, 3} + cloner := Clone(func(x int) int { return x * 2 }) + result := cloner(src) + assert.Equal(t, []int{2, 4, 6}, result) +} + +func TestFoldMapWithIndex(t *testing.T) { + src := []string{"a", "b", "c"} + folder := FoldMapWithIndex[string](S.Monoid)(func(i int, s string) string { + return fmt.Sprintf("%d:%s", i, s) + }) + result := folder(src) + assert.Equal(t, "0:a1:b2:c", result) +} + +func TestFold(t *testing.T) { + src := []int{1, 2, 3, 4, 5} + folder := Fold(N.MonoidSum[int]()) + result := folder(src) + assert.Equal(t, 15, result) +} + +func TestPush(t *testing.T) { + src := []int{1, 2, 3} + pusher := Push(4) + result := pusher(src) + assert.Equal(t, []int{1, 2, 3, 4}, result) +} + +func TestMonadFlap(t *testing.T) { + fns := []func(int) string{ + func(x int) string { return fmt.Sprintf("a%d", x) }, + func(x int) string { return fmt.Sprintf("b%d", x) }, + } + result := MonadFlap(fns, 5) + assert.Equal(t, []string{"a5", "b5"}, result) +} + +func TestFlap(t *testing.T) { + fns := []func(int) string{ + func(x int) string { return fmt.Sprintf("a%d", x) }, + func(x int) string { return fmt.Sprintf("b%d", x) }, + } + flapper := Flap[string](5) + result := flapper(fns) + assert.Equal(t, []string{"a5", "b5"}, result) +} + +func TestPrepend(t *testing.T) { + src := []int{2, 3, 4} + prepender := Prepend(1) + result := prepender(src) + assert.Equal(t, []int{1, 2, 3, 4}, result) +} diff --git a/v2/array/array_test.go b/v2/array/array_test.go new file mode 100644 index 0000000..31f476e --- /dev/null +++ b/v2/array/array_test.go @@ -0,0 +1,216 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package array + +import ( + "fmt" + "strings" + "testing" + + F "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/internal/utils" + O "github.com/IBM/fp-go/v2/option" + S "github.com/IBM/fp-go/v2/string" + T "github.com/IBM/fp-go/v2/tuple" + "github.com/stretchr/testify/assert" +) + +func TestMap1(t *testing.T) { + + src := []string{"a", "b", "c"} + + up := Map(strings.ToUpper)(src) + + var up1 = []string{} + for _, s := range src { + up1 = append(up1, strings.ToUpper(s)) + } + + var up2 = []string{} + for i := range src { + up2 = append(up2, strings.ToUpper(src[i])) + } + + assert.Equal(t, up, up1) + assert.Equal(t, up, up2) +} + +func TestMap(t *testing.T) { + + mapper := Map(utils.Upper) + + src := []string{"a", "b", "c"} + + dst := mapper(src) + + assert.Equal(t, dst, []string{"A", "B", "C"}) +} + +func TestReduceRight(t *testing.T) { + values := From("a", "b", "c") + f := func(a, acc string) string { + return fmt.Sprintf("%s%s", acc, a) + } + b := "" + + assert.Equal(t, "cba", ReduceRight(f, b)(values)) + assert.Equal(t, "", ReduceRight(f, b)(Empty[string]())) +} + +func TestReduce(t *testing.T) { + + values := MakeBy(101, F.Identity[int]) + + sum := func(val int, current int) int { + return val + current + } + reducer := Reduce(sum, 0) + + result := reducer(values) + assert.Equal(t, result, 5050) + +} + +func TestEmpty(t *testing.T) { + assert.True(t, IsNonEmpty(MakeBy(101, F.Identity[int]))) + assert.True(t, IsEmpty([]int{})) +} + +func TestAp(t *testing.T) { + assert.Equal(t, + []int{2, 4, 6, 3, 6, 9}, + F.Pipe1( + []func(int) int{ + utils.Double, + utils.Triple, + }, + Ap[int, int]([]int{1, 2, 3}), + ), + ) +} + +func TestIntercalate(t *testing.T) { + is := Intercalate(S.Monoid)("-") + + assert.Equal(t, "", is(Empty[string]())) + assert.Equal(t, "a", is([]string{"a"})) + assert.Equal(t, "a-b-c", is([]string{"a", "b", "c"})) + assert.Equal(t, "a--c", is([]string{"a", "", "c"})) + assert.Equal(t, "a-b", is([]string{"a", "b"})) + assert.Equal(t, "a-b-c-d", is([]string{"a", "b", "c", "d"})) +} + +func TestIntersperse(t *testing.T) { + // Test with empty array + assert.Equal(t, []int{}, Intersperse(0)([]int{})) + + // Test with single element + assert.Equal(t, []int{1}, Intersperse(0)([]int{1})) + + // Test with multiple elements + assert.Equal(t, []int{1, 0, 2, 0, 3}, Intersperse(0)([]int{1, 2, 3})) +} + +func TestPrependAll(t *testing.T) { + empty := Empty[int]() + prep := PrependAll(0) + assert.Equal(t, empty, prep(empty)) + assert.Equal(t, []int{0, 1, 0, 2, 0, 3}, prep([]int{1, 2, 3})) + assert.Equal(t, []int{0, 1}, prep([]int{1})) + assert.Equal(t, []int{0, 1, 0, 2, 0, 3, 0, 4}, prep([]int{1, 2, 3, 4})) +} + +func TestFlatten(t *testing.T) { + assert.Equal(t, []int{1, 2, 3}, Flatten([][]int{{1}, {2}, {3}})) +} + +func TestLookup(t *testing.T) { + data := []int{0, 1, 2} + none := O.None[int]() + + assert.Equal(t, none, Lookup[int](-1)(data)) + assert.Equal(t, none, Lookup[int](10)(data)) + assert.Equal(t, O.Some(1), Lookup[int](1)(data)) +} + +func TestSlice(t *testing.T) { + data := []int{0, 1, 2, 3} + + assert.Equal(t, []int{1, 2}, Slice[int](1, 3)(data)) +} + +func TestFrom(t *testing.T) { + assert.Equal(t, []int{1, 2, 3}, From(1, 2, 3)) +} + +func TestPartition(t *testing.T) { + + pred := func(n int) bool { + return n > 2 + } + + assert.Equal(t, T.MakeTuple2(Empty[int](), Empty[int]()), Partition(pred)(Empty[int]())) + assert.Equal(t, T.MakeTuple2(From(1), From(3)), Partition(pred)(From(1, 3))) +} + +func TestFilterChain(t *testing.T) { + src := From(1, 2, 3) + + f := func(i int) O.Option[[]string] { + if i%2 != 0 { + return O.Of(From(fmt.Sprintf("a%d", i), fmt.Sprintf("b%d", i))) + } + return O.None[[]string]() + } + + res := FilterChain(f)(src) + + assert.Equal(t, From("a1", "b1", "a3", "b3"), res) +} + +func TestFilterMap(t *testing.T) { + src := From(1, 2, 3) + + f := func(i int) O.Option[string] { + if i%2 != 0 { + return O.Of(fmt.Sprintf("a%d", i)) + } + return O.None[string]() + } + + res := FilterMap(f)(src) + + assert.Equal(t, From("a1", "a3"), res) +} + +func TestFoldMap(t *testing.T) { + src := From("a", "b", "c") + + fold := FoldMap[string](S.Monoid)(strings.ToUpper) + + assert.Equal(t, "ABC", fold(src)) +} + +func ExampleFoldMap() { + src := From("a", "b", "c") + + fold := FoldMap[string](S.Monoid)(strings.ToUpper) + + fmt.Println(fold(src)) + + // Output: ABC + +} diff --git a/v2/array/bind.go b/v2/array/bind.go new file mode 100644 index 0000000..61d063b --- /dev/null +++ b/v2/array/bind.go @@ -0,0 +1,136 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package array + +import ( + G "github.com/IBM/fp-go/v2/array/generic" +) + +// Do creates an empty context of type S to be used with the Bind operation. +// This is the starting point for monadic do-notation style computations. +// +// Example: +// +// type State struct { +// X int +// Y int +// } +// result := array.Do(State{}) +func Do[S any]( + empty S, +) []S { + return G.Do[[]S, S](empty) +} + +// Bind attaches the result of a computation to a context S1 to produce a context S2. +// The setter function defines how to update the context with the computation result. +// This enables monadic composition where each step can produce multiple results. +// +// Example: +// +// result := F.Pipe2( +// array.Do(struct{ X, Y int }{}), +// array.Bind( +// func(x int) func(s struct{}) struct{ X int } { +// return func(s struct{}) struct{ X int } { return struct{ X int }{x} } +// }, +// func(s struct{}) []int { return []int{1, 2} }, +// ), +// ) +func Bind[S1, S2, T any]( + setter func(T) func(S1) S2, + f func(S1) []T, +) func([]S1) []S2 { + return G.Bind[[]S1, []S2, []T, S1, S2, T](setter, f) +} + +// Let attaches the result of a pure computation to a context S1 to produce a context S2. +// Unlike Bind, the computation function returns a plain value T rather than []T. +// +// Example: +// +// result := array.Let( +// func(sum int) func(s struct{ X int }) struct{ X, Sum int } { +// return func(s struct{ X int }) struct{ X, Sum int } { +// return struct{ X, Sum int }{s.X, sum} +// } +// }, +// func(s struct{ X int }) int { return s.X * 2 }, +// ) +func Let[S1, S2, T any]( + setter func(T) func(S1) S2, + f func(S1) T, +) func([]S1) []S2 { + return G.Let[[]S1, []S2, S1, S2, T](setter, f) +} + +// LetTo attaches a constant value to a context S1 to produce a context S2. +// This is useful for adding constant values to the context. +// +// Example: +// +// result := array.LetTo( +// func(name string) func(s struct{ X int }) struct{ X int; Name string } { +// return func(s struct{ X int }) struct{ X int; Name string } { +// return struct{ X int; Name string }{s.X, name} +// } +// }, +// "constant", +// ) +func LetTo[S1, S2, T any]( + setter func(T) func(S1) S2, + b T, +) func([]S1) []S2 { + return G.LetTo[[]S1, []S2, S1, S2, T](setter, b) +} + +// BindTo initializes a new state S1 from a value T. +// This is typically the first operation after Do to start building the context. +// +// Example: +// +// result := F.Pipe2( +// []int{1, 2, 3}, +// array.BindTo(func(x int) struct{ X int } { +// return struct{ X int }{x} +// }), +// ) +func BindTo[S1, T any]( + setter func(T) S1, +) func([]T) []S1 { + return G.BindTo[[]S1, []T, S1, T](setter) +} + +// ApS attaches a value to a context S1 to produce a context S2 by considering +// the context and the value concurrently (using applicative semantics). +// This produces all combinations of context values and array values. +// +// Example: +// +// result := array.ApS( +// func(y int) func(s struct{ X int }) struct{ X, Y int } { +// return func(s struct{ X int }) struct{ X, Y int } { +// return struct{ X, Y int }{s.X, y} +// } +// }, +// []int{10, 20}, +// ) +func ApS[S1, S2, T any]( + setter func(T) func(S1) S2, + fa []T, +) func([]S1) []S2 { + return G.ApS[[]S1, []S2, []T, S1, S2, T](setter, fa) +} diff --git a/v2/array/bind_extended_test.go b/v2/array/bind_extended_test.go new file mode 100644 index 0000000..30d581b --- /dev/null +++ b/v2/array/bind_extended_test.go @@ -0,0 +1,78 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package array + +import ( + "testing" + + F "github.com/IBM/fp-go/v2/function" + "github.com/stretchr/testify/assert" +) + +type TestState1 struct { + X int +} + +type TestState2 struct { + X int + Y int +} + +func TestLet(t *testing.T) { + result := F.Pipe2( + Do(TestState1{}), + Let( + func(y int) func(s TestState1) TestState2 { + return func(s TestState1) TestState2 { + return TestState2{X: s.X, Y: y} + } + }, + func(s TestState1) int { return s.X * 2 }, + ), + Map(func(s TestState2) int { return s.X + s.Y }), + ) + + assert.Equal(t, []int{0}, result) +} + +func TestLetTo(t *testing.T) { + result := F.Pipe2( + Do(TestState1{X: 5}), + LetTo( + func(y int) func(s TestState1) TestState2 { + return func(s TestState1) TestState2 { + return TestState2{X: s.X, Y: y} + } + }, + 42, + ), + Map(func(s TestState2) int { return s.X + s.Y }), + ) + + assert.Equal(t, []int{47}, result) +} + +func TestBindTo(t *testing.T) { + result := F.Pipe1( + []int{1, 2, 3}, + BindTo(func(x int) TestState1 { + return TestState1{X: x} + }), + ) + + expected := []TestState1{{X: 1}, {X: 2}, {X: 3}} + assert.Equal(t, expected, result) +} diff --git a/v2/array/bind_test.go b/v2/array/bind_test.go new file mode 100644 index 0000000..fd26293 --- /dev/null +++ b/v2/array/bind_test.go @@ -0,0 +1,56 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package array + +import ( + "testing" + + F "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/internal/utils" + "github.com/stretchr/testify/assert" +) + +func getLastName(s utils.Initial) []string { + return Of("Doe") +} + +func getGivenName(s utils.WithLastName) []string { + return Of("John") +} + +func TestBind(t *testing.T) { + + res := F.Pipe3( + Do(utils.Empty), + Bind(utils.SetLastName, getLastName), + Bind(utils.SetGivenName, getGivenName), + Map(utils.GetFullName), + ) + + assert.Equal(t, res, Of("John Doe")) +} + +func TestApS(t *testing.T) { + + res := F.Pipe3( + Do(utils.Empty), + ApS(utils.SetLastName, Of("Doe")), + ApS(utils.SetGivenName, Of("John")), + Map(utils.GetFullName), + ) + + assert.Equal(t, res, Of("John Doe")) +} diff --git a/v2/array/doc.go b/v2/array/doc.go new file mode 100644 index 0000000..dea4cfd --- /dev/null +++ b/v2/array/doc.go @@ -0,0 +1,251 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +// Package array provides functional programming utilities for working with Go slices. +// +// This package treats Go slices as immutable arrays and provides a rich set of operations +// for transforming, filtering, folding, and combining arrays in a functional style. +// All operations return new arrays rather than modifying existing ones. +// +// # Core Concepts +// +// The array package implements several functional programming abstractions: +// - Functor: Transform array elements with Map +// - Applicative: Apply functions in arrays to values in arrays +// - Monad: Chain operations that produce arrays with Chain/FlatMap +// - Foldable: Reduce arrays to single values with Reduce/Fold +// - Traversable: Transform arrays while preserving structure +// +// # Basic Operations +// +// // Creating arrays +// arr := array.From(1, 2, 3, 4, 5) +// repeated := array.Replicate(3, "hello") +// generated := array.MakeBy(5, func(i int) int { return i * 2 }) +// +// // Transforming arrays +// doubled := array.Map(func(x int) int { return x * 2 })(arr) +// filtered := array.Filter(func(x int) bool { return x > 2 })(arr) +// +// // Combining arrays +// combined := array.Flatten([][]int{{1, 2}, {3, 4}}) +// zipped := array.Zip([]string{"a", "b"})([]int{1, 2}) +// +// # Mapping and Filtering +// +// Transform array elements with Map, or filter elements with Filter: +// +// numbers := []int{1, 2, 3, 4, 5} +// +// // Map transforms each element +// doubled := array.Map(func(x int) int { return x * 2 })(numbers) +// // Result: [2, 4, 6, 8, 10] +// +// // Filter keeps elements matching a predicate +// evens := array.Filter(func(x int) bool { return x%2 == 0 })(numbers) +// // Result: [2, 4] +// +// // FilterMap combines both operations +// import "github.com/IBM/fp-go/v2/option" +// result := array.FilterMap(func(x int) option.Option[int] { +// if x%2 == 0 { +// return option.Some(x * 2) +// } +// return option.None[int]() +// })(numbers) +// // Result: [4, 8] +// +// # Folding and Reducing +// +// Reduce arrays to single values: +// +// numbers := []int{1, 2, 3, 4, 5} +// +// // Sum all elements +// sum := array.Reduce(func(acc, x int) int { return acc + x }, 0)(numbers) +// // Result: 15 +// +// // Using a Monoid +// import "github.com/IBM/fp-go/v2/monoid" +// sum := array.Fold(monoid.MonoidSum[int]())(numbers) +// // Result: 15 +// +// # Chaining Operations +// +// Chain operations that produce arrays (also known as FlatMap): +// +// numbers := []int{1, 2, 3} +// result := array.Chain(func(x int) []int { +// return []int{x, x * 10} +// })(numbers) +// // Result: [1, 10, 2, 20, 3, 30] +// +// # Finding Elements +// +// Search for elements matching predicates: +// +// numbers := []int{1, 2, 3, 4, 5} +// +// // Find first element > 3 +// first := array.FindFirst(func(x int) bool { return x > 3 })(numbers) +// // Result: Some(4) +// +// // Find last element > 3 +// last := array.FindLast(func(x int) bool { return x > 3 })(numbers) +// // Result: Some(5) +// +// // Get head and tail +// head := array.Head(numbers) // Some(1) +// tail := array.Tail(numbers) // Some([2, 3, 4, 5]) +// +// # Sorting +// +// Sort arrays using Ord instances: +// +// import "github.com/IBM/fp-go/v2/ord" +// +// numbers := []int{3, 1, 4, 1, 5} +// sorted := array.Sort(ord.FromStrictCompare[int]())(numbers) +// // Result: [1, 1, 3, 4, 5] +// +// // Sort by extracted key +// type Person struct { Name string; Age int } +// people := []Person{{"Alice", 30}, {"Bob", 25}} +// byAge := array.SortByKey(ord.FromStrictCompare[int](), func(p Person) int { +// return p.Age +// })(people) +// +// # Uniqueness +// +// Remove duplicate elements: +// +// numbers := []int{1, 2, 2, 3, 3, 3} +// unique := array.StrictUniq(numbers) +// // Result: [1, 2, 3] +// +// // Unique by key +// type Person struct { Name string; Age int } +// people := []Person{{"Alice", 30}, {"Bob", 25}, {"Alice", 35}} +// uniqueByName := array.Uniq(func(p Person) string { return p.Name })(people) +// // Result: [{"Alice", 30}, {"Bob", 25}] +// +// # Zipping +// +// Combine multiple arrays: +// +// names := []string{"Alice", "Bob", "Charlie"} +// ages := []int{30, 25, 35} +// +// // Zip into tuples +// pairs := array.Zip(ages)(names) +// // Result: [(Alice, 30), (Bob, 25), (Charlie, 35)] +// +// // Zip with custom function +// result := array.ZipWith(names, ages, func(name string, age int) string { +// return fmt.Sprintf("%s is %d", name, age) +// }) +// +// # Monadic Do Notation +// +// Build complex array computations using do-notation style: +// +// result := array.Do( +// struct{ X, Y int }{}, +// )( +// array.Bind( +// func(x int) func(s struct{}) struct{ X int } { +// return func(s struct{}) struct{ X int } { return struct{ X int }{x} } +// }, +// func(s struct{}) []int { return []int{1, 2, 3} }, +// ), +// array.Bind( +// func(y int) func(s struct{ X int }) struct{ X, Y int } { +// return func(s struct{ X int }) struct{ X, Y int } { +// return struct{ X, Y int }{s.X, y} +// } +// }, +// func(s struct{ X int }) []int { return []int{4, 5} }, +// ), +// ) +// // Produces all combinations: [{1,4}, {1,5}, {2,4}, {2,5}, {3,4}, {3,5}] +// +// # Sequence and Traverse +// +// Transform arrays of effects into effects of arrays: +// +// import "github.com/IBM/fp-go/v2/option" +// +// // Sequence: []Option[A] -> Option[[]A] +// opts := []option.Option[int]{ +// option.Some(1), +// option.Some(2), +// option.Some(3), +// } +// result := array.ArrayOption[int]()(opts) +// // Result: Some([1, 2, 3]) +// +// // If any is None, result is None +// opts2 := []option.Option[int]{ +// option.Some(1), +// option.None[int](), +// option.Some(3), +// } +// result2 := array.ArrayOption[int]()(opts2) +// // Result: None +// +// # Equality and Comparison +// +// Compare arrays for equality: +// +// import "github.com/IBM/fp-go/v2/eq" +// +// eq := array.Eq(eq.FromStrictEquals[int]()) +// equal := eq.Equals([]int{1, 2, 3}, []int{1, 2, 3}) +// // Result: true +// +// # Monoid Operations +// +// Combine arrays using monoid operations: +// +// import "github.com/IBM/fp-go/v2/monoid" +// +// // Concatenate arrays +// m := array.Monoid[int]() +// result := m.Concat([]int{1, 2}, []int{3, 4}) +// // Result: [1, 2, 3, 4] +// +// // Concatenate multiple arrays efficiently +// result := array.ArrayConcatAll( +// []int{1, 2}, +// []int{3, 4}, +// []int{5, 6}, +// ) +// // Result: [1, 2, 3, 4, 5, 6] +// +// # Performance Considerations +// +// Most operations create new arrays rather than modifying existing ones. For performance-critical +// code, consider: +// - Using Copy for shallow copies when needed +// - Using Clone with a custom cloning function for deep copies +// - Batching operations to minimize intermediate allocations +// - Using ArrayConcatAll for efficient concatenation of multiple arrays +// +// # Subpackages +// +// - array/generic: Generic implementations for custom array-like types +// - array/nonempty: Operations for non-empty arrays with compile-time guarantees +// - array/testing: Testing utilities for array laws and properties +package array diff --git a/v2/array/eq.go b/v2/array/eq.go new file mode 100644 index 0000000..e1bfd25 --- /dev/null +++ b/v2/array/eq.go @@ -0,0 +1,51 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package array + +import ( + E "github.com/IBM/fp-go/v2/eq" +) + +func equals[T any](left []T, right []T, eq func(T, T) bool) bool { + if len(left) != len(right) { + return false + } + for i, v1 := range left { + v2 := right[i] + if !eq(v1, v2) { + return false + } + } + return true +} + +// Eq creates an equality checker for arrays given an equality checker for elements. +// Two arrays are considered equal if they have the same length and all corresponding +// elements are equal according to the provided Eq instance. +// +// Example: +// +// import "github.com/IBM/fp-go/v2/eq" +// +// intArrayEq := array.Eq(eq.FromStrictEquals[int]()) +// result := intArrayEq.Equals([]int{1, 2, 3}, []int{1, 2, 3}) // true +// result2 := intArrayEq.Equals([]int{1, 2, 3}, []int{1, 2, 4}) // false +func Eq[T any](e E.Eq[T]) E.Eq[[]T] { + eq := e.Equals + return E.FromEquals(func(left, right []T) bool { + return equals(left, right, eq) + }) +} diff --git a/v2/array/eq_test.go b/v2/array/eq_test.go new file mode 100644 index 0000000..56c64c0 --- /dev/null +++ b/v2/array/eq_test.go @@ -0,0 +1,44 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package array + +import ( + "testing" + + E "github.com/IBM/fp-go/v2/eq" + "github.com/stretchr/testify/assert" +) + +func TestEq(t *testing.T) { + intEq := Eq(E.FromStrictEquals[int]()) + + // Test equal arrays + assert.True(t, intEq.Equals([]int{1, 2, 3}, []int{1, 2, 3})) + + // Test different lengths + assert.False(t, intEq.Equals([]int{1, 2, 3}, []int{1, 2})) + + // Test different values + assert.False(t, intEq.Equals([]int{1, 2, 3}, []int{1, 2, 4})) + + // Test empty arrays + assert.True(t, intEq.Equals([]int{}, []int{})) + + // Test string arrays + stringEq := Eq(E.FromStrictEquals[string]()) + assert.True(t, stringEq.Equals([]string{"a", "b"}, []string{"a", "b"})) + assert.False(t, stringEq.Equals([]string{"a", "b"}, []string{"a", "c"})) +} diff --git a/v2/array/example_any_test.go b/v2/array/example_any_test.go new file mode 100644 index 0000000..19f3037 --- /dev/null +++ b/v2/array/example_any_test.go @@ -0,0 +1,77 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package array + +import ( + "fmt" + + F "github.com/IBM/fp-go/v2/function" + O "github.com/IBM/fp-go/v2/option" +) + +func Example_any() { + + pred := func(val int) bool { + return val&2 == 0 + } + + data1 := From(1, 2, 3) + + fmt.Println(Any(pred)(data1)) + + // Output: + // true +} + +func Example_any_filter() { + + pred := func(val int) bool { + return val&2 == 0 + } + + data1 := From(1, 2, 3) + + // Any tests if any of the entries in the array matches the condition + Any := F.Flow2( + Filter(pred), + IsNonEmpty[int], + ) + + fmt.Println(Any(data1)) + + // Output: + // true +} + +func Example_any_find() { + + pred := func(val int) bool { + return val&2 == 0 + } + + data1 := From(1, 2, 3) + + // Any tests if any of the entries in the array matches the condition + Any := F.Flow2( + FindFirst(pred), + O.IsSome[int], + ) + + fmt.Println(Any(data1)) + + // Output: + // true +} diff --git a/v2/array/example_find_test.go b/v2/array/example_find_test.go new file mode 100644 index 0000000..34a79e4 --- /dev/null +++ b/v2/array/example_find_test.go @@ -0,0 +1,55 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package array + +import ( + "fmt" + + F "github.com/IBM/fp-go/v2/function" +) + +func Example_find() { + + pred := func(val int) bool { + return val&2 == 0 + } + + data1 := From(1, 2, 3) + + fmt.Println(FindFirst(pred)(data1)) + + // Output: + // Some[int](1) +} + +func Example_find_filter() { + + pred := func(val int) bool { + return val&2 == 0 + } + + data1 := From(1, 2, 3) + + Find := F.Flow2( + Filter(pred), + Head[int], + ) + + fmt.Println(Find(data1)) + + // Output: + // Some[int](1) +} diff --git a/v2/array/examples_basic_test.go b/v2/array/examples_basic_test.go new file mode 100644 index 0000000..69e4d17 --- /dev/null +++ b/v2/array/examples_basic_test.go @@ -0,0 +1,59 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package array + +import ( + "fmt" + + F "github.com/IBM/fp-go/v2/function" + O "github.com/IBM/fp-go/v2/option" +) + +// Example_basic adapts examples from [https://github.com/inato/fp-ts-cheatsheet#basic-manipulation] +func Example_basic() { + + someArray := From(0, 1, 2, 3, 4, 5, 6, 7, 8, 9) // []int + + isEven := func(num int) bool { + return num%2 == 0 + } + + square := func(num int) int { + return num * num + } + + // filter and map + result := F.Pipe2( + someArray, + Filter(isEven), + Map(square), + ) // [0 4 16 36 64] + + // or in one go with filterMap + resultFilterMap := F.Pipe1( + someArray, + FilterMap( + F.Flow2(O.FromPredicate(isEven), O.Map(square)), + ), + ) + + fmt.Println(result) + fmt.Println(resultFilterMap) + + // Output: + // [0 4 16 36 64] + // [0 4 16 36 64] +} diff --git a/v2/array/examples_sort_test.go b/v2/array/examples_sort_test.go new file mode 100644 index 0000000..0a9bf4a --- /dev/null +++ b/v2/array/examples_sort_test.go @@ -0,0 +1,92 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package array + +import ( + "fmt" + + F "github.com/IBM/fp-go/v2/function" + I "github.com/IBM/fp-go/v2/number/integer" + O "github.com/IBM/fp-go/v2/option" + "github.com/IBM/fp-go/v2/ord" + S "github.com/IBM/fp-go/v2/string" +) + +type user struct { + name string + age O.Option[int] +} + +func (user user) GetName() string { + return user.name +} + +func (user user) GetAge() O.Option[int] { + return user.age +} + +// Example_sort adapts examples from [https://github.com/inato/fp-ts-cheatsheet#sort-elements-with-ord] +func Example_sort() { + + strings := From("zyx", "abc", "klm") + + sortedStrings := F.Pipe1( + strings, + Sort(S.Ord), + ) // => ['abc', 'klm', 'zyx'] + + // reverse sort + reverseSortedStrings := F.Pipe1( + strings, + Sort(ord.Reverse(S.Ord)), + ) // => ['zyx', 'klm', 'abc'] + + // sort Option + optionalNumbers := From(O.Some(1337), O.None[int](), O.Some(42)) + + sortedNums := F.Pipe1( + optionalNumbers, + Sort(O.Ord(I.Ord)), + ) + + // complex object with different rules + byName := F.Pipe1( + S.Ord, + ord.Contramap(user.GetName), + ) // ord.Ord[user] + + byAge := F.Pipe1( + O.Ord(I.Ord), + ord.Contramap(user.GetAge), + ) // ord.Ord[user] + + sortedUsers := F.Pipe1( + From(user{name: "a", age: O.Of(30)}, user{name: "d", age: O.Of(10)}, user{name: "c"}, user{name: "b", age: O.Of(10)}), + SortBy(From(byAge, byName)), + ) + + fmt.Println(sortedStrings) + fmt.Println(reverseSortedStrings) + fmt.Println(sortedNums) + fmt.Println(sortedUsers) + + // Output: + // [abc klm zyx] + // [zyx klm abc] + // [None[int] Some[int](42) Some[int](1337)] + // [{c {false 0}} {b {true 10}} {d {true 10}} {a {true 30}}] + +} diff --git a/v2/array/find.go b/v2/array/find.go new file mode 100644 index 0000000..19d3c3c --- /dev/null +++ b/v2/array/find.go @@ -0,0 +1,99 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package array + +import ( + G "github.com/IBM/fp-go/v2/array/generic" + O "github.com/IBM/fp-go/v2/option" +) + +// FindFirst finds the first element which satisfies a predicate function. +// Returns Some(element) if found, None if no element matches. +// +// Example: +// +// findGreaterThan3 := array.FindFirst(func(x int) bool { return x > 3 }) +// result := findGreaterThan3([]int{1, 2, 4, 5}) // Some(4) +// result2 := findGreaterThan3([]int{1, 2, 3}) // None +func FindFirst[A any](pred func(A) bool) func([]A) O.Option[A] { + return G.FindFirst[[]A](pred) +} + +// FindFirstWithIndex finds the first element which satisfies a predicate function that also receives the index. +// Returns Some(element) if found, None if no element matches. +// +// Example: +// +// findEvenAtEvenIndex := array.FindFirstWithIndex(func(i, x int) bool { +// return i%2 == 0 && x%2 == 0 +// }) +// result := findEvenAtEvenIndex([]int{1, 3, 4, 5}) // Some(4) +func FindFirstWithIndex[A any](pred func(int, A) bool) func([]A) O.Option[A] { + return G.FindFirstWithIndex[[]A](pred) +} + +// FindFirstMap finds the first element for which the selector function returns Some. +// This combines finding and mapping in a single operation. +// +// Example: +// +// import "strconv" +// +// parseFirst := array.FindFirstMap(func(s string) option.Option[int] { +// if n, err := strconv.Atoi(s); err == nil { +// return option.Some(n) +// } +// return option.None[int]() +// }) +// result := parseFirst([]string{"a", "42", "b"}) // Some(42) +func FindFirstMap[A, B any](sel func(A) O.Option[B]) func([]A) O.Option[B] { + return G.FindFirstMap[[]A](sel) +} + +// FindFirstMapWithIndex finds the first element for which the selector function returns Some. +// The selector receives both the index and the element. +func FindFirstMapWithIndex[A, B any](sel func(int, A) O.Option[B]) func([]A) O.Option[B] { + return G.FindFirstMapWithIndex[[]A](sel) +} + +// FindLast finds the last element which satisfies a predicate function. +// Returns Some(element) if found, None if no element matches. +// +// Example: +// +// findGreaterThan3 := array.FindLast(func(x int) bool { return x > 3 }) +// result := findGreaterThan3([]int{1, 4, 2, 5}) // Some(5) +func FindLast[A any](pred func(A) bool) func([]A) O.Option[A] { + return G.FindLast[[]A](pred) +} + +// FindLastWithIndex finds the last element which satisfies a predicate function that also receives the index. +// Returns Some(element) if found, None if no element matches. +func FindLastWithIndex[A any](pred func(int, A) bool) func([]A) O.Option[A] { + return G.FindLastWithIndex[[]A](pred) +} + +// FindLastMap finds the last element for which the selector function returns Some. +// This combines finding and mapping in a single operation, searching from the end. +func FindLastMap[A, B any](sel func(A) O.Option[B]) func([]A) O.Option[B] { + return G.FindLastMap[[]A](sel) +} + +// FindLastMapWithIndex finds the last element for which the selector function returns Some. +// The selector receives both the index and the element, searching from the end. +func FindLastMapWithIndex[A, B any](sel func(int, A) O.Option[B]) func([]A) O.Option[B] { + return G.FindLastMapWithIndex[[]A](sel) +} diff --git a/v2/array/find_test.go b/v2/array/find_test.go new file mode 100644 index 0000000..19ba299 --- /dev/null +++ b/v2/array/find_test.go @@ -0,0 +1,105 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package array + +import ( + "fmt" + "testing" + + O "github.com/IBM/fp-go/v2/option" + "github.com/stretchr/testify/assert" +) + +func TestFindFirstWithIndex(t *testing.T) { + src := []int{1, 2, 3, 4, 5} + finder := FindFirstWithIndex(func(i, x int) bool { + return i > 2 && x%2 == 0 + }) + result := finder(src) + assert.Equal(t, O.Some(4), result) + + notFound := FindFirstWithIndex(func(i, x int) bool { + return i > 10 + }) + assert.Equal(t, O.None[int](), notFound(src)) +} + +func TestFindFirstMap(t *testing.T) { + src := []string{"a", "42", "b", "100"} + finder := FindFirstMap(func(s string) O.Option[int] { + if len(s) > 1 { + return O.Some(len(s)) + } + return O.None[int]() + }) + result := finder(src) + assert.Equal(t, O.Some(2), result) +} + +func TestFindFirstMapWithIndex(t *testing.T) { + src := []string{"a", "b", "c", "d"} + finder := FindFirstMapWithIndex(func(i int, s string) O.Option[string] { + if i > 1 { + return O.Some(fmt.Sprintf("%d:%s", i, s)) + } + return O.None[string]() + }) + result := finder(src) + assert.Equal(t, O.Some("2:c"), result) +} + +func TestFindLast(t *testing.T) { + src := []int{1, 2, 3, 4, 5} + finder := FindLast(func(x int) bool { return x%2 == 0 }) + result := finder(src) + assert.Equal(t, O.Some(4), result) + + notFound := FindLast(func(x int) bool { return x > 10 }) + assert.Equal(t, O.None[int](), notFound(src)) +} + +func TestFindLastWithIndex(t *testing.T) { + src := []int{1, 2, 3, 4, 5} + finder := FindLastWithIndex(func(i, x int) bool { + return i < 3 && x%2 == 0 + }) + result := finder(src) + assert.Equal(t, O.Some(2), result) +} + +func TestFindLastMap(t *testing.T) { + src := []string{"a", "42", "b", "100"} + finder := FindLastMap(func(s string) O.Option[int] { + if len(s) > 1 { + return O.Some(len(s)) + } + return O.None[int]() + }) + result := finder(src) + assert.Equal(t, O.Some(3), result) +} + +func TestFindLastMapWithIndex(t *testing.T) { + src := []string{"a", "b", "c", "d"} + finder := FindLastMapWithIndex(func(i int, s string) O.Option[string] { + if i < 3 { + return O.Some(fmt.Sprintf("%d:%s", i, s)) + } + return O.None[string]() + }) + result := finder(src) + assert.Equal(t, O.Some("2:c"), result) +} diff --git a/v2/array/generic/any.go b/v2/array/generic/any.go new file mode 100644 index 0000000..06ac726 --- /dev/null +++ b/v2/array/generic/any.go @@ -0,0 +1,34 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + F "github.com/IBM/fp-go/v2/function" + O "github.com/IBM/fp-go/v2/option" +) + +// AnyWithIndex tests if any of the elements in the array matches the predicate +func AnyWithIndex[AS ~[]A, PRED ~func(int, A) bool, A any](pred PRED) func(AS) bool { + return F.Flow2( + FindFirstWithIndex[AS](pred), + O.IsSome[A], + ) +} + +// Any tests if any of the elements in the array matches the predicate +func Any[AS ~[]A, PRED ~func(A) bool, A any](pred PRED) func(AS) bool { + return AnyWithIndex[AS](F.Ignore1of2[int](pred)) +} diff --git a/v2/array/generic/array.go b/v2/array/generic/array.go new file mode 100644 index 0000000..11acf19 --- /dev/null +++ b/v2/array/generic/array.go @@ -0,0 +1,368 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + F "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/internal/array" + FC "github.com/IBM/fp-go/v2/internal/functor" + M "github.com/IBM/fp-go/v2/monoid" + O "github.com/IBM/fp-go/v2/option" + "github.com/IBM/fp-go/v2/tuple" +) + +// Of constructs a single element array +func Of[GA ~[]A, A any](value A) GA { + return GA{value} +} + +func Reduce[GA ~[]A, A, B any](f func(B, A) B, initial B) func(GA) B { + return func(as GA) B { + return MonadReduce[GA](as, f, initial) + } +} + +func ReduceWithIndex[GA ~[]A, A, B any](f func(int, B, A) B, initial B) func(GA) B { + return func(as GA) B { + return MonadReduceWithIndex[GA](as, f, initial) + } +} + +func ReduceRight[GA ~[]A, A, B any](f func(A, B) B, initial B) func(GA) B { + return func(as GA) B { + return MonadReduceRight[GA](as, f, initial) + } +} + +func ReduceRightWithIndex[GA ~[]A, A, B any](f func(int, A, B) B, initial B) func(GA) B { + return func(as GA) B { + return MonadReduceRightWithIndex[GA](as, f, initial) + } +} + +func MonadReduce[GA ~[]A, A, B any](fa GA, f func(B, A) B, initial B) B { + return array.Reduce(fa, f, initial) +} + +func MonadReduceWithIndex[GA ~[]A, A, B any](fa GA, f func(int, B, A) B, initial B) B { + return array.ReduceWithIndex(fa, f, initial) +} + +func MonadReduceRight[GA ~[]A, A, B any](fa GA, f func(A, B) B, initial B) B { + return array.ReduceRight(fa, f, initial) +} + +func MonadReduceRightWithIndex[GA ~[]A, A, B any](fa GA, f func(int, A, B) B, initial B) B { + return array.ReduceRightWithIndex(fa, f, initial) +} + +// From constructs an array from a set of variadic arguments +func From[GA ~[]A, A any](data ...A) GA { + return data +} + +// MakeBy returns a `Array` of length `n` with element `i` initialized with `f(i)`. +func MakeBy[AS ~[]A, F ~func(int) A, A any](n int, f F) AS { + // sanity check + if n <= 0 { + return Empty[AS]() + } + // run the generator function across the input + as := make(AS, n) + for i := n - 1; i >= 0; i-- { + as[i] = f(i) + } + return as +} + +func Replicate[AS ~[]A, A any](n int, a A) AS { + return MakeBy[AS](n, F.Constant1[int](a)) +} + +func Lookup[GA ~[]A, A any](idx int) func(GA) O.Option[A] { + none := O.None[A]() + if idx < 0 { + return F.Constant1[GA](none) + } + return func(as GA) O.Option[A] { + if idx < len(as) { + return O.Some(as[idx]) + } + return none + } +} + +func Tail[GA ~[]A, A any](as GA) O.Option[GA] { + if array.IsEmpty(as) { + return O.None[GA]() + } + return O.Some(as[1:]) +} + +func Head[GA ~[]A, A any](as GA) O.Option[A] { + if array.IsEmpty(as) { + return O.None[A]() + } + return O.Some(as[0]) +} + +func First[GA ~[]A, A any](as GA) O.Option[A] { + return Head(as) +} + +func Last[GA ~[]A, A any](as GA) O.Option[A] { + if array.IsEmpty(as) { + return O.None[A]() + } + return O.Some(as[len(as)-1]) +} + +func Append[GA ~[]A, A any](as GA, a A) GA { + return array.Append(as, a) +} + +func Empty[GA ~[]A, A any]() GA { + return array.Empty[GA]() +} + +func UpsertAt[GA ~[]A, A any](a A) func(GA) GA { + return array.UpsertAt[GA](a) +} + +func MonadMap[GA ~[]A, GB ~[]B, A, B any](as GA, f func(a A) B) GB { + return array.MonadMap[GA, GB](as, f) +} + +func Map[GA ~[]A, GB ~[]B, A, B any](f func(a A) B) func(GA) GB { + return array.Map[GA, GB](f) +} + +func MonadMapWithIndex[GA ~[]A, GB ~[]B, A, B any](as GA, f func(int, A) B) GB { + return array.MonadMapWithIndex[GA, GB](as, f) +} + +func MapWithIndex[GA ~[]A, GB ~[]B, A, B any](f func(int, A) B) func(GA) GB { + return F.Bind2nd(MonadMapWithIndex[GA, GB, A, B], f) +} + +func Size[GA ~[]A, A any](as GA) int { + return len(as) +} + +func filterMap[GA ~[]A, GB ~[]B, A, B any](fa GA, f func(A) O.Option[B]) GB { + result := make(GB, 0, len(fa)) + for _, a := range fa { + O.Map(func(b B) B { + result = append(result, b) + return b + })(f(a)) + } + return result +} + +func filterMapWithIndex[GA ~[]A, GB ~[]B, A, B any](fa GA, f func(int, A) O.Option[B]) GB { + result := make(GB, 0, len(fa)) + for i, a := range fa { + O.Map(func(b B) B { + result = append(result, b) + return b + })(f(i, a)) + } + return result +} + +func MonadFilterMap[GA ~[]A, GB ~[]B, A, B any](fa GA, f func(A) O.Option[B]) GB { + return filterMap[GA, GB](fa, f) +} + +func MonadFilterMapWithIndex[GA ~[]A, GB ~[]B, A, B any](fa GA, f func(int, A) O.Option[B]) GB { + return filterMapWithIndex[GA, GB](fa, f) +} + +func filterWithIndex[AS ~[]A, PRED ~func(int, A) bool, A any](fa AS, pred PRED) AS { + result := make(AS, 0, len(fa)) + for i, a := range fa { + if pred(i, a) { + result = append(result, a) + } + } + return result +} + +func FilterWithIndex[AS ~[]A, PRED ~func(int, A) bool, A any](pred PRED) func(AS) AS { + return F.Bind2nd(filterWithIndex[AS, PRED, A], pred) +} + +func Filter[AS ~[]A, PRED ~func(A) bool, A any](pred PRED) func(AS) AS { + return FilterWithIndex[AS](F.Ignore1of2[int](pred)) +} + +func FilterChain[GA ~[]A, GB ~[]B, A, B any](f func(a A) O.Option[GB]) func(GA) GB { + return F.Flow2( + FilterMap[GA, []GB](f), + Flatten[[]GB], + ) +} + +func Flatten[GAA ~[]GA, GA ~[]A, A any](mma GAA) GA { + return MonadChain(mma, F.Identity[GA]) +} + +func FilterMap[GA ~[]A, GB ~[]B, A, B any](f func(A) O.Option[B]) func(GA) GB { + return F.Bind2nd(MonadFilterMap[GA, GB, A, B], f) +} + +func FilterMapWithIndex[GA ~[]A, GB ~[]B, A, B any](f func(int, A) O.Option[B]) func(GA) GB { + return F.Bind2nd(MonadFilterMapWithIndex[GA, GB, A, B], f) +} + +func MonadPartition[GA ~[]A, A any](as GA, pred func(A) bool) tuple.Tuple2[GA, GA] { + left := Empty[GA]() + right := Empty[GA]() + array.Reduce(as, func(c bool, a A) bool { + if pred(a) { + right = append(right, a) + } else { + left = append(left, a) + } + return c + }, true) + // returns the partition + return tuple.MakeTuple2(left, right) +} + +func Partition[GA ~[]A, A any](pred func(A) bool) func(GA) tuple.Tuple2[GA, GA] { + return F.Bind2nd(MonadPartition[GA, A], pred) +} + +func MonadChain[AS ~[]A, BS ~[]B, A, B any](fa AS, f func(a A) BS) BS { + return array.Reduce(fa, func(bs BS, a A) BS { + return append(bs, f(a)...) + }, Empty[BS]()) +} + +func Chain[AS ~[]A, BS ~[]B, A, B any](f func(A) BS) func(AS) BS { + return F.Bind2nd(MonadChain[AS, BS, A, B], f) +} + +func MonadAp[BS ~[]B, ABS ~[]func(A) B, AS ~[]A, B, A any](fab ABS, fa AS) BS { + return MonadChain(fab, F.Bind1st(MonadMap[AS, BS, A, B], fa)) +} + +func Ap[BS ~[]B, ABS ~[]func(A) B, AS ~[]A, B, A any](fa AS) func(ABS) BS { + return F.Bind2nd(MonadAp[BS, ABS, AS], fa) +} + +func IsEmpty[AS ~[]A, A any](as AS) bool { + return array.IsEmpty(as) +} + +func IsNil[GA ~[]A, A any](as GA) bool { + return array.IsNil(as) +} + +func IsNonNil[GA ~[]A, A any](as GA) bool { + return array.IsNonNil(as) +} + +func Match[AS ~[]A, A, B any](onEmpty func() B, onNonEmpty func(AS) B) func(AS) B { + return func(as AS) B { + if IsEmpty(as) { + return onEmpty() + } + return onNonEmpty(as) + } +} + +func MatchLeft[AS ~[]A, A, B any](onEmpty func() B, onNonEmpty func(A, AS) B) func(AS) B { + return func(as AS) B { + if IsEmpty(as) { + return onEmpty() + } + return onNonEmpty(as[0], as[1:]) + } +} + +func Slice[AS ~[]A, A any](start int, end int) func(AS) AS { + return func(a AS) AS { + return a[start:end] + } +} + +func SliceRight[AS ~[]A, A any](start int) func(AS) AS { + return func(a AS) AS { + return a[start:] + } +} + +func Copy[AS ~[]A, A any](b AS) AS { + buf := make(AS, len(b)) + copy(buf, b) + return buf +} + +func Clone[AS ~[]A, A any](f func(A) A) func(as AS) AS { + // implementation assumes that map does not optimize for the empty array + return Map[AS, AS](f) +} + +func FoldMap[AS ~[]A, A, B any](m M.Monoid[B]) func(func(A) B) func(AS) B { + empty := m.Empty() + concat := m.Concat + return func(f func(A) B) func(AS) B { + return func(as AS) B { + return array.Reduce(as, func(cur B, a A) B { + return concat(cur, f(a)) + }, empty) + } + } +} + +func FoldMapWithIndex[AS ~[]A, A, B any](m M.Monoid[B]) func(func(int, A) B) func(AS) B { + empty := m.Empty() + concat := m.Concat + return func(f func(int, A) B) func(AS) B { + return func(as AS) B { + return array.ReduceWithIndex(as, func(idx int, cur B, a A) B { + return concat(cur, f(idx, a)) + }, empty) + } + } +} + +func Fold[AS ~[]A, A any](m M.Monoid[A]) func(AS) A { + empty := m.Empty() + concat := m.Concat + return func(as AS) A { + return array.Reduce(as, concat, empty) + } +} + +func Push[ENDO ~func(GA) GA, GA ~[]A, A any](a A) ENDO { + return F.Bind2nd(array.Push[GA, A], a) +} + +func MonadFlap[FAB ~func(A) B, GFAB ~[]FAB, GB ~[]B, A, B any](fab GFAB, a A) GB { + return FC.MonadFlap(MonadMap[GFAB, GB], fab, a) +} + +func Flap[FAB ~func(A) B, GFAB ~[]FAB, GB ~[]B, A, B any](a A) func(GFAB) GB { + return FC.Flap(Map[GFAB, GB], a) +} + +func Prepend[ENDO ~func(AS) AS, AS []A, A any](head A) ENDO { + return array.Prepend[ENDO](head) +} diff --git a/v2/array/generic/bind.go b/v2/array/generic/bind.go new file mode 100644 index 0000000..6f615f8 --- /dev/null +++ b/v2/array/generic/bind.go @@ -0,0 +1,89 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + A "github.com/IBM/fp-go/v2/internal/apply" + C "github.com/IBM/fp-go/v2/internal/chain" + F "github.com/IBM/fp-go/v2/internal/functor" +) + +// Bind creates an empty context of type [S] to be used with the [Bind] operation +func Do[GS ~[]S, S any]( + empty S, +) GS { + return Of[GS](empty) +} + +// Bind attaches the result of a computation to a context [S1] to produce a context [S2] +func Bind[GS1 ~[]S1, GS2 ~[]S2, GT ~[]T, S1, S2, T any]( + setter func(T) func(S1) S2, + f func(S1) GT, +) func(GS1) GS2 { + return C.Bind( + Chain[GS1, GS2, S1, S2], + Map[GT, GS2, T, S2], + setter, + f, + ) +} + +// Let attaches the result of a computation to a context [S1] to produce a context [S2] +func Let[GS1 ~[]S1, GS2 ~[]S2, S1, S2, T any]( + key func(T) func(S1) S2, + f func(S1) T, +) func(GS1) GS2 { + return F.Let( + Map[GS1, GS2, S1, S2], + key, + f, + ) +} + +// LetTo attaches the a value to a context [S1] to produce a context [S2] +func LetTo[GS1 ~[]S1, GS2 ~[]S2, S1, S2, B any]( + key func(B) func(S1) S2, + b B, +) func(GS1) GS2 { + return F.LetTo( + Map[GS1, GS2, S1, S2], + key, + b, + ) +} + +// BindTo initializes a new state [S1] from a value [T] +func BindTo[GS1 ~[]S1, GT ~[]T, S1, T any]( + setter func(T) S1, +) func(GT) GS1 { + return C.BindTo( + Map[GT, GS1, T, S1], + setter, + ) +} + +// ApS attaches a value to a context [S1] to produce a context [S2] by considering the context and the value concurrently +func ApS[GS1 ~[]S1, GS2 ~[]S2, GT ~[]T, S1, S2, T any]( + setter func(T) func(S1) S2, + fa GT, +) func(GS1) GS2 { + return A.ApS( + Ap[GS2, []func(T) S2, GT, S2, T], + Map[GS1, []func(T) S2, S1, func(T) S2], + setter, + fa, + ) +} diff --git a/v2/array/generic/find.go b/v2/array/generic/find.go new file mode 100644 index 0000000..d56d4bd --- /dev/null +++ b/v2/array/generic/find.go @@ -0,0 +1,97 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + F "github.com/IBM/fp-go/v2/function" + O "github.com/IBM/fp-go/v2/option" +) + +// FindFirstWithIndex finds the first element which satisfies a predicate (or a refinement) function +func FindFirstWithIndex[AS ~[]A, PRED ~func(int, A) bool, A any](pred PRED) func(AS) O.Option[A] { + none := O.None[A]() + return func(as AS) O.Option[A] { + for i, a := range as { + if pred(i, a) { + return O.Some(a) + } + } + return none + } +} + +// FindFirst finds the first element which satisfies a predicate (or a refinement) function +func FindFirst[AS ~[]A, PRED ~func(A) bool, A any](pred PRED) func(AS) O.Option[A] { + return FindFirstWithIndex[AS](F.Ignore1of2[int](pred)) +} + +// FindFirstMapWithIndex finds the first element returned by an [O.Option] based selector function +func FindFirstMapWithIndex[AS ~[]A, PRED ~func(int, A) O.Option[B], A, B any](pred PRED) func(AS) O.Option[B] { + none := O.None[B]() + return func(as AS) O.Option[B] { + count := len(as) + for i := 0; i < count; i++ { + out := pred(i, as[i]) + if O.IsSome(out) { + return out + } + } + return none + } +} + +// FindFirstMap finds the first element returned by an [O.Option] based selector function +func FindFirstMap[AS ~[]A, PRED ~func(A) O.Option[B], A, B any](pred PRED) func(AS) O.Option[B] { + return FindFirstMapWithIndex[AS](F.Ignore1of2[int](pred)) +} + +// FindLastWithIndex finds the first element which satisfies a predicate (or a refinement) function +func FindLastWithIndex[AS ~[]A, PRED ~func(int, A) bool, A any](pred PRED) func(AS) O.Option[A] { + none := O.None[A]() + return func(as AS) O.Option[A] { + for i := len(as) - 1; i >= 0; i-- { + a := as[i] + if pred(i, a) { + return O.Some(a) + } + } + return none + } +} + +// FindLast finds the first element which satisfies a predicate (or a refinement) function +func FindLast[AS ~[]A, PRED ~func(A) bool, A any](pred PRED) func(AS) O.Option[A] { + return FindLastWithIndex[AS](F.Ignore1of2[int](pred)) +} + +// FindLastMapWithIndex finds the first element returned by an [O.Option] based selector function +func FindLastMapWithIndex[AS ~[]A, PRED ~func(int, A) O.Option[B], A, B any](pred PRED) func(AS) O.Option[B] { + none := O.None[B]() + return func(as AS) O.Option[B] { + for i := len(as) - 1; i >= 0; i-- { + out := pred(i, as[i]) + if O.IsSome(out) { + return out + } + } + return none + } +} + +// FindLastMap finds the first element returned by an [O.Option] based selector function +func FindLastMap[AS ~[]A, PRED ~func(A) O.Option[B], A, B any](pred PRED) func(AS) O.Option[B] { + return FindLastMapWithIndex[AS](F.Ignore1of2[int](pred)) +} diff --git a/v2/array/generic/monad.go b/v2/array/generic/monad.go new file mode 100644 index 0000000..3967b98 --- /dev/null +++ b/v2/array/generic/monad.go @@ -0,0 +1,43 @@ +// Copyright (c) 2024 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + "github.com/IBM/fp-go/v2/internal/monad" +) + +type arrayMonad[A, B any, GA ~[]A, GB ~[]B, GAB ~[]func(A) B] struct{} + +func (o *arrayMonad[A, B, GA, GB, GAB]) Of(a A) GA { + return Of[GA, A](a) +} + +func (o *arrayMonad[A, B, GA, GB, GAB]) Map(f func(A) B) func(GA) GB { + return Map[GA, GB, A, B](f) +} + +func (o *arrayMonad[A, B, GA, GB, GAB]) Chain(f func(A) GB) func(GA) GB { + return Chain[GA, GB, A, B](f) +} + +func (o *arrayMonad[A, B, GA, GB, GAB]) Ap(fa GA) func(GAB) GB { + return Ap[GB, GAB, GA, B, A](fa) +} + +// Monad implements the monadic operations for an array +func Monad[A, B any, GA ~[]A, GB ~[]B, GAB ~[]func(A) B]() monad.Monad[A, B, GA, GB, GAB] { + return &arrayMonad[A, B, GA, GB, GAB]{} +} diff --git a/v2/array/generic/sort.go b/v2/array/generic/sort.go new file mode 100644 index 0000000..e3ddd6e --- /dev/null +++ b/v2/array/generic/sort.go @@ -0,0 +1,56 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + "sort" + + F "github.com/IBM/fp-go/v2/function" + O "github.com/IBM/fp-go/v2/ord" +) + +// Sort implements a stable sort on the array given the provided ordering +func Sort[GA ~[]T, T any](ord O.Ord[T]) func(ma GA) GA { + return SortByKey[GA](ord, F.Identity[T]) +} + +// SortByKey implements a stable sort on the array given the provided ordering on an extracted key +func SortByKey[GA ~[]T, K, T any](ord O.Ord[K], f func(T) K) func(ma GA) GA { + + return func(ma GA) GA { + // nothing to sort + l := len(ma) + if l < 2 { + return ma + } + // copy + cpy := make(GA, l) + copy(cpy, ma) + sort.Slice(cpy, func(i, j int) bool { + return ord.Compare(f(cpy[i]), f(cpy[j])) < 0 + }) + return cpy + } +} + +// SortBy implements a stable sort on the array given the provided ordering +func SortBy[GA ~[]T, GO ~[]O.Ord[T], T any](ord GO) func(ma GA) GA { + return F.Pipe2( + ord, + Fold[GO](O.Monoid[T]()), + Sort[GA, T], + ) +} diff --git a/v2/array/generic/uniq.go b/v2/array/generic/uniq.go new file mode 100644 index 0000000..66330ae --- /dev/null +++ b/v2/array/generic/uniq.go @@ -0,0 +1,32 @@ +package generic + +import F "github.com/IBM/fp-go/v2/function" + +// StrictUniq converts an array of arbitrary items into an array or unique items +// where uniqueness is determined by the built-in uniqueness constraint +func StrictUniq[AS ~[]A, A comparable](as AS) AS { + return Uniq[AS](F.Identity[A])(as) +} + +// uniquePredUnsafe returns a predicate on a map for uniqueness +func uniquePredUnsafe[PRED ~func(A) K, A any, K comparable](f PRED) func(int, A) bool { + lookup := make(map[K]bool) + return func(_ int, a A) bool { + k := f(a) + _, has := lookup[k] + if has { + return false + } + lookup[k] = true + return true + } +} + +// Uniq converts an array of arbitrary items into an array or unique items +// where uniqueness is determined based on a key extractor function +func Uniq[AS ~[]A, PRED ~func(A) K, A any, K comparable](f PRED) func(as AS) AS { + return func(as AS) AS { + // we need to create a new predicate for each iteration + return filterWithIndex(as, uniquePredUnsafe(f)) + } +} diff --git a/v2/array/generic/zip.go b/v2/array/generic/zip.go new file mode 100644 index 0000000..a19104a --- /dev/null +++ b/v2/array/generic/zip.go @@ -0,0 +1,52 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + F "github.com/IBM/fp-go/v2/function" + N "github.com/IBM/fp-go/v2/number" + T "github.com/IBM/fp-go/v2/tuple" +) + +// ZipWith applies a function to pairs of elements at the same index in two arrays, collecting the results in a new array. If one +// input array is short, excess elements of the longer array are discarded. +func ZipWith[AS ~[]A, BS ~[]B, CS ~[]C, FCT ~func(A, B) C, A, B, C any](fa AS, fb BS, f FCT) CS { + l := N.Min(len(fa), len(fb)) + res := make(CS, l) + for i := l - 1; i >= 0; i-- { + res[i] = f(fa[i], fb[i]) + } + return res +} + +// Zip takes two arrays and returns an array of corresponding pairs. If one input array is short, excess elements of the +// longer array are discarded +func Zip[AS ~[]A, BS ~[]B, CS ~[]T.Tuple2[A, B], A, B any](fb BS) func(AS) CS { + return F.Bind23of3(ZipWith[AS, BS, CS, func(A, B) T.Tuple2[A, B]])(fb, T.MakeTuple2[A, B]) +} + +// Unzip is the function is reverse of [Zip]. Takes an array of pairs and return two corresponding arrays +func Unzip[AS ~[]A, BS ~[]B, CS ~[]T.Tuple2[A, B], A, B any](cs CS) T.Tuple2[AS, BS] { + l := len(cs) + as := make(AS, l) + bs := make(BS, l) + for i := l - 1; i >= 0; i-- { + t := cs[i] + as[i] = t.F1 + bs[i] = t.F2 + } + return T.MakeTuple2(as, bs) +} diff --git a/v2/array/magma.go b/v2/array/magma.go new file mode 100644 index 0000000..37d1432 --- /dev/null +++ b/v2/array/magma.go @@ -0,0 +1,38 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package array + +import ( + M "github.com/IBM/fp-go/v2/monoid" +) + +// ConcatAll concatenates all elements of an array using the provided Monoid. +// This reduces the array to a single value by repeatedly applying the Monoid's concat operation. +// +// Example: +// +// import "github.com/IBM/fp-go/v2/monoid" +// +// // Sum all numbers +// sumAll := array.ConcatAll(monoid.MonoidSum[int]()) +// result := sumAll([]int{1, 2, 3, 4, 5}) // 15 +// +// // Concatenate all strings +// concatStrings := array.ConcatAll(monoid.MonoidString()) +// result2 := concatStrings([]string{"Hello", " ", "World"}) // "Hello World" +func ConcatAll[A any](m M.Monoid[A]) func([]A) A { + return Reduce(m.Concat, m.Empty()) +} diff --git a/v2/array/magma_test.go b/v2/array/magma_test.go new file mode 100644 index 0000000..0c7927d --- /dev/null +++ b/v2/array/magma_test.go @@ -0,0 +1,36 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package array + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + M "github.com/IBM/fp-go/v2/monoid" +) + +var subInt = M.MakeMonoid(func(first int, second int) int { + return first - second +}, 0) + +func TestConcatAll(t *testing.T) { + + var subAll = ConcatAll(subInt) + + assert.Equal(t, subAll([]int{1, 2, 3}), -6) + +} diff --git a/v2/array/misc_test.go b/v2/array/misc_test.go new file mode 100644 index 0000000..114a315 --- /dev/null +++ b/v2/array/misc_test.go @@ -0,0 +1,161 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package array + +import ( + "testing" + + O "github.com/IBM/fp-go/v2/option" + OR "github.com/IBM/fp-go/v2/ord" + "github.com/stretchr/testify/assert" +) + +func TestAnyWithIndex(t *testing.T) { + src := []int{1, 2, 3, 4, 5} + checker := AnyWithIndex(func(i, x int) bool { + return i == 2 && x == 3 + }) + assert.True(t, checker(src)) + + checker2 := AnyWithIndex(func(i, x int) bool { + return i == 10 + }) + assert.False(t, checker2(src)) +} + +func TestSemigroup(t *testing.T) { + sg := Semigroup[int]() + result := sg.Concat([]int{1, 2}, []int{3, 4}) + assert.Equal(t, []int{1, 2, 3, 4}, result) +} + +func TestArrayConcatAll(t *testing.T) { + result := ArrayConcatAll( + []int{1, 2}, + []int{3, 4}, + []int{5, 6}, + ) + assert.Equal(t, []int{1, 2, 3, 4, 5, 6}, result) + + // Test with empty arrays + result2 := ArrayConcatAll( + []int{}, + []int{1}, + []int{}, + ) + assert.Equal(t, []int{1}, result2) +} + +func TestMonad(t *testing.T) { + m := Monad[int, string]() + + // Test Map + mapFn := m.Map(func(x int) string { + return string(rune('a' + x - 1)) + }) + mapped := mapFn([]int{1, 2, 3}) + assert.Equal(t, []string{"a", "b", "c"}, mapped) + + // Test Chain + chainFn := m.Chain(func(x int) []string { + return []string{string(rune('a' + x - 1))} + }) + chained := chainFn([]int{1, 2}) + assert.Equal(t, []string{"a", "b"}, chained) + + // Test Of + ofResult := m.Of(42) + assert.Equal(t, []int{42}, ofResult) +} + +func TestSortByKey(t *testing.T) { + type Person struct { + Name string + Age int + } + + people := []Person{ + {"Alice", 30}, + {"Bob", 25}, + {"Charlie", 35}, + } + + sorter := SortByKey(OR.FromStrictCompare[int](), func(p Person) int { + return p.Age + }) + result := sorter(people) + + assert.Equal(t, "Bob", result[0].Name) + assert.Equal(t, "Alice", result[1].Name) + assert.Equal(t, "Charlie", result[2].Name) +} + +func TestMonadTraverse(t *testing.T) { + result := MonadTraverse( + O.Of[[]int], + O.Map[[]int, func(int) []int], + O.Ap[[]int, int], + []int{1, 3, 5}, + func(n int) O.Option[int] { + if n%2 == 1 { + return O.Some(n * 2) + } + return O.None[int]() + }, + ) + + assert.Equal(t, O.Some([]int{2, 6, 10}), result) + + // Test with None case + result2 := MonadTraverse( + O.Of[[]int], + O.Map[[]int, func(int) []int], + O.Ap[[]int, int], + []int{1, 2, 3}, + func(n int) O.Option[int] { + if n%2 == 1 { + return O.Some(n * 2) + } + return O.None[int]() + }, + ) + + assert.Equal(t, O.None[[]int](), result2) +} + +func TestUniqByKey(t *testing.T) { + type Person struct { + Name string + Age int + } + + people := []Person{ + {"Alice", 30}, + {"Bob", 25}, + {"Alice", 35}, + {"Charlie", 30}, + } + + uniquer := Uniq(func(p Person) string { + return p.Name + }) + result := uniquer(people) + + assert.Equal(t, 3, len(result)) + assert.Equal(t, "Alice", result[0].Name) + assert.Equal(t, "Bob", result[1].Name) + assert.Equal(t, "Charlie", result[2].Name) +} diff --git a/v2/array/monad.go b/v2/array/monad.go new file mode 100644 index 0000000..e37ac9b --- /dev/null +++ b/v2/array/monad.go @@ -0,0 +1,39 @@ +// Copyright (c) 2024 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package array + +import ( + G "github.com/IBM/fp-go/v2/array/generic" + "github.com/IBM/fp-go/v2/internal/monad" +) + +// Monad returns the monadic operations for an array. +// This provides a structured way to access all monad operations (Map, Chain, Ap, Of) +// for arrays in a single interface. +// +// The Monad interface is useful when you need to pass monadic operations as parameters +// or when working with generic code that operates on any monad. +// +// Example: +// +// m := array.Monad[int, string]() +// result := m.Chain([]int{1, 2, 3}, func(x int) []string { +// return []string{fmt.Sprintf("%d", x), fmt.Sprintf("%d!", x)} +// }) +// // Result: ["1", "1!", "2", "2!", "3", "3!"] +func Monad[A, B any]() monad.Monad[A, B, []A, []B, []func(A) B] { + return G.Monad[A, B, []A, []B, []func(A) B]() +} diff --git a/v2/array/monoid.go b/v2/array/monoid.go new file mode 100644 index 0000000..645cbb6 --- /dev/null +++ b/v2/array/monoid.go @@ -0,0 +1,89 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package array + +import ( + "github.com/IBM/fp-go/v2/internal/array" + M "github.com/IBM/fp-go/v2/monoid" + S "github.com/IBM/fp-go/v2/semigroup" +) + +func concat[T any](left, right []T) []T { + // some performance checks + ll := len(left) + if ll == 0 { + return right + } + lr := len(right) + if lr == 0 { + return left + } + // need to copy + buf := make([]T, ll+lr) + copy(buf[copy(buf, left):], right) + return buf +} + +// Monoid returns a Monoid instance for arrays. +// The Monoid combines arrays through concatenation, with an empty array as the identity element. +// +// Example: +// +// m := array.Monoid[int]() +// result := m.Concat([]int{1, 2}, []int{3, 4}) // [1, 2, 3, 4] +// empty := m.Empty() // [] +func Monoid[T any]() M.Monoid[[]T] { + return M.MakeMonoid(concat[T], Empty[T]()) +} + +// Semigroup returns a Semigroup instance for arrays. +// The Semigroup combines arrays through concatenation. +// +// Example: +// +// s := array.Semigroup[int]() +// result := s.Concat([]int{1, 2}, []int{3, 4}) // [1, 2, 3, 4] +func Semigroup[T any]() S.Semigroup[[]T] { + return S.MakeSemigroup(concat[T]) +} + +func addLen[A any](count int, data []A) int { + return count + len(data) +} + +// ArrayConcatAll efficiently concatenates multiple arrays into a single array. +// This function pre-allocates the exact amount of memory needed and performs +// a single copy operation for each input array, making it more efficient than +// repeated concatenations. +// +// Example: +// +// result := array.ArrayConcatAll( +// []int{1, 2}, +// []int{3, 4}, +// []int{5, 6}, +// ) // [1, 2, 3, 4, 5, 6] +func ArrayConcatAll[A any](data ...[]A) []A { + // get the full size + count := array.Reduce(data, addLen[A], 0) + buf := make([]A, count) + // copy + array.Reduce(data, func(idx int, seg []A) int { + return idx + copy(buf[idx:], seg) + }, 0) + // returns the final array + return buf +} diff --git a/v2/array/monoid_test.go b/v2/array/monoid_test.go new file mode 100644 index 0000000..fe1e1d6 --- /dev/null +++ b/v2/array/monoid_test.go @@ -0,0 +1,26 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package array + +import ( + "testing" + + M "github.com/IBM/fp-go/v2/monoid/testing" +) + +func TestMonoid(t *testing.T) { + M.AssertLaws(t, Monoid[int]())([][]int{{}, {1}, {1, 2}}) +} diff --git a/v2/array/nonempty/array.go b/v2/array/nonempty/array.go new file mode 100644 index 0000000..29b630a --- /dev/null +++ b/v2/array/nonempty/array.go @@ -0,0 +1,136 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package nonempty + +import ( + G "github.com/IBM/fp-go/v2/array/generic" + EM "github.com/IBM/fp-go/v2/endomorphism" + F "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/internal/array" + S "github.com/IBM/fp-go/v2/semigroup" +) + +// NonEmptyArray represents an array with at least one element +type NonEmptyArray[A any] []A + +// Of constructs a single element array +func Of[A any](first A) NonEmptyArray[A] { + return G.Of[NonEmptyArray[A]](first) +} + +// From constructs a [NonEmptyArray] from a set of variadic arguments +func From[A any](first A, data ...A) NonEmptyArray[A] { + count := len(data) + if count == 0 { + return Of(first) + } + // allocate the requested buffer + buffer := make(NonEmptyArray[A], count+1) + buffer[0] = first + copy(buffer[1:], data) + return buffer +} + +func IsEmpty[A any](_ NonEmptyArray[A]) bool { + return false +} + +func IsNonEmpty[A any](_ NonEmptyArray[A]) bool { + return true +} + +func MonadMap[A, B any](as NonEmptyArray[A], f func(a A) B) NonEmptyArray[B] { + return G.MonadMap[NonEmptyArray[A], NonEmptyArray[B]](as, f) +} + +func Map[A, B any](f func(a A) B) func(NonEmptyArray[A]) NonEmptyArray[B] { + return F.Bind2nd(MonadMap[A, B], f) +} + +func Reduce[A, B any](f func(B, A) B, initial B) func(NonEmptyArray[A]) B { + return func(as NonEmptyArray[A]) B { + return array.Reduce(as, f, initial) + } +} + +func ReduceRight[A, B any](f func(A, B) B, initial B) func(NonEmptyArray[A]) B { + return func(as NonEmptyArray[A]) B { + return array.ReduceRight(as, f, initial) + } +} + +func Tail[A any](as NonEmptyArray[A]) []A { + return as[1:] +} + +func Head[A any](as NonEmptyArray[A]) A { + return as[0] +} + +func First[A any](as NonEmptyArray[A]) A { + return as[0] +} + +func Last[A any](as NonEmptyArray[A]) A { + return as[len(as)-1] +} + +func Size[A any](as NonEmptyArray[A]) int { + return G.Size(as) +} + +func Flatten[A any](mma NonEmptyArray[NonEmptyArray[A]]) NonEmptyArray[A] { + return G.Flatten(mma) +} + +func MonadChain[A, B any](fa NonEmptyArray[A], f func(a A) NonEmptyArray[B]) NonEmptyArray[B] { + return G.MonadChain[NonEmptyArray[A], NonEmptyArray[B]](fa, f) +} + +func Chain[A, B any](f func(A) NonEmptyArray[B]) func(NonEmptyArray[A]) NonEmptyArray[B] { + return G.Chain[NonEmptyArray[A], NonEmptyArray[B]](f) +} + +func MonadAp[B, A any](fab NonEmptyArray[func(A) B], fa NonEmptyArray[A]) NonEmptyArray[B] { + return G.MonadAp[NonEmptyArray[B]](fab, fa) +} + +func Ap[B, A any](fa NonEmptyArray[A]) func(NonEmptyArray[func(A) B]) NonEmptyArray[B] { + return G.Ap[NonEmptyArray[B], NonEmptyArray[func(A) B]](fa) +} + +// FoldMap maps and folds a [NonEmptyArray]. Map the [NonEmptyArray] passing each value to the iterating function. Then fold the results using the provided [Semigroup]. +func FoldMap[A, B any](s S.Semigroup[B]) func(func(A) B) func(NonEmptyArray[A]) B { + return func(f func(A) B) func(NonEmptyArray[A]) B { + return func(as NonEmptyArray[A]) B { + return array.Reduce(Tail(as), func(cur B, a A) B { + return s.Concat(cur, f(a)) + }, f(Head(as))) + } + } +} + +// Fold folds the [NonEmptyArray] using the provided [Semigroup]. +func Fold[A any](s S.Semigroup[A]) func(NonEmptyArray[A]) A { + return func(as NonEmptyArray[A]) A { + return array.Reduce(Tail(as), s.Concat, Head(as)) + } +} + +// Prepend prepends a single value to an array +func Prepend[A any](head A) EM.Endomorphism[NonEmptyArray[A]] { + return array.Prepend[EM.Endomorphism[NonEmptyArray[A]]](head) +} diff --git a/v2/array/sequence.go b/v2/array/sequence.go new file mode 100644 index 0000000..c6aefe1 --- /dev/null +++ b/v2/array/sequence.go @@ -0,0 +1,95 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package array + +import ( + F "github.com/IBM/fp-go/v2/function" + O "github.com/IBM/fp-go/v2/option" +) + +// Sequence takes an array where elements are HKT (higher kinded type) and, +// using an applicative of that HKT, returns an HKT of []A. +// +// For example, it can turn: +// - []Either[error, string] into Either[error, []string] +// - []Option[int] into Option[[]int] +// +// Sequence requires an Applicative of the HKT you are targeting. To turn an +// []Either[E, A] into an Either[E, []A], it needs an Applicative for Either. +// To turn an []Option[A] into an Option[[]A], it needs an Applicative for Option. +// +// Note: We need to pass the members of the applicative explicitly because Go does not +// support higher kinded types or template methods on structs or interfaces. +// +// Type parameters: +// - HKTA = HKT (e.g., Option[A], Either[E, A]) +// - HKTRA = HKT<[]A> (e.g., Option[[]A], Either[E, []A]) +// - HKTFRA = HKT (e.g., Option[func(A)[]A]) +// +// Example: +// +// import "github.com/IBM/fp-go/v2/option" +// +// opts := []option.Option[int]{ +// option.Some(1), +// option.Some(2), +// option.Some(3), +// } +// +// seq := array.Sequence( +// option.Of[[]int], +// option.MonadMap[[]int, func(int) []int], +// option.MonadAp[[]int, int], +// ) +// result := seq(opts) // Some([1, 2, 3]) +func Sequence[A, HKTA, HKTRA, HKTFRA any]( + _of func([]A) HKTRA, + _map func(HKTRA, func([]A) func(A) []A) HKTFRA, + _ap func(HKTFRA, HKTA) HKTRA, +) func([]HKTA) HKTRA { + ca := F.Curry2(Append[A]) + empty := _of(Empty[A]()) + return Reduce(func(fas HKTRA, fa HKTA) HKTRA { + return _ap(_map(fas, ca), fa) + }, empty) +} + +// ArrayOption returns a function to convert a sequence of options into an option of a sequence. +// If all options are Some, returns Some containing an array of all values. +// If any option is None, returns None. +// +// Example: +// +// opts := []option.Option[int]{ +// option.Some(1), +// option.Some(2), +// option.Some(3), +// } +// result := array.ArrayOption[int]()(opts) // Some([1, 2, 3]) +// +// opts2 := []option.Option[int]{ +// option.Some(1), +// option.None[int](), +// option.Some(3), +// } +// result2 := array.ArrayOption[int]()(opts2) // None +func ArrayOption[A any]() func([]O.Option[A]) O.Option[[]A] { + return Sequence( + O.Of[[]A], + O.MonadMap[[]A, func(A) []A], + O.MonadAp[[]A, A], + ) +} diff --git a/v2/array/sequence_test.go b/v2/array/sequence_test.go new file mode 100644 index 0000000..432eb39 --- /dev/null +++ b/v2/array/sequence_test.go @@ -0,0 +1,31 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package array + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + O "github.com/IBM/fp-go/v2/option" +) + +func TestSequenceOption(t *testing.T) { + seq := ArrayOption[int]() + + assert.Equal(t, O.Of([]int{1, 3}), seq([]O.Option[int]{O.Of(1), O.Of(3)})) + assert.Equal(t, O.None[[]int](), seq([]O.Option[int]{O.Of(1), O.None[int]()})) +} diff --git a/v2/array/sort.go b/v2/array/sort.go new file mode 100644 index 0000000..ad06612 --- /dev/null +++ b/v2/array/sort.go @@ -0,0 +1,92 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package array + +import ( + G "github.com/IBM/fp-go/v2/array/generic" + O "github.com/IBM/fp-go/v2/ord" +) + +// Sort implements a stable sort on the array given the provided ordering. +// The sort is stable, meaning that elements that compare equal retain their original order. +// +// Example: +// +// import "github.com/IBM/fp-go/v2/ord" +// +// numbers := []int{3, 1, 4, 1, 5, 9, 2, 6} +// sorted := array.Sort(ord.FromStrictCompare[int]())(numbers) +// // Result: [1, 1, 2, 3, 4, 5, 6, 9] +func Sort[T any](ord O.Ord[T]) func(ma []T) []T { + return G.Sort[[]T](ord) +} + +// SortByKey implements a stable sort on the array given the provided ordering on an extracted key. +// This is useful when you want to sort complex types by a specific field. +// +// Example: +// +// import "github.com/IBM/fp-go/v2/ord" +// +// type Person struct { +// Name string +// Age int +// } +// +// people := []Person{ +// {"Alice", 30}, +// {"Bob", 25}, +// {"Charlie", 35}, +// } +// +// sortByAge := array.SortByKey( +// ord.FromStrictCompare[int](), +// func(p Person) int { return p.Age }, +// ) +// sorted := sortByAge(people) +// // Result: [{"Bob", 25}, {"Alice", 30}, {"Charlie", 35}] +func SortByKey[K, T any](ord O.Ord[K], f func(T) K) func(ma []T) []T { + return G.SortByKey[[]T](ord, f) +} + +// SortBy implements a stable sort on the array using multiple ordering criteria. +// The orderings are applied in sequence: if two elements are equal according to the first +// ordering, the second ordering is used, and so on. +// +// Example: +// +// import "github.com/IBM/fp-go/v2/ord" +// +// type Person struct { +// LastName string +// FirstName string +// } +// +// people := []Person{ +// {"Smith", "John"}, +// {"Smith", "Alice"}, +// {"Jones", "Bob"}, +// } +// +// sortByName := array.SortBy([]ord.Ord[Person]{ +// ord.Contramap(func(p Person) string { return p.LastName })(ord.FromStrictCompare[string]()), +// ord.Contramap(func(p Person) string { return p.FirstName })(ord.FromStrictCompare[string]()), +// }) +// sorted := sortByName(people) +// // Result: [{"Jones", "Bob"}, {"Smith", "Alice"}, {"Smith", "John"}] +func SortBy[T any](ord []O.Ord[T]) func(ma []T) []T { + return G.SortBy[[]T, []O.Ord[T]](ord) +} diff --git a/v2/array/sort_test.go b/v2/array/sort_test.go new file mode 100644 index 0000000..34efb1f --- /dev/null +++ b/v2/array/sort_test.go @@ -0,0 +1,36 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package array + +import ( + "testing" + + O "github.com/IBM/fp-go/v2/ord" + "github.com/stretchr/testify/assert" +) + +func TestSort(t *testing.T) { + + ordInt := O.FromStrictCompare[int]() + + input := []int{2, 1, 3} + + res := Sort(ordInt)(input) + + assert.Equal(t, []int{1, 2, 3}, res) + assert.Equal(t, []int{2, 1, 3}, input) + +} diff --git a/v2/array/testing/laws.go b/v2/array/testing/laws.go new file mode 100644 index 0000000..bbd0115 --- /dev/null +++ b/v2/array/testing/laws.go @@ -0,0 +1,74 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package testing + +import ( + "testing" + + RA "github.com/IBM/fp-go/v2/array" + EQ "github.com/IBM/fp-go/v2/eq" + L "github.com/IBM/fp-go/v2/internal/monad/testing" +) + +// AssertLaws asserts the apply monad laws for the array monad +func AssertLaws[A, B, C any](t *testing.T, + eqa EQ.Eq[A], + eqb EQ.Eq[B], + eqc EQ.Eq[C], + + ab func(A) B, + bc func(B) C, +) func(a A) bool { + + return L.AssertLaws(t, + RA.Eq(eqa), + RA.Eq(eqb), + RA.Eq(eqc), + + RA.Of[A], + RA.Of[B], + RA.Of[C], + + RA.Of[func(A) A], + RA.Of[func(A) B], + RA.Of[func(B) C], + RA.Of[func(func(A) B) B], + + RA.MonadMap[A, A], + RA.MonadMap[A, B], + RA.MonadMap[A, C], + RA.MonadMap[B, C], + + RA.MonadMap[func(B) C, func(func(A) B) func(A) C], + + RA.MonadChain[A, A], + RA.MonadChain[A, B], + RA.MonadChain[A, C], + RA.MonadChain[B, C], + + RA.MonadAp[A, A], + RA.MonadAp[B, A], + RA.MonadAp[C, B], + RA.MonadAp[C, A], + + RA.MonadAp[B, func(A) B], + RA.MonadAp[func(A) C, func(A) B], + + ab, + bc, + ) + +} diff --git a/v2/array/testing/laws_test.go b/v2/array/testing/laws_test.go new file mode 100644 index 0000000..faff6d2 --- /dev/null +++ b/v2/array/testing/laws_test.go @@ -0,0 +1,47 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package testing + +import ( + "fmt" + "testing" + + EQ "github.com/IBM/fp-go/v2/eq" + "github.com/stretchr/testify/assert" +) + +func TestMonadLaws(t *testing.T) { + // some comparison + eqa := EQ.FromStrictEquals[bool]() + eqb := EQ.FromStrictEquals[int]() + eqc := EQ.FromStrictEquals[string]() + + ab := func(a bool) int { + if a { + return 1 + } + return 0 + } + + bc := func(b int) string { + return fmt.Sprintf("value %d", b) + } + + laws := AssertLaws(t, eqa, eqb, eqc, ab, bc) + + assert.True(t, laws(true)) + assert.True(t, laws(false)) +} diff --git a/v2/array/traverse.go b/v2/array/traverse.go new file mode 100644 index 0000000..954b352 --- /dev/null +++ b/v2/array/traverse.go @@ -0,0 +1,78 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package array + +import ( + "github.com/IBM/fp-go/v2/internal/array" +) + +// Traverse maps each element of an array to an effect (HKT), then collects the results +// into an effect of an array. This is like a combination of Map and Sequence. +// +// Unlike Sequence which works with []HKT -> HKT<[]A>, Traverse works with +// []A -> (A -> HKT) -> HKT<[]B>, allowing you to transform elements while sequencing effects. +// +// Type parameters: +// - HKTB = HKT (e.g., Option[B], Either[E, B]) +// - HKTAB = HKT (intermediate type for applicative) +// - HKTRB = HKT<[]B> (e.g., Option[[]B], Either[E, []B]) +// +// Example: +// +// import ( +// "github.com/IBM/fp-go/v2/option" +// "strconv" +// ) +// +// // Parse strings to ints, returning None if any parse fails +// parseAll := array.Traverse( +// option.Of[[]int], +// option.Map[[]int, func(int) []int], +// option.Ap[[]int, int], +// func(s string) option.Option[int] { +// if n, err := strconv.Atoi(s); err == nil { +// return option.Some(n) +// } +// return option.None[int]() +// }, +// ) +// +// result := parseAll([]string{"1", "2", "3"}) // Some([1, 2, 3]) +// result2 := parseAll([]string{"1", "x", "3"}) // None +func Traverse[A, B, HKTB, HKTAB, HKTRB any]( + fof func([]B) HKTRB, + fmap func(func([]B) func(B) []B) func(HKTRB) HKTAB, + fap func(HKTB) func(HKTAB) HKTRB, + + f func(A) HKTB) func([]A) HKTRB { + return array.Traverse[[]A](fof, fmap, fap, f) +} + +// MonadTraverse is the monadic version of Traverse that takes the array as a parameter. +// It maps each element of an array to an effect (HKT), then collects the results +// into an effect of an array. +// +// This is useful when you want to apply the traverse operation directly without currying. +func MonadTraverse[A, B, HKTB, HKTAB, HKTRB any]( + fof func([]B) HKTRB, + fmap func(func([]B) func(B) []B) func(HKTRB) HKTAB, + fap func(HKTB) func(HKTAB) HKTRB, + + ta []A, + f func(A) HKTB) HKTRB { + + return array.MonadTraverse(fof, fmap, fap, ta, f) +} diff --git a/v2/array/traverse_test.go b/v2/array/traverse_test.go new file mode 100644 index 0000000..220f989 --- /dev/null +++ b/v2/array/traverse_test.go @@ -0,0 +1,43 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package array + +import ( + "testing" + + O "github.com/IBM/fp-go/v2/option" + "github.com/stretchr/testify/assert" +) + +type ArrayType = []int + +func TestTraverse(t *testing.T) { + + traverse := Traverse( + O.Of[ArrayType], + O.Map[ArrayType, func(int) ArrayType], + O.Ap[ArrayType, int], + + func(n int) O.Option[int] { + if n%2 == 0 { + return O.None[int]() + } + return O.Of(n) + }) + + assert.Equal(t, O.None[[]int](), traverse(ArrayType{1, 2})) + assert.Equal(t, O.Of(ArrayType{1, 3}), traverse(ArrayType{1, 3})) +} diff --git a/v2/array/uniq.go b/v2/array/uniq.go new file mode 100644 index 0000000..c54fcb9 --- /dev/null +++ b/v2/array/uniq.go @@ -0,0 +1,47 @@ +package array + +import ( + G "github.com/IBM/fp-go/v2/array/generic" +) + +// StrictUniq converts an array of arbitrary items into an array of unique items +// where uniqueness is determined by the built-in equality constraint (comparable). +// The first occurrence of each unique value is kept, subsequent duplicates are removed. +// +// Example: +// +// numbers := []int{1, 2, 2, 3, 3, 3, 4} +// unique := array.StrictUniq(numbers) // [1, 2, 3, 4] +// +// strings := []string{"a", "b", "a", "c", "b"} +// unique2 := array.StrictUniq(strings) // ["a", "b", "c"] +func StrictUniq[A comparable](as []A) []A { + return G.StrictUniq[[]A](as) +} + +// Uniq converts an array of arbitrary items into an array of unique items +// where uniqueness is determined based on a key extractor function. +// The first occurrence of each unique key is kept, subsequent duplicates are removed. +// +// This is useful for removing duplicates from arrays of complex types based on a specific field. +// +// Example: +// +// type Person struct { +// Name string +// Age int +// } +// +// people := []Person{ +// {"Alice", 30}, +// {"Bob", 25}, +// {"Alice", 35}, // duplicate name +// {"Charlie", 30}, +// } +// +// uniqueByName := array.Uniq(func(p Person) string { return p.Name }) +// result := uniqueByName(people) +// // Result: [{"Alice", 30}, {"Bob", 25}, {"Charlie", 30}] +func Uniq[A any, K comparable](f func(A) K) func(as []A) []A { + return G.Uniq[[]A](f) +} diff --git a/v2/array/uniq_test.go b/v2/array/uniq_test.go new file mode 100644 index 0000000..0b32749 --- /dev/null +++ b/v2/array/uniq_test.go @@ -0,0 +1,14 @@ +package array + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestUniq(t *testing.T) { + data := From(1, 2, 3, 2, 4, 1) + + uniq := StrictUniq(data) + assert.Equal(t, From(1, 2, 3, 4), uniq) +} diff --git a/v2/array/zip.go b/v2/array/zip.go new file mode 100644 index 0000000..a85288b --- /dev/null +++ b/v2/array/zip.go @@ -0,0 +1,77 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package array + +import ( + G "github.com/IBM/fp-go/v2/array/generic" + T "github.com/IBM/fp-go/v2/tuple" +) + +// ZipWith applies a function to pairs of elements at the same index in two arrays, +// collecting the results in a new array. If one input array is shorter, excess elements +// of the longer array are discarded. +// +// Example: +// +// names := []string{"Alice", "Bob", "Charlie"} +// ages := []int{30, 25, 35} +// +// result := array.ZipWith(names, ages, func(name string, age int) string { +// return fmt.Sprintf("%s is %d years old", name, age) +// }) +// // Result: ["Alice is 30 years old", "Bob is 25 years old", "Charlie is 35 years old"] +func ZipWith[FCT ~func(A, B) C, A, B, C any](fa []A, fb []B, f FCT) []C { + return G.ZipWith[[]A, []B, []C, FCT](fa, fb, f) +} + +// Zip takes two arrays and returns an array of corresponding pairs (tuples). +// If one input array is shorter, excess elements of the longer array are discarded. +// +// Example: +// +// names := []string{"Alice", "Bob", "Charlie"} +// ages := []int{30, 25, 35} +// +// pairs := array.Zip(ages)(names) +// // Result: [(Alice, 30), (Bob, 25), (Charlie, 35)] +// +// // With different lengths +// pairs2 := array.Zip([]int{1, 2})([]string{"a", "b", "c"}) +// // Result: [(a, 1), (b, 2)] +func Zip[A, B any](fb []B) func([]A) []T.Tuple2[A, B] { + return G.Zip[[]A, []B, []T.Tuple2[A, B]](fb) +} + +// Unzip is the reverse of Zip. It takes an array of pairs (tuples) and returns +// two corresponding arrays, one containing all first elements and one containing all second elements. +// +// Example: +// +// import "github.com/IBM/fp-go/v2/tuple" +// +// pairs := []tuple.Tuple2[string, int]{ +// tuple.MakeTuple2("Alice", 30), +// tuple.MakeTuple2("Bob", 25), +// tuple.MakeTuple2("Charlie", 35), +// } +// +// result := array.Unzip(pairs) +// // Result: (["Alice", "Bob", "Charlie"], [30, 25, 35]) +// names := result.Head // ["Alice", "Bob", "Charlie"] +// ages := result.Tail // [30, 25, 35] +func Unzip[A, B any](cs []T.Tuple2[A, B]) T.Tuple2[[]A, []B] { + return G.Unzip[[]A, []B, []T.Tuple2[A, B]](cs) +} diff --git a/v2/array/zip_test.go b/v2/array/zip_test.go new file mode 100644 index 0000000..4a741f4 --- /dev/null +++ b/v2/array/zip_test.go @@ -0,0 +1,56 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package array + +import ( + "fmt" + "testing" + + T "github.com/IBM/fp-go/v2/tuple" + "github.com/stretchr/testify/assert" +) + +func TestZipWith(t *testing.T) { + left := From(1, 2, 3) + right := From("a", "b", "c", "d") + + res := ZipWith(left, right, func(l int, r string) string { + return fmt.Sprintf("%s%d", r, l) + }) + + assert.Equal(t, From("a1", "b2", "c3"), res) +} + +func TestZip(t *testing.T) { + left := From(1, 2, 3) + right := From("a", "b", "c", "d") + + res := Zip[string](left)(right) + + assert.Equal(t, From(T.MakeTuple2("a", 1), T.MakeTuple2("b", 2), T.MakeTuple2("c", 3)), res) +} + +func TestUnzip(t *testing.T) { + left := From(1, 2, 3) + right := From("a", "b", "c") + + zipped := Zip[string](left)(right) + + unzipped := Unzip(zipped) + + assert.Equal(t, right, unzipped.F1) + assert.Equal(t, left, unzipped.F2) +} diff --git a/v2/assert/assert_test.go b/v2/assert/assert_test.go new file mode 100644 index 0000000..5467d72 --- /dev/null +++ b/v2/assert/assert_test.go @@ -0,0 +1,109 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package assert + +import ( + "fmt" + "testing" + + E "github.com/IBM/fp-go/v2/either" + EQ "github.com/IBM/fp-go/v2/eq" + "github.com/stretchr/testify/assert" +) + +var ( + errTest = fmt.Errorf("test failure") + + // Eq is the equal predicate checking if objects are equal + Eq = EQ.FromEquals(assert.ObjectsAreEqual) +) + +func wrap1[T any](wrapped func(t assert.TestingT, expected, actual any, msgAndArgs ...any) bool, t *testing.T, expected T) func(actual T) E.Either[error, T] { + return func(actual T) E.Either[error, T] { + ok := wrapped(t, expected, actual) + if ok { + return E.Of[error](actual) + } + return E.Left[T](errTest) + } +} + +// NotEqual tests if the expected and the actual values are not equal +func NotEqual[T any](t *testing.T, expected T) func(actual T) E.Either[error, T] { + return wrap1(assert.NotEqual, t, expected) +} + +// Equal tests if the expected and the actual values are equal +func Equal[T any](t *testing.T, expected T) func(actual T) E.Either[error, T] { + return wrap1(assert.Equal, t, expected) +} + +// Length tests if an array has the expected length +func Length[T any](t *testing.T, expected int) func(actual []T) E.Either[error, []T] { + return func(actual []T) E.Either[error, []T] { + ok := assert.Len(t, actual, expected) + if ok { + return E.Of[error](actual) + } + return E.Left[[]T](errTest) + } +} + +// NoError validates that there is no error +func NoError[T any](t *testing.T) func(actual E.Either[error, T]) E.Either[error, T] { + return func(actual E.Either[error, T]) E.Either[error, T] { + return E.MonadFold(actual, func(e error) E.Either[error, T] { + assert.NoError(t, e) + return E.Left[T](e) + }, func(value T) E.Either[error, T] { + assert.NoError(t, nil) + return E.Right[error](value) + }) + } +} + +// ArrayContains tests if a value is contained in an array +func ArrayContains[T any](t *testing.T, expected T) func(actual []T) E.Either[error, []T] { + return func(actual []T) E.Either[error, []T] { + ok := assert.Contains(t, actual, expected) + if ok { + return E.Of[error](actual) + } + return E.Left[[]T](errTest) + } +} + +// ContainsKey tests if a key is contained in a map +func ContainsKey[T any, K comparable](t *testing.T, expected K) func(actual map[K]T) E.Either[error, map[K]T] { + return func(actual map[K]T) E.Either[error, map[K]T] { + ok := assert.Contains(t, actual, expected) + if ok { + return E.Of[error](actual) + } + return E.Left[map[K]T](errTest) + } +} + +// NotContainsKey tests if a key is not contained in a map +func NotContainsKey[T any, K comparable](t *testing.T, expected K) func(actual map[K]T) E.Either[error, map[K]T] { + return func(actual map[K]T) E.Either[error, map[K]T] { + ok := assert.NotContains(t, actual, expected) + if ok { + return E.Of[error](actual) + } + return E.Left[map[K]T](errTest) + } +} diff --git a/v2/boolean/boolean.go b/v2/boolean/boolean.go new file mode 100644 index 0000000..8f0fed0 --- /dev/null +++ b/v2/boolean/boolean.go @@ -0,0 +1,59 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package boolean + +import ( + "github.com/IBM/fp-go/v2/eq" + "github.com/IBM/fp-go/v2/monoid" + "github.com/IBM/fp-go/v2/ord" +) + +var ( + // MonoidAny is the boolean [monoid.Monoid] under disjunction + MonoidAny = monoid.MakeMonoid( + func(l, r bool) bool { + return l || r + }, + false, + ) + + // MonoidAll is the boolean [monoid.Monoid] under conjuction + MonoidAll = monoid.MakeMonoid( + func(l, r bool) bool { + return l && r + }, + true, + ) + + // Eq is the equals predicate for boolean + Eq = eq.FromStrictEquals[bool]() + + // Ord is the strict ordering for boolean + Ord = ord.MakeOrd(func(l, r bool) int { + if l { + if r { + return 0 + } + return +1 + } + if r { + return -1 + } + return 0 + }, func(l, r bool) bool { + return l == r + }) +) diff --git a/v2/boolean/boolean_test.go b/v2/boolean/boolean_test.go new file mode 100644 index 0000000..d7b2009 --- /dev/null +++ b/v2/boolean/boolean_test.go @@ -0,0 +1,246 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package boolean + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestMonoidAny(t *testing.T) { + t.Run("identity element is false", func(t *testing.T) { + assert.Equal(t, false, MonoidAny.Empty()) + }) + + t.Run("false OR false = false", func(t *testing.T) { + result := MonoidAny.Concat(false, false) + assert.Equal(t, false, result) + }) + + t.Run("false OR true = true", func(t *testing.T) { + result := MonoidAny.Concat(false, true) + assert.Equal(t, true, result) + }) + + t.Run("true OR false = true", func(t *testing.T) { + result := MonoidAny.Concat(true, false) + assert.Equal(t, true, result) + }) + + t.Run("true OR true = true", func(t *testing.T) { + result := MonoidAny.Concat(true, true) + assert.Equal(t, true, result) + }) + + t.Run("left identity: empty OR x = x", func(t *testing.T) { + assert.Equal(t, true, MonoidAny.Concat(MonoidAny.Empty(), true)) + assert.Equal(t, false, MonoidAny.Concat(MonoidAny.Empty(), false)) + }) + + t.Run("right identity: x OR empty = x", func(t *testing.T) { + assert.Equal(t, true, MonoidAny.Concat(true, MonoidAny.Empty())) + assert.Equal(t, false, MonoidAny.Concat(false, MonoidAny.Empty())) + }) + + t.Run("associativity: (a OR b) OR c = a OR (b OR c)", func(t *testing.T) { + a, b, c := true, false, true + left := MonoidAny.Concat(MonoidAny.Concat(a, b), c) + right := MonoidAny.Concat(a, MonoidAny.Concat(b, c)) + assert.Equal(t, left, right) + }) +} + +func TestMonoidAll(t *testing.T) { + t.Run("identity element is true", func(t *testing.T) { + assert.Equal(t, true, MonoidAll.Empty()) + }) + + t.Run("false AND false = false", func(t *testing.T) { + result := MonoidAll.Concat(false, false) + assert.Equal(t, false, result) + }) + + t.Run("false AND true = false", func(t *testing.T) { + result := MonoidAll.Concat(false, true) + assert.Equal(t, false, result) + }) + + t.Run("true AND false = false", func(t *testing.T) { + result := MonoidAll.Concat(true, false) + assert.Equal(t, false, result) + }) + + t.Run("true AND true = true", func(t *testing.T) { + result := MonoidAll.Concat(true, true) + assert.Equal(t, true, result) + }) + + t.Run("left identity: empty AND x = x", func(t *testing.T) { + assert.Equal(t, true, MonoidAll.Concat(MonoidAll.Empty(), true)) + assert.Equal(t, false, MonoidAll.Concat(MonoidAll.Empty(), false)) + }) + + t.Run("right identity: x AND empty = x", func(t *testing.T) { + assert.Equal(t, true, MonoidAll.Concat(true, MonoidAll.Empty())) + assert.Equal(t, false, MonoidAll.Concat(false, MonoidAll.Empty())) + }) + + t.Run("associativity: (a AND b) AND c = a AND (b AND c)", func(t *testing.T) { + a, b, c := true, false, true + left := MonoidAll.Concat(MonoidAll.Concat(a, b), c) + right := MonoidAll.Concat(a, MonoidAll.Concat(b, c)) + assert.Equal(t, left, right) + }) +} + +func TestEq(t *testing.T) { + t.Run("true equals true", func(t *testing.T) { + assert.True(t, Eq.Equals(true, true)) + }) + + t.Run("false equals false", func(t *testing.T) { + assert.True(t, Eq.Equals(false, false)) + }) + + t.Run("true not equals false", func(t *testing.T) { + assert.False(t, Eq.Equals(true, false)) + }) + + t.Run("false not equals true", func(t *testing.T) { + assert.False(t, Eq.Equals(false, true)) + }) + + t.Run("reflexivity: x equals x", func(t *testing.T) { + assert.True(t, Eq.Equals(true, true)) + assert.True(t, Eq.Equals(false, false)) + }) + + t.Run("symmetry: if x equals y then y equals x", func(t *testing.T) { + assert.Equal(t, Eq.Equals(true, false), Eq.Equals(false, true)) + assert.Equal(t, Eq.Equals(true, true), Eq.Equals(true, true)) + }) + + t.Run("transitivity: if x equals y and y equals z then x equals z", func(t *testing.T) { + x, y, z := true, true, true + if Eq.Equals(x, y) && Eq.Equals(y, z) { + assert.True(t, Eq.Equals(x, z)) + } + }) +} + +func TestOrd(t *testing.T) { + t.Run("false < true", func(t *testing.T) { + result := Ord.Compare(false, true) + assert.Equal(t, -1, result) + }) + + t.Run("true > false", func(t *testing.T) { + result := Ord.Compare(true, false) + assert.Equal(t, 1, result) + }) + + t.Run("true == true", func(t *testing.T) { + result := Ord.Compare(true, true) + assert.Equal(t, 0, result) + }) + + t.Run("false == false", func(t *testing.T) { + result := Ord.Compare(false, false) + assert.Equal(t, 0, result) + }) + + t.Run("Equals method works", func(t *testing.T) { + assert.True(t, Ord.Equals(true, true)) + assert.True(t, Ord.Equals(false, false)) + assert.False(t, Ord.Equals(true, false)) + assert.False(t, Ord.Equals(false, true)) + }) + + t.Run("reflexivity: x <= x", func(t *testing.T) { + assert.True(t, Ord.Compare(true, true) == 0) + assert.True(t, Ord.Compare(false, false) == 0) + }) + + t.Run("antisymmetry: if x <= y and y <= x then x == y", func(t *testing.T) { + x, y := true, true + if Ord.Compare(x, y) <= 0 && Ord.Compare(y, x) <= 0 { + assert.Equal(t, 0, Ord.Compare(x, y)) + } + }) + + t.Run("transitivity: if x <= y and y <= z then x <= z", func(t *testing.T) { + x, y, z := false, true, true + if Ord.Compare(x, y) <= 0 && Ord.Compare(y, z) <= 0 { + assert.True(t, Ord.Compare(x, z) <= 0) + } + }) + + t.Run("totality: x <= y or y <= x", func(t *testing.T) { + assert.True(t, Ord.Compare(true, false) >= 0 || Ord.Compare(false, true) >= 0) + assert.True(t, Ord.Compare(false, true) <= 0 || Ord.Compare(true, false) <= 0) + }) +} + +// Example tests that also serve as documentation +func ExampleMonoidAny() { + // Combine booleans with OR + result := MonoidAny.Concat(false, true) + println(result) // true + + // Identity element + identity := MonoidAny.Empty() + println(identity) // false + + // Output: +} + +func ExampleMonoidAll() { + // Combine booleans with AND + result := MonoidAll.Concat(true, true) + println(result) // true + + // Identity element + identity := MonoidAll.Empty() + println(identity) // true + + // Output: +} + +func ExampleEq() { + // Check equality + equal := Eq.Equals(true, true) + println(equal) // true + + notEqual := Eq.Equals(true, false) + println(notEqual) // false + + // Output: +} + +func ExampleOrd() { + // Compare booleans (false < true) + cmp := Ord.Compare(false, true) + println(cmp) // -1 + + cmp2 := Ord.Compare(true, false) + println(cmp2) // 1 + + cmp3 := Ord.Compare(true, true) + println(cmp3) // 0 + + // Output: +} diff --git a/v2/boolean/doc.go b/v2/boolean/doc.go new file mode 100644 index 0000000..f731d94 --- /dev/null +++ b/v2/boolean/doc.go @@ -0,0 +1,138 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +// Package boolean provides functional programming utilities for working with boolean values. +// +// This package offers algebraic structures (Monoid, Eq, Ord) for boolean values, +// enabling functional composition and reasoning about boolean operations. +// +// # Monoids +// +// The package provides two monoid instances for booleans: +// +// - MonoidAny: Combines booleans using logical OR (disjunction), with false as identity +// - MonoidAll: Combines booleans using logical AND (conjunction), with true as identity +// +// # MonoidAny - Logical OR +// +// MonoidAny implements the boolean monoid under disjunction (OR operation). +// The identity element is false, meaning false OR x = x for any boolean x. +// +// Example: +// +// import "github.com/IBM/fp-go/v2/boolean" +// +// // Combine multiple booleans with OR +// result := boolean.MonoidAny.Concat(false, true) // true +// result2 := boolean.MonoidAny.Concat(false, false) // false +// +// // Identity element +// identity := boolean.MonoidAny.Empty() // false +// +// // Check if any value in a collection is true +// import "github.com/IBM/fp-go/v2/array" +// values := []bool{false, false, true, false} +// anyTrue := array.Fold(boolean.MonoidAny)(values) // true +// +// # MonoidAll - Logical AND +// +// MonoidAll implements the boolean monoid under conjunction (AND operation). +// The identity element is true, meaning true AND x = x for any boolean x. +// +// Example: +// +// import "github.com/IBM/fp-go/v2/boolean" +// +// // Combine multiple booleans with AND +// result := boolean.MonoidAll.Concat(true, true) // true +// result2 := boolean.MonoidAll.Concat(true, false) // false +// +// // Identity element +// identity := boolean.MonoidAll.Empty() // true +// +// // Check if all values in a collection are true +// import "github.com/IBM/fp-go/v2/array" +// values := []bool{true, true, true} +// allTrue := array.Fold(boolean.MonoidAll)(values) // true +// +// # Equality +// +// The Eq instance provides structural equality for booleans: +// +// import "github.com/IBM/fp-go/v2/boolean" +// +// equal := boolean.Eq.Equals(true, true) // true +// equal2 := boolean.Eq.Equals(true, false) // false +// +// # Ordering +// +// The Ord instance provides a total ordering for booleans where false < true: +// +// import "github.com/IBM/fp-go/v2/boolean" +// +// cmp := boolean.Ord.Compare(false, true) // -1 (false < true) +// cmp2 := boolean.Ord.Compare(true, false) // +1 (true > false) +// cmp3 := boolean.Ord.Compare(true, true) // 0 (equal) +// +// # Use Cases +// +// The boolean package is particularly useful for: +// +// - Combining multiple boolean conditions functionally +// - Implementing validation logic that accumulates results +// - Working with predicates in a composable way +// - Folding collections of boolean values +// +// Example - Validation: +// +// import ( +// "github.com/IBM/fp-go/v2/array" +// "github.com/IBM/fp-go/v2/boolean" +// ) +// +// type User struct { +// Name string +// Email string +// Age int +// } +// +// // Define validation predicates +// validations := []func(User) bool{ +// func(u User) bool { return len(u.Name) > 0 }, +// func(u User) bool { return len(u.Email) > 0 }, +// func(u User) bool { return u.Age >= 18 }, +// } +// +// // Check if user passes all validations +// user := User{"Alice", "alice@example.com", 25} +// results := array.Map(func(v func(User) bool) bool { +// return v(user) +// })(validations) +// allValid := array.Fold(boolean.MonoidAll)(results) // true +// +// Example - Any Match: +// +// import ( +// "github.com/IBM/fp-go/v2/array" +// "github.com/IBM/fp-go/v2/boolean" +// ) +// +// // Check if any number is even +// numbers := []int{1, 3, 5, 7, 8, 9} +// checks := array.Map(func(n int) bool { +// return n%2 == 0 +// })(numbers) +// hasEven := array.Fold(boolean.MonoidAny)(checks) // true +package boolean diff --git a/v2/boolean/types.go b/v2/boolean/types.go new file mode 100644 index 0000000..da7c59c --- /dev/null +++ b/v2/boolean/types.go @@ -0,0 +1,22 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package boolean + +import "github.com/IBM/fp-go/v2/monoid" + +type ( + Monoid = monoid.Monoid[bool] +) diff --git a/v2/bounded/bounded.go b/v2/bounded/bounded.go new file mode 100644 index 0000000..6efc7ed --- /dev/null +++ b/v2/bounded/bounded.go @@ -0,0 +1,62 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package bounded + +import "github.com/IBM/fp-go/v2/ord" + +type Bounded[T any] interface { + ord.Ord[T] + Top() T + Bottom() T +} + +type bounded[T any] struct { + c func(x, y T) int + e func(x, y T) bool + t T + b T +} + +func (b bounded[T]) Equals(x, y T) bool { + return b.e(x, y) +} + +func (b bounded[T]) Compare(x, y T) int { + return b.c(x, y) +} + +func (b bounded[T]) Top() T { + return b.t +} + +func (b bounded[T]) Bottom() T { + return b.b +} + +// MakeBounded creates an instance of a bounded type +func MakeBounded[T any](o ord.Ord[T], t, b T) Bounded[T] { + return bounded[T]{c: o.Compare, e: o.Equals, t: t, b: b} +} + +// Clamp returns a function that clamps against the bounds defined in the bounded type +func Clamp[T any](b Bounded[T]) func(T) T { + return ord.Clamp[T](b)(b.Bottom(), b.Top()) +} + +// Reverse reverses the ordering and swaps the bounds +func Reverse[T any](b Bounded[T]) Bounded[T] { + return MakeBounded(ord.Reverse(b), b.Bottom(), b.Top()) +} diff --git a/v2/bounded/bounded_test.go b/v2/bounded/bounded_test.go new file mode 100644 index 0000000..3132725 --- /dev/null +++ b/v2/bounded/bounded_test.go @@ -0,0 +1,212 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package bounded + +import ( + "testing" + + "github.com/IBM/fp-go/v2/ord" + "github.com/stretchr/testify/assert" +) + +func TestMakeBounded(t *testing.T) { + t.Run("creates bounded instance with correct top and bottom", func(t *testing.T) { + b := MakeBounded(ord.FromStrictCompare[int](), 100, 0) + + assert.Equal(t, 100, b.Top()) + assert.Equal(t, 0, b.Bottom()) + }) + + t.Run("preserves ordering from Ord", func(t *testing.T) { + b := MakeBounded(ord.FromStrictCompare[int](), 100, 0) + + assert.Equal(t, -1, b.Compare(5, 10)) + assert.Equal(t, 0, b.Compare(5, 5)) + assert.Equal(t, 1, b.Compare(10, 5)) + }) + + t.Run("preserves equality from Ord", func(t *testing.T) { + b := MakeBounded(ord.FromStrictCompare[int](), 100, 0) + + assert.True(t, b.Equals(5, 5)) + assert.False(t, b.Equals(5, 10)) + }) +} + +func TestClamp(t *testing.T) { + t.Run("returns value within bounds unchanged", func(t *testing.T) { + b := MakeBounded(ord.FromStrictCompare[int](), 100, 0) + clamp := Clamp(b) + + assert.Equal(t, 50, clamp(50)) + assert.Equal(t, 0, clamp(0)) + assert.Equal(t, 100, clamp(100)) + }) + + t.Run("clamps value above top to top", func(t *testing.T) { + b := MakeBounded(ord.FromStrictCompare[int](), 100, 0) + clamp := Clamp(b) + + assert.Equal(t, 100, clamp(150)) + assert.Equal(t, 100, clamp(200)) + }) + + t.Run("clamps value below bottom to bottom", func(t *testing.T) { + b := MakeBounded(ord.FromStrictCompare[int](), 100, 0) + clamp := Clamp(b) + + assert.Equal(t, 0, clamp(-10)) + assert.Equal(t, 0, clamp(-100)) + }) + + t.Run("works with float64", func(t *testing.T) { + b := MakeBounded(ord.FromStrictCompare[float64](), 1.0, 0.0) + clamp := Clamp(b) + + assert.Equal(t, 0.5, clamp(0.5)) + assert.Equal(t, 1.0, clamp(1.5)) + assert.Equal(t, 0.0, clamp(-0.5)) + }) + + t.Run("works with strings", func(t *testing.T) { + b := MakeBounded(ord.FromStrictCompare[string](), "z", "a") + clamp := Clamp(b) + + assert.Equal(t, "m", clamp("m")) + assert.Equal(t, "z", clamp("zzz")) + assert.Equal(t, "a", clamp("A")) + }) +} + +func TestReverse(t *testing.T) { + t.Run("reverses the ordering", func(t *testing.T) { + original := MakeBounded(ord.FromStrictCompare[int](), 100, 0) + reversed := Reverse(original) + + // In original: 5 < 10, so Compare(5, 10) = -1 + assert.Equal(t, -1, original.Compare(5, 10)) + + // In reversed: 5 > 10, so Compare(5, 10) = 1 + assert.Equal(t, 1, reversed.Compare(5, 10)) + }) + + t.Run("swaps top and bottom values", func(t *testing.T) { + original := MakeBounded(ord.FromStrictCompare[int](), 100, 0) + reversed := Reverse(original) + + // Reverse swaps the bounds + assert.Equal(t, original.Bottom(), reversed.Top()) + assert.Equal(t, original.Top(), reversed.Bottom()) + }) + + t.Run("double reverse returns to original ordering", func(t *testing.T) { + original := MakeBounded(ord.FromStrictCompare[int](), 100, 0) + reversed := Reverse(original) + doubleReversed := Reverse(reversed) + + assert.Equal(t, original.Compare(5, 10), doubleReversed.Compare(5, 10)) + assert.Equal(t, original.Compare(10, 5), doubleReversed.Compare(10, 5)) + }) + + t.Run("preserves equality", func(t *testing.T) { + original := MakeBounded(ord.FromStrictCompare[int](), 100, 0) + reversed := Reverse(original) + + assert.Equal(t, original.Equals(5, 5), reversed.Equals(5, 5)) + assert.Equal(t, original.Equals(5, 10), reversed.Equals(5, 10)) + }) +} + +func TestBoundedLaws(t *testing.T) { + t.Run("bottom is less than or equal to all values", func(t *testing.T) { + b := MakeBounded(ord.FromStrictCompare[int](), 100, 0) + + testValues := []int{0, 25, 50, 75, 100} + for _, v := range testValues { + assert.True(t, b.Compare(b.Bottom(), v) <= 0, + "Bottom (%d) should be <= %d", b.Bottom(), v) + } + }) + + t.Run("top is greater than or equal to all values", func(t *testing.T) { + b := MakeBounded(ord.FromStrictCompare[int](), 100, 0) + + testValues := []int{0, 25, 50, 75, 100} + for _, v := range testValues { + assert.True(t, b.Compare(b.Top(), v) >= 0, + "Top (%d) should be >= %d", b.Top(), v) + } + }) + + t.Run("bottom is less than or equal to top", func(t *testing.T) { + b := MakeBounded(ord.FromStrictCompare[int](), 100, 0) + + assert.True(t, b.Compare(b.Bottom(), b.Top()) <= 0, + "Bottom should be <= Top") + }) +} + +// Example tests +func ExampleMakeBounded() { + // Create a bounded type for percentages (0-100) + percentage := MakeBounded( + ord.FromStrictCompare[int](), + 100, // top + 0, // bottom + ) + + println(percentage.Top()) // 100 + println(percentage.Bottom()) // 0 + + // Output: +} + +func ExampleClamp() { + // Create bounded type for percentages + percentage := MakeBounded( + ord.FromStrictCompare[int](), + 100, // top + 0, // bottom + ) + + clamp := Clamp(percentage) + + println(clamp(50)) // 50 (within bounds) + println(clamp(150)) // 100 (clamped to top) + println(clamp(-10)) // 0 (clamped to bottom) + + // Output: +} + +func ExampleReverse() { + original := MakeBounded( + ord.FromStrictCompare[int](), + 100, // top + 0, // bottom + ) + + reversed := Reverse(original) + + // Ordering is reversed + println(original.Compare(5, 10)) // -1 (5 < 10) + println(reversed.Compare(5, 10)) // 1 (5 > 10 in reversed) + + // Bounds are swapped + println(reversed.Top()) // 0 + println(reversed.Bottom()) // 100 + + // Output: +} diff --git a/v2/bounded/doc.go b/v2/bounded/doc.go new file mode 100644 index 0000000..e4a7616 --- /dev/null +++ b/v2/bounded/doc.go @@ -0,0 +1,149 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +// Package bounded provides types and functions for working with bounded ordered types. +// +// A Bounded type extends Ord with minimum (Bottom) and maximum (Top) values, +// representing types that have well-defined lower and upper bounds. +// +// # Bounded Interface +// +// The Bounded interface combines ordering (Ord) with boundary values: +// +// type Bounded[T any] interface { +// ord.Ord[T] +// Top() T // Maximum value +// Bottom() T // Minimum value +// } +// +// # Creating Bounded Instances +// +// Use MakeBounded to create a Bounded instance from an Ord and boundary values: +// +// import ( +// "github.com/IBM/fp-go/v2/bounded" +// "github.com/IBM/fp-go/v2/ord" +// ) +// +// // Bounded integers from 0 to 100 +// boundedInt := bounded.MakeBounded( +// ord.FromStrictCompare[int](), +// 100, // top +// 0, // bottom +// ) +// +// top := boundedInt.Top() // 100 +// bottom := boundedInt.Bottom() // 0 +// +// # Clamping Values +// +// The Clamp function restricts values to stay within the bounds: +// +// import ( +// "github.com/IBM/fp-go/v2/bounded" +// "github.com/IBM/fp-go/v2/ord" +// ) +// +// // Create bounded type for percentages (0-100) +// percentage := bounded.MakeBounded( +// ord.FromStrictCompare[int](), +// 100, // top +// 0, // bottom +// ) +// +// clamp := bounded.Clamp(percentage) +// +// result1 := clamp(50) // 50 (within bounds) +// result2 := clamp(150) // 100 (clamped to top) +// result3 := clamp(-10) // 0 (clamped to bottom) +// +// # Reversing Bounds +// +// The Reverse function swaps the ordering and bounds: +// +// import ( +// "github.com/IBM/fp-go/v2/bounded" +// "github.com/IBM/fp-go/v2/ord" +// ) +// +// original := bounded.MakeBounded( +// ord.FromStrictCompare[int](), +// 100, // top +// 0, // bottom +// ) +// +// reversed := bounded.Reverse(original) +// +// // In reversed, ordering is flipped and bounds are swapped +// // Compare(10, 20) returns 1 instead of -1 +// // Top() returns 0 and Bottom() returns 100 +// +// # Use Cases +// +// Bounded types are useful for: +// +// - Representing ranges with well-defined limits (e.g., percentages, grades) +// - Implementing safe arithmetic that stays within bounds +// - Validating input values against constraints +// - Creating domain-specific types with natural boundaries +// +// # Example - Temperature Range +// +// import ( +// "github.com/IBM/fp-go/v2/bounded" +// "github.com/IBM/fp-go/v2/ord" +// ) +// +// // Celsius temperature range for a thermostat +// thermostat := bounded.MakeBounded( +// ord.FromStrictCompare[float64](), +// 30.0, // max temperature +// 15.0, // min temperature +// ) +// +// clampTemp := bounded.Clamp(thermostat) +// +// // User tries to set temperature +// desired := 35.0 +// actual := clampTemp(desired) // 30.0 (clamped to maximum) +// +// # Example - Bounded Characters +// +// import ( +// "github.com/IBM/fp-go/v2/bounded" +// "github.com/IBM/fp-go/v2/ord" +// ) +// +// // Lowercase letters only +// lowercase := bounded.MakeBounded( +// ord.FromStrictCompare[rune](), +// 'z', // top +// 'a', // bottom +// ) +// +// clampChar := bounded.Clamp(lowercase) +// +// result1 := clampChar('m') // 'm' (within bounds) +// result2 := clampChar('A') // 'a' (clamped to bottom) +// result3 := clampChar('~') // 'z' (clamped to top) +// +// # Laws +// +// Bounded instances must satisfy the Ord laws plus: +// +// - Bottom is less than or equal to all values: Compare(Bottom(), x) <= 0 +// - Top is greater than or equal to all values: Compare(Top(), x) >= 0 +// - Bottom <= Top: Compare(Bottom(), Top()) <= 0 +package bounded diff --git a/v2/bytes/bytes.go b/v2/bytes/bytes.go new file mode 100644 index 0000000..5c59498 --- /dev/null +++ b/v2/bytes/bytes.go @@ -0,0 +1,28 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package bytes + +func Empty() []byte { + return Monoid.Empty() +} + +func ToString(a []byte) string { + return string(a) +} + +func Size(as []byte) int { + return len(as) +} diff --git a/v2/bytes/bytes_test.go b/v2/bytes/bytes_test.go new file mode 100644 index 0000000..7dfa829 --- /dev/null +++ b/v2/bytes/bytes_test.go @@ -0,0 +1,221 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package bytes + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestEmpty(t *testing.T) { + t.Run("returns empty byte slice", func(t *testing.T) { + result := Empty() + assert.NotNil(t, result) + assert.Equal(t, 0, len(result)) + }) + + t.Run("is identity for Monoid", func(t *testing.T) { + data := []byte("test") + + // Left identity: empty + data = data + left := Monoid.Concat(Empty(), data) + assert.Equal(t, data, left) + + // Right identity: data + empty = data + right := Monoid.Concat(data, Empty()) + assert.Equal(t, data, right) + }) +} + +func TestToString(t *testing.T) { + t.Run("converts byte slice to string", func(t *testing.T) { + result := ToString([]byte("hello")) + assert.Equal(t, "hello", result) + }) + + t.Run("handles empty byte slice", func(t *testing.T) { + result := ToString([]byte{}) + assert.Equal(t, "", result) + }) + + t.Run("handles binary data", func(t *testing.T) { + data := []byte{0x48, 0x65, 0x6c, 0x6c, 0x6f} // "Hello" + result := ToString(data) + assert.Equal(t, "Hello", result) + }) +} + +func TestSize(t *testing.T) { + t.Run("returns size of byte slice", func(t *testing.T) { + assert.Equal(t, 0, Size([]byte{})) + assert.Equal(t, 5, Size([]byte("hello"))) + assert.Equal(t, 10, Size([]byte("0123456789"))) + }) + + t.Run("handles binary data", func(t *testing.T) { + data := []byte{0x00, 0x01, 0x02, 0x03} + assert.Equal(t, 4, Size(data)) + }) +} + +func TestMonoidConcat(t *testing.T) { + t.Run("concatenates two byte slices", func(t *testing.T) { + result := Monoid.Concat([]byte("Hello"), []byte(" World")) + assert.Equal(t, []byte("Hello World"), result) + }) + + t.Run("handles empty slices", func(t *testing.T) { + result1 := Monoid.Concat([]byte{}, []byte("test")) + assert.Equal(t, []byte("test"), result1) + + result2 := Monoid.Concat([]byte("test"), []byte{}) + assert.Equal(t, []byte("test"), result2) + + result3 := Monoid.Concat([]byte{}, []byte{}) + assert.Equal(t, []byte{}, result3) + }) + + t.Run("associativity: (a + b) + c = a + (b + c)", func(t *testing.T) { + a := []byte("a") + b := []byte("b") + c := []byte("c") + + left := Monoid.Concat(Monoid.Concat(a, b), c) + right := Monoid.Concat(a, Monoid.Concat(b, c)) + + assert.Equal(t, left, right) + }) +} + +func TestConcatAll(t *testing.T) { + t.Run("concatenates multiple byte slices", func(t *testing.T) { + result := ConcatAll( + []byte("Hello"), + []byte(" "), + []byte("World"), + []byte("!"), + ) + assert.Equal(t, []byte("Hello World!"), result) + }) + + t.Run("handles empty input", func(t *testing.T) { + result := ConcatAll() + assert.Equal(t, []byte{}, result) + }) + + t.Run("handles single slice", func(t *testing.T) { + result := ConcatAll([]byte("test")) + assert.Equal(t, []byte("test"), result) + }) + + t.Run("handles slices with empty elements", func(t *testing.T) { + result := ConcatAll( + []byte("a"), + []byte{}, + []byte("b"), + []byte{}, + []byte("c"), + ) + assert.Equal(t, []byte("abc"), result) + }) + + t.Run("handles binary data", func(t *testing.T) { + result := ConcatAll( + []byte{0x01, 0x02}, + []byte{0x03, 0x04}, + []byte{0x05}, + ) + assert.Equal(t, []byte{0x01, 0x02, 0x03, 0x04, 0x05}, result) + }) +} + +func TestOrd(t *testing.T) { + t.Run("compares byte slices lexicographically", func(t *testing.T) { + // "abc" < "abd" + assert.Equal(t, -1, Ord.Compare([]byte("abc"), []byte("abd"))) + + // "abd" > "abc" + assert.Equal(t, 1, Ord.Compare([]byte("abd"), []byte("abc"))) + + // "abc" == "abc" + assert.Equal(t, 0, Ord.Compare([]byte("abc"), []byte("abc"))) + }) + + t.Run("handles different lengths", func(t *testing.T) { + // "ab" < "abc" + assert.Equal(t, -1, Ord.Compare([]byte("ab"), []byte("abc"))) + + // "abc" > "ab" + assert.Equal(t, 1, Ord.Compare([]byte("abc"), []byte("ab"))) + }) + + t.Run("handles empty slices", func(t *testing.T) { + // "" < "a" + assert.Equal(t, -1, Ord.Compare([]byte{}, []byte("a"))) + + // "a" > "" + assert.Equal(t, 1, Ord.Compare([]byte("a"), []byte{})) + + // "" == "" + assert.Equal(t, 0, Ord.Compare([]byte{}, []byte{})) + }) + + t.Run("Equals method works", func(t *testing.T) { + assert.True(t, Ord.Equals([]byte("test"), []byte("test"))) + assert.False(t, Ord.Equals([]byte("test"), []byte("Test"))) + assert.True(t, Ord.Equals([]byte{}, []byte{})) + }) + + t.Run("handles binary data", func(t *testing.T) { + assert.Equal(t, -1, Ord.Compare([]byte{0x01}, []byte{0x02})) + assert.Equal(t, 1, Ord.Compare([]byte{0x02}, []byte{0x01})) + assert.Equal(t, 0, Ord.Compare([]byte{0x01, 0x02}, []byte{0x01, 0x02})) + }) +} + +// Example tests +func ExampleEmpty() { + empty := Empty() + println(len(empty)) // 0 + + // Output: +} + +func ExampleToString() { + str := ToString([]byte("hello")) + println(str) // hello + + // Output: +} + +func ExampleSize() { + size := Size([]byte("hello")) + println(size) // 5 + + // Output: +} + +func ExampleConcatAll() { + result := ConcatAll( + []byte("Hello"), + []byte(" "), + []byte("World"), + ) + println(string(result)) // Hello World + + // Output: +} diff --git a/v2/bytes/doc.go b/v2/bytes/doc.go new file mode 100644 index 0000000..9ca8dfd --- /dev/null +++ b/v2/bytes/doc.go @@ -0,0 +1,114 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +// Package bytes provides functional programming utilities for working with byte slices. +// +// This package offers algebraic structures (Monoid, Ord) and utility functions +// for byte slice operations in a functional style. +// +// # Monoid +// +// The Monoid instance for byte slices combines them through concatenation, +// with an empty byte slice as the identity element. +// +// Example: +// +// import "github.com/IBM/fp-go/v2/bytes" +// +// // Concatenate byte slices +// result := bytes.Monoid.Concat([]byte("Hello"), []byte(" World")) +// // result: []byte("Hello World") +// +// // Identity element +// empty := bytes.Empty() // []byte{} +// +// # ConcatAll +// +// Efficiently concatenates multiple byte slices into a single slice: +// +// import "github.com/IBM/fp-go/v2/bytes" +// +// result := bytes.ConcatAll( +// []byte("Hello"), +// []byte(" "), +// []byte("World"), +// ) +// // result: []byte("Hello World") +// +// # Ordering +// +// The Ord instance provides lexicographic ordering for byte slices: +// +// import "github.com/IBM/fp-go/v2/bytes" +// +// cmp := bytes.Ord.Compare([]byte("abc"), []byte("abd")) +// // cmp: -1 (abc < abd) +// +// equal := bytes.Ord.Equals([]byte("test"), []byte("test")) +// // equal: true +// +// # Utility Functions +// +// The package provides several utility functions: +// +// // Get empty byte slice +// empty := bytes.Empty() // []byte{} +// +// // Convert to string +// str := bytes.ToString([]byte("hello")) // "hello" +// +// // Get size +// size := bytes.Size([]byte("hello")) // 5 +// +// # Use Cases +// +// The bytes package is particularly useful for: +// +// - Building byte buffers functionally +// - Combining multiple byte slices efficiently +// - Comparing byte slices lexicographically +// - Working with binary data in a functional style +// +// # Example - Building a Protocol Message +// +// import ( +// "github.com/IBM/fp-go/v2/bytes" +// "encoding/binary" +// ) +// +// // Build a simple protocol message +// header := []byte{0x01, 0x02} +// length := make([]byte, 4) +// binary.BigEndian.PutUint32(length, 100) +// payload := []byte("data") +// +// message := bytes.ConcatAll(header, length, payload) +// +// # Example - Sorting Byte Slices +// +// import ( +// "github.com/IBM/fp-go/v2/array" +// "github.com/IBM/fp-go/v2/bytes" +// ) +// +// data := [][]byte{ +// []byte("zebra"), +// []byte("apple"), +// []byte("mango"), +// } +// +// sorted := array.Sort(bytes.Ord)(data) +// // sorted: [[]byte("apple"), []byte("mango"), []byte("zebra")] +package bytes diff --git a/v2/bytes/monoid.go b/v2/bytes/monoid.go new file mode 100644 index 0000000..3d26271 --- /dev/null +++ b/v2/bytes/monoid.go @@ -0,0 +1,34 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package bytes + +import ( + "bytes" + + A "github.com/IBM/fp-go/v2/array" + O "github.com/IBM/fp-go/v2/ord" +) + +var ( + // monoid for byte arrays + Monoid = A.Monoid[byte]() + + // ConcatAll concatenates all bytes + ConcatAll = A.ArrayConcatAll[byte] + + // Ord implements the default ordering on bytes + Ord = O.MakeOrd(bytes.Compare, bytes.Equal) +) diff --git a/v2/bytes/monoid_test.go b/v2/bytes/monoid_test.go new file mode 100644 index 0000000..0b4bb30 --- /dev/null +++ b/v2/bytes/monoid_test.go @@ -0,0 +1,26 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package bytes + +import ( + "testing" + + M "github.com/IBM/fp-go/v2/monoid/testing" +) + +func TestMonoid(t *testing.T) { + M.AssertLaws(t, Monoid)([][]byte{[]byte(""), []byte("a"), []byte("some value")}) +} diff --git a/v2/cli/apply.go b/v2/cli/apply.go new file mode 100644 index 0000000..ca6fda2 --- /dev/null +++ b/v2/cli/apply.go @@ -0,0 +1,432 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package cli + +import ( + "fmt" + "log" + "os" + "path/filepath" + "time" + + C "github.com/urfave/cli/v2" +) + +func generateTraverseTuple(f *os.File, i int) { + fmt.Fprintf(f, "\n// TraverseTuple%d is a utility function used to implement the sequence operation for higher kinded types based only on map and ap.\n", i) + fmt.Fprintf(f, "// The function takes a [Tuple%d] of base types and %d functions that transform these based types into higher higher kinded types. It returns a higher kinded type of a [Tuple%d] with the resolved values.\n", i, i, i) + fmt.Fprintf(f, "func TraverseTuple%d[\n", i) + // map as the starting point + fmt.Fprintf(f, " MAP ~func(") + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, " ") + } + fmt.Fprintf(f, "func(T%d)", j+1) + } + fmt.Fprintf(f, " ") + fmt.Fprintf(f, "T.") + writeTupleType(f, "T", i) + fmt.Fprintf(f, ") func(HKT_T1)") + if i > 1 { + fmt.Fprintf(f, " HKT_F") + for k := 1; k < i; k++ { + fmt.Fprintf(f, "_T%d", k+1) + } + } else { + fmt.Fprintf(f, " HKT_TUPLE%d", i) + } + fmt.Fprintf(f, ",\n") + // the applicatives + for j := 1; j < i; j++ { + fmt.Fprintf(f, " AP%d ~func(", j) + fmt.Fprintf(f, "HKT_T%d) func(", j+1) + fmt.Fprintf(f, "HKT_F") + for k := j; k < i; k++ { + fmt.Fprintf(f, "_T%d", k+1) + } + fmt.Fprintf(f, ")") + if j+1 < i { + fmt.Fprintf(f, " HKT_F") + for k := j + 1; k < i; k++ { + fmt.Fprintf(f, "_T%d", k+1) + } + } else { + fmt.Fprintf(f, " HKT_TUPLE%d", i) + } + fmt.Fprintf(f, ",\n") + } + for j := 0; j < i; j++ { + fmt.Fprintf(f, " F%d ~func(A%d) HKT_T%d,\n", j+1, j+1, j+1) + } + for j := 0; j < i; j++ { + fmt.Fprintf(f, " A%d, T%d,\n", j+1, j+1) + } + for j := 0; j < i; j++ { + fmt.Fprintf(f, " HKT_T%d, // HKT[T%d]\n", j+1, j+1) + } + for j := 1; j < i; j++ { + fmt.Fprintf(f, " HKT_F") + for k := j; k < i; k++ { + fmt.Fprintf(f, "_T%d", k+1) + } + fmt.Fprintf(f, ", // HKT[") + for k := j; k < i; k++ { + fmt.Fprintf(f, "func(T%d) ", k+1) + } + fmt.Fprintf(f, "T.") + writeTupleType(f, "T", i) + fmt.Fprintf(f, "]\n") + } + fmt.Fprintf(f, " HKT_TUPLE%d any, // HKT[", i) + writeTupleType(f, "T", i) + fmt.Fprintf(f, "]\n") + fmt.Fprintf(f, "](\n") + + // the callbacks + fmt.Fprintf(f, " fmap MAP,\n") + for j := 1; j < i; j++ { + fmt.Fprintf(f, " fap%d AP%d,\n", j, j) + } + // the transformer functions + for j := 1; j <= i; j++ { + fmt.Fprintf(f, " f%d F%d,\n", j, j) + } + // the parameters + fmt.Fprintf(f, " t T.Tuple%d[", i) + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "A%d", j+1) + } + fmt.Fprintf(f, "],\n") + fmt.Fprintf(f, ") HKT_TUPLE%d {\n", i) + + fmt.Fprintf(f, " return F.Pipe%d(\n", i) + fmt.Fprintf(f, " f1(t.F1),\n") + fmt.Fprintf(f, " fmap(tupleConstructor%d[", i) + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", j+1) + } + fmt.Fprintf(f, "]()),\n") + for j := 1; j < i; j++ { + fmt.Fprintf(f, " fap%d(f%d(t.F%d)),\n", j, j+1, j+1) + } + fmt.Fprintf(f, ")\n") + fmt.Fprintf(f, "}\n") +} + +func generateSequenceTuple(f *os.File, i int) { + fmt.Fprintf(f, "\n// SequenceTuple%d is a utility function used to implement the sequence operation for higher kinded types based only on map and ap.\n", i) + fmt.Fprintf(f, "// The function takes a [Tuple%d] of higher higher kinded types and returns a higher kinded type of a [Tuple%d] with the resolved values.\n", i, i) + fmt.Fprintf(f, "func SequenceTuple%d[\n", i) + // map as the starting point + fmt.Fprintf(f, " MAP ~func(") + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, " ") + } + fmt.Fprintf(f, "func(T%d)", j+1) + } + fmt.Fprintf(f, " ") + fmt.Fprintf(f, "T.") + writeTupleType(f, "T", i) + fmt.Fprintf(f, ") func(HKT_T1)") + if i > 1 { + fmt.Fprintf(f, " HKT_F") + for k := 1; k < i; k++ { + fmt.Fprintf(f, "_T%d", k+1) + } + } else { + fmt.Fprintf(f, " HKT_TUPLE%d", i) + } + fmt.Fprintf(f, ",\n") + // the applicatives + for j := 1; j < i; j++ { + fmt.Fprintf(f, " AP%d ~func(", j) + fmt.Fprintf(f, "HKT_T%d) func(", j+1) + fmt.Fprintf(f, "HKT_F") + for k := j; k < i; k++ { + fmt.Fprintf(f, "_T%d", k+1) + } + fmt.Fprintf(f, ")") + if j+1 < i { + fmt.Fprintf(f, " HKT_F") + for k := j + 1; k < i; k++ { + fmt.Fprintf(f, "_T%d", k+1) + } + } else { + fmt.Fprintf(f, " HKT_TUPLE%d", i) + } + fmt.Fprintf(f, ",\n") + } + + for j := 0; j < i; j++ { + fmt.Fprintf(f, " T%d,\n", j+1) + } + for j := 0; j < i; j++ { + fmt.Fprintf(f, " HKT_T%d, // HKT[T%d]\n", j+1, j+1) + } + for j := 1; j < i; j++ { + fmt.Fprintf(f, " HKT_F") + for k := j; k < i; k++ { + fmt.Fprintf(f, "_T%d", k+1) + } + fmt.Fprintf(f, ", // HKT[") + for k := j; k < i; k++ { + fmt.Fprintf(f, "func(T%d) ", k+1) + } + fmt.Fprintf(f, "T.") + writeTupleType(f, "T", i) + fmt.Fprintf(f, "]\n") + } + fmt.Fprintf(f, " HKT_TUPLE%d any, // HKT[", i) + writeTupleType(f, "T", i) + fmt.Fprintf(f, "]\n") + fmt.Fprintf(f, "](\n") + + // the callbacks + fmt.Fprintf(f, " fmap MAP,\n") + for j := 1; j < i; j++ { + fmt.Fprintf(f, " fap%d AP%d,\n", j, j) + } + // the parameters + fmt.Fprintf(f, " t T.Tuple%d[", i) + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "HKT_T%d", j+1) + } + fmt.Fprintf(f, "],\n") + fmt.Fprintf(f, ") HKT_TUPLE%d {\n", i) + + fmt.Fprintf(f, " return F.Pipe%d(\n", i) + fmt.Fprintf(f, " t.F1,\n") + fmt.Fprintf(f, " fmap(tupleConstructor%d[", i) + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", j+1) + } + fmt.Fprintf(f, "]()),\n") + for j := 1; j < i; j++ { + fmt.Fprintf(f, " fap%d(t.F%d),\n", j, j+1) + } + fmt.Fprintf(f, ")\n") + fmt.Fprintf(f, "}\n") +} + +func generateSequenceT(f *os.File, i int) { + fmt.Fprintf(f, "\n// SequenceT%d is a utility function used to implement the sequence operation for higher kinded types based only on map and ap.\n", i) + fmt.Fprintf(f, "// The function takes %d higher higher kinded types and returns a higher kinded type of a [Tuple%d] with the resolved values.\n", i, i) + fmt.Fprintf(f, "func SequenceT%d[\n", i) + // map as the starting point + fmt.Fprintf(f, " MAP ~func(") + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, " ") + } + fmt.Fprintf(f, "func(T%d)", j+1) + } + fmt.Fprintf(f, " ") + fmt.Fprintf(f, "T.") + writeTupleType(f, "T", i) + fmt.Fprintf(f, ") func(HKT_T1)") + if i > 1 { + fmt.Fprintf(f, " HKT_F") + for k := 1; k < i; k++ { + fmt.Fprintf(f, "_T%d", k+1) + } + } else { + fmt.Fprintf(f, " HKT_TUPLE%d", i) + } + fmt.Fprintf(f, ",\n") + // the applicatives + for j := 1; j < i; j++ { + fmt.Fprintf(f, " AP%d ~func(", j) + fmt.Fprintf(f, "HKT_T%d) func(", j+1) + fmt.Fprintf(f, "HKT_F") + for k := j; k < i; k++ { + fmt.Fprintf(f, "_T%d", k+1) + } + fmt.Fprintf(f, ")") + if j+1 < i { + fmt.Fprintf(f, " HKT_F") + for k := j + 1; k < i; k++ { + fmt.Fprintf(f, "_T%d", k+1) + } + } else { + fmt.Fprintf(f, " HKT_TUPLE%d", i) + } + fmt.Fprintf(f, ",\n") + } + + for j := 0; j < i; j++ { + fmt.Fprintf(f, " T%d,\n", j+1) + } + for j := 0; j < i; j++ { + fmt.Fprintf(f, " HKT_T%d, // HKT[T%d]\n", j+1, j+1) + } + for j := 1; j < i; j++ { + fmt.Fprintf(f, " HKT_F") + for k := j; k < i; k++ { + fmt.Fprintf(f, "_T%d", k+1) + } + fmt.Fprintf(f, ", // HKT[") + for k := j; k < i; k++ { + fmt.Fprintf(f, "func(T%d) ", k+1) + } + fmt.Fprintf(f, "T.") + writeTupleType(f, "T", i) + fmt.Fprintf(f, "]\n") + } + fmt.Fprintf(f, " HKT_TUPLE%d any, // HKT[", i) + writeTupleType(f, "T", i) + fmt.Fprintf(f, "]\n") + fmt.Fprintf(f, "](\n") + + // the callbacks + fmt.Fprintf(f, " fmap MAP,\n") + for j := 1; j < i; j++ { + fmt.Fprintf(f, " fap%d AP%d,\n", j, j) + } + // the parameters + for j := 0; j < i; j++ { + fmt.Fprintf(f, " t%d HKT_T%d,\n", j+1, j+1) + } + fmt.Fprintf(f, ") HKT_TUPLE%d {\n", i) + + fmt.Fprintf(f, " return F.Pipe%d(\n", i) + fmt.Fprintf(f, " t1,\n") + fmt.Fprintf(f, " fmap(tupleConstructor%d[", i) + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", j+1) + } + fmt.Fprintf(f, "]()),\n") + for j := 1; j < i; j++ { + fmt.Fprintf(f, " fap%d(t%d),\n", j, j+1) + } + fmt.Fprintf(f, ")\n") + fmt.Fprintf(f, "}\n") +} + +func generateTupleConstructor(f *os.File, i int) { + // Create the optionize version + fmt.Fprintf(f, "\n// tupleConstructor%d returns a curried version of [T.MakeTuple%d]\n", i, i) + fmt.Fprintf(f, "func tupleConstructor%d[", i) + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", j+1) + } + fmt.Fprintf(f, " any]()") + for j := 0; j < i; j++ { + fmt.Fprintf(f, " func(T%d)", j+1) + } + fmt.Fprintf(f, " T.Tuple%d[", i) + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", j+1) + } + fmt.Fprintf(f, "] {\n") + + fmt.Fprintf(f, " return F.Curry%d(T.MakeTuple%d[", i, i) + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", j+1) + } + fmt.Fprintf(f, "])\n") + + fmt.Fprintf(f, "}\n") +} + +func generateApplyHelpers(filename string, count int) error { + dir, err := os.Getwd() + if err != nil { + return err + } + absDir, err := filepath.Abs(dir) + if err != nil { + return err + } + pkg := filepath.Base(absDir) + f, err := os.Create(filepath.Clean(filename)) + if err != nil { + return err + } + defer f.Close() + // log + log.Printf("Generating code in [%s] for package [%s] with [%d] repetitions ...", filename, pkg, count) + + // some header + fmt.Fprintln(f, "// Code generated by go generate; DO NOT EDIT.") + fmt.Fprintln(f, "// This file was generated by robots at") + fmt.Fprintf(f, "// %s\n\n", time.Now()) + + fmt.Fprintf(f, "package %s\n\n", pkg) + + // print out some helpers + fmt.Fprintf(f, ` +import ( + F "github.com/IBM/fp-go/v2/function" + T "github.com/IBM/fp-go/v2/tuple" +) +`) + + for i := 1; i <= count; i++ { + // tuple constructor + generateTupleConstructor(f, i) + // sequenceT + generateSequenceT(f, i) + // sequenceTuple + generateSequenceTuple(f, i) + // traverseTuple + generateTraverseTuple(f, i) + } + + return nil +} + +func ApplyCommand() *C.Command { + return &C.Command{ + Name: "apply", + Usage: "generate code for the sequence operations of apply", + Flags: []C.Flag{ + flagCount, + flagFilename, + }, + Action: func(ctx *C.Context) error { + return generateApplyHelpers( + ctx.String(keyFilename), + ctx.Int(keyCount), + ) + }, + } +} diff --git a/v2/cli/bind.go b/v2/cli/bind.go new file mode 100644 index 0000000..77b7bb5 --- /dev/null +++ b/v2/cli/bind.go @@ -0,0 +1,294 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package cli + +import ( + "fmt" + "log" + "os" + "path/filepath" + "time" + + C "github.com/urfave/cli/v2" +) + +func createCombinations(n int, all, prev []int) [][]int { + l := len(prev) + if l == n { + return [][]int{prev} + } + var res [][]int + for idx, val := range all { + cpy := make([]int, l+1) + copy(cpy, prev) + cpy[l] = val + + res = append(res, createCombinations(n, all[idx+1:], cpy)...) + } + return res +} + +func remaining(comb []int, total int) []int { + var res []int + mp := make(map[int]int) + for _, idx := range comb { + mp[idx] = idx + } + for i := 1; i <= total; i++ { + _, ok := mp[i] + if !ok { + res = append(res, i) + } + } + return res +} + +func generateCombSingleBind(f *os.File, comb [][]int, total int) { + for _, c := range comb { + // remaining indexes + rem := remaining(c, total) + + // bind function + fmt.Fprintf(f, "\n// Bind") + for _, idx := range c { + fmt.Fprintf(f, "%d", idx) + } + fmt.Fprintf(f, "of%d takes a function with %d parameters and returns a new function with %d parameters that will bind these parameters to the positions [", total, total, len(c)) + for i, idx := range c { + if i > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "%d", idx) + } + fmt.Fprintf(f, "] of the original function.\n// The return value of is a function with the remaining %d parameters at positions [", len(rem)) + for i, idx := range rem { + if i > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "%d", idx) + } + fmt.Fprintf(f, "] of the original function.\n") + fmt.Fprintf(f, "func Bind") + for _, idx := range c { + fmt.Fprintf(f, "%d", idx) + } + fmt.Fprintf(f, "of%d[F ~func(", total) + for i := 0; i < total; i++ { + if i > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", i+1) + } + fmt.Fprintf(f, ") R") + for i := 0; i < total; i++ { + fmt.Fprintf(f, ", T%d", i+1) + } + fmt.Fprintf(f, ", R any](f F) func(") + for i, idx := range c { + if i > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", idx) + } + fmt.Fprintf(f, ") func(") + for i, idx := range rem { + if i > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", idx) + } + fmt.Fprintf(f, ") R {\n") + + fmt.Fprintf(f, " return func(") + + for i, idx := range c { + if i > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "t%d T%d", idx, idx) + } + fmt.Fprintf(f, ") func(") + for i, idx := range rem { + if i > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", idx) + } + fmt.Fprintf(f, ") R {\n") + + fmt.Fprintf(f, " return func(") + for i, idx := range rem { + if i > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "t%d T%d", idx, idx) + } + fmt.Fprintf(f, ") R {\n") + + fmt.Fprintf(f, " return f(") + for i := 1; i <= total; i++ { + if i > 1 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "t%d", i) + } + fmt.Fprintf(f, ")\n") + + fmt.Fprintf(f, " }\n") + fmt.Fprintf(f, " }\n") + fmt.Fprintf(f, "}\n") + + // ignore function + fmt.Fprintf(f, "\n// Ignore") + for _, idx := range c { + fmt.Fprintf(f, "%d", idx) + } + fmt.Fprintf(f, "of%d takes a function with %d parameters and returns a new function with %d parameters that will ignore the values at positions [", total, len(rem), total) + for i, idx := range c { + if i > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "%d", idx) + } + fmt.Fprintf(f, "] and pass the remaining %d parameters to the original function\n", len(rem)) + fmt.Fprintf(f, "func Ignore") + for _, idx := range c { + fmt.Fprintf(f, "%d", idx) + } + fmt.Fprintf(f, "of%d[", total) + // start with the undefined parameters + for i, idx := range c { + if i > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", idx) + } + if len(c) > 0 { + fmt.Fprintf(f, " any, ") + } + fmt.Fprintf(f, "F ~func(") + for i, idx := range rem { + if i > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", idx) + } + fmt.Fprintf(f, ") R") + for _, idx := range rem { + fmt.Fprintf(f, ", T%d", idx) + } + fmt.Fprintf(f, ", R any](f F) func(") + for i := 0; i < total; i++ { + if i > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", i+1) + } + fmt.Fprintf(f, ") R {\n") + + fmt.Fprintf(f, " return func(") + for i := 0; i < total; i++ { + if i > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "t%d T%d", i+1, i+1) + } + fmt.Fprintf(f, ") R {\n") + fmt.Fprintf(f, " return f(") + for i, idx := range rem { + if i > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "t%d", idx) + } + fmt.Fprintf(f, ")\n") + + fmt.Fprintf(f, " }\n") + fmt.Fprintf(f, "}\n") + + } +} + +func generateSingleBind(f *os.File, total int) { + + fmt.Fprintf(f, "// Combinations for a total of %d arguments\n", total) + + // construct the indexes + all := make([]int, total) + for i := 0; i < total; i++ { + all[i] = i + 1 + } + // for all permutations of a certain length + for j := 0; j < total; j++ { + // get combinations of that size + comb := createCombinations(j+1, all, []int{}) + generateCombSingleBind(f, comb, total) + } +} + +func generateBind(f *os.File, i int) { + for j := 1; j < i; j++ { + generateSingleBind(f, j) + } +} + +func generateBindHelpers(filename string, count int) error { + dir, err := os.Getwd() + if err != nil { + return err + } + absDir, err := filepath.Abs(dir) + if err != nil { + return err + } + pkg := filepath.Base(absDir) + f, err := os.Create(filepath.Clean(filename)) + if err != nil { + return err + } + defer f.Close() + // log + log.Printf("Generating code in [%s] for package [%s] with [%d] repetitions ...", filename, pkg, count) + + // some header + fmt.Fprintln(f, "// Code generated by go generate; DO NOT EDIT.") + fmt.Fprintln(f, "// This file was generated by robots at") + fmt.Fprintf(f, "// %s\n\n", time.Now()) + + fmt.Fprintf(f, "package %s\n", pkg) + + generateBind(f, count) + + return nil +} + +func BindCommand() *C.Command { + return &C.Command{ + Name: "bind", + Usage: "generate code for binder functions etc", + Description: "Code generation for bind, etc", + Flags: []C.Flag{ + flagCount, + flagFilename, + }, + Action: func(ctx *C.Context) error { + return generateBindHelpers( + ctx.String(keyFilename), + ctx.Int(keyCount), + ) + }, + } +} diff --git a/v2/cli/commands.go b/v2/cli/commands.go new file mode 100644 index 0000000..e85bb62 --- /dev/null +++ b/v2/cli/commands.go @@ -0,0 +1,39 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package cli + +import ( + C "github.com/urfave/cli/v2" +) + +func Commands() []*C.Command { + return []*C.Command{ + PipeCommand(), + IdentityCommand(), + OptionCommand(), + EitherCommand(), + TupleCommand(), + BindCommand(), + ApplyCommand(), + ContextReaderIOEitherCommand(), + ReaderIOEitherCommand(), + ReaderCommand(), + IOEitherCommand(), + IOCommand(), + IOOptionCommand(), + DICommand(), + } +} diff --git a/v2/cli/common.go b/v2/cli/common.go new file mode 100644 index 0000000..7040012 --- /dev/null +++ b/v2/cli/common.go @@ -0,0 +1,39 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package cli + +import ( + C "github.com/urfave/cli/v2" +) + +const ( + keyFilename = "filename" + keyCount = "count" +) + +var ( + flagFilename = &C.StringFlag{ + Name: keyFilename, + Value: "gen.go", + Usage: "Name of the generated file", + } + + flagCount = &C.IntFlag{ + Name: keyCount, + Value: 20, + Usage: "Number of variations to create", + } +) diff --git a/v2/cli/contextreaderioeither.go b/v2/cli/contextreaderioeither.go new file mode 100644 index 0000000..46a8fff --- /dev/null +++ b/v2/cli/contextreaderioeither.go @@ -0,0 +1,271 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package cli + +import ( + "fmt" + "log" + "os" + "path/filepath" + "strings" + + A "github.com/IBM/fp-go/v2/array" + C "github.com/urfave/cli/v2" +) + +// Deprecated: +func generateNestedCallbacks(i, total int) string { + var buf strings.Builder + for j := i; j < total; j++ { + if j > i { + buf.WriteString(" ") + } + buf.WriteString(fmt.Sprintf("func(T%d)", j+1)) + } + if i > 0 { + buf.WriteString(" ") + } + buf.WriteString(tupleType("T")(total)) + return buf.String() +} + +func generateNestedCallbacksPlain(i, total int) string { + fs := A.MakeBy(total-i, func(j int) string { + return fmt.Sprintf("func(T%d)", j+i+1) + }) + ts := A.Of(tupleTypePlain("T")(total)) + return joinAll(" ")(fs, ts) +} + +func generateContextReaderIOEitherEitherize(f, fg *os.File, i int) { + // non generic version + fmt.Fprintf(f, "\n// Eitherize%d converts a function with %d parameters returning a tuple into a function with %d parameters returning a [ReaderIOEither[R]]\n// The inverse function is [Uneitherize%d]\n", i, i, i, i) + fmt.Fprintf(f, "func Eitherize%d[F ~func(context.Context", i) + for j := 0; j < i; j++ { + fmt.Fprintf(f, ", T%d", j) + } + fmt.Fprintf(f, ") (R, error)") + for j := 0; j < i; j++ { + fmt.Fprintf(f, ", T%d", j) + } + fmt.Fprintf(f, ", R any](f F) func(") + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", j) + } + fmt.Fprintf(f, ") ReaderIOEither[R] {\n") + fmt.Fprintf(f, " return G.Eitherize%d[ReaderIOEither[R]](f)\n", i) + fmt.Fprintln(f, "}") + + // generic version + fmt.Fprintf(fg, "\n// Eitherize%d converts a function with %d parameters returning a tuple into a function with %d parameters returning a [GRA]\n// The inverse function is [Uneitherize%d]\n", i, i, i, i) + fmt.Fprintf(fg, "func Eitherize%d[GRA ~func(context.Context) GIOA, F ~func(context.Context", i) + for j := 0; j < i; j++ { + fmt.Fprintf(fg, ", T%d", j) + } + fmt.Fprintf(fg, ") (R, error), GIOA ~func() E.Either[error, R]") + for j := 0; j < i; j++ { + fmt.Fprintf(fg, ", T%d", j) + } + fmt.Fprintf(fg, ", R any](f F) func(") + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(fg, ", ") + } + fmt.Fprintf(fg, "T%d", j) + } + fmt.Fprintf(fg, ") GRA {\n") + fmt.Fprintf(fg, " return RE.Eitherize%d[GRA](f)\n", i) + fmt.Fprintln(fg, "}") +} + +func generateContextReaderIOEitherUneitherize(f, fg *os.File, i int) { + // non generic version + fmt.Fprintf(f, "\n// Uneitherize%d converts a function with %d parameters returning a [ReaderIOEither[R]] into a function with %d parameters returning a tuple.\n// The first parameter is considered to be the [context.Context].\n", i, i+1, i) + fmt.Fprintf(f, "func Uneitherize%d[F ~func(", i) + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", j) + } + fmt.Fprintf(f, ") ReaderIOEither[R]") + for j := 0; j < i; j++ { + fmt.Fprintf(f, ", T%d", j) + } + fmt.Fprintf(f, ", R any](f F) func(context.Context") + for j := 0; j < i; j++ { + fmt.Fprintf(f, ", T%d", j) + } + fmt.Fprintf(f, ") (R, error) {\n") + fmt.Fprintf(f, " return G.Uneitherize%d[ReaderIOEither[R]", i) + + fmt.Fprintf(f, ", func(context.Context") + for j := 0; j < i; j++ { + fmt.Fprintf(f, ", T%d", j) + } + fmt.Fprintf(f, ")(R, error)](f)\n") + fmt.Fprintln(f, "}") + + // generic version + fmt.Fprintf(fg, "\n// Uneitherize%d converts a function with %d parameters returning a [GRA] into a function with %d parameters returning a tuple.\n// The first parameter is considered to be the [context.Context].\n", i, i, i) + fmt.Fprintf(fg, "func Uneitherize%d[GRA ~func(context.Context) GIOA, F ~func(context.Context", i) + for j := 0; j < i; j++ { + fmt.Fprintf(fg, ", T%d", j) + } + fmt.Fprintf(fg, ") (R, error), GIOA ~func() E.Either[error, R]") + for j := 0; j < i; j++ { + fmt.Fprintf(fg, ", T%d", j) + } + fmt.Fprintf(fg, ", R any](f func(") + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(fg, ", ") + } + fmt.Fprintf(fg, "T%d", j) + } + fmt.Fprintf(fg, ") GRA) F {\n") + + fmt.Fprintf(fg, " return func(c context.Context") + for j := 0; j < i; j++ { + fmt.Fprintf(fg, ", t%d T%d", j, j) + } + fmt.Fprintf(fg, ") (R, error) {\n") + fmt.Fprintf(fg, " return E.UnwrapError(f(") + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(fg, ", ") + } + fmt.Fprintf(fg, "t%d", j) + } + fmt.Fprintf(fg, ")(c)())\n") + fmt.Fprintf(fg, " }\n") + fmt.Fprintf(fg, "}\n") +} + +func nonGenericContextReaderIOEither(param string) string { + return fmt.Sprintf("ReaderIOEither[%s]", param) +} + +var extrasContextReaderIOEither = A.Empty[string]() + +func generateContextReaderIOEitherSequenceT(f *os.File, i int) { + generateGenericSequenceT("", nonGenericContextReaderIOEither, extrasContextReaderIOEither)(f, i) + generateGenericSequenceT("Seq", nonGenericContextReaderIOEither, extrasContextReaderIOEither)(f, i) + generateGenericSequenceT("Par", nonGenericContextReaderIOEither, extrasContextReaderIOEither)(f, i) +} + +func generateContextReaderIOEitherSequenceTuple(f *os.File, i int) { + generateGenericSequenceTuple("", nonGenericContextReaderIOEither, extrasContextReaderIOEither)(f, i) + generateGenericSequenceTuple("Seq", nonGenericContextReaderIOEither, extrasContextReaderIOEither)(f, i) + generateGenericSequenceTuple("Par", nonGenericContextReaderIOEither, extrasContextReaderIOEither)(f, i) +} + +func generateContextReaderIOEitherTraverseTuple(f *os.File, i int) { + generateGenericTraverseTuple("", nonGenericContextReaderIOEither, extrasContextReaderIOEither)(f, i) + generateGenericTraverseTuple("Seq", nonGenericContextReaderIOEither, extrasContextReaderIOEither)(f, i) + generateGenericTraverseTuple("Par", nonGenericContextReaderIOEither, extrasContextReaderIOEither)(f, i) +} + +func generateContextReaderIOEitherHelpers(filename string, count int) error { + dir, err := os.Getwd() + if err != nil { + return err + } + absDir, err := filepath.Abs(dir) + if err != nil { + return err + } + pkg := filepath.Base(absDir) + f, err := os.Create(filepath.Clean(filename)) + if err != nil { + return err + } + defer f.Close() + // construct subdirectory + genFilename := filepath.Join("generic", filename) + err = os.MkdirAll("generic", os.ModePerm) + if err != nil { + return err + } + fg, err := os.Create(filepath.Clean(genFilename)) + if err != nil { + return err + } + defer fg.Close() + + // log + log.Printf("Generating code in [%s] for package [%s] with [%d] repetitions ...", filename, pkg, count) + + writePackage(f, pkg) + + fmt.Fprintf(f, ` +import ( + "context" + + G "github.com/IBM/fp-go/v2/context/%s/generic" + "github.com/IBM/fp-go/v2/internal/apply" + "github.com/IBM/fp-go/v2/tuple" +) +`, pkg) + + writePackage(fg, "generic") + + fmt.Fprintf(fg, ` +import ( + "context" + + E "github.com/IBM/fp-go/v2/either" + RE "github.com/IBM/fp-go/v2/readerioeither/generic" +) +`) + + generateContextReaderIOEitherEitherize(f, fg, 0) + generateContextReaderIOEitherUneitherize(f, fg, 0) + + for i := 1; i <= count; i++ { + // eitherize + generateContextReaderIOEitherEitherize(f, fg, i) + generateContextReaderIOEitherUneitherize(f, fg, i) + // sequenceT + generateContextReaderIOEitherSequenceT(f, i) + // sequenceTuple + generateContextReaderIOEitherSequenceTuple(f, i) + // traverseTuple + generateContextReaderIOEitherTraverseTuple(f, i) + } + + return nil +} + +func ContextReaderIOEitherCommand() *C.Command { + return &C.Command{ + Name: "contextreaderioeither", + Usage: "generate code for ContextReaderIOEither", + Flags: []C.Flag{ + flagCount, + flagFilename, + }, + Action: func(ctx *C.Context) error { + return generateContextReaderIOEitherHelpers( + ctx.String(keyFilename), + ctx.Int(keyCount), + ) + }, + } +} diff --git a/v2/cli/di.go b/v2/cli/di.go new file mode 100644 index 0000000..df19384 --- /dev/null +++ b/v2/cli/di.go @@ -0,0 +1,231 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package cli + +import ( + "fmt" + "log" + "os" + "path/filepath" + "time" + + C "github.com/urfave/cli/v2" +) + +func generateMakeProvider(f *os.File, i int) { + // non generic version + fmt.Fprintf(f, "\n// MakeProvider%d creates a [DIE.Provider] for an [InjectionToken] from a function with %d dependencies\n", i, i) + fmt.Fprintf(f, "func MakeProvider%d[", i) + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", j+1) + } + fmt.Fprintf(f, " any, R any](\n") + fmt.Fprintf(f, " token InjectionToken[R],\n") + for j := 0; j < i; j++ { + fmt.Fprintf(f, " d%d Dependency[T%d],\n", j+1, j+1) + } + fmt.Fprintf(f, " f func(") + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", j+1) + } + fmt.Fprintf(f, ") IOE.IOEither[error, R],\n") + fmt.Fprintf(f, ") DIE.Provider {\n") + fmt.Fprint(f, " return DIE.MakeProvider(\n") + fmt.Fprint(f, " token,\n") + fmt.Fprintf(f, " MakeProviderFactory%d(\n", i) + for j := 0; j < i; j++ { + fmt.Fprintf(f, " d%d,\n", j+1) + } + fmt.Fprint(f, " f,\n") + fmt.Fprint(f, " ))\n") + fmt.Fprintf(f, "}\n") +} + +func generateMakeTokenWithDefault(f *os.File, i int) { + // non generic version + fmt.Fprintf(f, "\n// MakeTokenWithDefault%d creates an [InjectionToken] with a default implementation with %d dependencies\n", i, i) + fmt.Fprintf(f, "func MakeTokenWithDefault%d[", i) + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", j+1) + } + fmt.Fprintf(f, " any, R any](\n") + fmt.Fprintf(f, " name string,\n") + for j := 0; j < i; j++ { + fmt.Fprintf(f, " d%d Dependency[T%d],\n", j+1, j+1) + } + fmt.Fprintf(f, " f func(") + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", j+1) + } + fmt.Fprintf(f, ") IOE.IOEither[error, R],\n") + fmt.Fprintf(f, ") InjectionToken[R] {\n") + fmt.Fprintf(f, " return MakeTokenWithDefault[R](name, MakeProviderFactory%d(\n", i) + for j := 0; j < i; j++ { + fmt.Fprintf(f, " d%d,\n", j+1) + } + fmt.Fprint(f, " f,\n") + fmt.Fprint(f, " ))\n") + fmt.Fprintf(f, "}\n") +} + +func generateMakeProviderFactory(f *os.File, i int) { + // non generic version + fmt.Fprintf(f, "\n// MakeProviderFactory%d creates a [DIE.ProviderFactory] from a function with %d arguments and %d dependencies\n", i, i, i) + fmt.Fprintf(f, "func MakeProviderFactory%d[", i) + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", j+1) + } + fmt.Fprintf(f, " any, R any](\n") + for j := 0; j < i; j++ { + fmt.Fprintf(f, " d%d Dependency[T%d],\n", j+1, j+1) + } + fmt.Fprintf(f, " f func(") + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", j+1) + } + fmt.Fprintf(f, ") IOE.IOEither[error, R],\n") + fmt.Fprintf(f, ") DIE.ProviderFactory {\n") + fmt.Fprint(f, " return DIE.MakeProviderFactory(\n") + fmt.Fprint(f, " A.From[DIE.Dependency](\n") + for j := 0; j < i; j++ { + fmt.Fprintf(f, " d%d,\n", j+1) + } + fmt.Fprint(f, " ),\n") + fmt.Fprintf(f, " eraseProviderFactory%d(\n", i) + for j := 0; j < i; j++ { + fmt.Fprintf(f, " d%d,\n", j+1) + } + fmt.Fprint(f, " f,\n") + fmt.Fprint(f, " ),\n") + fmt.Fprint(f, " )\n") + fmt.Fprintf(f, "}\n") +} + +func generateEraseProviderFactory(f *os.File, i int) { + // non generic version + fmt.Fprintf(f, "\n// eraseProviderFactory%d creates a function that takes a variadic number of untyped arguments and from a function of %d strongly typed arguments and %d dependencies\n", i, i, i) + fmt.Fprintf(f, "func eraseProviderFactory%d[", i) + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", j+1) + } + fmt.Fprintf(f, " any, R any](\n") + for j := 0; j < i; j++ { + fmt.Fprintf(f, " d%d Dependency[T%d],\n", j+1, j+1) + } + fmt.Fprintf(f, " f func(") + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", j+1) + } + fmt.Fprintf(f, ") IOE.IOEither[error, R]) func(params ...any) IOE.IOEither[error, any] {\n") + fmt.Fprintf(f, " ft := eraseTuple(T.Tupled%d(f))\n", i) + for j := 0; j < i; j++ { + fmt.Fprintf(f, " t%d := lookupAt[T%d](%d, d%d)\n", j+1, j+1, j, j+1) + } + fmt.Fprint(f, " return func(params ...any) IOE.IOEither[error, any] {\n") + fmt.Fprintf(f, " return ft(E.SequenceT%d(\n", i) + for j := 0; j < i; j++ { + fmt.Fprintf(f, " t%d(params),\n", j+1) + } + fmt.Fprint(f, " ))\n") + fmt.Fprint(f, " }\n") + fmt.Fprintf(f, "}\n") +} + +func generateDIHelpers(filename string, count int) error { + dir, err := os.Getwd() + if err != nil { + return err + } + absDir, err := filepath.Abs(dir) + if err != nil { + return err + } + pkg := filepath.Base(absDir) + f, err := os.Create(filepath.Clean(filename)) + if err != nil { + return err + } + defer f.Close() + // log + log.Printf("Generating code in [%s] for package [%s] with [%d] repetitions ...", filename, pkg, count) + + // some header + fmt.Fprintln(f, "// Code generated by go generate; DO NOT EDIT.") + fmt.Fprintln(f, "// This file was generated by robots at") + fmt.Fprintf(f, "// %s\n\n", time.Now()) + + fmt.Fprintf(f, "package %s\n\n", pkg) + + fmt.Fprint(f, ` +import ( + E "github.com/IBM/fp-go/v2/either" + IOE "github.com/IBM/fp-go/v2/ioeither" + T "github.com/IBM/fp-go/v2/tuple" + A "github.com/IBM/fp-go/v2/array" + DIE "github.com/IBM/fp-go/v2/di/erasure" +) +`) + + for i := 1; i <= count; i++ { + generateEraseProviderFactory(f, i) + generateMakeProviderFactory(f, i) + generateMakeTokenWithDefault(f, i) + generateMakeProvider(f, i) + } + + return nil +} + +func DICommand() *C.Command { + return &C.Command{ + Name: "di", + Usage: "generate code for the dependency injection package", + Flags: []C.Flag{ + flagCount, + flagFilename, + }, + Action: func(ctx *C.Context) error { + return generateDIHelpers( + ctx.String(keyFilename), + ctx.Int(keyCount), + ) + }, + } +} diff --git a/v2/cli/either.go b/v2/cli/either.go new file mode 100644 index 0000000..80cb41d --- /dev/null +++ b/v2/cli/either.go @@ -0,0 +1,200 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package cli + +import ( + "fmt" + "log" + "os" + "path/filepath" + "time" + + C "github.com/urfave/cli/v2" +) + +func eitherHKT(typeE string) func(typeA string) string { + return func(typeA string) string { + return fmt.Sprintf("Either[%s, %s]", typeE, typeA) + } +} + +func generateEitherTraverseTuple(f *os.File, i int) { + generateTraverseTuple1(eitherHKT("E"), "E")(f, i) +} + +func generateEitherSequenceTuple(f *os.File, i int) { + generateSequenceTuple1(eitherHKT("E"), "E")(f, i) +} + +func generateEitherSequenceT(f *os.File, i int) { + generateSequenceT1(eitherHKT("E"), "E")(f, i) +} + +func generateUneitherize(f *os.File, i int) { + // Create the optionize version + fmt.Fprintf(f, "\n// Uneitherize%d converts a function with %d parameters returning an Either into a function with %d parameters returning a tuple\n// The inverse function is [Eitherize%d]\n", i, i, i, i) + fmt.Fprintf(f, "func Uneitherize%d[F ~func(", i) + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", j) + } + fmt.Fprintf(f, ") Either[error, R]") + for j := 0; j < i; j++ { + fmt.Fprintf(f, ", T%d", j) + } + fmt.Fprintf(f, ", R any](f F) func(") + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", j) + } + fmt.Fprintf(f, ") (R, error) {\n") + fmt.Fprintf(f, " return func(") + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "t%d T%d", j, j) + } + fmt.Fprintf(f, ") (R, error) {\n") + fmt.Fprintf(f, " return UnwrapError(f(") + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "t%d", j) + } + fmt.Fprintln(f, "))") + fmt.Fprintln(f, " }") + fmt.Fprintln(f, "}") +} + +func generateEitherize(f *os.File, i int) { + // Create the optionize version + fmt.Fprintf(f, "\n// Eitherize%d converts a function with %d parameters returning a tuple into a function with %d parameters returning an Either\n// The inverse function is [Uneitherize%d]\n", i, i, i, i) + fmt.Fprintf(f, "func Eitherize%d[F ~func(", i) + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", j) + } + fmt.Fprintf(f, ") (R, error)") + for j := 0; j < i; j++ { + fmt.Fprintf(f, ", T%d", j) + } + fmt.Fprintf(f, ", R any](f F) func(") + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", j) + } + fmt.Fprintf(f, ") Either[error, R] {\n") + fmt.Fprintf(f, " return func(") + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "t%d T%d", j, j) + } + fmt.Fprintf(f, ") Either[error, R] {\n") + fmt.Fprintf(f, " return TryCatchError(f(") + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "t%d", j) + } + fmt.Fprintln(f, "))") + fmt.Fprintln(f, " }") + fmt.Fprintln(f, "}") +} + +func generateEitherHelpers(filename string, count int) error { + dir, err := os.Getwd() + if err != nil { + return err + } + absDir, err := filepath.Abs(dir) + if err != nil { + return err + } + pkg := filepath.Base(absDir) + f, err := os.Create(filepath.Clean(filename)) + if err != nil { + return err + } + defer f.Close() + // log + log.Printf("Generating code in [%s] for package [%s] with [%d] repetitions ...", filename, pkg, count) + + // some header + fmt.Fprintln(f, "// Code generated by go generate; DO NOT EDIT.") + fmt.Fprintln(f, "// This file was generated by robots at") + fmt.Fprintf(f, "// %s\n\n", time.Now()) + + fmt.Fprintf(f, "package %s\n\n", pkg) + + fmt.Fprintf(f, ` +import ( + A "github.com/IBM/fp-go/v2/internal/apply" + T "github.com/IBM/fp-go/v2/tuple" +) +`) + + // zero level functions + + // optionize + generateEitherize(f, 0) + // unoptionize + generateUneitherize(f, 0) + + for i := 1; i <= count; i++ { + // optionize + generateEitherize(f, i) + // unoptionize + generateUneitherize(f, i) + // sequenceT + generateEitherSequenceT(f, i) + // sequenceTuple + generateEitherSequenceTuple(f, i) + // traverseTuple + generateEitherTraverseTuple(f, i) + } + + return nil +} + +func EitherCommand() *C.Command { + return &C.Command{ + Name: "either", + Usage: "generate code for Either", + Flags: []C.Flag{ + flagCount, + flagFilename, + }, + Action: func(ctx *C.Context) error { + return generateEitherHelpers( + ctx.String(keyFilename), + ctx.Int(keyCount), + ) + }, + } +} diff --git a/v2/cli/header.go b/v2/cli/header.go new file mode 100644 index 0000000..4e2ef90 --- /dev/null +++ b/v2/cli/header.go @@ -0,0 +1,31 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package cli + +import ( + "fmt" + "os" + "time" +) + +func writePackage(f *os.File, pkg string) { + // print package + fmt.Fprintf(f, "package %s\n\n", pkg) + // some header + fmt.Fprintln(f, "// Code generated by go generate; DO NOT EDIT.") + fmt.Fprintln(f, "// This file was generated by robots at") + fmt.Fprintf(f, "// %s\n\n", time.Now()) +} diff --git a/v2/cli/identity.go b/v2/cli/identity.go new file mode 100644 index 0000000..378a8af --- /dev/null +++ b/v2/cli/identity.go @@ -0,0 +1,103 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package cli + +import ( + "fmt" + "log" + "os" + "path/filepath" + "time" + + C "github.com/urfave/cli/v2" +) + +func identityHKT(typeA string) string { + return typeA +} + +func generateIdentityTraverseTuple(f *os.File, i int) { + generateTraverseTuple1(identityHKT, "")(f, i) +} + +func generateIdentitySequenceTuple(f *os.File, i int) { + generateSequenceTuple1(identityHKT, "")(f, i) +} + +func generateIdentitySequenceT(f *os.File, i int) { + generateSequenceT1(identityHKT, "")(f, i) +} + +func generateIdentityHelpers(filename string, count int) error { + dir, err := os.Getwd() + if err != nil { + return err + } + absDir, err := filepath.Abs(dir) + if err != nil { + return err + } + pkg := filepath.Base(absDir) + f, err := os.Create(filepath.Clean(filename)) + if err != nil { + return err + } + defer f.Close() + // log + log.Printf("Generating code in [%s] for package [%s] with [%d] repetitions ...", filename, pkg, count) + + // some header + fmt.Fprintln(f, "// Code generated by go generate; DO NOT EDIT.") + fmt.Fprintln(f, "// This file was generated by robots at") + fmt.Fprintf(f, "// %s\n\n", time.Now()) + + fmt.Fprintf(f, "package %s\n\n", pkg) + + fmt.Fprintf(f, ` +import ( + A "github.com/IBM/fp-go/v2/internal/apply" + T "github.com/IBM/fp-go/v2/tuple" +) +`) + + for i := 1; i <= count; i++ { + // sequenceT + generateIdentitySequenceT(f, i) + // sequenceTuple + generateIdentitySequenceTuple(f, i) + // traverseTuple + generateIdentityTraverseTuple(f, i) + } + + return nil +} + +func IdentityCommand() *C.Command { + return &C.Command{ + Name: "identity", + Usage: "generate code for Identity", + Flags: []C.Flag{ + flagCount, + flagFilename, + }, + Action: func(ctx *C.Context) error { + return generateIdentityHelpers( + ctx.String(keyFilename), + ctx.Int(keyCount), + ) + }, + } +} diff --git a/v2/cli/io.go b/v2/cli/io.go new file mode 100644 index 0000000..cd5dd47 --- /dev/null +++ b/v2/cli/io.go @@ -0,0 +1,112 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package cli + +import ( + "fmt" + "log" + "os" + "path/filepath" + "time" + + A "github.com/IBM/fp-go/v2/array" + C "github.com/urfave/cli/v2" +) + +func nonGenericIO(param string) string { + return fmt.Sprintf("IO[%s]", param) +} + +var extrasIO = A.Empty[string]() + +func generateIOSequenceT(f *os.File, i int) { + generateGenericSequenceT("", nonGenericIO, extrasIO)(f, i) + generateGenericSequenceT("Seq", nonGenericIO, extrasIO)(f, i) + generateGenericSequenceT("Par", nonGenericIO, extrasIO)(f, i) +} + +func generateIOSequenceTuple(f *os.File, i int) { + generateGenericSequenceTuple("", nonGenericIO, extrasIO)(f, i) + generateGenericSequenceTuple("Seq", nonGenericIO, extrasIO)(f, i) + generateGenericSequenceTuple("Par", nonGenericIO, extrasIO)(f, i) +} + +func generateIOTraverseTuple(f *os.File, i int) { + generateGenericTraverseTuple("", nonGenericIO, extrasIO)(f, i) + generateGenericTraverseTuple("Seq", nonGenericIO, extrasIO)(f, i) + generateGenericTraverseTuple("Par", nonGenericIO, extrasIO)(f, i) +} + +func generateIOHelpers(filename string, count int) error { + dir, err := os.Getwd() + if err != nil { + return err + } + absDir, err := filepath.Abs(dir) + if err != nil { + return err + } + pkg := filepath.Base(absDir) + f, err := os.Create(filepath.Clean(filename)) + if err != nil { + return err + } + defer f.Close() + // log + log.Printf("Generating code in [%s] for package [%s] with [%d] repetitions ...", filename, pkg, count) + + // some header + fmt.Fprintln(f, "// Code generated by go generate; DO NOT EDIT.") + fmt.Fprintln(f, "// This file was generated by robots at") + fmt.Fprintf(f, "// %s\n\n", time.Now()) + + fmt.Fprintf(f, "package %s\n\n", pkg) + + fmt.Fprintf(f, ` +import ( + "github.com/IBM/fp-go/v2/tuple" + "github.com/IBM/fp-go/v2/internal/apply" +) +`) + + for i := 1; i <= count; i++ { + // sequenceT + generateIOSequenceT(f, i) + // sequenceTuple + generateIOSequenceTuple(f, i) + // traverseTuple + generateIOTraverseTuple(f, i) + } + + return nil +} + +func IOCommand() *C.Command { + return &C.Command{ + Name: "io", + Usage: "generate code for IO", + Flags: []C.Flag{ + flagCount, + flagFilename, + }, + Action: func(ctx *C.Context) error { + return generateIOHelpers( + ctx.String(keyFilename), + ctx.Int(keyCount), + ) + }, + } +} diff --git a/v2/cli/ioeither.go b/v2/cli/ioeither.go new file mode 100644 index 0000000..08e174f --- /dev/null +++ b/v2/cli/ioeither.go @@ -0,0 +1,283 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package cli + +import ( + "fmt" + "log" + "os" + "path/filepath" + "time" + + A "github.com/IBM/fp-go/v2/array" + C "github.com/urfave/cli/v2" +) + +// [GA ~func() ET.Either[E, A], GB ~func() ET.Either[E, B], GTAB ~func() ET.Either[E, T.Tuple2[A, B]], E, A, B any](a GA, b GB) GTAB { + +func nonGenericIOEither(param string) string { + return fmt.Sprintf("IOEither[E, %s]", param) +} + +var extrasIOEither = A.From("E") + +func generateIOEitherSequenceT(f, fg *os.File, i int) { + generateGenericSequenceT("", nonGenericIOEither, extrasIOEither)(f, i) + generateGenericSequenceT("Seq", nonGenericIOEither, extrasIOEither)(f, i) + generateGenericSequenceT("Par", nonGenericIOEither, extrasIOEither)(f, i) +} + +func generateIOEitherSequenceTuple(f, fg *os.File, i int) { + generateGenericSequenceTuple("", nonGenericIOEither, extrasIOEither)(f, i) + generateGenericSequenceTuple("Seq", nonGenericIOEither, extrasIOEither)(f, i) + generateGenericSequenceTuple("Par", nonGenericIOEither, extrasIOEither)(f, i) +} + +func generateIOEitherTraverseTuple(f, fg *os.File, i int) { + generateGenericTraverseTuple("", nonGenericIOEither, extrasIOEither)(f, i) + generateGenericTraverseTuple("Seq", nonGenericIOEither, extrasIOEither)(f, i) + generateGenericTraverseTuple("Par", nonGenericIOEither, extrasIOEither)(f, i) +} + +func generateIOEitherUneitherize(f, fg *os.File, i int) { + // non generic version + fmt.Fprintf(f, "\n// Uneitherize%d converts a function with %d parameters returning a tuple into a function with %d parameters returning a [IOEither[error, R]]\n", i, i+1, i) + fmt.Fprintf(f, "func Uneitherize%d[F ~func(", i) + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", j+1) + } + fmt.Fprintf(f, ") IOEither[error, R]") + for j := 0; j < i; j++ { + fmt.Fprintf(f, ", T%d", j+1) + } + fmt.Fprintf(f, ", R any](f F) func(") + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", j+1) + } + fmt.Fprintf(f, ") (R, error) {\n") + fmt.Fprintf(f, " return G.Uneitherize%d[IOEither[error, R]](f)\n", i) + fmt.Fprintln(f, "}") + + // generic version + fmt.Fprintf(fg, "\n// Uneitherize%d converts a function with %d parameters returning a tuple into a function with %d parameters returning a [GIOA]\n", i, i, i) + fmt.Fprintf(fg, "func Uneitherize%d[GIOA ~func() ET.Either[error, R], GTA ~func(", i) + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(fg, ", ") + } + fmt.Fprintf(fg, "T%d", j+1) + } + fmt.Fprintf(fg, ") GIOA") + for j := 0; j < i; j++ { + fmt.Fprintf(fg, ", T%d", j+1) + } + fmt.Fprintf(fg, ", R any](f GTA) func(") + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(fg, ", ") + } + fmt.Fprintf(fg, "T%d", j+1) + } + fmt.Fprintf(fg, ") (R, error) {\n") + fmt.Fprintf(fg, " return func(") + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(fg, ", ") + } + fmt.Fprintf(fg, "t%d T%d", j+1, j+1) + } + fmt.Fprintf(fg, ") (R, error) {\n") + fmt.Fprintf(fg, " return ET.Unwrap(f(") + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(fg, ", ") + } + fmt.Fprintf(fg, "t%d", j+1) + } + fmt.Fprintf(fg, ")())\n") + fmt.Fprintf(fg, " }\n") + fmt.Fprintf(fg, "}\n") +} + +func generateIOEitherEitherize(f, fg *os.File, i int) { + // non generic version + fmt.Fprintf(f, "\n// Eitherize%d converts a function with %d parameters returning a tuple into a function with %d parameters returning a [IOEither[error, R]]\n", i, i+1, i) + fmt.Fprintf(f, "func Eitherize%d[F ~func(", i) + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", j+1) + } + fmt.Fprintf(f, ") (R, error)") + for j := 0; j < i; j++ { + fmt.Fprintf(f, ", T%d", j+1) + } + fmt.Fprintf(f, ", R any](f F) func(") + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", j+1) + } + fmt.Fprintf(f, ") IOEither[error, R] {\n") + fmt.Fprintf(f, " return G.Eitherize%d[IOEither[error, R]](f)\n", i) + fmt.Fprintln(f, "}") + + // generic version + fmt.Fprintf(fg, "\n// Eitherize%d converts a function with %d parameters returning a tuple into a function with %d parameters returning a [GIOA]\n", i, i, i) + fmt.Fprintf(fg, "func Eitherize%d[GIOA ~func() ET.Either[error, R], F ~func(", i) + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(fg, ", ") + } + fmt.Fprintf(fg, "T%d", j+1) + } + fmt.Fprintf(fg, ") (R, error)") + for j := 0; j < i; j++ { + fmt.Fprintf(fg, ", T%d", j+1) + } + fmt.Fprintf(fg, ", R any](f F) func(") + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(fg, ", ") + } + fmt.Fprintf(fg, "T%d", j+1) + } + fmt.Fprintf(fg, ") GIOA {\n") + fmt.Fprintf(fg, " e := ET.Eitherize%d(f)\n", i) + fmt.Fprintf(fg, " return func(") + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(fg, ", ") + } + fmt.Fprintf(fg, "t%d T%d", j+1, j+1) + } + fmt.Fprintf(fg, ") GIOA {\n") + fmt.Fprintf(fg, " return func() ET.Either[error, R] {\n") + fmt.Fprintf(fg, " return e(") + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(fg, ", ") + } + fmt.Fprintf(fg, "t%d", j+1) + } + fmt.Fprintf(fg, ")\n") + fmt.Fprintf(fg, " }}\n") + fmt.Fprintf(fg, "}\n") +} + +func generateIOEitherHelpers(filename string, count int) error { + dir, err := os.Getwd() + if err != nil { + return err + } + absDir, err := filepath.Abs(dir) + if err != nil { + return err + } + pkg := filepath.Base(absDir) + f, err := os.Create(filepath.Clean(filename)) + if err != nil { + return err + } + defer f.Close() + // construct subdirectory + genFilename := filepath.Join("generic", filename) + err = os.MkdirAll("generic", os.ModePerm) + if err != nil { + return err + } + fg, err := os.Create(filepath.Clean(genFilename)) + if err != nil { + return err + } + defer fg.Close() + + // log + log.Printf("Generating code in [%s] for package [%s] with [%d] repetitions ...", filename, pkg, count) + + // some header + fmt.Fprintln(f, "// Code generated by go generate; DO NOT EDIT.") + fmt.Fprintln(f, "// This file was generated by robots at") + fmt.Fprintf(f, "// %s\n\n", time.Now()) + + fmt.Fprintf(f, "package %s\n\n", pkg) + + fmt.Fprintf(f, ` +import ( + G "github.com/IBM/fp-go/v2/%s/generic" + "github.com/IBM/fp-go/v2/internal/apply" + "github.com/IBM/fp-go/v2/tuple" +) +`, pkg) + + // some header + fmt.Fprintln(fg, "// Code generated by go generate; DO NOT EDIT.") + fmt.Fprintln(fg, "// This file was generated by robots at") + fmt.Fprintf(fg, "// %s\n", time.Now()) + + fmt.Fprintf(fg, "package generic\n\n") + + fmt.Fprintf(fg, ` +import ( + ET "github.com/IBM/fp-go/v2/either" +) +`) + + // eitherize + generateIOEitherEitherize(f, fg, 0) + // uneitherize + generateIOEitherUneitherize(f, fg, 0) + + for i := 1; i <= count; i++ { + // eitherize + generateIOEitherEitherize(f, fg, i) + // uneitherize + generateIOEitherUneitherize(f, fg, i) + // sequenceT + generateIOEitherSequenceT(f, fg, i) + // sequenceTuple + generateIOEitherSequenceTuple(f, fg, i) + // traverseTuple + generateIOEitherTraverseTuple(f, fg, i) + } + + return nil +} + +func IOEitherCommand() *C.Command { + return &C.Command{ + Name: "ioeither", + Usage: "generate code for IOEither", + Flags: []C.Flag{ + flagCount, + flagFilename, + }, + Action: func(ctx *C.Context) error { + return generateIOEitherHelpers( + ctx.String(keyFilename), + ctx.Int(keyCount), + ) + }, + } +} diff --git a/v2/cli/iooption.go b/v2/cli/iooption.go new file mode 100644 index 0000000..a369e9b --- /dev/null +++ b/v2/cli/iooption.go @@ -0,0 +1,117 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package cli + +import ( + "fmt" + "log" + "os" + "path/filepath" + "time" + + A "github.com/IBM/fp-go/v2/array" + C "github.com/urfave/cli/v2" +) + +func nonGenericIOOption(param string) string { + return fmt.Sprintf("IOOption[%s]", param) +} + +func genericIOOption(param string) string { + return fmt.Sprintf("func() O.Option[%s]", param) +} + +var extrasIOOption = A.Empty[string]() + +func generateIOOptionSequenceT(f *os.File, i int) { + generateGenericSequenceT("", nonGenericIOOption, extrasIOOption)(f, i) + generateGenericSequenceT("Seq", nonGenericIOOption, extrasIOOption)(f, i) + generateGenericSequenceT("Par", nonGenericIOOption, extrasIOOption)(f, i) +} + +func generateIOOptionSequenceTuple(f *os.File, i int) { + generateGenericSequenceTuple("", nonGenericIOOption, extrasIOOption)(f, i) + generateGenericSequenceTuple("Seq", nonGenericIOOption, extrasIOOption)(f, i) + generateGenericSequenceTuple("Par", nonGenericIOOption, extrasIOOption)(f, i) +} + +func generateIOOptionTraverseTuple(f *os.File, i int) { + generateGenericTraverseTuple("", nonGenericIOOption, extrasIOOption)(f, i) + generateGenericTraverseTuple("Seq", nonGenericIOOption, extrasIOOption)(f, i) + generateGenericTraverseTuple("Par", nonGenericIOOption, extrasIOOption)(f, i) +} + +func generateIOOptionHelpers(filename string, count int) error { + dir, err := os.Getwd() + if err != nil { + return err + } + absDir, err := filepath.Abs(dir) + if err != nil { + return err + } + pkg := filepath.Base(absDir) + f, err := os.Create(filepath.Clean(filename)) + if err != nil { + return err + } + defer f.Close() + + // log + log.Printf("Generating code in [%s] for package [%s] with [%d] repetitions ...", filename, pkg, count) + + // some header + fmt.Fprintln(f, "// Code generated by go generate; DO NOT EDIT.") + fmt.Fprintln(f, "// This file was generated by robots at") + fmt.Fprintf(f, "// %s\n\n", time.Now()) + + fmt.Fprintf(f, "package %s\n\n", pkg) + + fmt.Fprintf(f, ` +import ( + "github.com/IBM/fp-go/v2/tuple" + "github.com/IBM/fp-go/v2/internal/apply" +) +`) + + for i := 1; i <= count; i++ { + // sequenceT + generateIOOptionSequenceT(f, i) + // sequenceTuple + generateIOOptionSequenceTuple(f, i) + // traverseTuple + generateIOOptionTraverseTuple(f, i) + } + + return nil +} + +func IOOptionCommand() *C.Command { + return &C.Command{ + Name: "iooption", + Usage: "generate code for IOOption", + Flags: []C.Flag{ + flagCount, + flagFilename, + }, + Action: func(ctx *C.Context) error { + return generateIOOptionHelpers( + ctx.String(keyFilename), + ctx.Int(keyCount), + ) + }, + } +} diff --git a/v2/cli/monad.go b/v2/cli/monad.go new file mode 100644 index 0000000..7c320ed --- /dev/null +++ b/v2/cli/monad.go @@ -0,0 +1,376 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package cli + +import ( + "fmt" + "os" + "strings" +) + +// Deprecated: +func tupleType(name string) func(i int) string { + return func(i int) string { + var buf strings.Builder + buf.WriteString(fmt.Sprintf("T.Tuple%d[", i)) + for j := 0; j < i; j++ { + if j > 0 { + buf.WriteString(", ") + } + buf.WriteString(fmt.Sprintf("%s%d", name, j+1)) + } + buf.WriteString("]") + + return buf.String() + } +} + +func tupleTypePlain(name string) func(i int) string { + return func(i int) string { + var buf strings.Builder + buf.WriteString(fmt.Sprintf("tuple.Tuple%d[", i)) + for j := 0; j < i; j++ { + if j > 0 { + buf.WriteString(", ") + } + buf.WriteString(fmt.Sprintf("%s%d", name, j+1)) + } + buf.WriteString("]") + + return buf.String() + } +} + +func monadGenerateSequenceTNonGeneric( + hkt func(string) string, + fmap func(string, string) string, + fap func(string, string) string, +) func(f *os.File, i int) { + return func(f *os.File, i int) { + + tuple := tupleType("T")(i) + + fmt.Fprintf(f, "SequenceT%d[", i) + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", j+1) + } + fmt.Fprintf(f, "](") + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "t%d %s", j+1, hkt(fmt.Sprintf("T%d", j+1))) + } + fmt.Fprintf(f, ") %s {", hkt(tuple)) + // the actual apply callback + fmt.Fprintf(f, " return apply.SequenceT%d(\n", i) + // map callback + + curried := func(count int) string { + var buf strings.Builder + for j := count; j < i; j++ { + buf.WriteString(fmt.Sprintf("func(T%d)", j+1)) + } + buf.WriteString(tuple) + return buf.String() + } + + fmt.Fprintf(f, " %s,\n", fmap("T1", curried(1))) + for j := 1; j < i; j++ { + fmt.Fprintf(f, " %s,\n", fap(curried(j+1), fmt.Sprintf("T%d", j))) + } + + for j := 0; j < i; j++ { + fmt.Fprintf(f, " T%d,\n", j+1) + } + + fmt.Fprintf(f, " )\n") + + fmt.Fprintf(f, "}\n") + + } +} + +func monadGenerateSequenceTGeneric( + hkt func(string) string, + fmap func(string, string) string, + fap func(string, string) string, +) func(f *os.File, i int) { + return func(f *os.File, i int) { + + tuple := tupleType("T")(i) + + fmt.Fprintf(f, "SequenceT%d[", i) + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", j+1) + } + fmt.Fprintf(f, "](") + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "t%d %s", j+1, hkt(fmt.Sprintf("T%d", j+1))) + } + fmt.Fprintf(f, ") %s {", hkt(tuple)) + // the actual apply callback + fmt.Fprintf(f, " return apply.SequenceT%d(\n", i) + // map callback + + curried := func(count int) string { + var buf strings.Builder + for j := count; j < i; j++ { + buf.WriteString(fmt.Sprintf("func(T%d)", j+1)) + } + buf.WriteString(tuple) + return buf.String() + } + + fmt.Fprintf(f, " %s,\n", fmap("T1", curried(1))) + for j := 1; j < i; j++ { + fmt.Fprintf(f, " %s,\n", fap(curried(j+1), fmt.Sprintf("T%d", j))) + } + + for j := 0; j < i; j++ { + fmt.Fprintf(f, " T%d,\n", j+1) + } + + fmt.Fprintf(f, " )\n") + + fmt.Fprintf(f, "}\n") + + } +} + +func generateTraverseTuple1( + hkt func(string) string, + infix string) func(f *os.File, i int) { + + return func(f *os.File, i int) { + tuple := tupleType("T")(i) + + fmt.Fprintf(f, "\n// TraverseTuple%d converts a [Tuple%d] of [A] via transformation functions transforming [A] to [%s] into a [%s].\n", i, i, hkt("A"), hkt(fmt.Sprintf("Tuple%d", i))) + fmt.Fprintf(f, "func TraverseTuple%d[", i) + // functions + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "F%d ~func(A%d) %s", j+1, j+1, hkt(fmt.Sprintf("T%d", j+1))) + } + if infix != "" { + fmt.Fprintf(f, ", %s", infix) + } + // types + for j := 0; j < i; j++ { + fmt.Fprintf(f, ", A%d, T%d", j+1, j+1) + } + fmt.Fprintf(f, " any](") + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "f%d F%d", j+1, j+1) + } + fmt.Fprintf(f, ") func (T.Tuple%d[", i) + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "A%d", j+1) + } + fmt.Fprintf(f, "]) %s {\n", hkt(tuple)) + fmt.Fprintf(f, " return func(t T.Tuple%d[", i) + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "A%d", j+1) + } + fmt.Fprintf(f, "]) %s {\n", hkt(tuple)) + fmt.Fprintf(f, " return A.TraverseTuple%d(\n", i) + // map + fmt.Fprintf(f, " Map[") + if infix != "" { + fmt.Fprintf(f, "%s, T1,", infix) + } else { + fmt.Fprintf(f, "T1,") + } + for j := 1; j < i; j++ { + fmt.Fprintf(f, " func(T%d)", j+1) + } + fmt.Fprintf(f, " %s],\n", tuple) + // applicatives + for j := 1; j < i; j++ { + fmt.Fprintf(f, " Ap[") + for k := j + 1; k < i; k++ { + if k > j+1 { + fmt.Fprintf(f, " ") + } + fmt.Fprintf(f, "func(T%d)", k+1) + } + if j < i-1 { + fmt.Fprintf(f, " ") + } + fmt.Fprintf(f, "%s", tuple) + if infix != "" { + fmt.Fprintf(f, ", %s", infix) + } + fmt.Fprintf(f, ", T%d],\n", j+1) + } + for j := 0; j < i; j++ { + fmt.Fprintf(f, " f%d,\n", j+1) + } + fmt.Fprintf(f, " t,\n") + fmt.Fprintf(f, " )\n") + fmt.Fprintf(f, " }\n") + fmt.Fprintf(f, "}\n") + } +} + +func generateSequenceTuple1( + hkt func(string) string, + infix string) func(f *os.File, i int) { + + return func(f *os.File, i int) { + + tuple := tupleType("T")(i) + + fmt.Fprintf(f, "\n// SequenceTuple%d converts a [Tuple%d] of [%s] into an [%s].\n", i, i, hkt("T"), hkt(fmt.Sprintf("Tuple%d", i))) + fmt.Fprintf(f, "func SequenceTuple%d[", i) + if infix != "" { + fmt.Fprintf(f, "%s", infix) + } + for j := 0; j < i; j++ { + if infix != "" || j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", j+1) + } + fmt.Fprintf(f, " any](t T.Tuple%d[", i) + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "%s", hkt(fmt.Sprintf("T%d", j+1))) + } + fmt.Fprintf(f, "]) %s {\n", hkt(tuple)) + fmt.Fprintf(f, " return A.SequenceTuple%d(\n", i) + // map + fmt.Fprintf(f, " Map[") + if infix != "" { + fmt.Fprintf(f, "%s, T1,", infix) + } else { + fmt.Fprintf(f, "T1,") + } + for j := 1; j < i; j++ { + fmt.Fprintf(f, " func(T%d)", j+1) + } + fmt.Fprintf(f, " %s],\n", tuple) + // applicatives + for j := 1; j < i; j++ { + fmt.Fprintf(f, " Ap[") + for k := j + 1; k < i; k++ { + if k > j+1 { + fmt.Fprintf(f, " ") + } + fmt.Fprintf(f, "func(T%d)", k+1) + } + if j < i-1 { + fmt.Fprintf(f, " ") + } + fmt.Fprintf(f, "%s", tuple) + if infix != "" { + fmt.Fprintf(f, ", %s", infix) + } + fmt.Fprintf(f, ", T%d],\n", j+1) + } + fmt.Fprintf(f, " t,\n") + fmt.Fprintf(f, " )\n") + fmt.Fprintf(f, "}\n") + } +} + +func generateSequenceT1( + hkt func(string) string, + infix string) func(f *os.File, i int) { + + return func(f *os.File, i int) { + + tuple := tupleType("T")(i) + + fmt.Fprintf(f, "\n// SequenceT%d converts %d parameters of [%s] into a [%s].\n", i, i, hkt("T"), hkt(fmt.Sprintf("Tuple%d", i))) + fmt.Fprintf(f, "func SequenceT%d[", i) + if infix != "" { + fmt.Fprintf(f, "%s", infix) + } + for j := 0; j < i; j++ { + if infix != "" || j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", j+1) + } + fmt.Fprintf(f, " any](") + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "t%d %s", j+1, hkt(fmt.Sprintf("T%d", j+1))) + } + fmt.Fprintf(f, ") %s {\n", hkt(tuple)) + fmt.Fprintf(f, " return A.SequenceT%d(\n", i) + // map + fmt.Fprintf(f, " Map[") + if infix != "" { + fmt.Fprintf(f, "%s, T1,", infix) + } else { + fmt.Fprintf(f, "T1,") + } + for j := 1; j < i; j++ { + fmt.Fprintf(f, " func(T%d)", j+1) + } + fmt.Fprintf(f, " %s],\n", tuple) + // applicatives + for j := 1; j < i; j++ { + fmt.Fprintf(f, " Ap[") + for k := j + 1; k < i; k++ { + if k > j+1 { + fmt.Fprintf(f, " ") + } + fmt.Fprintf(f, "func(T%d)", k+1) + } + if j < i-1 { + fmt.Fprintf(f, " ") + } + fmt.Fprintf(f, "%s", tuple) + if infix != "" { + fmt.Fprintf(f, ", %s", infix) + } + fmt.Fprintf(f, ", T%d],\n", j+1) + } + for j := 0; j < i; j++ { + fmt.Fprintf(f, " t%d,\n", j+1) + } + fmt.Fprintf(f, " )\n") + fmt.Fprintf(f, "}\n") + } + +} diff --git a/v2/cli/monad2.go b/v2/cli/monad2.go new file mode 100644 index 0000000..740c248 --- /dev/null +++ b/v2/cli/monad2.go @@ -0,0 +1,413 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package cli + +import ( + "fmt" + "os" + + A "github.com/IBM/fp-go/v2/array" + F "github.com/IBM/fp-go/v2/function" + N "github.com/IBM/fp-go/v2/number" + S "github.com/IBM/fp-go/v2/string" +) + +var ( + concStrgs = A.Monoid[string]().Concat + intercalStrgs = A.Intercalate(S.Monoid) + concAllStrgs = A.ConcatAll(A.Monoid[string]()) +) + +func joinAll(middle string) func(all ...[]string) string { + ic := intercalStrgs(middle) + return func(all ...[]string) string { + return ic(concAllStrgs(all)) + } +} + +// Deprecated: +func deprecatedGenerateGenericSequenceT( + nonGenericType func(string) string, + genericType func(string) string, + extra []string, +) func(f, fg *os.File, i int) { + return func(f, fg *os.File, i int) { + // tuple + tuple := tupleType("T")(i) + // all types T + typesT := A.MakeBy(i, F.Flow2( + N.Inc[int], + S.Format[int]("T%d"), + )) + // non generic version + fmt.Fprintf(f, "\n// SequenceT%d converts %d [%s] into a [%s]\n", i, i, nonGenericType("T"), nonGenericType(tuple)) + fmt.Fprintf(f, "func SequenceT%d[%s any](\n", i, joinAll(", ")(extra, typesT)) + for j := 0; j < i; j++ { + fmt.Fprintf(f, " t%d %s,\n", j+1, nonGenericType(fmt.Sprintf("T%d", j+1))) + } + fmt.Fprintf(f, ") %s {\n", nonGenericType(tuple)) + fmt.Fprintf(f, " return G.SequenceT%d[\n", i) + fmt.Fprintf(f, " %s,\n", nonGenericType(tuple)) + for j := 0; j < i; j++ { + fmt.Fprintf(f, " %s,\n", nonGenericType(fmt.Sprintf("T%d", j+1))) + } + fmt.Fprintf(f, " ](") + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "t%d", j+1) + } + fmt.Fprintf(f, ")\n") + fmt.Fprintf(f, "}\n") + + // generic version + fmt.Fprintf(fg, "\n// SequenceT%d converts %d [%s] into a [%s]\n", i, i, genericType("T"), genericType(tuple)) + fmt.Fprintf(fg, "func SequenceT%d[\n", i) + fmt.Fprintf(fg, " G_TUPLE%d ~%s,\n", i, genericType(tuple)) + for j := 0; j < i; j++ { + fmt.Fprintf(fg, " G_T%d ~%s, \n", j+1, genericType(fmt.Sprintf("T%d", j+1))) + } + fmt.Fprintf(fg, " %s any](\n", joinAll(", ")(extra, typesT)) + for j := 0; j < i; j++ { + fmt.Fprintf(fg, " t%d G_T%d,\n", j+1, j+1) + } + fmt.Fprintf(fg, ") G_TUPLE%d {\n", i) + fmt.Fprintf(fg, " return A.SequenceT%d(\n", i) + // map call + var cio string + cb := generateNestedCallbacks(1, i) + if i > 1 { + cio = genericType(cb) + } else { + cio = fmt.Sprintf("G_TUPLE%d", i) + } + fmt.Fprintf(fg, " Map[%s],\n", joinAll(", ")(A.From("G_T1", cio), extra, A.From("T1", cb))) + // the apply calls + for j := 1; j < i; j++ { + if j < i-1 { + cb := generateNestedCallbacks(j+1, i) + cio = genericType(cb) + } else { + cio = fmt.Sprintf("G_TUPLE%d", i) + } + fmt.Fprintf(fg, " Ap[%s, %s, G_T%d],\n", cio, genericType(generateNestedCallbacks(j, i)), j+1) + } + // function parameters + for j := 0; j < i; j++ { + fmt.Fprintf(fg, " t%d,\n", j+1) + } + + fmt.Fprintf(fg, " )\n") + fmt.Fprintf(fg, "}\n") + } +} + +// Deprecated: +func deprecatedGenerateGenericSequenceTuple( + nonGenericType func(string) string, + genericType func(string) string, + extra []string, +) func(f, fg *os.File, i int) { + return func(f, fg *os.File, i int) { + // tuple + tuple := tupleType("T")(i) + // all types T + typesT := A.MakeBy(i, F.Flow2( + N.Inc[int], + S.Format[int]("T%d"), + )) + // non generic version + fmt.Fprintf(f, "\n// SequenceTuple%d converts a [T.Tuple%d[%s]] into a [%s]\n", i, i, nonGenericType("T"), nonGenericType(tuple)) + fmt.Fprintf(f, "func SequenceTuple%d[%s any](t T.Tuple%d[", i, joinAll(", ")(extra, typesT), i) + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "%s", nonGenericType(fmt.Sprintf("T%d", j+1))) + } + fmt.Fprintf(f, "]) %s {\n", nonGenericType(tuple)) + fmt.Fprintf(f, " return G.SequenceTuple%d[\n", i) + fmt.Fprintf(f, " %s,\n", nonGenericType(tuple)) + for j := 0; j < i; j++ { + fmt.Fprintf(f, " %s,\n", nonGenericType(fmt.Sprintf("T%d", j+1))) + } + fmt.Fprintf(f, " ](t)\n") + fmt.Fprintf(f, "}\n") + + // generic version + fmt.Fprintf(fg, "\n// SequenceTuple%d converts a [T.Tuple%d[%s]] into a [%s]\n", i, i, genericType("T"), genericType(tuple)) + fmt.Fprintf(fg, "func SequenceTuple%d[\n", i) + fmt.Fprintf(fg, " G_TUPLE%d ~%s,\n", i, genericType(tuple)) + for j := 0; j < i; j++ { + fmt.Fprintf(fg, " G_T%d ~%s, \n", j+1, genericType(fmt.Sprintf("T%d", j+1))) + } + fmt.Fprintf(fg, " %s any](t T.Tuple%d[", joinAll(", ")(extra, typesT), i) + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(fg, ", ") + } + fmt.Fprintf(fg, "G_T%d", j+1) + } + fmt.Fprintf(fg, "]) G_TUPLE%d {\n", i) + fmt.Fprintf(fg, " return A.SequenceTuple%d(\n", i) + // map call + var cio string + cb := generateNestedCallbacks(1, i) + if i > 1 { + cio = genericType(cb) + } else { + cio = fmt.Sprintf("G_TUPLE%d", i) + } + fmt.Fprintf(fg, " Map[%s],\n", joinAll(", ")(A.From("G_T1", cio), extra, A.From("T1", cb))) + // the apply calls + for j := 1; j < i; j++ { + if j < i-1 { + cb := generateNestedCallbacks(j+1, i) + cio = genericType(cb) + } else { + cio = fmt.Sprintf("G_TUPLE%d", i) + } + fmt.Fprintf(fg, " Ap[%s, %s, G_T%d],\n", cio, genericType(generateNestedCallbacks(j, i)), j+1) + } + // function parameters + fmt.Fprintf(fg, " t)\n") + fmt.Fprintf(fg, "}\n") + } +} + +// Deprecated: +func deprecatedGenerateGenericTraverseTuple( + nonGenericType func(string) string, + genericType func(string) string, + extra []string, +) func(f, fg *os.File, i int) { + return func(f, fg *os.File, i int) { + // tuple + tupleT := tupleType("T")(i) + tupleA := tupleType("A")(i) + // all types T + typesT := A.MakeBy(i, F.Flow2( + N.Inc[int], + S.Format[int]("T%d"), + )) + // all types A + typesA := A.MakeBy(i, F.Flow2( + N.Inc[int], + S.Format[int]("A%d"), + )) + // all function types + typesF := A.MakeBy(i, F.Flow2( + N.Inc[int], + func(j int) string { + return fmt.Sprintf("F%d ~func(A%d) %s", j, j, nonGenericType(fmt.Sprintf("T%d", j))) + }, + )) + // non generic version + fmt.Fprintf(f, "\n// TraverseTuple%d converts a [T.Tuple%d[%s]] into a [%s]\n", i, i, nonGenericType("T"), nonGenericType(tupleT)) + fmt.Fprintf(f, "func TraverseTuple%d[%s any](", i, joinAll(", ")(typesF, extra, typesA, typesT)) + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "f%d F%d", j+1, j+1) + } + fmt.Fprintf(f, ") func(%s) %s {\n", tupleA, nonGenericType(tupleT)) + fmt.Fprintf(f, " return G.TraverseTuple%d[%s](", i, nonGenericType(tupleT)) + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "f%d", j+1) + } + fmt.Fprintf(f, ")\n") + fmt.Fprintf(f, "}\n") + + // generic version + fmt.Fprintf(fg, "\n// TraverseTuple%d converts a [T.Tuple%d[%s]] into a [%s]\n", i, i, genericType("T"), genericType(tupleT)) + fmt.Fprintf(fg, "func TraverseTuple%d[\n", i) + fmt.Fprintf(fg, " G_TUPLE%d ~%s,\n", i, genericType(tupleT)) + for j := 0; j < i; j++ { + fmt.Fprintf(fg, " F%d ~func(A%d) G_T%d,\n", j+1, j+1, j+1) + } + for j := 0; j < i; j++ { + fmt.Fprintf(fg, " G_T%d ~%s, \n", j+1, genericType(fmt.Sprintf("T%d", j+1))) + } + fmt.Fprintf(fg, " %s any](", joinAll(", ")(extra, typesA, typesT)) + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(fg, ", ") + } + fmt.Fprintf(fg, "f%d F%d", j+1, j+1) + } + fmt.Fprintf(fg, ") func(%s) G_TUPLE%d {\n", tupleA, i) + fmt.Fprintf(fg, " return func(t %s) G_TUPLE%d {\n", tupleA, i) + fmt.Fprintf(fg, " return A.TraverseTuple%d(\n", i) + // map call + var cio string + cb := generateNestedCallbacks(1, i) + if i > 1 { + cio = genericType(cb) + } else { + cio = fmt.Sprintf("G_TUPLE%d", i) + } + fmt.Fprintf(fg, " Map[%s],\n", joinAll(", ")(A.From("G_T1", cio), extra, A.From("T1", cb))) + // the apply calls + for j := 1; j < i; j++ { + if j < i-1 { + cb := generateNestedCallbacks(j+1, i) + cio = genericType(cb) + } else { + cio = fmt.Sprintf("G_TUPLE%d", i) + } + fmt.Fprintf(fg, " Ap[%s, %s, G_T%d],\n", cio, genericType(generateNestedCallbacks(j, i)), j+1) + } + // function parameters + for j := 0; j < i; j++ { + fmt.Fprintf(fg, " f%d,\n", j+1) + } + // tuple parameter + fmt.Fprintf(fg, " t)\n") + fmt.Fprintf(fg, " }\n") + fmt.Fprintf(fg, "}\n") + } +} + +func generateGenericSequenceT( + suffix string, + nonGenericType func(string) string, + extra []string, +) func(f *os.File, i int) { + return func(f *os.File, i int) { + // tuple + tuple := tupleTypePlain("T")(i) + // all types T + typesT := A.MakeBy(i, F.Flow2( + N.Inc[int], + S.Format[int]("T%d"), + )) + // non generic version + fmt.Fprintf(f, "\n// Sequence%sT%d converts %d [%s] into a [%s]\n", suffix, i, i, nonGenericType("T"), nonGenericType(tuple)) + fmt.Fprintf(f, "func Sequence%sT%d[%s any](\n", suffix, i, joinAll(", ")(extra, typesT)) + for j := 0; j < i; j++ { + fmt.Fprintf(f, " t%d %s,\n", j+1, nonGenericType(fmt.Sprintf("T%d", j+1))) + } + fmt.Fprintf(f, ") %s {\n", nonGenericType(tuple)) + fmt.Fprintf(f, " return apply.SequenceT%d(\n", i) + fmt.Fprintf(f, " Map[%s],\n", joinAll(", ")(extra, A.From("T1", generateNestedCallbacksPlain(1, i)))) + + // the apply calls + for j := 2; j <= i; j++ { + fmt.Fprintf(f, " Ap%s[%s],\n", suffix, joinAll(", ")(A.Of(generateNestedCallbacksPlain(j, i)), extra, A.Of(fmt.Sprintf("T%d", j)))) + } + // function parameters + for j := 1; j <= i; j++ { + fmt.Fprintf(f, " t%d,\n", j) + } + + fmt.Fprintf(f, " )\n") + fmt.Fprintf(f, "}\n") + } +} + +func generateGenericSequenceTuple( + suffix string, + nonGenericType func(string) string, + extra []string, +) func(f *os.File, i int) { + return func(f *os.File, i int) { + // tuple + tuple := tupleTypePlain("T")(i) + // all types T + typesT := A.MakeBy(i, F.Flow2( + N.Inc[int], + S.Format[int]("T%d"), + )) + // non generic version + fmt.Fprintf(f, "\n// Sequence%sTuple%d converts a [tuple.Tuple%d[%s]] into a [%s]\n", suffix, i, i, nonGenericType("T"), nonGenericType(tuple)) + fmt.Fprintf(f, "func Sequence%sTuple%d[%s any](t tuple.Tuple%d[", suffix, i, joinAll(", ")(extra, typesT), i) + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "%s", nonGenericType(fmt.Sprintf("T%d", j+1))) + } + fmt.Fprintf(f, "]) %s {\n", nonGenericType(tuple)) + fmt.Fprintf(f, " return apply.SequenceTuple%d(\n", i) + fmt.Fprintf(f, " Map[%s],\n", joinAll(", ")(extra, A.From("T1", generateNestedCallbacksPlain(1, i)))) + + // the apply calls + for j := 2; j <= i; j++ { + fmt.Fprintf(f, " Ap%s[%s],\n", suffix, joinAll(", ")(A.Of(generateNestedCallbacksPlain(j, i)), extra, A.Of(fmt.Sprintf("T%d", j)))) + } + + // function parameters + fmt.Fprintf(f, " t,\n") + // function parameters + fmt.Fprintf(f, " )\n") + fmt.Fprintf(f, "}\n") + } +} + +func generateGenericTraverseTuple( + suffix string, + nonGenericType func(string) string, + extra []string, +) func(f *os.File, i int) { + return func(f *os.File, i int) { + // all types T + typesT := A.MakeBy(i, F.Flow2( + N.Inc[int], + S.Format[int]("T%d"), + )) + // all types A + typesA := A.MakeBy(i, F.Flow2( + N.Inc[int], + S.Format[int]("A%d"), + )) + // function types + typesF := A.MakeBy(i, func(j int) string { + return fmt.Sprintf("F%d ~func(A%d) %s", j+1, j+1, nonGenericType(fmt.Sprintf("T%d", j+1))) + }) + paramF := A.MakeBy(i, func(j int) string { + return fmt.Sprintf("f%d F%d", j+1, j+1) + }) + // return type + paramType := fmt.Sprintf("tuple.Tuple%d[%s]", i, joinAll(", ")(typesA)) + returnType := nonGenericType(fmt.Sprintf("tuple.Tuple%d[%s]", i, joinAll(", ")(typesT))) + // non generic version + fmt.Fprintf(f, "\n// Traverse%sTuple%d converts a [%s] into a [%s]\n", suffix, i, paramType, returnType) + fmt.Fprintf(f, "func Traverse%sTuple%d[%s any](%s) func(%s) %s {\n", suffix, i, joinAll(", ")(extra, typesF, typesT, typesA), joinAll(", ")(paramF), paramType, returnType) + fmt.Fprintf(f, " return func(t %s) %s {\n", paramType, returnType) + fmt.Fprintf(f, " return apply.TraverseTuple%d(\n", i) + fmt.Fprintf(f, " Map[%s],\n", joinAll(", ")(extra, A.From("T1", generateNestedCallbacksPlain(1, i)))) + + // the apply calls + for j := 2; j <= i; j++ { + fmt.Fprintf(f, " Ap%s[%s],\n", suffix, joinAll(", ")(A.Of(generateNestedCallbacksPlain(j, i)), extra, A.Of(fmt.Sprintf("T%d", j)))) + } + // the function parameters + for j := 1; j <= i; j++ { + fmt.Fprintf(f, " f%d,\n", j) + } + // function parameters + fmt.Fprintf(f, " t,\n") + // function parameters + fmt.Fprintf(f, " )\n") + fmt.Fprintf(f, " }\n") + fmt.Fprintf(f, "}\n") + } +} diff --git a/v2/cli/option.go b/v2/cli/option.go new file mode 100644 index 0000000..37cd574 --- /dev/null +++ b/v2/cli/option.go @@ -0,0 +1,210 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package cli + +import ( + "fmt" + "log" + "os" + "path/filepath" + "time" + + C "github.com/urfave/cli/v2" +) + +func optionHKT(typeA string) string { + return fmt.Sprintf("Option[%s]", typeA) +} + +func generateOptionTraverseTuple(f *os.File, i int) { + generateTraverseTuple1(optionHKT, "")(f, i) +} + +func generateOptionSequenceTuple(f *os.File, i int) { + generateSequenceTuple1(optionHKT, "")(f, i) +} + +func generateOptionSequenceT(f *os.File, i int) { + generateSequenceT1(optionHKT, "")(f, i) +} + +func generateOptionize(f *os.File, i int) { + // Create the optionize version + fmt.Fprintf(f, "\n// Optionize%d converts a function with %d parameters returning a tuple of a return value R and a boolean into a function with %d parameters returning an Option[R]\n", i, i, i) + fmt.Fprintf(f, "func Optionize%d[F ~func(", i) + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", j) + } + fmt.Fprintf(f, ") (R, bool)") + for j := 0; j < i; j++ { + fmt.Fprintf(f, ", T%d", j) + } + fmt.Fprintf(f, ", R any](f F) func(") + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", j) + } + fmt.Fprintf(f, ") Option[R] {\n") + fmt.Fprintf(f, " return func(") + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "t%d T%d", j, j) + } + fmt.Fprintf(f, ") Option[R] {\n") + fmt.Fprintf(f, " return optionize(func() (R, bool) {\n") + fmt.Fprintf(f, " return f(") + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "t%d", j) + } + fmt.Fprintln(f, ")") + fmt.Fprintln(f, " })") + fmt.Fprintln(f, " }") + fmt.Fprintln(f, "}") +} + +func generateUnoptionize(f *os.File, i int) { + // Create the optionize version + fmt.Fprintf(f, "\n// Unoptionize%d converts a function with %d parameters returning a tuple of a return value R and a boolean into a function with %d parameters returning an Option[R]\n", i, i, i) + fmt.Fprintf(f, "func Unoptionize%d[F ~func(", i) + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", j) + } + fmt.Fprintf(f, ") Option[R]") + for j := 0; j < i; j++ { + fmt.Fprintf(f, ", T%d", j) + } + fmt.Fprintf(f, ", R any](f F) func(") + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", j) + } + fmt.Fprintf(f, ") (R, bool) {\n") + fmt.Fprintf(f, " return func(") + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "t%d T%d", j, j) + } + fmt.Fprintf(f, ") (R, bool) {\n") + fmt.Fprintf(f, " return Unwrap(f(") + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "t%d", j) + } + fmt.Fprintln(f, "))") + fmt.Fprintln(f, " }") + fmt.Fprintln(f, "}") +} + +func generateOptionHelpers(filename string, count int) error { + dir, err := os.Getwd() + if err != nil { + return err + } + absDir, err := filepath.Abs(dir) + if err != nil { + return err + } + pkg := filepath.Base(absDir) + f, err := os.Create(filepath.Clean(filename)) + if err != nil { + return err + } + defer f.Close() + // log + log.Printf("Generating code in [%s] for package [%s] with [%d] repetitions ...", filename, pkg, count) + + // some header + fmt.Fprintln(f, "// Code generated by go generate; DO NOT EDIT.") + fmt.Fprintln(f, "// This file was generated by robots at") + fmt.Fprintf(f, "// %s\n\n", time.Now()) + + fmt.Fprintf(f, "package %s\n\n", pkg) + + fmt.Fprintf(f, ` +import ( + A "github.com/IBM/fp-go/v2/internal/apply" + T "github.com/IBM/fp-go/v2/tuple" +) +`) + + // print out some helpers + fmt.Fprintf(f, `// optionize converts a nullary function to an option +func optionize[R any](f func() (R, bool)) Option[R] { + if r, ok := f(); ok { + return Some(r) + } + return None[R]() +} +`) + + // zero level functions + + // optionize + generateOptionize(f, 0) + // unoptionize + generateUnoptionize(f, 0) + + for i := 1; i <= count; i++ { + // optionize + generateOptionize(f, i) + // unoptionize + generateUnoptionize(f, i) + // sequenceT + generateOptionSequenceT(f, i) + // sequenceTuple + generateOptionSequenceTuple(f, i) + // traverseTuple + generateOptionTraverseTuple(f, i) + } + + return nil +} + +func OptionCommand() *C.Command { + return &C.Command{ + Name: "option", + Usage: "generate code for Option", + Flags: []C.Flag{ + flagCount, + flagFilename, + }, + Action: func(ctx *C.Context) error { + return generateOptionHelpers( + ctx.String(keyFilename), + ctx.Int(keyCount), + ) + }, + } +} diff --git a/v2/cli/pipe.go b/v2/cli/pipe.go new file mode 100644 index 0000000..6a9db9f --- /dev/null +++ b/v2/cli/pipe.go @@ -0,0 +1,433 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package cli + +import ( + "fmt" + "log" + "os" + "path/filepath" + "time" + + C "github.com/urfave/cli/v2" +) + +func generateUnsliced(f *os.File, i int) { + // Create the optionize version + fmt.Fprintf(f, "\n// Unsliced%d converts a function taking a slice parameter into a function with %d parameters\n", i, i) + fmt.Fprintf(f, "func Unsliced%d[F ~func([]T) R, T, R any](f F) func(", i) + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T") + } + fmt.Fprintf(f, ") R {\n") + fmt.Fprintf(f, " return func(") + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "t%d", j+1) + } + if i > 0 { + fmt.Fprintf(f, " T") + } + fmt.Fprintf(f, ") R {\n") + fmt.Fprintf(f, " return f([]T{") + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "t%d", j+1) + } + fmt.Fprintln(f, "})") + fmt.Fprintln(f, " }") + fmt.Fprintln(f, "}") +} + +func generateVariadic(f *os.File, i int) { + // Create the nullary version + fmt.Fprintf(f, "\n// Variadic%d converts a function taking %d parameters and a final slice into a function with %d parameters but a final variadic argument\n", i, i, i) + fmt.Fprintf(f, "func Variadic%d[", i) + for j := 1; j <= i; j++ { + if j > 1 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", j) + } + if i > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "V, R any](f func(") + for j := 1; j <= i; j++ { + if j > 1 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", j) + } + if i > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "[]V) R) func(") + for j := 1; j <= i; j++ { + if j > 1 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", j) + } + if i > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "...V) R {\n") + fmt.Fprintf(f, " return func(") + for j := 1; j <= i; j++ { + if j > 1 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "t%d T%d", j, j) + } + if i > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "v ...V) R {\n") + fmt.Fprintf(f, " return f(") + for j := 1; j <= i; j++ { + if j > 1 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "t%d", j) + } + if i > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "v)\n") + fmt.Fprintf(f, " }\n") + + fmt.Fprintf(f, "}\n") +} + +func generateUnvariadic(f *os.File, i int) { + // Create the nullary version + fmt.Fprintf(f, "\n// Unvariadic%d converts a function taking %d parameters and a final variadic argument into a function with %d parameters but a final slice argument\n", i, i, i) + fmt.Fprintf(f, "func Unvariadic%d[", i) + for j := 1; j <= i; j++ { + if j > 1 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", j) + } + if i > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "V, R any](f func(") + for j := 1; j <= i; j++ { + if j > 1 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", j) + } + if i > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "...V) R) func(") + for j := 1; j <= i; j++ { + if j > 1 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", j) + } + if i > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "[]V) R {\n") + fmt.Fprintf(f, " return func(") + for j := 1; j <= i; j++ { + if j > 1 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "t%d T%d", j, j) + } + if i > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "v []V) R {\n") + fmt.Fprintf(f, " return f(") + for j := 1; j <= i; j++ { + if j > 1 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "t%d", j) + } + if i > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "v...)\n") + fmt.Fprintf(f, " }\n") + + fmt.Fprintf(f, "}\n") +} + +func generateNullary(f *os.File, i int) { + // Create the nullary version + fmt.Fprintf(f, "\n// Nullary%d creates a parameter less function from a parameter less function and %d functions. When executed the first parameter less function gets executed and then the result is piped through the remaining functions\n", i, i-1) + fmt.Fprintf(f, "func Nullary%d[F1 ~func() T1", i) + for j := 2; j <= i; j++ { + fmt.Fprintf(f, ", F%d ~func(T%d) T%d", j, j-1, j) + } + for j := 1; j <= i; j++ { + fmt.Fprintf(f, ", T%d", j) + } + fmt.Fprintf(f, " any](f1 F1") + for j := 2; j <= i; j++ { + fmt.Fprintf(f, ", f%d F%d", j, j) + } + fmt.Fprintf(f, ") func() T%d {\n", i) + fmt.Fprintf(f, " return func() T%d {\n", i) + fmt.Fprintf(f, " return Pipe%d(f1()", i-1) + for j := 2; j <= i; j++ { + fmt.Fprintf(f, ", f%d", j) + } + fmt.Fprintf(f, ")\n") + fmt.Fprintln(f, " }") + + fmt.Fprintln(f, "}") +} + +func generateFlow(f *os.File, i int) { + // Create the flow version + fmt.Fprintf(f, "\n// Flow%d creates a function that takes an initial value t0 and successively applies %d functions where the input of a function is the return value of the previous function\n// The final return value is the result of the last function application\n", i, i) + fmt.Fprintf(f, "func Flow%d[", i) + for j := 1; j <= i; j++ { + if j > 1 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "F%d ~func(T%d) T%d", j, j-1, j) + } + for j := 0; j <= i; j++ { + fmt.Fprintf(f, ", T%d", j) + } + fmt.Fprintf(f, " any](") + for j := 1; j <= i; j++ { + if j > 1 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "f%d F%d", j, j) + } + fmt.Fprintf(f, ") func(T0) T%d {\n", i) + fmt.Fprintf(f, " return func(t0 T0) T%d {\n", i) + fmt.Fprintf(f, " return Pipe%d(t0", i) + for j := 1; j <= i; j++ { + fmt.Fprintf(f, ", f%d", j) + } + fmt.Fprintf(f, ")\n") + fmt.Fprintln(f, " }") + + fmt.Fprintln(f, "}") + +} + +func generatePipe(f *os.File, i int) { + // Create the pipe version + fmt.Fprintf(f, "\n// Pipe%d takes an initial value t0 and successively applies %d functions where the input of a function is the return value of the previous function\n// The final return value is the result of the last function application\n", i, i) + fmt.Fprintf(f, "func Pipe%d[", i) + for j := 1; j <= i; j++ { + if j > 1 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "F%d ~func(T%d) T%d", j, j-1, j) + } + if i > 0 { + fmt.Fprintf(f, ", ") + } + for j := 0; j <= i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", j) + } + + fmt.Fprintf(f, " any](t0 T0") + for j := 1; j <= i; j++ { + fmt.Fprintf(f, ", f%d F%d", j, j) + } + fmt.Fprintf(f, ") T%d {\n", i) + fmt.Fprintf(f, " return ") + for j := i; j >= 1; j-- { + fmt.Fprintf(f, "f%d(", j) + } + fmt.Fprintf(f, "t0") + for j := 1; j <= i; j++ { + fmt.Fprintf(f, ")") + } + fmt.Fprintf(f, "\n") + fmt.Fprintln(f, "}") +} + +func recurseCurry(f *os.File, indent string, total, count int) { + if count == 1 { + fmt.Fprintf(f, "%sreturn func(t%d T%d) T%d {\n", indent, total-1, total-1, total) + fmt.Fprintf(f, "%s return f(t0", indent) + for i := 1; i < total; i++ { + fmt.Fprintf(f, ", t%d", i) + } + fmt.Fprintf(f, ")\n") + fmt.Fprintf(f, "%s}\n", indent) + } else { + fmt.Fprintf(f, "%sreturn", indent) + for i := total - count + 1; i <= total; i++ { + fmt.Fprintf(f, " func(t%d T%d)", i-1, i-1) + } + fmt.Fprintf(f, " T%d {\n", total) + recurseCurry(f, fmt.Sprintf(" %s", indent), total, count-1) + fmt.Fprintf(f, "%s}\n", indent) + } +} + +func generateCurry(f *os.File, i int) { + // Create the curry version + fmt.Fprintf(f, "\n// Curry%d takes a function with %d parameters and returns a cascade of functions each taking only one parameter.\n// The inverse function is [Uncurry%d]\n", i, i, i) + fmt.Fprintf(f, "func Curry%d[FCT ~func(T0", i) + for j := 1; j < i; j++ { + fmt.Fprintf(f, ", T%d", j) + } + fmt.Fprintf(f, ") T%d", i) + // type arguments + for j := 0; j <= i; j++ { + fmt.Fprintf(f, ", T%d", j) + } + fmt.Fprintf(f, " any](f FCT) func(T0)") + for j := 2; j <= i; j++ { + fmt.Fprintf(f, " func(T%d)", j-1) + } + fmt.Fprintf(f, " T%d {\n", i) + recurseCurry(f, " ", i, i) + fmt.Fprintf(f, "}\n") +} + +func generateUncurry(f *os.File, i int) { + // Create the uncurry version + fmt.Fprintf(f, "\n// Uncurry%d takes a cascade of %d functions each taking only one parameter and returns a function with %d parameters .\n// The inverse function is [Curry%d]\n", i, i, i, i) + fmt.Fprintf(f, "func Uncurry%d[FCT ~func(T0)", i) + for j := 1; j < i; j++ { + fmt.Fprintf(f, " func(T%d)", j) + } + fmt.Fprintf(f, " T%d", i) + // the type parameters + for j := 0; j <= i; j++ { + fmt.Fprintf(f, ", T%d", j) + } + fmt.Fprintf(f, " any](f FCT) func(") + for j := 1; j <= i; j++ { + if j > 1 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", j-1) + } + fmt.Fprintf(f, ") T%d {\n", i) + fmt.Fprintf(f, " return func(") + for j := 1; j <= i; j++ { + if j > 1 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "t%d T%d", j-1, j-1) + } + fmt.Fprintf(f, ") T%d {\n", i) + fmt.Fprintf(f, " return f") + for j := 1; j <= i; j++ { + fmt.Fprintf(f, "(t%d)", j-1) + } + fmt.Fprintln(f) + + fmt.Fprintf(f, " }\n") + + fmt.Fprintf(f, "}\n") +} + +func generatePipeHelpers(filename string, count int) error { + dir, err := os.Getwd() + if err != nil { + return err + } + absDir, err := filepath.Abs(dir) + if err != nil { + return err + } + pkg := filepath.Base(absDir) + f, err := os.Create(filepath.Clean(filename)) + if err != nil { + return err + } + defer f.Close() + // log + log.Printf("Generating code in [%s] for package [%s] with [%d] repetitions ...", filename, pkg, count) + + // some header + fmt.Fprintln(f, "// Code generated by go generate; DO NOT EDIT.") + fmt.Fprintln(f, "// This file was generated by robots at") + fmt.Fprintf(f, "// %s\n\n", time.Now()) + + fmt.Fprintf(f, "package %s\n", pkg) + + // pipe + generatePipe(f, 0) + // variadic + generateVariadic(f, 0) + // unvariadic + generateUnvariadic(f, 0) + // unsliced + generateUnsliced(f, 0) + + for i := 1; i <= count; i++ { + + // pipe + generatePipe(f, i) + // flow + generateFlow(f, i) + // nullary + generateNullary(f, i) + // curry + generateCurry(f, i) + // uncurry + generateUncurry(f, i) + // variadic + generateVariadic(f, i) + // unvariadic + generateUnvariadic(f, i) + // unsliced + generateUnsliced(f, i) + } + + return nil +} + +func PipeCommand() *C.Command { + return &C.Command{ + Name: "pipe", + Usage: "generate code for pipe, flow, curry, etc", + Description: "Code generation for pipe, flow, curry, etc", + Flags: []C.Flag{ + flagCount, + flagFilename, + }, + Action: func(ctx *C.Context) error { + return generatePipeHelpers( + ctx.String(keyFilename), + ctx.Int(keyCount), + ) + }, + } +} diff --git a/v2/cli/reader.go b/v2/cli/reader.go new file mode 100644 index 0000000..cfaedbb --- /dev/null +++ b/v2/cli/reader.go @@ -0,0 +1,164 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package cli + +import ( + "fmt" + "log" + "os" + "path/filepath" + "time" + + C "github.com/urfave/cli/v2" +) + +func generateReaderFrom(f, fg *os.File, i int) { + // non generic version + fmt.Fprintf(f, "\n// From%d converts a function with %d parameters returning a [R] into a function with %d parameters returning a [Reader[C, R]]\n// The first parameter is considered to be the context [C] of the reader\n", i, i+1, i) + fmt.Fprintf(f, "func From%d[F ~func(C", i) + for j := 0; j < i; j++ { + fmt.Fprintf(f, ", T%d", j) + } + fmt.Fprintf(f, ") R") + for j := 0; j < i; j++ { + fmt.Fprintf(f, ", T%d", j) + } + fmt.Fprintf(f, ", C, R any](f F) func(") + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", j) + } + fmt.Fprintf(f, ") Reader[C, R] {\n") + fmt.Fprintf(f, " return G.From%d[Reader[C, R]](f)\n", i) + fmt.Fprintln(f, "}") + + // generic version + fmt.Fprintf(fg, "\n// From%d converts a function with %d parameters returning a [R] into a function with %d parameters returning a [GRA]\n// The first parameter is considered to be the context [C].\n", i, i+1, i) + fmt.Fprintf(fg, "func From%d[GRA ~func(C) R, F ~func(C", i) + for j := 0; j < i; j++ { + fmt.Fprintf(fg, ", T%d", j) + } + fmt.Fprintf(fg, ") R") + for j := 0; j < i; j++ { + fmt.Fprintf(fg, ", T%d", j) + } + fmt.Fprintf(fg, ", C, R any](f F) func(") + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(fg, ", ") + } + fmt.Fprintf(fg, "T%d", j) + } + fmt.Fprintf(fg, ") GRA {\n") + + fmt.Fprintf(fg, " return func(") + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(fg, ", ") + } + fmt.Fprintf(fg, "t%d T%d", j, j) + } + fmt.Fprintf(fg, ") GRA {\n") + fmt.Fprintf(fg, " return MakeReader[GRA](func(r C) R {\n") + fmt.Fprintf(fg, " return f(r") + for j := 0; j < i; j++ { + fmt.Fprintf(fg, ", t%d", j) + } + fmt.Fprintf(fg, ")\n") + fmt.Fprintf(fg, " })\n") + fmt.Fprintf(fg, " }\n") + fmt.Fprintf(fg, "}\n") +} + +func generateReaderHelpers(filename string, count int) error { + dir, err := os.Getwd() + if err != nil { + return err + } + absDir, err := filepath.Abs(dir) + if err != nil { + return err + } + pkg := filepath.Base(absDir) + f, err := os.Create(filepath.Clean(filename)) + if err != nil { + return err + } + defer f.Close() + // construct subdirectory + genFilename := filepath.Join("generic", filename) + err = os.MkdirAll("generic", os.ModePerm) + if err != nil { + return err + } + fg, err := os.Create(filepath.Clean(genFilename)) + if err != nil { + return err + } + defer fg.Close() + + // log + log.Printf("Generating code in [%s] for package [%s] with [%d] repetitions ...", filename, pkg, count) + + // some header + fmt.Fprintln(f, "// Code generated by go generate; DO NOT EDIT.") + fmt.Fprintln(f, "// This file was generated by robots at") + fmt.Fprintf(f, "// %s\n\n", time.Now()) + + fmt.Fprintf(f, "package %s\n\n", pkg) + + fmt.Fprintf(f, ` +import ( + G "github.com/IBM/fp-go/v2/%s/generic" +) +`, pkg) + + // some header + fmt.Fprintln(fg, "// Code generated by go generate; DO NOT EDIT.") + fmt.Fprintln(fg, "// This file was generated by robots at") + fmt.Fprintf(fg, "// %s\n", time.Now()) + + fmt.Fprintf(fg, "package generic\n\n") + + // from + generateReaderFrom(f, fg, 0) + + for i := 1; i <= count; i++ { + // from + generateReaderFrom(f, fg, i) + } + + return nil +} + +func ReaderCommand() *C.Command { + return &C.Command{ + Name: "reader", + Usage: "generate code for Reader", + Flags: []C.Flag{ + flagCount, + flagFilename, + }, + Action: func(ctx *C.Context) error { + return generateReaderHelpers( + ctx.String(keyFilename), + ctx.Int(keyCount), + ) + }, + } +} diff --git a/v2/cli/readerioeither.go b/v2/cli/readerioeither.go new file mode 100644 index 0000000..dffff9e --- /dev/null +++ b/v2/cli/readerioeither.go @@ -0,0 +1,294 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package cli + +import ( + "fmt" + "log" + "os" + "path/filepath" + "time" + + C "github.com/urfave/cli/v2" +) + +func generateReaderIOEitherFrom(f, fg *os.File, i int) { + // non generic version + fmt.Fprintf(f, "\n// From%d converts a function with %d parameters returning a tuple into a function with %d parameters returning a [ReaderIOEither[R]]\n// The first parameter is considered to be the context [C].\n", i, i+1, i) + fmt.Fprintf(f, "func From%d[F ~func(C", i) + for j := 0; j < i; j++ { + fmt.Fprintf(f, ", T%d", j) + } + fmt.Fprintf(f, ") func() (R, error)") + for j := 0; j < i; j++ { + fmt.Fprintf(f, ", T%d", j) + } + fmt.Fprintf(f, ", C, R any](f F) func(") + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", j) + } + fmt.Fprintf(f, ") ReaderIOEither[C, error, R] {\n") + fmt.Fprintf(f, " return G.From%d[ReaderIOEither[C, error, R]](f)\n", i) + fmt.Fprintln(f, "}") + + // generic version + fmt.Fprintf(fg, "\n// From%d converts a function with %d parameters returning a tuple into a function with %d parameters returning a [GRA]\n// The first parameter is considerd to be the context [C].\n", i, i+1, i) + fmt.Fprintf(fg, "func From%d[GRA ~func(C) GIOA, F ~func(C", i) + for j := 0; j < i; j++ { + fmt.Fprintf(fg, ", T%d", j) + } + fmt.Fprintf(fg, ") func() (R, error), GIOA ~func() E.Either[error, R]") + for j := 0; j < i; j++ { + fmt.Fprintf(fg, ", T%d", j) + } + fmt.Fprintf(fg, ", C, R any](f F) func(") + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(fg, ", ") + } + fmt.Fprintf(fg, "T%d", j) + } + fmt.Fprintf(fg, ") GRA {\n") + + fmt.Fprintf(fg, " return RD.From%d[GRA](func(r C", i) + for j := 0; j < i; j++ { + fmt.Fprintf(fg, ", t%d T%d", j, j) + } + fmt.Fprintf(fg, ") GIOA {\n") + fmt.Fprintf(fg, " return E.Eitherize0(f(r") + for j := 0; j < i; j++ { + fmt.Fprintf(fg, ", t%d", j) + } + fmt.Fprintf(fg, "))\n") + fmt.Fprintf(fg, " })\n") + fmt.Fprintf(fg, "}\n") +} + +func generateReaderIOEitherEitherize(f, fg *os.File, i int) { + // non generic version + fmt.Fprintf(f, "\n// Eitherize%d converts a function with %d parameters returning a tuple into a function with %d parameters returning a [ReaderIOEither[C, error, R]]\n// The first parameter is considered to be the context [C].\n", i, i+1, i) + fmt.Fprintf(f, "func Eitherize%d[F ~func(C", i) + for j := 0; j < i; j++ { + fmt.Fprintf(f, ", T%d", j) + } + fmt.Fprintf(f, ") (R, error)") + for j := 0; j < i; j++ { + fmt.Fprintf(f, ", T%d", j) + } + fmt.Fprintf(f, ", C, R any](f F) func(") + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", j) + } + fmt.Fprintf(f, ") ReaderIOEither[C, error, R] {\n") + fmt.Fprintf(f, " return G.Eitherize%d[ReaderIOEither[C, error, R]](f)\n", i) + fmt.Fprintln(f, "}") + + // generic version + fmt.Fprintf(fg, "\n// Eitherize%d converts a function with %d parameters returning a tuple into a function with %d parameters returning a [GRA]\n// The first parameter is considered to be the context [C].\n", i, i, i) + fmt.Fprintf(fg, "func Eitherize%d[GRA ~func(C) GIOA, F ~func(C", i) + for j := 0; j < i; j++ { + fmt.Fprintf(fg, ", T%d", j) + } + fmt.Fprintf(fg, ") (R, error), GIOA ~func() E.Either[error, R]") + for j := 0; j < i; j++ { + fmt.Fprintf(fg, ", T%d", j) + } + fmt.Fprintf(fg, ", C, R any](f F) func(") + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(fg, ", ") + } + fmt.Fprintf(fg, "T%d", j) + } + fmt.Fprintf(fg, ") GRA {\n") + + fmt.Fprintf(fg, " return From%d[GRA](func(r C", i) + for j := 0; j < i; j++ { + fmt.Fprintf(fg, ", t%d T%d", j, j) + } + fmt.Fprintf(fg, ") func() (R, error) {\n") + fmt.Fprintf(fg, " return func() (R, error) {\n") + fmt.Fprintf(fg, " return f(r") + for j := 0; j < i; j++ { + fmt.Fprintf(fg, ", t%d", j) + } + fmt.Fprintf(fg, ")\n") + fmt.Fprintf(fg, " }})\n") + fmt.Fprintf(fg, "}\n") +} + +func generateReaderIOEitherUneitherize(f, fg *os.File, i int) { + // non generic version + fmt.Fprintf(f, "\n// Uneitherize%d converts a function with %d parameters returning a [ReaderIOEither[C, error, R]] into a function with %d parameters returning a tuple.\n// The first parameter is considered to be the context [C].\n", i, i+1, i) + fmt.Fprintf(f, "func Uneitherize%d[F ~func(", i) + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", j) + } + fmt.Fprintf(f, ") ReaderIOEither[C, error, R]") + for j := 0; j < i; j++ { + fmt.Fprintf(f, ", T%d", j) + } + fmt.Fprintf(f, ", C, R any](f F) func(C") + for j := 0; j < i; j++ { + fmt.Fprintf(f, ", T%d", j) + } + fmt.Fprintf(f, ") (R, error) {\n") + fmt.Fprintf(f, " return G.Uneitherize%d[ReaderIOEither[C, error, R]", i) + + fmt.Fprintf(f, ", func(C") + for j := 0; j < i; j++ { + fmt.Fprintf(f, ", T%d", j) + } + fmt.Fprintf(f, ")(R, error)](f)\n") + fmt.Fprintln(f, "}") + + // generic version + fmt.Fprintf(fg, "\n// Uneitherize%d converts a function with %d parameters returning a [GRA] into a function with %d parameters returning a tuple.\n// The first parameter is considered to be the context [C].\n", i, i, i) + fmt.Fprintf(fg, "func Uneitherize%d[GRA ~func(C) GIOA, F ~func(C", i) + for j := 0; j < i; j++ { + fmt.Fprintf(fg, ", T%d", j) + } + fmt.Fprintf(fg, ") (R, error), GIOA ~func() E.Either[error, R]") + for j := 0; j < i; j++ { + fmt.Fprintf(fg, ", T%d", j) + } + fmt.Fprintf(fg, ", C, R any](f func(") + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(fg, ", ") + } + fmt.Fprintf(fg, "T%d", j) + } + fmt.Fprintf(fg, ") GRA) F {\n") + + fmt.Fprintf(fg, " return func(c C") + for j := 0; j < i; j++ { + fmt.Fprintf(fg, ", t%d T%d", j, j) + } + fmt.Fprintf(fg, ") (R, error) {\n") + fmt.Fprintf(fg, " return E.UnwrapError(f(") + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(fg, ", ") + } + fmt.Fprintf(fg, "t%d", j) + } + fmt.Fprintf(fg, ")(c)())\n") + fmt.Fprintf(fg, " }\n") + fmt.Fprintf(fg, "}\n") +} + +func generateReaderIOEitherHelpers(filename string, count int) error { + dir, err := os.Getwd() + if err != nil { + return err + } + absDir, err := filepath.Abs(dir) + if err != nil { + return err + } + pkg := filepath.Base(absDir) + f, err := os.Create(filepath.Clean(filename)) + if err != nil { + return err + } + defer f.Close() + // construct subdirectory + genFilename := filepath.Join("generic", filename) + err = os.MkdirAll("generic", os.ModePerm) + if err != nil { + return err + } + fg, err := os.Create(filepath.Clean(genFilename)) + if err != nil { + return err + } + defer fg.Close() + + // log + log.Printf("Generating code in [%s] for package [%s] with [%d] repetitions ...", filename, pkg, count) + + // some header + fmt.Fprintln(f, "// Code generated by go generate; DO NOT EDIT.") + fmt.Fprintln(f, "// This file was generated by robots at") + fmt.Fprintf(f, "// %s\n\n", time.Now()) + + fmt.Fprintf(f, "package %s\n\n", pkg) + + fmt.Fprintf(f, ` +import ( + G "github.com/IBM/fp-go/v2/%s/generic" +) +`, pkg) + + // some header + fmt.Fprintln(fg, "// Code generated by go generate; DO NOT EDIT.") + fmt.Fprintln(fg, "// This file was generated by robots at") + fmt.Fprintf(fg, "// %s\n", time.Now()) + + fmt.Fprintf(fg, "package generic\n\n") + + fmt.Fprintf(fg, ` +import ( + E "github.com/IBM/fp-go/v2/either" + RD "github.com/IBM/fp-go/v2/reader/generic" +) +`) + + // from + generateReaderIOEitherFrom(f, fg, 0) + // eitherize + generateReaderIOEitherEitherize(f, fg, 0) + // uneitherize + generateReaderIOEitherUneitherize(f, fg, 0) + + for i := 1; i <= count; i++ { + // from + generateReaderIOEitherFrom(f, fg, i) + // eitherize + generateReaderIOEitherEitherize(f, fg, i) + // uneitherize + generateReaderIOEitherUneitherize(f, fg, i) + } + + return nil +} + +func ReaderIOEitherCommand() *C.Command { + return &C.Command{ + Name: "readerioeither", + Usage: "generate code for ReaderIOEither", + Flags: []C.Flag{ + flagCount, + flagFilename, + }, + Action: func(ctx *C.Context) error { + return generateReaderIOEitherHelpers( + ctx.String(keyFilename), + ctx.Int(keyCount), + ) + }, + } +} diff --git a/v2/cli/templates/functions.go b/v2/cli/templates/functions.go new file mode 100644 index 0000000..2ed4f6f --- /dev/null +++ b/v2/cli/templates/functions.go @@ -0,0 +1,15 @@ +package templates + +import ( + "text/template" + + E "github.com/IBM/fp-go/v2/either" +) + +var ( + templateFunctions = template.FuncMap{} +) + +func Parse(name, tmpl string) E.Either[error, *template.Template] { + return E.TryCatchError(template.New(name).Funcs(templateFunctions).Parse(tmpl)) +} diff --git a/v2/cli/tuple.go b/v2/cli/tuple.go new file mode 100644 index 0000000..a5ad2a9 --- /dev/null +++ b/v2/cli/tuple.go @@ -0,0 +1,625 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package cli + +import ( + "fmt" + "log" + "os" + "path/filepath" + "strings" + "time" + + C "github.com/urfave/cli/v2" +) + +func writeTupleType(f *os.File, symbol string, i int) { + fmt.Fprintf(f, "Tuple%d[", i) + for j := 1; j <= i; j++ { + if j > 1 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "%s%d", symbol, j) + } + fmt.Fprintf(f, "]") +} + +func makeTupleType(name string) func(i int) string { + return func(i int) string { + var buf strings.Builder + buf.WriteString(fmt.Sprintf("Tuple%d[", i)) + for j := 0; j < i; j++ { + if j > 0 { + buf.WriteString(", ") + } + buf.WriteString(fmt.Sprintf("%s%d", name, j+1)) + } + buf.WriteString("]") + + return buf.String() + } +} + +func generatePush(f *os.File, i int) { + tuple1 := makeTupleType("T")(i) + tuple2 := makeTupleType("T")(i + 1) + // Create the replicate version + fmt.Fprintf(f, "\n// Push%d creates a [Tuple%d] from a [Tuple%d] by appending a constant value\n", i, i+1, i) + fmt.Fprintf(f, "func Push%d[", i) + // function prototypes + for j := 0; j <= i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", j+1) + } + fmt.Fprintf(f, " any](value T%d) func(%s) %s {\n", i+1, tuple1, tuple2) + fmt.Fprintf(f, " return func(t %s) %s {\n", tuple1, tuple2) + fmt.Fprintf(f, " return MakeTuple%d(", i+1) + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "t.F%d", j+1) + } + fmt.Fprintf(f, ", value)\n") + fmt.Fprintf(f, " }\n") + fmt.Fprintf(f, "}\n") +} + +func generateReplicate(f *os.File, i int) { + // Create the replicate version + fmt.Fprintf(f, "\n// Replicate%d creates a [Tuple%d] with all fields set to the input value `t`\n", i, i) + fmt.Fprintf(f, "func Replicate%d[T any](t T) Tuple%d[", i, i) + for j := 1; j <= i; j++ { + if j > 1 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T") + } + fmt.Fprintf(f, "] {\n") + // execute the mapping + fmt.Fprintf(f, " return MakeTuple%d(", i) + for j := 1; j <= i; j++ { + if j > 1 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "t") + } + fmt.Fprintf(f, ")\n") + fmt.Fprintf(f, "}\n") +} + +func generateMap(f *os.File, i int) { + // Create the optionize version + fmt.Fprintf(f, "\n// Map%d maps each value of a [Tuple%d] via a mapping function\n", i, i) + fmt.Fprintf(f, "func Map%d[", i) + // function prototypes + for j := 1; j <= i; j++ { + if j > 1 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "F%d ~func(T%d) R%d", j, j, j) + } + for j := 1; j <= i; j++ { + fmt.Fprintf(f, ", T%d, R%d", j, j) + } + fmt.Fprintf(f, " any](") + for j := 1; j <= i; j++ { + if j > 1 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "f%d F%d", j, j) + } + fmt.Fprintf(f, ") func(") + writeTupleType(f, "T", i) + fmt.Fprintf(f, ") ") + writeTupleType(f, "R", i) + fmt.Fprintf(f, " {\n") + + fmt.Fprintf(f, " return func(t ") + writeTupleType(f, "T", i) + fmt.Fprintf(f, ") ") + writeTupleType(f, "R", i) + fmt.Fprintf(f, " {\n") + + // execute the mapping + fmt.Fprintf(f, " return MakeTuple%d(\n", i) + for j := 1; j <= i; j++ { + fmt.Fprintf(f, " f%d(t.F%d),\n", j, j) + } + fmt.Fprintf(f, " )\n") + + fmt.Fprintf(f, " }\n") + fmt.Fprintf(f, "}\n") +} + +func generateMonoid(f *os.File, i int) { + // Create the optionize version + fmt.Fprintf(f, "\n// Monoid%d creates a [Monoid] for a [Tuple%d] based on %d monoids for the contained types\n", i, i, i) + fmt.Fprintf(f, "func Monoid%d[", i) + for j := 1; j <= i; j++ { + if j > 1 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", j) + } + fmt.Fprintf(f, " any](") + for j := 1; j <= i; j++ { + if j > 1 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "m%d M.Monoid[T%d]", j, j) + } + fmt.Fprintf(f, ") M.Monoid[") + writeTupleType(f, "T", i) + fmt.Fprintf(f, "] {\n") + + fmt.Fprintf(f, " return M.MakeMonoid(func(l, r ") + writeTupleType(f, "T", i) + fmt.Fprintf(f, ") ") + writeTupleType(f, "T", i) + fmt.Fprintf(f, "{\n") + + fmt.Fprintf(f, " return MakeTuple%d(", i) + for j := 1; j <= i; j++ { + if j > 1 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "m%d.Concat(l.F%d, r.F%d)", j, j, j) + } + fmt.Fprintf(f, ")\n") + fmt.Fprintf(f, " }, MakeTuple%d(", i) + for j := 1; j <= i; j++ { + if j > 1 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "m%d.Empty()", j) + } + fmt.Fprintf(f, "))\n") + + fmt.Fprintf(f, "}\n") +} + +func generateOrd(f *os.File, i int) { + // Create the optionize version + fmt.Fprintf(f, "\n// Ord%d creates n [Ord] for a [Tuple%d] based on %d [Ord]s for the contained types\n", i, i, i) + fmt.Fprintf(f, "func Ord%d[", i) + for j := 1; j <= i; j++ { + if j > 1 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", j) + } + fmt.Fprintf(f, " any](") + for j := 1; j <= i; j++ { + if j > 1 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "o%d O.Ord[T%d]", j, j) + } + fmt.Fprintf(f, ") O.Ord[") + writeTupleType(f, "T", i) + fmt.Fprintf(f, "] {\n") + + fmt.Fprintf(f, " return O.MakeOrd(func(l, r ") + writeTupleType(f, "T", i) + fmt.Fprintf(f, ") int {\n") + + for j := 1; j <= i; j++ { + fmt.Fprintf(f, " if c:= o%d.Compare(l.F%d, r.F%d); c != 0 {return c}\n", j, j, j) + } + fmt.Fprintf(f, " return 0\n") + fmt.Fprintf(f, " }, func(l, r ") + writeTupleType(f, "T", i) + fmt.Fprintf(f, ") bool {\n") + fmt.Fprintf(f, " return ") + for j := 1; j <= i; j++ { + if j > 1 { + fmt.Fprintf(f, " && ") + } + fmt.Fprintf(f, "o%d.Equals(l.F%d, r.F%d)", j, j, j) + } + fmt.Fprintf(f, "\n") + fmt.Fprintf(f, " })\n") + + fmt.Fprintf(f, "}\n") +} + +func generateTupleType(f *os.File, i int) { + // Create the optionize version + fmt.Fprintf(f, "\n// Tuple%d is a struct that carries %d independently typed values\n", i, i) + fmt.Fprintf(f, "type Tuple%d[", i) + for j := 1; j <= i; j++ { + if j > 1 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", j) + } + fmt.Fprintf(f, " any] struct {\n") + for j := 1; j <= i; j++ { + fmt.Fprintf(f, " F%d T%d\n", j, j) + } + fmt.Fprintf(f, "}\n") +} + +func generateMakeTupleType(f *os.File, i int) { + // Create the optionize version + fmt.Fprintf(f, "\n// MakeTuple%d is a function that converts its %d parameters into a [Tuple%d]\n", i, i, i) + fmt.Fprintf(f, "func MakeTuple%d[", i) + for j := 1; j <= i; j++ { + if j > 1 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", j) + } + fmt.Fprintf(f, " any](") + for j := 1; j <= i; j++ { + if j > 1 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "t%d T%d", j, j) + } + fmt.Fprintf(f, ") ") + writeTupleType(f, "T", i) + fmt.Fprintf(f, " {\n") + fmt.Fprintf(f, " return Tuple%d[", i) + for j := 1; j <= i; j++ { + if j > 1 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", j) + } + fmt.Fprintf(f, "]{") + for j := 1; j <= i; j++ { + if j > 1 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "t%d", j) + } + fmt.Fprintf(f, "}\n") + fmt.Fprintf(f, "}\n") +} + +func generateUntupled(f *os.File, i int) { + // Create the optionize version + fmt.Fprintf(f, "\n// Untupled%d converts a function with a [Tuple%d] parameter into a function with %d parameters\n// The inverse function is [Tupled%d]\n", i, i, i, i) + fmt.Fprintf(f, "func Untupled%d[F ~func(Tuple%d[", i, i) + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", j+1) + } + fmt.Fprintf(f, "]) R") + for j := 0; j < i; j++ { + fmt.Fprintf(f, ", T%d", j+1) + } + fmt.Fprintf(f, ", R any](f F) func(") + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", j+1) + } + fmt.Fprintf(f, ") R {\n") + fmt.Fprintf(f, " return func(") + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "t%d T%d", j+1, j+1) + } + fmt.Fprintf(f, ") R {\n") + fmt.Fprintf(f, " return f(MakeTuple%d(", i) + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "t%d", j+1) + } + fmt.Fprintln(f, "))") + fmt.Fprintln(f, " }") + fmt.Fprintln(f, "}") +} + +func generateTupled(f *os.File, i int) { + // Create the optionize version + fmt.Fprintf(f, "\n// Tupled%d converts a function with %d parameters into a function taking a Tuple%d\n// The inverse function is [Untupled%d]\n", i, i, i, i) + fmt.Fprintf(f, "func Tupled%d[F ~func(", i) + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", j+1) + } + fmt.Fprintf(f, ") R") + for j := 0; j < i; j++ { + fmt.Fprintf(f, ", T%d", j+1) + } + fmt.Fprintf(f, ", R any](f F) func(Tuple%d[", i) + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", j+1) + } + fmt.Fprintf(f, "]) R {\n") + fmt.Fprintf(f, " return func(t Tuple%d[", i) + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "T%d", j+1) + } + fmt.Fprintf(f, "]) R {\n") + fmt.Fprintf(f, " return f(") + for j := 0; j < i; j++ { + if j > 0 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "t.F%d", j+1) + } + fmt.Fprintf(f, ")\n") + fmt.Fprintf(f, " }\n") + fmt.Fprintln(f, "}") +} + +func generateTupleHelpers(filename string, count int) error { + dir, err := os.Getwd() + if err != nil { + return err + } + absDir, err := filepath.Abs(dir) + if err != nil { + return err + } + pkg := filepath.Base(absDir) + f, err := os.Create(filepath.Clean(filename)) + if err != nil { + return err + } + defer f.Close() + // log + log.Printf("Generating code in [%s] for package [%s] with [%d] repetitions ...", filename, pkg, count) + + // some header + fmt.Fprintln(f, "// Code generated by go generate; DO NOT EDIT.") + fmt.Fprintln(f, "// This file was generated by robots at") + fmt.Fprintf(f, "// %s\n\n", time.Now()) + + fmt.Fprintf(f, "package %s\n\n", pkg) + + fmt.Fprintf(f, ` +import ( + M "github.com/IBM/fp-go/v2/monoid" + O "github.com/IBM/fp-go/v2/ord" +) +`) + + for i := 1; i <= count; i++ { + // tuple type + generateTupleType(f, i) + } + + for i := 1; i <= count; i++ { + // tuple generator + generateMakeTupleType(f, i) + // tupled wrapper + generateTupled(f, i) + // untupled wrapper + generateUntupled(f, i) + // monoid + generateMonoid(f, i) + // generate order + generateOrd(f, i) + // generate map + generateMap(f, i) + // generate replicate + generateReplicate(f, i) + // generate tuple functions such as string and fmt + generateTupleString(f, i) + // generate json support + generateTupleMarshal(f, i) + // generate json support + generateTupleUnmarshal(f, i) + // generate toArray + generateToArray(f, i) + // generate fromArray + generateFromArray(f, i) + // generate push + if i < count { + generatePush(f, i) + } + } + + return nil +} + +func generateTupleMarshal(f *os.File, i int) { + // Create the stringify version + fmt.Fprintf(f, "\n// MarshalJSON marshals the [Tuple%d] into a JSON array\n", i) + fmt.Fprintf(f, "func (t ") + writeTupleType(f, "T", i) + fmt.Fprintf(f, ") MarshalJSON() ([]byte, error) {\n") + fmt.Fprintf(f, " return tupleMarshalJSON(") + // function prototypes + for j := 1; j <= i; j++ { + if j > 1 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "t.F%d", j) + } + fmt.Fprintf(f, ")\n") + fmt.Fprintf(f, "}\n") +} + +func generateTupleUnmarshal(f *os.File, i int) { + // Create the stringify version + fmt.Fprintf(f, "\n// UnmarshalJSON unmarshals a JSON array into a [Tuple%d]\n", i) + fmt.Fprintf(f, "func (t *") + writeTupleType(f, "T", i) + fmt.Fprintf(f, ") UnmarshalJSON(data []byte) error {\n") + fmt.Fprintf(f, " return tupleUnmarshalJSON(data") + // function prototypes + for j := 1; j <= i; j++ { + fmt.Fprintf(f, ", &t.F%d", j) + } + fmt.Fprintf(f, ")\n") + fmt.Fprintf(f, "}\n") +} + +func generateToArray(f *os.File, i int) { + // Create the stringify version + fmt.Fprintf(f, "\n// ToArray converts the [Tuple%d] into an array of type [R] using %d transformation functions from [T] to [R]\n// The inverse function is [FromArray%d]\n", i, i, i) + fmt.Fprintf(f, "func ToArray%d[", i) + // function prototypes + for j := 1; j <= i; j++ { + if j > 1 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "F%d ~func(T%d) R", j, j) + } + for j := 1; j <= i; j++ { + fmt.Fprintf(f, ", T%d", j) + } + fmt.Fprintf(f, ", R any](") + for j := 1; j <= i; j++ { + if j > 1 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "f%d F%d", j, j) + } + fmt.Fprintf(f, ") func(t ") + writeTupleType(f, "T", i) + fmt.Fprintf(f, ") []R {\n") + fmt.Fprintf(f, " return func(t ") + writeTupleType(f, "T", i) + fmt.Fprintf(f, ") []R {\n") + fmt.Fprintf(f, " return []R{\n") + for j := 1; j <= i; j++ { + fmt.Fprintf(f, " f%d(t.F%d),\n", j, j) + } + fmt.Fprintf(f, " }\n") + fmt.Fprintf(f, " }\n") + fmt.Fprintf(f, "}\n") +} + +func generateFromArray(f *os.File, i int) { + // Create the stringify version + fmt.Fprintf(f, "\n// FromArray converts an array of [R] into a [Tuple%d] using %d functions from [R] to [T]\n// The inverse function is [ToArray%d]\n", i, i, i) + fmt.Fprintf(f, "func FromArray%d[", i) + // function prototypes + for j := 1; j <= i; j++ { + if j > 1 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "F%d ~func(R) T%d", j, j) + } + for j := 1; j <= i; j++ { + fmt.Fprintf(f, ", T%d", j) + } + fmt.Fprintf(f, ", R any](") + for j := 1; j <= i; j++ { + if j > 1 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "f%d F%d", j, j) + } + fmt.Fprintf(f, ") func(r []R) ") + writeTupleType(f, "T", i) + fmt.Fprintf(f, " {\n") + fmt.Fprintf(f, " return func(r []R) ") + writeTupleType(f, "T", i) + fmt.Fprintf(f, " {\n") + fmt.Fprintf(f, " return MakeTuple%d(\n", i) + for j := 1; j <= i; j++ { + fmt.Fprintf(f, " f%d(r[%d]),\n", j, j-1) + } + fmt.Fprintf(f, " )\n") + fmt.Fprintf(f, " }\n") + fmt.Fprintf(f, "}\n") +} + +func generateTupleString(f *os.File, i int) { + // Create the stringify version + fmt.Fprintf(f, "\n// String prints some debug info for the [Tuple%d]\n", i) + fmt.Fprintf(f, "func (t ") + writeTupleType(f, "T", i) + fmt.Fprintf(f, ") String() string {\n") + // convert to string + fmt.Fprint(f, " return tupleString(") + for j := 1; j <= i; j++ { + if j > 1 { + fmt.Fprintf(f, ", ") + } + fmt.Fprintf(f, "t.F%d", j) + } + fmt.Fprintf(f, ")\n") + fmt.Fprintf(f, "}\n") +} + +// func generateTupleJson(f *os.File, i int) { +// // Create the stringify version +// fmt.Fprintf(f, "\n// MarshalJSON converts the [Tuple%d] into a JSON byte stream\n", i) +// fmt.Fprintf(f, "func (t ") +// writeTupleType(f, "T", i) +// fmt.Fprintf(f, ") MarshalJSON() ([]byte, error) {\n") +// // convert to string +// fmt.Fprintf(f, " return fmt.Sprintf(\"Tuple%d[", i) +// for j := 1; j <= i; j++ { +// if j > 1 { +// fmt.Fprintf(f, ", ") +// } +// fmt.Fprintf(f, "%s", "%T") +// } +// fmt.Fprintf(f, "](") +// for j := 1; j <= i; j++ { +// if j > 1 { +// fmt.Fprintf(f, ", ") +// } +// fmt.Fprintf(f, "%s", "%v") +// } +// fmt.Fprintf(f, ")\", ") +// for j := 1; j <= i; j++ { +// if j > 1 { +// fmt.Fprintf(f, ", ") +// } +// fmt.Fprintf(f, "t.F%d", j) +// } +// for j := 1; j <= i; j++ { +// fmt.Fprintf(f, ", t.F%d", j) +// } +// fmt.Fprintf(f, ")\n") +// fmt.Fprintf(f, "}\n") +// } + +func TupleCommand() *C.Command { + return &C.Command{ + Name: "tuple", + Usage: "generate code for Tuple", + Flags: []C.Flag{ + flagCount, + flagFilename, + }, + Action: func(ctx *C.Context) error { + return generateTupleHelpers( + ctx.String(keyFilename), + ctx.Int(keyCount), + ) + }, + } +} diff --git a/v2/constant/const.go b/v2/constant/const.go new file mode 100644 index 0000000..cbbbdfa --- /dev/null +++ b/v2/constant/const.go @@ -0,0 +1,59 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package constant + +import ( + F "github.com/IBM/fp-go/v2/function" + M "github.com/IBM/fp-go/v2/monoid" + S "github.com/IBM/fp-go/v2/semigroup" +) + +type Const[E, A any] struct { + value E +} + +func Make[E, A any](e E) Const[E, A] { + return Const[E, A]{value: e} +} + +func Unwrap[E, A any](c Const[E, A]) E { + return c.value +} + +func Of[E, A any](m M.Monoid[E]) func(A) Const[E, A] { + return F.Constant1[A](Make[E, A](m.Empty())) +} + +func MonadMap[E, A, B any](fa Const[E, A], _ func(A) B) Const[E, B] { + return Make[E, B](fa.value) +} + +func MonadAp[E, A, B any](s S.Semigroup[E]) func(fab Const[E, func(A) B], fa Const[E, A]) Const[E, B] { + return func(fab Const[E, func(A) B], fa Const[E, A]) Const[E, B] { + return Make[E, B](s.Concat(fab.value, fa.value)) + } +} + +func Map[E, A, B any](f func(A) B) func(fa Const[E, A]) Const[E, B] { + return F.Bind2nd(MonadMap[E, A, B], f) +} + +func Ap[E, A, B any](s S.Semigroup[E]) func(fa Const[E, A]) func(fab Const[E, func(A) B]) Const[E, B] { + monadap := MonadAp[E, A, B](s) + return func(fa Const[E, A]) func(fab Const[E, func(A) B]) Const[E, B] { + return F.Bind2nd(monadap, fa) + } +} diff --git a/v2/constant/const_test.go b/v2/constant/const_test.go new file mode 100644 index 0000000..aa22df8 --- /dev/null +++ b/v2/constant/const_test.go @@ -0,0 +1,40 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package constant + +import ( + "testing" + + F "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/internal/utils" + S "github.com/IBM/fp-go/v2/string" + + "github.com/stretchr/testify/assert" +) + +func TestMap(t *testing.T) { + fa := Make[string, int]("foo") + assert.Equal(t, fa, F.Pipe1(fa, Map[string, int](utils.Double))) +} + +func TestOf(t *testing.T) { + assert.Equal(t, Make[string, int](""), Of[string, int](S.Monoid)(1)) +} + +func TestAp(t *testing.T) { + fab := Make[string, int]("bar") + assert.Equal(t, Make[string, int]("foobar"), Ap[string, int, int](S.Monoid)(fab)(Make[string, func(int) int]("foo"))) +} diff --git a/v2/constraints/constraints.go b/v2/constraints/constraints.go new file mode 100644 index 0000000..2949b0e --- /dev/null +++ b/v2/constraints/constraints.go @@ -0,0 +1,40 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package constraints + +type Ordered interface { + Integer | Float | ~string +} + +type Signed interface { + ~int | ~int8 | ~int16 | ~int32 | ~int64 +} + +type Unsigned interface { + ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr +} + +type Integer interface { + Signed | Unsigned +} + +type Float interface { + ~float32 | ~float64 +} + +type Complex interface { + ~complex64 | ~complex128 +} diff --git a/v2/context/doc.go b/v2/context/doc.go new file mode 100644 index 0000000..3e6cf1e --- /dev/null +++ b/v2/context/doc.go @@ -0,0 +1,17 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +// Package context contains versions of reader IO monads that work with a golang [context.Context] +package context diff --git a/v2/context/ioeither/generic/ioeither.go b/v2/context/ioeither/generic/ioeither.go new file mode 100644 index 0000000..f2af079 --- /dev/null +++ b/v2/context/ioeither/generic/ioeither.go @@ -0,0 +1,33 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + "context" + + E "github.com/IBM/fp-go/v2/either" + IOE "github.com/IBM/fp-go/v2/ioeither/generic" +) + +// WithContext wraps an existing IOEither and performs a context check for cancellation before delegating +func WithContext[GIO ~func() E.Either[error, A], A any](ctx context.Context, ma GIO) GIO { + return IOE.MakeIO[GIO](func() E.Either[error, A] { + if err := context.Cause(ctx); err != nil { + return E.Left[A](err) + } + return ma() + }) +} diff --git a/v2/context/ioeither/ioeither.go b/v2/context/ioeither/ioeither.go new file mode 100644 index 0000000..36201b8 --- /dev/null +++ b/v2/context/ioeither/ioeither.go @@ -0,0 +1,33 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package ioeither + +import ( + "context" + + "github.com/IBM/fp-go/v2/either" + IOE "github.com/IBM/fp-go/v2/ioeither" +) + +// withContext wraps an existing IOEither and performs a context check for cancellation before delegating +func WithContext[A any](ctx context.Context, ma IOE.IOEither[error, A]) IOE.IOEither[error, A] { + return func() either.Either[error, A] { + if err := context.Cause(ctx); err != nil { + return either.Left[A](err) + } + return ma() + } +} diff --git a/v2/context/readereither/array.go b/v2/context/readereither/array.go new file mode 100644 index 0000000..e1ecb76 --- /dev/null +++ b/v2/context/readereither/array.go @@ -0,0 +1,33 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readereither + +import "github.com/IBM/fp-go/v2/readereither" + +// TraverseArray transforms an array +func TraverseArray[A, B any](f func(A) ReaderEither[B]) func([]A) ReaderEither[[]B] { + return readereither.TraverseArray(f) +} + +// TraverseArrayWithIndex transforms an array +func TraverseArrayWithIndex[A, B any](f func(int, A) ReaderEither[B]) func([]A) ReaderEither[[]B] { + return readereither.TraverseArrayWithIndex(f) +} + +// SequenceArray converts a homogeneous sequence of either into an either of sequence +func SequenceArray[A any](ma []ReaderEither[A]) ReaderEither[[]A] { + return readereither.SequenceArray(ma) +} diff --git a/v2/context/readereither/bind.go b/v2/context/readereither/bind.go new file mode 100644 index 0000000..2554060 --- /dev/null +++ b/v2/context/readereither/bind.go @@ -0,0 +1,68 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readereither + +import ( + "context" + + G "github.com/IBM/fp-go/v2/readereither/generic" +) + +// Bind creates an empty context of type [S] to be used with the [Bind] operation +func Do[S any]( + empty S, +) ReaderEither[S] { + return G.Do[ReaderEither[S], context.Context, error, S](empty) +} + +// Bind attaches the result of a computation to a context [S1] to produce a context [S2] +func Bind[S1, S2, T any]( + setter func(T) func(S1) S2, + f func(S1) ReaderEither[T], +) func(ReaderEither[S1]) ReaderEither[S2] { + return G.Bind[ReaderEither[S1], ReaderEither[S2], ReaderEither[T], context.Context, error, S1, S2, T](setter, f) +} + +// Let attaches the result of a computation to a context [S1] to produce a context [S2] +func Let[S1, S2, T any]( + setter func(T) func(S1) S2, + f func(S1) T, +) func(ReaderEither[S1]) ReaderEither[S2] { + return G.Let[ReaderEither[S1], ReaderEither[S2], context.Context, error, S1, S2, T](setter, f) +} + +// LetTo attaches the a value to a context [S1] to produce a context [S2] +func LetTo[S1, S2, T any]( + setter func(T) func(S1) S2, + b T, +) func(ReaderEither[S1]) ReaderEither[S2] { + return G.LetTo[ReaderEither[S1], ReaderEither[S2], context.Context, error, S1, S2, T](setter, b) +} + +// BindTo initializes a new state [S1] from a value [T] +func BindTo[S1, T any]( + setter func(T) S1, +) func(ReaderEither[T]) ReaderEither[S1] { + return G.BindTo[ReaderEither[S1], ReaderEither[T], context.Context, error, S1, T](setter) +} + +// ApS attaches a value to a context [S1] to produce a context [S2] by considering the context and the value concurrently +func ApS[S1, S2, T any]( + setter func(T) func(S1) S2, + fa ReaderEither[T], +) func(ReaderEither[S1]) ReaderEither[S2] { + return G.ApS[ReaderEither[S1], ReaderEither[S2], ReaderEither[T], context.Context, error, S1, S2, T](setter, fa) +} diff --git a/v2/context/readereither/bind_test.go b/v2/context/readereither/bind_test.go new file mode 100644 index 0000000..64a29a3 --- /dev/null +++ b/v2/context/readereither/bind_test.go @@ -0,0 +1,58 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readereither + +import ( + "context" + "testing" + + E "github.com/IBM/fp-go/v2/either" + F "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/internal/utils" + "github.com/stretchr/testify/assert" +) + +func getLastName(s utils.Initial) ReaderEither[string] { + return Of("Doe") +} + +func getGivenName(s utils.WithLastName) ReaderEither[string] { + return Of("John") +} + +func TestBind(t *testing.T) { + + res := F.Pipe3( + Do(utils.Empty), + Bind(utils.SetLastName, getLastName), + Bind(utils.SetGivenName, getGivenName), + Map(utils.GetFullName), + ) + + assert.Equal(t, res(context.Background()), E.Of[error]("John Doe")) +} + +func TestApS(t *testing.T) { + + res := F.Pipe3( + Do(utils.Empty), + ApS(utils.SetLastName, Of("Doe")), + ApS(utils.SetGivenName, Of("John")), + Map(utils.GetFullName), + ) + + assert.Equal(t, res(context.Background()), E.Of[error]("John Doe")) +} diff --git a/v2/context/readereither/cancel.go b/v2/context/readereither/cancel.go new file mode 100644 index 0000000..aa1f38a --- /dev/null +++ b/v2/context/readereither/cancel.go @@ -0,0 +1,32 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readereither + +import ( + "context" + + E "github.com/IBM/fp-go/v2/either" +) + +// withContext wraps an existing ReaderEither and performs a context check for cancellation before deletating +func WithContext[A any](ma ReaderEither[A]) ReaderEither[A] { + return func(ctx context.Context) E.Either[error, A] { + if err := context.Cause(ctx); err != nil { + return E.Left[A](err) + } + return ma(ctx) + } +} diff --git a/v2/context/readereither/curry.go b/v2/context/readereither/curry.go new file mode 100644 index 0000000..0afa256 --- /dev/null +++ b/v2/context/readereither/curry.go @@ -0,0 +1,53 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readereither + +import ( + "context" + + "github.com/IBM/fp-go/v2/readereither" +) + +// these functions curry a golang function with the context as the firsr parameter into a either reader with the context as the last parameter +// this goes back to the advice in https://pkg.go.dev/context to put the context as a first parameter as a convention + +func Curry0[A any](f func(context.Context) (A, error)) ReaderEither[A] { + return readereither.Curry0(f) +} + +func Curry1[T1, A any](f func(context.Context, T1) (A, error)) func(T1) ReaderEither[A] { + return readereither.Curry1(f) +} + +func Curry2[T1, T2, A any](f func(context.Context, T1, T2) (A, error)) func(T1) func(T2) ReaderEither[A] { + return readereither.Curry2(f) +} + +func Curry3[T1, T2, T3, A any](f func(context.Context, T1, T2, T3) (A, error)) func(T1) func(T2) func(T3) ReaderEither[A] { + return readereither.Curry3(f) +} + +func Uncurry1[T1, A any](f func(T1) ReaderEither[A]) func(context.Context, T1) (A, error) { + return readereither.Uncurry1(f) +} + +func Uncurry2[T1, T2, A any](f func(T1) func(T2) ReaderEither[A]) func(context.Context, T1, T2) (A, error) { + return readereither.Uncurry2(f) +} + +func Uncurry3[T1, T2, T3, A any](f func(T1) func(T2) func(T3) ReaderEither[A]) func(context.Context, T1, T2, T3) (A, error) { + return readereither.Uncurry3(f) +} diff --git a/v2/context/readereither/exec/exec.go b/v2/context/readereither/exec/exec.go new file mode 100644 index 0000000..72162c2 --- /dev/null +++ b/v2/context/readereither/exec/exec.go @@ -0,0 +1,39 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package exec + +import ( + "context" + + "github.com/IBM/fp-go/v2/context/readereither" + "github.com/IBM/fp-go/v2/either" + "github.com/IBM/fp-go/v2/exec" + "github.com/IBM/fp-go/v2/function" + INTE "github.com/IBM/fp-go/v2/internal/exec" +) + +var ( + // Command executes a command + // use this version if the command does not produce any side effect, i.e. if the output is uniquely determined by by the input + // typically you'd rather use the ReaderIOEither version of the command + Command = function.Curry3(command) +) + +func command(name string, args []string, in []byte) readereither.ReaderEither[exec.CommandOutput] { + return func(ctx context.Context) either.Either[error, exec.CommandOutput] { + return either.TryCatchError(INTE.Exec(ctx, name, args, in)) + } +} diff --git a/v2/context/readereither/from.go b/v2/context/readereither/from.go new file mode 100644 index 0000000..50cf914 --- /dev/null +++ b/v2/context/readereither/from.go @@ -0,0 +1,41 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readereither + +import ( + "context" + + "github.com/IBM/fp-go/v2/readereither" +) + +// these functions curry a golang function with the context as the firsr parameter into a either reader with the context as the last parameter +// this goes back to the advice in https://pkg.go.dev/context to put the context as a first parameter as a convention + +func From0[A any](f func(context.Context) (A, error)) func() ReaderEither[A] { + return readereither.From0(f) +} + +func From1[T1, A any](f func(context.Context, T1) (A, error)) func(T1) ReaderEither[A] { + return readereither.From1(f) +} + +func From2[T1, T2, A any](f func(context.Context, T1, T2) (A, error)) func(T1, T2) ReaderEither[A] { + return readereither.From2(f) +} + +func From3[T1, T2, T3, A any](f func(context.Context, T1, T2, T3) (A, error)) func(T1, T2, T3) ReaderEither[A] { + return readereither.From3(f) +} diff --git a/v2/context/readereither/reader.go b/v2/context/readereither/reader.go new file mode 100644 index 0000000..96c4a88 --- /dev/null +++ b/v2/context/readereither/reader.go @@ -0,0 +1,94 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readereither + +import ( + "context" + + "github.com/IBM/fp-go/v2/readereither" +) + +func FromEither[A any](e Either[A]) ReaderEither[A] { + return readereither.FromEither[context.Context](e) +} + +func Left[A any](l error) ReaderEither[A] { + return readereither.Left[context.Context, A](l) +} + +func Right[A any](r A) ReaderEither[A] { + return readereither.Right[context.Context, error](r) +} + +func MonadMap[A, B any](fa ReaderEither[A], f func(A) B) ReaderEither[B] { + return readereither.MonadMap(fa, f) +} + +func Map[A, B any](f func(A) B) Operator[A, B] { + return readereither.Map[context.Context, error](f) +} + +func MonadChain[A, B any](ma ReaderEither[A], f func(A) ReaderEither[B]) ReaderEither[B] { + return readereither.MonadChain(ma, f) +} + +func Chain[A, B any](f func(A) ReaderEither[B]) Operator[A, B] { + return readereither.Chain(f) +} + +func Of[A any](a A) ReaderEither[A] { + return readereither.Of[context.Context, error](a) +} + +func MonadAp[A, B any](fab ReaderEither[func(A) B], fa ReaderEither[A]) ReaderEither[B] { + return readereither.MonadAp(fab, fa) +} + +func Ap[A, B any](fa ReaderEither[A]) func(ReaderEither[func(A) B]) ReaderEither[B] { + return readereither.Ap[B](fa) +} + +func FromPredicate[A any](pred func(A) bool, onFalse func(A) error) func(A) ReaderEither[A] { + return readereither.FromPredicate[context.Context](pred, onFalse) +} + +func OrElse[A any](onLeft func(error) ReaderEither[A]) func(ReaderEither[A]) ReaderEither[A] { + return readereither.OrElse(onLeft) +} + +func Ask() ReaderEither[context.Context] { + return readereither.Ask[context.Context, error]() +} + +func MonadChainEitherK[A, B any](ma ReaderEither[A], f func(A) Either[B]) ReaderEither[B] { + return readereither.MonadChainEitherK(ma, f) +} + +func ChainEitherK[A, B any](f func(A) Either[B]) func(ma ReaderEither[A]) ReaderEither[B] { + return readereither.ChainEitherK[context.Context](f) +} + +func ChainOptionK[A, B any](onNone func() error) func(func(A) Option[B]) Operator[A, B] { + return readereither.ChainOptionK[context.Context, A, B](onNone) +} + +func MonadFlap[B, A any](fab ReaderEither[func(A) B], a A) ReaderEither[B] { + return readereither.MonadFlap(fab, a) +} + +func Flap[B, A any](a A) Operator[func(A) B, B] { + return readereither.Flap[context.Context, error, B](a) +} diff --git a/v2/context/readereither/sequence.go b/v2/context/readereither/sequence.go new file mode 100644 index 0000000..be697b2 --- /dev/null +++ b/v2/context/readereither/sequence.go @@ -0,0 +1,39 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readereither + +import ( + "github.com/IBM/fp-go/v2/readereither" + "github.com/IBM/fp-go/v2/tuple" +) + +// SequenceT converts n inputs of higher kinded types into a higher kinded types of n strongly typed values, represented as a tuple + +func SequenceT1[A any](a ReaderEither[A]) ReaderEither[tuple.Tuple1[A]] { + return readereither.SequenceT1(a) +} + +func SequenceT2[A, B any](a ReaderEither[A], b ReaderEither[B]) ReaderEither[tuple.Tuple2[A, B]] { + return readereither.SequenceT2(a, b) +} + +func SequenceT3[A, B, C any](a ReaderEither[A], b ReaderEither[B], c ReaderEither[C]) ReaderEither[tuple.Tuple3[A, B, C]] { + return readereither.SequenceT3(a, b, c) +} + +func SequenceT4[A, B, C, D any](a ReaderEither[A], b ReaderEither[B], c ReaderEither[C], d ReaderEither[D]) ReaderEither[tuple.Tuple4[A, B, C, D]] { + return readereither.SequenceT4(a, b, c, d) +} diff --git a/v2/context/readereither/type.go b/v2/context/readereither/type.go new file mode 100644 index 0000000..fedf1cf --- /dev/null +++ b/v2/context/readereither/type.go @@ -0,0 +1,35 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +// Package readereither implements a specialization of the Reader monad assuming a golang context as the context of the monad and a standard golang error +package readereither + +import ( + "context" + + "github.com/IBM/fp-go/v2/either" + "github.com/IBM/fp-go/v2/option" + "github.com/IBM/fp-go/v2/reader" + "github.com/IBM/fp-go/v2/readereither" +) + +type ( + Option[A any] = option.Option[A] + Either[A any] = either.Either[error, A] + // ReaderEither is a specialization of the Reader monad for the typical golang scenario + ReaderEither[A any] = readereither.ReaderEither[context.Context, error, A] + + Operator[A, B any] = reader.Reader[ReaderEither[A], ReaderEither[B]] +) diff --git a/v2/context/readerioeither/bind.go b/v2/context/readerioeither/bind.go new file mode 100644 index 0000000..37fbe2b --- /dev/null +++ b/v2/context/readerioeither/bind.go @@ -0,0 +1,89 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readerioeither + +import ( + "github.com/IBM/fp-go/v2/internal/apply" + "github.com/IBM/fp-go/v2/internal/chain" + "github.com/IBM/fp-go/v2/internal/functor" +) + +// Bind creates an empty context of type [S] to be used with the [Bind] operation +func Do[S any]( + empty S, +) ReaderIOEither[S] { + return Of(empty) +} + +// Bind attaches the result of a computation to a context [S1] to produce a context [S2] +func Bind[S1, S2, T any]( + setter func(T) func(S1) S2, + f func(S1) ReaderIOEither[T], +) func(ReaderIOEither[S1]) ReaderIOEither[S2] { + return chain.Bind( + Chain[S1, S2], + Map[T, S2], + setter, + f, + ) +} + +// Let attaches the result of a computation to a context [S1] to produce a context [S2] +func Let[S1, S2, T any]( + setter func(T) func(S1) S2, + f func(S1) T, +) func(ReaderIOEither[S1]) ReaderIOEither[S2] { + return functor.Let( + Map[S1, S2], + setter, + f, + ) +} + +// LetTo attaches the a value to a context [S1] to produce a context [S2] +func LetTo[S1, S2, T any]( + setter func(T) func(S1) S2, + b T, +) func(ReaderIOEither[S1]) ReaderIOEither[S2] { + return functor.LetTo( + Map[S1, S2], + setter, + b, + ) +} + +// BindTo initializes a new state [S1] from a value [T] +func BindTo[S1, T any]( + setter func(T) S1, +) Operator[T, S1] { + return chain.BindTo( + Map[T, S1], + setter, + ) +} + +// ApS attaches a value to a context [S1] to produce a context [S2] by considering the context and the value concurrently +func ApS[S1, S2, T any]( + setter func(T) func(S1) S2, + fa ReaderIOEither[T], +) func(ReaderIOEither[S1]) ReaderIOEither[S2] { + return apply.ApS( + Ap[S2, T], + Map[S1, func(T) S2], + setter, + fa, + ) +} diff --git a/v2/context/readerioeither/bind_test.go b/v2/context/readerioeither/bind_test.go new file mode 100644 index 0000000..597ccce --- /dev/null +++ b/v2/context/readerioeither/bind_test.go @@ -0,0 +1,58 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readerioeither + +import ( + "context" + "testing" + + E "github.com/IBM/fp-go/v2/either" + F "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/internal/utils" + "github.com/stretchr/testify/assert" +) + +func getLastName(s utils.Initial) ReaderIOEither[string] { + return Of("Doe") +} + +func getGivenName(s utils.WithLastName) ReaderIOEither[string] { + return Of("John") +} + +func TestBind(t *testing.T) { + + res := F.Pipe3( + Do(utils.Empty), + Bind(utils.SetLastName, getLastName), + Bind(utils.SetGivenName, getGivenName), + Map(utils.GetFullName), + ) + + assert.Equal(t, res(context.Background())(), E.Of[error]("John Doe")) +} + +func TestApS(t *testing.T) { + + res := F.Pipe3( + Do(utils.Empty), + ApS(utils.SetLastName, Of("Doe")), + ApS(utils.SetGivenName, Of("John")), + Map(utils.GetFullName), + ) + + assert.Equal(t, res(context.Background())(), E.Of[error]("John Doe")) +} diff --git a/v2/context/readerioeither/bracket.go b/v2/context/readerioeither/bracket.go new file mode 100644 index 0000000..4d802e9 --- /dev/null +++ b/v2/context/readerioeither/bracket.go @@ -0,0 +1,44 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readerioeither + +import ( + "context" + + "github.com/IBM/fp-go/v2/internal/bracket" + "github.com/IBM/fp-go/v2/readerio" +) + +// Bracket makes sure that a resource is cleaned up in the event of an error. The release action is called regardless of +// whether the body action returns and error or not. +func Bracket[ + A, B, ANY any]( + + acquire ReaderIOEither[A], + use func(A) ReaderIOEither[B], + release func(A, Either[B]) ReaderIOEither[ANY], +) ReaderIOEither[B] { + return bracket.Bracket[ReaderIOEither[A], ReaderIOEither[B], ReaderIOEither[ANY], Either[B], A, B]( + readerio.Of[context.Context, Either[B]], + MonadChain[A, B], + readerio.MonadChain[context.Context, Either[B], Either[B]], + MonadChain[ANY, B], + + acquire, + use, + release, + ) +} diff --git a/v2/context/readerioeither/cancel.go b/v2/context/readerioeither/cancel.go new file mode 100644 index 0000000..9c541bc --- /dev/null +++ b/v2/context/readerioeither/cancel.go @@ -0,0 +1,42 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readerioeither + +import ( + "context" + + CIOE "github.com/IBM/fp-go/v2/context/ioeither" + "github.com/IBM/fp-go/v2/ioeither" +) + +// WithContext wraps an existing [ReaderIOEither] and performs a context check for cancellation before delegating. +// This ensures that if the context is already canceled, the computation short-circuits immediately +// without executing the wrapped computation. +// +// This is useful for adding cancellation awareness to computations that might not check the context themselves. +// +// Parameters: +// - ma: The ReaderIOEither to wrap with context checking +// +// Returns a ReaderIOEither that checks for cancellation before executing. +func WithContext[A any](ma ReaderIOEither[A]) ReaderIOEither[A] { + return func(ctx context.Context) IOEither[A] { + if err := context.Cause(ctx); err != nil { + return ioeither.Left[A](err) + } + return CIOE.WithContext(ctx, ma(ctx)) + } +} diff --git a/v2/context/readerioeither/coverage.out b/v2/context/readerioeither/coverage.out new file mode 100644 index 0000000..30c3a5d --- /dev/null +++ b/v2/context/readerioeither/coverage.out @@ -0,0 +1,251 @@ +mode: set +github.com/IBM/fp-go/v2/context/readerioeither/bind.go:27.21,29.2 1 1 +github.com/IBM/fp-go/v2/context/readerioeither/bind.go:35.47,42.2 1 1 +github.com/IBM/fp-go/v2/context/readerioeither/bind.go:48.47,54.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/bind.go:60.47,66.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/bind.go:71.46,76.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/bind.go:82.47,89.2 1 1 +github.com/IBM/fp-go/v2/context/readerioeither/bracket.go:33.21,44.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/cancel.go:35.65,36.47 1 1 +github.com/IBM/fp-go/v2/context/readerioeither/cancel.go:36.47,37.44 1 1 +github.com/IBM/fp-go/v2/context/readerioeither/cancel.go:37.44,39.4 1 1 +github.com/IBM/fp-go/v2/context/readerioeither/cancel.go:40.3,40.40 1 1 +github.com/IBM/fp-go/v2/context/readerioeither/eq.go:42.84,44.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:18.91,20.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:24.93,26.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:30.101,32.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:36.103,38.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:43.36,48.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:53.36,58.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:63.36,68.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:71.98,76.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:79.101,84.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:87.101,92.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:95.129,96.68 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:96.68,102.4 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:106.132,107.68 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:107.68,113.4 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:117.132,118.68 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:118.68,124.4 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:129.113,131.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:135.115,137.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:143.40,150.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:156.40,163.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:169.40,176.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:179.126,185.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:188.129,194.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:197.129,203.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:206.185,207.76 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:207.76,215.4 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:219.188,220.76 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:220.76,228.4 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:232.188,233.76 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:233.76,241.4 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:246.125,248.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:252.127,254.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:261.44,270.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:277.44,286.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:293.44,302.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:305.154,312.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:315.157,322.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:325.157,332.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:335.241,336.84 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:336.84,346.4 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:350.244,351.84 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:351.84,361.4 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:365.244,366.84 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:366.84,376.4 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:381.137,383.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:387.139,389.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:397.48,408.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:416.48,427.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:435.48,446.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:449.182,457.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:460.185,468.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:471.185,479.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:482.297,483.92 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:483.92,495.4 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:499.300,500.92 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:500.92,512.4 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:516.300,517.92 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:517.92,529.4 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:534.149,536.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:540.151,542.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:551.52,564.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:573.52,586.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:595.52,608.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:611.210,620.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:623.213,632.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:635.213,644.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:647.353,648.100 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:648.100,662.4 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:666.356,667.100 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:667.100,681.4 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:685.356,686.100 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:686.100,700.4 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:705.161,707.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:711.163,713.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:723.56,738.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:748.56,763.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:773.56,788.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:791.238,801.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:804.241,814.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:817.241,827.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:830.409,831.108 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:831.108,847.4 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:851.412,852.108 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:852.108,868.4 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:872.412,873.108 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:873.108,889.4 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:894.173,896.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:900.175,902.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:913.60,930.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:941.60,958.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:969.60,986.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:989.266,1000.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1003.269,1014.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1017.269,1028.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1031.465,1032.116 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1032.116,1050.4 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1054.468,1055.116 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1055.116,1073.4 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1077.468,1078.116 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1078.116,1096.4 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1101.185,1103.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1107.187,1109.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1121.64,1140.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1152.64,1171.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1183.64,1202.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1205.294,1217.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1220.297,1232.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1235.297,1247.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1250.521,1251.124 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1251.124,1271.4 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1275.524,1276.124 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1276.124,1296.4 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1300.524,1301.124 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1301.124,1321.4 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1326.197,1328.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1332.199,1334.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1347.68,1368.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1381.68,1402.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1415.68,1436.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1439.322,1452.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1455.325,1468.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1471.325,1484.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1487.577,1488.132 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1488.132,1510.4 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1514.580,1515.132 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1515.132,1537.4 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1541.580,1542.132 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1542.132,1564.4 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1569.210,1571.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1575.212,1577.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1591.74,1614.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1628.74,1651.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1665.74,1688.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1691.356,1705.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1708.359,1722.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1725.359,1739.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1742.645,1743.144 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1743.144,1767.4 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1771.648,1772.144 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1772.144,1796.4 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1800.648,1801.144 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/gen.go:1801.144,1825.4 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/monoid.go:36.61,43.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/monoid.go:52.64,59.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/monoid.go:68.64,75.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/monoid.go:85.61,93.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/monoid.go:103.63,108.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:42.55,44.2 1 1 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:52.45,54.2 1 1 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:62.42,64.2 1 1 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:74.78,76.2 1 1 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:85.75,87.2 1 1 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:97.72,99.2 1 1 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:108.69,110.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:120.96,122.2 1 1 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:131.93,133.2 1 1 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:143.101,145.2 1 1 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:154.71,156.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:165.39,167.2 1 1 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:169.93,173.56 1 1 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:173.56,174.32 1 1 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:174.32,174.47 1 1 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:189.98,194.47 3 1 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:194.47,196.44 1 1 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:196.44,198.4 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:200.3,200.27 1 1 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:200.27,202.45 1 1 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:202.45,204.5 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:207.4,213.47 5 1 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:227.95,229.17 1 1 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:229.17,231.3 1 1 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:232.2,232.28 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:243.98,245.2 1 1 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:254.91,256.2 1 1 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:265.94,267.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:276.94,278.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:288.95,290.2 1 1 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:299.73,301.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:307.44,309.2 1 1 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:319.95,321.2 1 1 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:330.95,332.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:342.100,344.2 1 1 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:353.100,355.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:364.116,366.2 1 1 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:375.75,377.2 1 1 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:386.47,388.2 1 1 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:398.51,400.2 1 1 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:406.39,407.47 1 1 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:407.47,408.27 1 1 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:408.27,411.4 2 1 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:423.87,425.2 1 1 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:434.87,436.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:446.92,448.2 1 1 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:457.92,459.2 1 1 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:468.115,470.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:479.85,480.54 1 1 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:480.54,481.48 1 1 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:481.48,482.28 1 1 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:482.28,487.12 3 1 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:488.30,489.22 1 1 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:490.23,491.47 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:505.59,511.2 1 1 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:520.66,522.2 1 1 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:531.83,533.2 1 1 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:543.97,545.2 1 1 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:554.64,556.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:566.62,568.2 1 1 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:577.78,579.2 1 1 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:589.80,591.2 1 1 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:600.76,602.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:612.136,614.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:623.91,625.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/reader.go:634.71,636.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/resource.go:58.151,63.2 1 1 +github.com/IBM/fp-go/v2/context/readerioeither/semigroup.go:39.41,43.2 1 1 +github.com/IBM/fp-go/v2/context/readerioeither/sync.go:46.78,54.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/traverse.go:31.89,39.2 1 1 +github.com/IBM/fp-go/v2/context/readerioeither/traverse.go:48.103,56.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/traverse.go:65.71,67.2 1 1 +github.com/IBM/fp-go/v2/context/readerioeither/traverse.go:75.112,83.2 1 1 +github.com/IBM/fp-go/v2/context/readerioeither/traverse.go:92.124,100.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/traverse.go:108.94,110.2 1 1 +github.com/IBM/fp-go/v2/context/readerioeither/traverse.go:120.95,128.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/traverse.go:137.92,145.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/traverse.go:148.106,156.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/traverse.go:165.74,167.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/traverse.go:170.118,178.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/traverse.go:181.115,189.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/traverse.go:192.127,200.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/traverse.go:203.97,205.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/traverse.go:215.95,223.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/traverse.go:232.92,240.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/traverse.go:243.106,251.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/traverse.go:260.74,262.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/traverse.go:265.115,273.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/traverse.go:276.127,284.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/traverse.go:287.118,295.2 1 0 +github.com/IBM/fp-go/v2/context/readerioeither/traverse.go:304.97,306.2 1 0 diff --git a/v2/context/readerioeither/data/file.txt b/v2/context/readerioeither/data/file.txt new file mode 100644 index 0000000..f6a7bf5 --- /dev/null +++ b/v2/context/readerioeither/data/file.txt @@ -0,0 +1 @@ +Carsten \ No newline at end of file diff --git a/v2/context/readerioeither/doc.go b/v2/context/readerioeither/doc.go new file mode 100644 index 0000000..58c804b --- /dev/null +++ b/v2/context/readerioeither/doc.go @@ -0,0 +1,169 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +// Package readerioeither provides a specialized version of [readerioeither.ReaderIOEither] that uses +// [context.Context] as the context type and [error] as the left (error) type. This package is designed +// for typical Go applications where context-aware, effectful computations with error handling are needed. +// +// # Core Concept +// +// ReaderIOEither[A] represents a computation that: +// - Depends on a [context.Context] (Reader aspect) +// - Performs side effects (IO aspect) +// - Can fail with an [error] (Either aspect) +// - Produces a value of type A on success +// +// The type is defined as: +// +// ReaderIOEither[A] = func(context.Context) func() Either[error, A] +// +// This combines three powerful functional programming concepts: +// - Reader: Dependency injection via context +// - IO: Deferred side effects +// - Either: Explicit error handling +// +// # Key Features +// +// - Context-aware operations with automatic cancellation support +// - Parallel and sequential execution modes for applicative operations +// - Resource management with automatic cleanup (RAII pattern) +// - Thread-safe operations with lock management +// - Comprehensive error handling combinators +// - Conversion utilities for standard Go error-returning functions +// +// # Core Operations +// +// Construction: +// - [Of], [Right]: Create successful computations +// - [Left]: Create failed computations +// - [FromEither], [FromIO], [FromIOEither]: Convert from other types +// - [TryCatch]: Wrap error-returning functions +// - [Eitherize0-10]: Convert standard Go functions to ReaderIOEither +// +// Transformation: +// - [Map]: Transform success values +// - [MapLeft]: Transform error values +// - [BiMap]: Transform both success and error values +// - [Chain]: Sequence dependent computations +// - [ChainFirst]: Sequence computations, keeping first result +// +// Combination: +// - [Ap], [ApSeq], [ApPar]: Apply functions to values (sequential/parallel) +// - [SequenceT2-10]: Combine multiple computations into tuples +// - [TraverseArray], [TraverseRecord]: Transform collections +// +// Error Handling: +// - [Fold]: Handle both success and error cases +// - [GetOrElse]: Provide default value on error +// - [OrElse]: Try alternative computation on error +// - [Alt]: Alternative computation with lazy evaluation +// +// Context Operations: +// - [Ask]: Access the context +// - [Asks]: Access and transform the context +// - [WithContext]: Add context cancellation checks +// - [Never]: Create a computation that waits for context cancellation +// +// Resource Management: +// - [Bracket]: Ensure resource cleanup (acquire/use/release pattern) +// - [WithResource]: Manage resource lifecycle with automatic cleanup +// - [WithLock]: Execute operations within lock scope +// +// Timing: +// - [Delay]: Delay execution by duration +// - [Timer]: Return current time after delay +// +// # Usage Example +// +// import ( +// "context" +// "fmt" +// RIOE "github.com/IBM/fp-go/v2/context/readerioeither" +// F "github.com/IBM/fp-go/v2/function" +// ) +// +// // Define a computation that reads from context and may fail +// func fetchUser(id string) RIOE.ReaderIOEither[User] { +// return F.Pipe2( +// RIOE.Ask(), +// RIOE.Chain(func(ctx context.Context) RIOE.ReaderIOEither[User] { +// return RIOE.TryCatch(func(ctx context.Context) func() (User, error) { +// return func() (User, error) { +// return userService.Get(ctx, id) +// } +// }) +// }), +// RIOE.Map(func(user User) User { +// // Transform the user +// return user +// }), +// ) +// } +// +// // Execute the computation +// ctx := context.Background() +// result := fetchUser("123")(ctx)() +// // result is Either[error, User] +// +// # Parallel vs Sequential Execution +// +// The package supports both parallel and sequential execution for applicative operations: +// +// // Sequential execution (default for Ap) +// result := RIOE.ApSeq(value)(function) +// +// // Parallel execution with automatic cancellation +// result := RIOE.ApPar(value)(function) +// +// When using parallel execution, if any operation fails, all other operations are automatically +// cancelled via context cancellation. +// +// # Resource Management Example +// +// // Automatic resource cleanup with WithResource +// result := F.Pipe1( +// RIOE.WithResource( +// openFile("data.txt"), +// closeFile, +// ), +// func(use func(func(*os.File) RIOE.ReaderIOEither[string]) RIOE.ReaderIOEither[string]) RIOE.ReaderIOEither[string] { +// return use(func(file *os.File) RIOE.ReaderIOEither[string] { +// return readContent(file) +// }) +// }, +// ) +// +// # Bind Operations +// +// The package provides do-notation style operations for building complex computations: +// +// result := F.Pipe3( +// RIOE.Do(State{}), +// RIOE.Bind(setState, getFirstValue), +// RIOE.Bind(setState2, getSecondValue), +// RIOE.Map(finalTransform), +// ) +// +// # Context Cancellation +// +// All operations respect context cancellation. When a context is cancelled, operations +// will return an error containing the cancellation cause: +// +// ctx, cancel := context.WithCancelCause(context.Background()) +// cancel(errors.New("operation cancelled")) +// result := computation(ctx)() // Returns Left with cancellation error +// +//go:generate go run ../.. contextreaderioeither --count 10 --filename gen.go +package readerioeither diff --git a/v2/context/readerioeither/eq.go b/v2/context/readerioeither/eq.go new file mode 100644 index 0000000..767e400 --- /dev/null +++ b/v2/context/readerioeither/eq.go @@ -0,0 +1,44 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readerioeither + +import ( + "context" + + "github.com/IBM/fp-go/v2/eq" + RIOE "github.com/IBM/fp-go/v2/readerioeither" +) + +// Eq implements the equals predicate for values contained in the [ReaderIOEither] monad. +// It creates an equality checker that can compare two ReaderIOEither values by executing them +// with a given context and comparing their results using the provided Either equality checker. +// +// Parameters: +// - eq: Equality checker for Either[A] values +// +// Returns a function that takes a context and returns an equality checker for ReaderIOEither[A]. +// +// Example: +// +// eqInt := eq.FromEquals(func(a, b either.Either[error, int]) bool { +// return either.Eq(eq.FromEquals(func(x, y int) bool { return x == y }))(a, b) +// }) +// eqRIE := Eq(eqInt) +// ctx := context.Background() +// equal := eqRIE(ctx).Equals(Right[int](42), Right[int](42)) // true +func Eq[A any](eq eq.Eq[Either[A]]) func(context.Context) eq.Eq[ReaderIOEither[A]] { + return RIOE.Eq[context.Context](eq) +} diff --git a/v2/context/readerioeither/exec/exec.go b/v2/context/readerioeither/exec/exec.go new file mode 100644 index 0000000..36b752e --- /dev/null +++ b/v2/context/readerioeither/exec/exec.go @@ -0,0 +1,39 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package exec + +import ( + "context" + + RIOE "github.com/IBM/fp-go/v2/context/readerioeither" + "github.com/IBM/fp-go/v2/exec" + F "github.com/IBM/fp-go/v2/function" + GE "github.com/IBM/fp-go/v2/internal/exec" + IOE "github.com/IBM/fp-go/v2/ioeither" +) + +var ( + // Command executes a cancelable command + Command = F.Curry3(command) +) + +func command(name string, args []string, in []byte) RIOE.ReaderIOEither[exec.CommandOutput] { + return func(ctx context.Context) IOE.IOEither[error, exec.CommandOutput] { + return IOE.TryCatchError(func() (exec.CommandOutput, error) { + return GE.Exec(ctx, name, args, in) + }) + } +} diff --git a/v2/context/readerioeither/file/data/file.json b/v2/context/readerioeither/file/data/file.json new file mode 100644 index 0000000..6f6b333 --- /dev/null +++ b/v2/context/readerioeither/file/data/file.json @@ -0,0 +1,3 @@ +{ + "data": "Carsten" +} \ No newline at end of file diff --git a/v2/context/readerioeither/file/file.go b/v2/context/readerioeither/file/file.go new file mode 100644 index 0000000..c5374f7 --- /dev/null +++ b/v2/context/readerioeither/file/file.go @@ -0,0 +1,64 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package file + +import ( + "context" + "io" + "os" + + RIOE "github.com/IBM/fp-go/v2/context/readerioeither" + ET "github.com/IBM/fp-go/v2/either" + F "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/internal/file" + IOE "github.com/IBM/fp-go/v2/ioeither" + IOEF "github.com/IBM/fp-go/v2/ioeither/file" +) + +var ( + // Open opens a file for reading within the given context + Open = F.Flow3( + IOEF.Open, + RIOE.FromIOEither[*os.File], + RIOE.WithContext[*os.File], + ) + + // Remove removes a file by name + Remove = F.Flow2( + IOEF.Remove, + RIOE.FromIOEither[string], + ) +) + +// Close closes an object +func Close[C io.Closer](c C) RIOE.ReaderIOEither[any] { + return F.Pipe2( + c, + IOEF.Close[C], + RIOE.FromIOEither[any], + ) +} + +// ReadFile reads a file in the scope of a context +func ReadFile(path string) RIOE.ReaderIOEither[[]byte] { + return RIOE.WithResource[[]byte](Open(path), Close[*os.File])(func(r *os.File) RIOE.ReaderIOEither[[]byte] { + return func(ctx context.Context) IOE.IOEither[error, []byte] { + return func() ET.Either[error, []byte] { + return file.ReadAll(ctx, r) + } + } + }) +} diff --git a/v2/context/readerioeither/file/file_test.go b/v2/context/readerioeither/file/file_test.go new file mode 100644 index 0000000..c237009 --- /dev/null +++ b/v2/context/readerioeither/file/file_test.go @@ -0,0 +1,51 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package file + +import ( + "context" + "fmt" + + R "github.com/IBM/fp-go/v2/context/readerioeither" + F "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/io" + J "github.com/IBM/fp-go/v2/json" +) + +type RecordType struct { + Data string `json:"data"` +} + +func getData(r RecordType) string { + return r.Data +} + +func ExampleReadFile() { + + data := F.Pipe3( + ReadFile("./data/file.json"), + R.ChainEitherK(J.Unmarshal[RecordType]), + R.ChainFirstIOK(io.Logf[RecordType]("Log: %v")), + R.Map(getData), + ) + + result := data(context.Background()) + + fmt.Println(result()) + + // Output: + // Right[string](Carsten) +} diff --git a/v2/context/readerioeither/file/tempfile.go b/v2/context/readerioeither/file/tempfile.go new file mode 100644 index 0000000..5d6c531 --- /dev/null +++ b/v2/context/readerioeither/file/tempfile.go @@ -0,0 +1,52 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package file + +import ( + "os" + + RIOE "github.com/IBM/fp-go/v2/context/readerioeither" + F "github.com/IBM/fp-go/v2/function" + IO "github.com/IBM/fp-go/v2/io" + IOF "github.com/IBM/fp-go/v2/io/file" + IOEF "github.com/IBM/fp-go/v2/ioeither/file" +) + +var ( + // onCreateTempFile creates a temp file with sensible defaults + onCreateTempFile = CreateTemp("", "*") + // destroy handler + onReleaseTempFile = F.Flow4( + IOF.Close[*os.File], + IO.Map((*os.File).Name), + RIOE.FromIO[string], + RIOE.Chain(Remove), + ) +) + +// CreateTemp created a temp file with proper parametrization +func CreateTemp(dir, pattern string) RIOE.ReaderIOEither[*os.File] { + return F.Pipe2( + IOEF.CreateTemp(dir, pattern), + RIOE.FromIOEither[*os.File], + RIOE.WithContext[*os.File], + ) +} + +// WithTempFile creates a temporary file, then invokes a callback to create a resource based on the file, then close and remove the temp file +func WithTempFile[A any](f func(*os.File) RIOE.ReaderIOEither[A]) RIOE.ReaderIOEither[A] { + return RIOE.WithResource[A](onCreateTempFile, onReleaseTempFile)(f) +} diff --git a/v2/context/readerioeither/file/tempfile_test.go b/v2/context/readerioeither/file/tempfile_test.go new file mode 100644 index 0000000..fcdfc2a --- /dev/null +++ b/v2/context/readerioeither/file/tempfile_test.go @@ -0,0 +1,47 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package file + +import ( + "context" + "os" + "testing" + + RIOE "github.com/IBM/fp-go/v2/context/readerioeither" + E "github.com/IBM/fp-go/v2/either" + F "github.com/IBM/fp-go/v2/function" + "github.com/stretchr/testify/assert" +) + +func TestWithTempFile(t *testing.T) { + + res := WithTempFile(onWriteAll[*os.File]([]byte("Carsten"))) + + assert.Equal(t, E.Of[error]([]byte("Carsten")), res(context.Background())()) +} + +func TestWithTempFileOnClosedFile(t *testing.T) { + + res := WithTempFile(func(f *os.File) RIOE.ReaderIOEither[[]byte] { + return F.Pipe2( + f, + onWriteAll[*os.File]([]byte("Carsten")), + RIOE.ChainFirst(F.Constant1[[]byte](Close(f))), + ) + }) + + assert.Equal(t, E.Of[error]([]byte("Carsten")), res(context.Background())()) +} diff --git a/v2/context/readerioeither/file/write.go b/v2/context/readerioeither/file/write.go new file mode 100644 index 0000000..07f8d77 --- /dev/null +++ b/v2/context/readerioeither/file/write.go @@ -0,0 +1,57 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package file + +import ( + "context" + "io" + + RIOE "github.com/IBM/fp-go/v2/context/readerioeither" + F "github.com/IBM/fp-go/v2/function" +) + +func onWriteAll[W io.Writer](data []byte) func(w W) RIOE.ReaderIOEither[[]byte] { + return func(w W) RIOE.ReaderIOEither[[]byte] { + return F.Pipe1( + RIOE.TryCatch(func(_ context.Context) func() ([]byte, error) { + return func() ([]byte, error) { + _, err := w.Write(data) + return data, err + } + }), + RIOE.WithContext[[]byte], + ) + } +} + +// WriteAll uses a generator function to create a stream, writes data to it and closes it +func WriteAll[W io.WriteCloser](data []byte) func(acquire RIOE.ReaderIOEither[W]) RIOE.ReaderIOEither[[]byte] { + onWrite := onWriteAll[W](data) + return func(onCreate RIOE.ReaderIOEither[W]) RIOE.ReaderIOEither[[]byte] { + return RIOE.WithResource[[]byte]( + onCreate, + Close[W])( + onWrite, + ) + } +} + +// Write uses a generator function to create a stream, writes data to it and closes it +func Write[R any, W io.WriteCloser](acquire RIOE.ReaderIOEither[W]) func(use func(W) RIOE.ReaderIOEither[R]) RIOE.ReaderIOEither[R] { + return RIOE.WithResource[R]( + acquire, + Close[W]) +} diff --git a/v2/context/readerioeither/gen.go b/v2/context/readerioeither/gen.go new file mode 100644 index 0000000..4f658a1 --- /dev/null +++ b/v2/context/readerioeither/gen.go @@ -0,0 +1,1826 @@ +package readerioeither + +// Code generated by go generate; DO NOT EDIT. +// This file was generated by robots at +// 2025-03-09 23:52:48.5677388 +0100 CET m=+0.002067301 + + +import ( + "context" + + G "github.com/IBM/fp-go/v2/context/readerioeither/generic" + "github.com/IBM/fp-go/v2/internal/apply" + "github.com/IBM/fp-go/v2/tuple" +) + +// Eitherize0 converts a function with 0 parameters returning a tuple into a function with 0 parameters returning a [ReaderIOEither[R]] +// The inverse function is [Uneitherize0] +func Eitherize0[F ~func(context.Context) (R, error), R any](f F) func() ReaderIOEither[R] { + return G.Eitherize0[ReaderIOEither[R]](f) +} + +// Uneitherize0 converts a function with 1 parameters returning a [ReaderIOEither[R]] into a function with 0 parameters returning a tuple. +// The first parameter is considered to be the [context.Context]. +func Uneitherize0[F ~func() ReaderIOEither[R], R any](f F) func(context.Context) (R, error) { + return G.Uneitherize0[ReaderIOEither[R], func(context.Context)(R, error)](f) +} + +// Eitherize1 converts a function with 1 parameters returning a tuple into a function with 1 parameters returning a [ReaderIOEither[R]] +// The inverse function is [Uneitherize1] +func Eitherize1[F ~func(context.Context, T0) (R, error), T0, R any](f F) func(T0) ReaderIOEither[R] { + return G.Eitherize1[ReaderIOEither[R]](f) +} + +// Uneitherize1 converts a function with 2 parameters returning a [ReaderIOEither[R]] into a function with 1 parameters returning a tuple. +// The first parameter is considered to be the [context.Context]. +func Uneitherize1[F ~func(T0) ReaderIOEither[R], T0, R any](f F) func(context.Context, T0) (R, error) { + return G.Uneitherize1[ReaderIOEither[R], func(context.Context, T0)(R, error)](f) +} + +// SequenceT1 converts 1 [ReaderIOEither[T]] into a [ReaderIOEither[tuple.Tuple1[T1]]] +func SequenceT1[T1 any]( + t1 ReaderIOEither[T1], +) ReaderIOEither[tuple.Tuple1[T1]] { + return apply.SequenceT1( + Map[T1, tuple.Tuple1[T1]], + t1, + ) +} + +// SequenceSeqT1 converts 1 [ReaderIOEither[T]] into a [ReaderIOEither[tuple.Tuple1[T1]]] +func SequenceSeqT1[T1 any]( + t1 ReaderIOEither[T1], +) ReaderIOEither[tuple.Tuple1[T1]] { + return apply.SequenceT1( + Map[T1, tuple.Tuple1[T1]], + t1, + ) +} + +// SequenceParT1 converts 1 [ReaderIOEither[T]] into a [ReaderIOEither[tuple.Tuple1[T1]]] +func SequenceParT1[T1 any]( + t1 ReaderIOEither[T1], +) ReaderIOEither[tuple.Tuple1[T1]] { + return apply.SequenceT1( + Map[T1, tuple.Tuple1[T1]], + t1, + ) +} + +// SequenceTuple1 converts a [tuple.Tuple1[ReaderIOEither[T]]] into a [ReaderIOEither[tuple.Tuple1[T1]]] +func SequenceTuple1[T1 any](t tuple.Tuple1[ReaderIOEither[T1]]) ReaderIOEither[tuple.Tuple1[T1]] { + return apply.SequenceTuple1( + Map[T1, tuple.Tuple1[T1]], + t, + ) +} + +// SequenceSeqTuple1 converts a [tuple.Tuple1[ReaderIOEither[T]]] into a [ReaderIOEither[tuple.Tuple1[T1]]] +func SequenceSeqTuple1[T1 any](t tuple.Tuple1[ReaderIOEither[T1]]) ReaderIOEither[tuple.Tuple1[T1]] { + return apply.SequenceTuple1( + Map[T1, tuple.Tuple1[T1]], + t, + ) +} + +// SequenceParTuple1 converts a [tuple.Tuple1[ReaderIOEither[T]]] into a [ReaderIOEither[tuple.Tuple1[T1]]] +func SequenceParTuple1[T1 any](t tuple.Tuple1[ReaderIOEither[T1]]) ReaderIOEither[tuple.Tuple1[T1]] { + return apply.SequenceTuple1( + Map[T1, tuple.Tuple1[T1]], + t, + ) +} + +// TraverseTuple1 converts a [tuple.Tuple1[A1]] into a [ReaderIOEither[tuple.Tuple1[T1]]] +func TraverseTuple1[F1 ~func(A1) ReaderIOEither[T1], T1, A1 any](f1 F1) func(tuple.Tuple1[A1]) ReaderIOEither[tuple.Tuple1[T1]] { + return func(t tuple.Tuple1[A1]) ReaderIOEither[tuple.Tuple1[T1]] { + return apply.TraverseTuple1( + Map[T1, tuple.Tuple1[T1]], + f1, + t, + ) + } +} + +// TraverseSeqTuple1 converts a [tuple.Tuple1[A1]] into a [ReaderIOEither[tuple.Tuple1[T1]]] +func TraverseSeqTuple1[F1 ~func(A1) ReaderIOEither[T1], T1, A1 any](f1 F1) func(tuple.Tuple1[A1]) ReaderIOEither[tuple.Tuple1[T1]] { + return func(t tuple.Tuple1[A1]) ReaderIOEither[tuple.Tuple1[T1]] { + return apply.TraverseTuple1( + Map[T1, tuple.Tuple1[T1]], + f1, + t, + ) + } +} + +// TraverseParTuple1 converts a [tuple.Tuple1[A1]] into a [ReaderIOEither[tuple.Tuple1[T1]]] +func TraverseParTuple1[F1 ~func(A1) ReaderIOEither[T1], T1, A1 any](f1 F1) func(tuple.Tuple1[A1]) ReaderIOEither[tuple.Tuple1[T1]] { + return func(t tuple.Tuple1[A1]) ReaderIOEither[tuple.Tuple1[T1]] { + return apply.TraverseTuple1( + Map[T1, tuple.Tuple1[T1]], + f1, + t, + ) + } +} + +// Eitherize2 converts a function with 2 parameters returning a tuple into a function with 2 parameters returning a [ReaderIOEither[R]] +// The inverse function is [Uneitherize2] +func Eitherize2[F ~func(context.Context, T0, T1) (R, error), T0, T1, R any](f F) func(T0, T1) ReaderIOEither[R] { + return G.Eitherize2[ReaderIOEither[R]](f) +} + +// Uneitherize2 converts a function with 3 parameters returning a [ReaderIOEither[R]] into a function with 2 parameters returning a tuple. +// The first parameter is considered to be the [context.Context]. +func Uneitherize2[F ~func(T0, T1) ReaderIOEither[R], T0, T1, R any](f F) func(context.Context, T0, T1) (R, error) { + return G.Uneitherize2[ReaderIOEither[R], func(context.Context, T0, T1)(R, error)](f) +} + +// SequenceT2 converts 2 [ReaderIOEither[T]] into a [ReaderIOEither[tuple.Tuple2[T1, T2]]] +func SequenceT2[T1, T2 any]( + t1 ReaderIOEither[T1], + t2 ReaderIOEither[T2], +) ReaderIOEither[tuple.Tuple2[T1, T2]] { + return apply.SequenceT2( + Map[T1, func(T2) tuple.Tuple2[T1, T2]], + Ap[tuple.Tuple2[T1, T2], T2], + t1, + t2, + ) +} + +// SequenceSeqT2 converts 2 [ReaderIOEither[T]] into a [ReaderIOEither[tuple.Tuple2[T1, T2]]] +func SequenceSeqT2[T1, T2 any]( + t1 ReaderIOEither[T1], + t2 ReaderIOEither[T2], +) ReaderIOEither[tuple.Tuple2[T1, T2]] { + return apply.SequenceT2( + Map[T1, func(T2) tuple.Tuple2[T1, T2]], + ApSeq[tuple.Tuple2[T1, T2], T2], + t1, + t2, + ) +} + +// SequenceParT2 converts 2 [ReaderIOEither[T]] into a [ReaderIOEither[tuple.Tuple2[T1, T2]]] +func SequenceParT2[T1, T2 any]( + t1 ReaderIOEither[T1], + t2 ReaderIOEither[T2], +) ReaderIOEither[tuple.Tuple2[T1, T2]] { + return apply.SequenceT2( + Map[T1, func(T2) tuple.Tuple2[T1, T2]], + ApPar[tuple.Tuple2[T1, T2], T2], + t1, + t2, + ) +} + +// SequenceTuple2 converts a [tuple.Tuple2[ReaderIOEither[T]]] into a [ReaderIOEither[tuple.Tuple2[T1, T2]]] +func SequenceTuple2[T1, T2 any](t tuple.Tuple2[ReaderIOEither[T1], ReaderIOEither[T2]]) ReaderIOEither[tuple.Tuple2[T1, T2]] { + return apply.SequenceTuple2( + Map[T1, func(T2) tuple.Tuple2[T1, T2]], + Ap[tuple.Tuple2[T1, T2], T2], + t, + ) +} + +// SequenceSeqTuple2 converts a [tuple.Tuple2[ReaderIOEither[T]]] into a [ReaderIOEither[tuple.Tuple2[T1, T2]]] +func SequenceSeqTuple2[T1, T2 any](t tuple.Tuple2[ReaderIOEither[T1], ReaderIOEither[T2]]) ReaderIOEither[tuple.Tuple2[T1, T2]] { + return apply.SequenceTuple2( + Map[T1, func(T2) tuple.Tuple2[T1, T2]], + ApSeq[tuple.Tuple2[T1, T2], T2], + t, + ) +} + +// SequenceParTuple2 converts a [tuple.Tuple2[ReaderIOEither[T]]] into a [ReaderIOEither[tuple.Tuple2[T1, T2]]] +func SequenceParTuple2[T1, T2 any](t tuple.Tuple2[ReaderIOEither[T1], ReaderIOEither[T2]]) ReaderIOEither[tuple.Tuple2[T1, T2]] { + return apply.SequenceTuple2( + Map[T1, func(T2) tuple.Tuple2[T1, T2]], + ApPar[tuple.Tuple2[T1, T2], T2], + t, + ) +} + +// TraverseTuple2 converts a [tuple.Tuple2[A1, A2]] into a [ReaderIOEither[tuple.Tuple2[T1, T2]]] +func TraverseTuple2[F1 ~func(A1) ReaderIOEither[T1], F2 ~func(A2) ReaderIOEither[T2], T1, T2, A1, A2 any](f1 F1, f2 F2) func(tuple.Tuple2[A1, A2]) ReaderIOEither[tuple.Tuple2[T1, T2]] { + return func(t tuple.Tuple2[A1, A2]) ReaderIOEither[tuple.Tuple2[T1, T2]] { + return apply.TraverseTuple2( + Map[T1, func(T2) tuple.Tuple2[T1, T2]], + Ap[tuple.Tuple2[T1, T2], T2], + f1, + f2, + t, + ) + } +} + +// TraverseSeqTuple2 converts a [tuple.Tuple2[A1, A2]] into a [ReaderIOEither[tuple.Tuple2[T1, T2]]] +func TraverseSeqTuple2[F1 ~func(A1) ReaderIOEither[T1], F2 ~func(A2) ReaderIOEither[T2], T1, T2, A1, A2 any](f1 F1, f2 F2) func(tuple.Tuple2[A1, A2]) ReaderIOEither[tuple.Tuple2[T1, T2]] { + return func(t tuple.Tuple2[A1, A2]) ReaderIOEither[tuple.Tuple2[T1, T2]] { + return apply.TraverseTuple2( + Map[T1, func(T2) tuple.Tuple2[T1, T2]], + ApSeq[tuple.Tuple2[T1, T2], T2], + f1, + f2, + t, + ) + } +} + +// TraverseParTuple2 converts a [tuple.Tuple2[A1, A2]] into a [ReaderIOEither[tuple.Tuple2[T1, T2]]] +func TraverseParTuple2[F1 ~func(A1) ReaderIOEither[T1], F2 ~func(A2) ReaderIOEither[T2], T1, T2, A1, A2 any](f1 F1, f2 F2) func(tuple.Tuple2[A1, A2]) ReaderIOEither[tuple.Tuple2[T1, T2]] { + return func(t tuple.Tuple2[A1, A2]) ReaderIOEither[tuple.Tuple2[T1, T2]] { + return apply.TraverseTuple2( + Map[T1, func(T2) tuple.Tuple2[T1, T2]], + ApPar[tuple.Tuple2[T1, T2], T2], + f1, + f2, + t, + ) + } +} + +// Eitherize3 converts a function with 3 parameters returning a tuple into a function with 3 parameters returning a [ReaderIOEither[R]] +// The inverse function is [Uneitherize3] +func Eitherize3[F ~func(context.Context, T0, T1, T2) (R, error), T0, T1, T2, R any](f F) func(T0, T1, T2) ReaderIOEither[R] { + return G.Eitherize3[ReaderIOEither[R]](f) +} + +// Uneitherize3 converts a function with 4 parameters returning a [ReaderIOEither[R]] into a function with 3 parameters returning a tuple. +// The first parameter is considered to be the [context.Context]. +func Uneitherize3[F ~func(T0, T1, T2) ReaderIOEither[R], T0, T1, T2, R any](f F) func(context.Context, T0, T1, T2) (R, error) { + return G.Uneitherize3[ReaderIOEither[R], func(context.Context, T0, T1, T2)(R, error)](f) +} + +// SequenceT3 converts 3 [ReaderIOEither[T]] into a [ReaderIOEither[tuple.Tuple3[T1, T2, T3]]] +func SequenceT3[T1, T2, T3 any]( + t1 ReaderIOEither[T1], + t2 ReaderIOEither[T2], + t3 ReaderIOEither[T3], +) ReaderIOEither[tuple.Tuple3[T1, T2, T3]] { + return apply.SequenceT3( + Map[T1, func(T2) func(T3) tuple.Tuple3[T1, T2, T3]], + Ap[func(T3) tuple.Tuple3[T1, T2, T3], T2], + Ap[tuple.Tuple3[T1, T2, T3], T3], + t1, + t2, + t3, + ) +} + +// SequenceSeqT3 converts 3 [ReaderIOEither[T]] into a [ReaderIOEither[tuple.Tuple3[T1, T2, T3]]] +func SequenceSeqT3[T1, T2, T3 any]( + t1 ReaderIOEither[T1], + t2 ReaderIOEither[T2], + t3 ReaderIOEither[T3], +) ReaderIOEither[tuple.Tuple3[T1, T2, T3]] { + return apply.SequenceT3( + Map[T1, func(T2) func(T3) tuple.Tuple3[T1, T2, T3]], + ApSeq[func(T3) tuple.Tuple3[T1, T2, T3], T2], + ApSeq[tuple.Tuple3[T1, T2, T3], T3], + t1, + t2, + t3, + ) +} + +// SequenceParT3 converts 3 [ReaderIOEither[T]] into a [ReaderIOEither[tuple.Tuple3[T1, T2, T3]]] +func SequenceParT3[T1, T2, T3 any]( + t1 ReaderIOEither[T1], + t2 ReaderIOEither[T2], + t3 ReaderIOEither[T3], +) ReaderIOEither[tuple.Tuple3[T1, T2, T3]] { + return apply.SequenceT3( + Map[T1, func(T2) func(T3) tuple.Tuple3[T1, T2, T3]], + ApPar[func(T3) tuple.Tuple3[T1, T2, T3], T2], + ApPar[tuple.Tuple3[T1, T2, T3], T3], + t1, + t2, + t3, + ) +} + +// SequenceTuple3 converts a [tuple.Tuple3[ReaderIOEither[T]]] into a [ReaderIOEither[tuple.Tuple3[T1, T2, T3]]] +func SequenceTuple3[T1, T2, T3 any](t tuple.Tuple3[ReaderIOEither[T1], ReaderIOEither[T2], ReaderIOEither[T3]]) ReaderIOEither[tuple.Tuple3[T1, T2, T3]] { + return apply.SequenceTuple3( + Map[T1, func(T2) func(T3) tuple.Tuple3[T1, T2, T3]], + Ap[func(T3) tuple.Tuple3[T1, T2, T3], T2], + Ap[tuple.Tuple3[T1, T2, T3], T3], + t, + ) +} + +// SequenceSeqTuple3 converts a [tuple.Tuple3[ReaderIOEither[T]]] into a [ReaderIOEither[tuple.Tuple3[T1, T2, T3]]] +func SequenceSeqTuple3[T1, T2, T3 any](t tuple.Tuple3[ReaderIOEither[T1], ReaderIOEither[T2], ReaderIOEither[T3]]) ReaderIOEither[tuple.Tuple3[T1, T2, T3]] { + return apply.SequenceTuple3( + Map[T1, func(T2) func(T3) tuple.Tuple3[T1, T2, T3]], + ApSeq[func(T3) tuple.Tuple3[T1, T2, T3], T2], + ApSeq[tuple.Tuple3[T1, T2, T3], T3], + t, + ) +} + +// SequenceParTuple3 converts a [tuple.Tuple3[ReaderIOEither[T]]] into a [ReaderIOEither[tuple.Tuple3[T1, T2, T3]]] +func SequenceParTuple3[T1, T2, T3 any](t tuple.Tuple3[ReaderIOEither[T1], ReaderIOEither[T2], ReaderIOEither[T3]]) ReaderIOEither[tuple.Tuple3[T1, T2, T3]] { + return apply.SequenceTuple3( + Map[T1, func(T2) func(T3) tuple.Tuple3[T1, T2, T3]], + ApPar[func(T3) tuple.Tuple3[T1, T2, T3], T2], + ApPar[tuple.Tuple3[T1, T2, T3], T3], + t, + ) +} + +// TraverseTuple3 converts a [tuple.Tuple3[A1, A2, A3]] into a [ReaderIOEither[tuple.Tuple3[T1, T2, T3]]] +func TraverseTuple3[F1 ~func(A1) ReaderIOEither[T1], F2 ~func(A2) ReaderIOEither[T2], F3 ~func(A3) ReaderIOEither[T3], T1, T2, T3, A1, A2, A3 any](f1 F1, f2 F2, f3 F3) func(tuple.Tuple3[A1, A2, A3]) ReaderIOEither[tuple.Tuple3[T1, T2, T3]] { + return func(t tuple.Tuple3[A1, A2, A3]) ReaderIOEither[tuple.Tuple3[T1, T2, T3]] { + return apply.TraverseTuple3( + Map[T1, func(T2) func(T3) tuple.Tuple3[T1, T2, T3]], + Ap[func(T3) tuple.Tuple3[T1, T2, T3], T2], + Ap[tuple.Tuple3[T1, T2, T3], T3], + f1, + f2, + f3, + t, + ) + } +} + +// TraverseSeqTuple3 converts a [tuple.Tuple3[A1, A2, A3]] into a [ReaderIOEither[tuple.Tuple3[T1, T2, T3]]] +func TraverseSeqTuple3[F1 ~func(A1) ReaderIOEither[T1], F2 ~func(A2) ReaderIOEither[T2], F3 ~func(A3) ReaderIOEither[T3], T1, T2, T3, A1, A2, A3 any](f1 F1, f2 F2, f3 F3) func(tuple.Tuple3[A1, A2, A3]) ReaderIOEither[tuple.Tuple3[T1, T2, T3]] { + return func(t tuple.Tuple3[A1, A2, A3]) ReaderIOEither[tuple.Tuple3[T1, T2, T3]] { + return apply.TraverseTuple3( + Map[T1, func(T2) func(T3) tuple.Tuple3[T1, T2, T3]], + ApSeq[func(T3) tuple.Tuple3[T1, T2, T3], T2], + ApSeq[tuple.Tuple3[T1, T2, T3], T3], + f1, + f2, + f3, + t, + ) + } +} + +// TraverseParTuple3 converts a [tuple.Tuple3[A1, A2, A3]] into a [ReaderIOEither[tuple.Tuple3[T1, T2, T3]]] +func TraverseParTuple3[F1 ~func(A1) ReaderIOEither[T1], F2 ~func(A2) ReaderIOEither[T2], F3 ~func(A3) ReaderIOEither[T3], T1, T2, T3, A1, A2, A3 any](f1 F1, f2 F2, f3 F3) func(tuple.Tuple3[A1, A2, A3]) ReaderIOEither[tuple.Tuple3[T1, T2, T3]] { + return func(t tuple.Tuple3[A1, A2, A3]) ReaderIOEither[tuple.Tuple3[T1, T2, T3]] { + return apply.TraverseTuple3( + Map[T1, func(T2) func(T3) tuple.Tuple3[T1, T2, T3]], + ApPar[func(T3) tuple.Tuple3[T1, T2, T3], T2], + ApPar[tuple.Tuple3[T1, T2, T3], T3], + f1, + f2, + f3, + t, + ) + } +} + +// Eitherize4 converts a function with 4 parameters returning a tuple into a function with 4 parameters returning a [ReaderIOEither[R]] +// The inverse function is [Uneitherize4] +func Eitherize4[F ~func(context.Context, T0, T1, T2, T3) (R, error), T0, T1, T2, T3, R any](f F) func(T0, T1, T2, T3) ReaderIOEither[R] { + return G.Eitherize4[ReaderIOEither[R]](f) +} + +// Uneitherize4 converts a function with 5 parameters returning a [ReaderIOEither[R]] into a function with 4 parameters returning a tuple. +// The first parameter is considered to be the [context.Context]. +func Uneitherize4[F ~func(T0, T1, T2, T3) ReaderIOEither[R], T0, T1, T2, T3, R any](f F) func(context.Context, T0, T1, T2, T3) (R, error) { + return G.Uneitherize4[ReaderIOEither[R], func(context.Context, T0, T1, T2, T3)(R, error)](f) +} + +// SequenceT4 converts 4 [ReaderIOEither[T]] into a [ReaderIOEither[tuple.Tuple4[T1, T2, T3, T4]]] +func SequenceT4[T1, T2, T3, T4 any]( + t1 ReaderIOEither[T1], + t2 ReaderIOEither[T2], + t3 ReaderIOEither[T3], + t4 ReaderIOEither[T4], +) ReaderIOEither[tuple.Tuple4[T1, T2, T3, T4]] { + return apply.SequenceT4( + Map[T1, func(T2) func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4]], + Ap[func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4], T2], + Ap[func(T4) tuple.Tuple4[T1, T2, T3, T4], T3], + Ap[tuple.Tuple4[T1, T2, T3, T4], T4], + t1, + t2, + t3, + t4, + ) +} + +// SequenceSeqT4 converts 4 [ReaderIOEither[T]] into a [ReaderIOEither[tuple.Tuple4[T1, T2, T3, T4]]] +func SequenceSeqT4[T1, T2, T3, T4 any]( + t1 ReaderIOEither[T1], + t2 ReaderIOEither[T2], + t3 ReaderIOEither[T3], + t4 ReaderIOEither[T4], +) ReaderIOEither[tuple.Tuple4[T1, T2, T3, T4]] { + return apply.SequenceT4( + Map[T1, func(T2) func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4]], + ApSeq[func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4], T2], + ApSeq[func(T4) tuple.Tuple4[T1, T2, T3, T4], T3], + ApSeq[tuple.Tuple4[T1, T2, T3, T4], T4], + t1, + t2, + t3, + t4, + ) +} + +// SequenceParT4 converts 4 [ReaderIOEither[T]] into a [ReaderIOEither[tuple.Tuple4[T1, T2, T3, T4]]] +func SequenceParT4[T1, T2, T3, T4 any]( + t1 ReaderIOEither[T1], + t2 ReaderIOEither[T2], + t3 ReaderIOEither[T3], + t4 ReaderIOEither[T4], +) ReaderIOEither[tuple.Tuple4[T1, T2, T3, T4]] { + return apply.SequenceT4( + Map[T1, func(T2) func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4]], + ApPar[func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4], T2], + ApPar[func(T4) tuple.Tuple4[T1, T2, T3, T4], T3], + ApPar[tuple.Tuple4[T1, T2, T3, T4], T4], + t1, + t2, + t3, + t4, + ) +} + +// SequenceTuple4 converts a [tuple.Tuple4[ReaderIOEither[T]]] into a [ReaderIOEither[tuple.Tuple4[T1, T2, T3, T4]]] +func SequenceTuple4[T1, T2, T3, T4 any](t tuple.Tuple4[ReaderIOEither[T1], ReaderIOEither[T2], ReaderIOEither[T3], ReaderIOEither[T4]]) ReaderIOEither[tuple.Tuple4[T1, T2, T3, T4]] { + return apply.SequenceTuple4( + Map[T1, func(T2) func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4]], + Ap[func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4], T2], + Ap[func(T4) tuple.Tuple4[T1, T2, T3, T4], T3], + Ap[tuple.Tuple4[T1, T2, T3, T4], T4], + t, + ) +} + +// SequenceSeqTuple4 converts a [tuple.Tuple4[ReaderIOEither[T]]] into a [ReaderIOEither[tuple.Tuple4[T1, T2, T3, T4]]] +func SequenceSeqTuple4[T1, T2, T3, T4 any](t tuple.Tuple4[ReaderIOEither[T1], ReaderIOEither[T2], ReaderIOEither[T3], ReaderIOEither[T4]]) ReaderIOEither[tuple.Tuple4[T1, T2, T3, T4]] { + return apply.SequenceTuple4( + Map[T1, func(T2) func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4]], + ApSeq[func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4], T2], + ApSeq[func(T4) tuple.Tuple4[T1, T2, T3, T4], T3], + ApSeq[tuple.Tuple4[T1, T2, T3, T4], T4], + t, + ) +} + +// SequenceParTuple4 converts a [tuple.Tuple4[ReaderIOEither[T]]] into a [ReaderIOEither[tuple.Tuple4[T1, T2, T3, T4]]] +func SequenceParTuple4[T1, T2, T3, T4 any](t tuple.Tuple4[ReaderIOEither[T1], ReaderIOEither[T2], ReaderIOEither[T3], ReaderIOEither[T4]]) ReaderIOEither[tuple.Tuple4[T1, T2, T3, T4]] { + return apply.SequenceTuple4( + Map[T1, func(T2) func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4]], + ApPar[func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4], T2], + ApPar[func(T4) tuple.Tuple4[T1, T2, T3, T4], T3], + ApPar[tuple.Tuple4[T1, T2, T3, T4], T4], + t, + ) +} + +// TraverseTuple4 converts a [tuple.Tuple4[A1, A2, A3, A4]] into a [ReaderIOEither[tuple.Tuple4[T1, T2, T3, T4]]] +func TraverseTuple4[F1 ~func(A1) ReaderIOEither[T1], F2 ~func(A2) ReaderIOEither[T2], F3 ~func(A3) ReaderIOEither[T3], F4 ~func(A4) ReaderIOEither[T4], T1, T2, T3, T4, A1, A2, A3, A4 any](f1 F1, f2 F2, f3 F3, f4 F4) func(tuple.Tuple4[A1, A2, A3, A4]) ReaderIOEither[tuple.Tuple4[T1, T2, T3, T4]] { + return func(t tuple.Tuple4[A1, A2, A3, A4]) ReaderIOEither[tuple.Tuple4[T1, T2, T3, T4]] { + return apply.TraverseTuple4( + Map[T1, func(T2) func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4]], + Ap[func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4], T2], + Ap[func(T4) tuple.Tuple4[T1, T2, T3, T4], T3], + Ap[tuple.Tuple4[T1, T2, T3, T4], T4], + f1, + f2, + f3, + f4, + t, + ) + } +} + +// TraverseSeqTuple4 converts a [tuple.Tuple4[A1, A2, A3, A4]] into a [ReaderIOEither[tuple.Tuple4[T1, T2, T3, T4]]] +func TraverseSeqTuple4[F1 ~func(A1) ReaderIOEither[T1], F2 ~func(A2) ReaderIOEither[T2], F3 ~func(A3) ReaderIOEither[T3], F4 ~func(A4) ReaderIOEither[T4], T1, T2, T3, T4, A1, A2, A3, A4 any](f1 F1, f2 F2, f3 F3, f4 F4) func(tuple.Tuple4[A1, A2, A3, A4]) ReaderIOEither[tuple.Tuple4[T1, T2, T3, T4]] { + return func(t tuple.Tuple4[A1, A2, A3, A4]) ReaderIOEither[tuple.Tuple4[T1, T2, T3, T4]] { + return apply.TraverseTuple4( + Map[T1, func(T2) func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4]], + ApSeq[func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4], T2], + ApSeq[func(T4) tuple.Tuple4[T1, T2, T3, T4], T3], + ApSeq[tuple.Tuple4[T1, T2, T3, T4], T4], + f1, + f2, + f3, + f4, + t, + ) + } +} + +// TraverseParTuple4 converts a [tuple.Tuple4[A1, A2, A3, A4]] into a [ReaderIOEither[tuple.Tuple4[T1, T2, T3, T4]]] +func TraverseParTuple4[F1 ~func(A1) ReaderIOEither[T1], F2 ~func(A2) ReaderIOEither[T2], F3 ~func(A3) ReaderIOEither[T3], F4 ~func(A4) ReaderIOEither[T4], T1, T2, T3, T4, A1, A2, A3, A4 any](f1 F1, f2 F2, f3 F3, f4 F4) func(tuple.Tuple4[A1, A2, A3, A4]) ReaderIOEither[tuple.Tuple4[T1, T2, T3, T4]] { + return func(t tuple.Tuple4[A1, A2, A3, A4]) ReaderIOEither[tuple.Tuple4[T1, T2, T3, T4]] { + return apply.TraverseTuple4( + Map[T1, func(T2) func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4]], + ApPar[func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4], T2], + ApPar[func(T4) tuple.Tuple4[T1, T2, T3, T4], T3], + ApPar[tuple.Tuple4[T1, T2, T3, T4], T4], + f1, + f2, + f3, + f4, + t, + ) + } +} + +// Eitherize5 converts a function with 5 parameters returning a tuple into a function with 5 parameters returning a [ReaderIOEither[R]] +// The inverse function is [Uneitherize5] +func Eitherize5[F ~func(context.Context, T0, T1, T2, T3, T4) (R, error), T0, T1, T2, T3, T4, R any](f F) func(T0, T1, T2, T3, T4) ReaderIOEither[R] { + return G.Eitherize5[ReaderIOEither[R]](f) +} + +// Uneitherize5 converts a function with 6 parameters returning a [ReaderIOEither[R]] into a function with 5 parameters returning a tuple. +// The first parameter is considered to be the [context.Context]. +func Uneitherize5[F ~func(T0, T1, T2, T3, T4) ReaderIOEither[R], T0, T1, T2, T3, T4, R any](f F) func(context.Context, T0, T1, T2, T3, T4) (R, error) { + return G.Uneitherize5[ReaderIOEither[R], func(context.Context, T0, T1, T2, T3, T4)(R, error)](f) +} + +// SequenceT5 converts 5 [ReaderIOEither[T]] into a [ReaderIOEither[tuple.Tuple5[T1, T2, T3, T4, T5]]] +func SequenceT5[T1, T2, T3, T4, T5 any]( + t1 ReaderIOEither[T1], + t2 ReaderIOEither[T2], + t3 ReaderIOEither[T3], + t4 ReaderIOEither[T4], + t5 ReaderIOEither[T5], +) ReaderIOEither[tuple.Tuple5[T1, T2, T3, T4, T5]] { + return apply.SequenceT5( + Map[T1, func(T2) func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5]], + Ap[func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T2], + Ap[func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T3], + Ap[func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T4], + Ap[tuple.Tuple5[T1, T2, T3, T4, T5], T5], + t1, + t2, + t3, + t4, + t5, + ) +} + +// SequenceSeqT5 converts 5 [ReaderIOEither[T]] into a [ReaderIOEither[tuple.Tuple5[T1, T2, T3, T4, T5]]] +func SequenceSeqT5[T1, T2, T3, T4, T5 any]( + t1 ReaderIOEither[T1], + t2 ReaderIOEither[T2], + t3 ReaderIOEither[T3], + t4 ReaderIOEither[T4], + t5 ReaderIOEither[T5], +) ReaderIOEither[tuple.Tuple5[T1, T2, T3, T4, T5]] { + return apply.SequenceT5( + Map[T1, func(T2) func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5]], + ApSeq[func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T2], + ApSeq[func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T3], + ApSeq[func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T4], + ApSeq[tuple.Tuple5[T1, T2, T3, T4, T5], T5], + t1, + t2, + t3, + t4, + t5, + ) +} + +// SequenceParT5 converts 5 [ReaderIOEither[T]] into a [ReaderIOEither[tuple.Tuple5[T1, T2, T3, T4, T5]]] +func SequenceParT5[T1, T2, T3, T4, T5 any]( + t1 ReaderIOEither[T1], + t2 ReaderIOEither[T2], + t3 ReaderIOEither[T3], + t4 ReaderIOEither[T4], + t5 ReaderIOEither[T5], +) ReaderIOEither[tuple.Tuple5[T1, T2, T3, T4, T5]] { + return apply.SequenceT5( + Map[T1, func(T2) func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5]], + ApPar[func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T2], + ApPar[func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T3], + ApPar[func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T4], + ApPar[tuple.Tuple5[T1, T2, T3, T4, T5], T5], + t1, + t2, + t3, + t4, + t5, + ) +} + +// SequenceTuple5 converts a [tuple.Tuple5[ReaderIOEither[T]]] into a [ReaderIOEither[tuple.Tuple5[T1, T2, T3, T4, T5]]] +func SequenceTuple5[T1, T2, T3, T4, T5 any](t tuple.Tuple5[ReaderIOEither[T1], ReaderIOEither[T2], ReaderIOEither[T3], ReaderIOEither[T4], ReaderIOEither[T5]]) ReaderIOEither[tuple.Tuple5[T1, T2, T3, T4, T5]] { + return apply.SequenceTuple5( + Map[T1, func(T2) func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5]], + Ap[func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T2], + Ap[func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T3], + Ap[func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T4], + Ap[tuple.Tuple5[T1, T2, T3, T4, T5], T5], + t, + ) +} + +// SequenceSeqTuple5 converts a [tuple.Tuple5[ReaderIOEither[T]]] into a [ReaderIOEither[tuple.Tuple5[T1, T2, T3, T4, T5]]] +func SequenceSeqTuple5[T1, T2, T3, T4, T5 any](t tuple.Tuple5[ReaderIOEither[T1], ReaderIOEither[T2], ReaderIOEither[T3], ReaderIOEither[T4], ReaderIOEither[T5]]) ReaderIOEither[tuple.Tuple5[T1, T2, T3, T4, T5]] { + return apply.SequenceTuple5( + Map[T1, func(T2) func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5]], + ApSeq[func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T2], + ApSeq[func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T3], + ApSeq[func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T4], + ApSeq[tuple.Tuple5[T1, T2, T3, T4, T5], T5], + t, + ) +} + +// SequenceParTuple5 converts a [tuple.Tuple5[ReaderIOEither[T]]] into a [ReaderIOEither[tuple.Tuple5[T1, T2, T3, T4, T5]]] +func SequenceParTuple5[T1, T2, T3, T4, T5 any](t tuple.Tuple5[ReaderIOEither[T1], ReaderIOEither[T2], ReaderIOEither[T3], ReaderIOEither[T4], ReaderIOEither[T5]]) ReaderIOEither[tuple.Tuple5[T1, T2, T3, T4, T5]] { + return apply.SequenceTuple5( + Map[T1, func(T2) func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5]], + ApPar[func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T2], + ApPar[func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T3], + ApPar[func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T4], + ApPar[tuple.Tuple5[T1, T2, T3, T4, T5], T5], + t, + ) +} + +// TraverseTuple5 converts a [tuple.Tuple5[A1, A2, A3, A4, A5]] into a [ReaderIOEither[tuple.Tuple5[T1, T2, T3, T4, T5]]] +func TraverseTuple5[F1 ~func(A1) ReaderIOEither[T1], F2 ~func(A2) ReaderIOEither[T2], F3 ~func(A3) ReaderIOEither[T3], F4 ~func(A4) ReaderIOEither[T4], F5 ~func(A5) ReaderIOEither[T5], T1, T2, T3, T4, T5, A1, A2, A3, A4, A5 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5) func(tuple.Tuple5[A1, A2, A3, A4, A5]) ReaderIOEither[tuple.Tuple5[T1, T2, T3, T4, T5]] { + return func(t tuple.Tuple5[A1, A2, A3, A4, A5]) ReaderIOEither[tuple.Tuple5[T1, T2, T3, T4, T5]] { + return apply.TraverseTuple5( + Map[T1, func(T2) func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5]], + Ap[func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T2], + Ap[func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T3], + Ap[func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T4], + Ap[tuple.Tuple5[T1, T2, T3, T4, T5], T5], + f1, + f2, + f3, + f4, + f5, + t, + ) + } +} + +// TraverseSeqTuple5 converts a [tuple.Tuple5[A1, A2, A3, A4, A5]] into a [ReaderIOEither[tuple.Tuple5[T1, T2, T3, T4, T5]]] +func TraverseSeqTuple5[F1 ~func(A1) ReaderIOEither[T1], F2 ~func(A2) ReaderIOEither[T2], F3 ~func(A3) ReaderIOEither[T3], F4 ~func(A4) ReaderIOEither[T4], F5 ~func(A5) ReaderIOEither[T5], T1, T2, T3, T4, T5, A1, A2, A3, A4, A5 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5) func(tuple.Tuple5[A1, A2, A3, A4, A5]) ReaderIOEither[tuple.Tuple5[T1, T2, T3, T4, T5]] { + return func(t tuple.Tuple5[A1, A2, A3, A4, A5]) ReaderIOEither[tuple.Tuple5[T1, T2, T3, T4, T5]] { + return apply.TraverseTuple5( + Map[T1, func(T2) func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5]], + ApSeq[func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T2], + ApSeq[func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T3], + ApSeq[func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T4], + ApSeq[tuple.Tuple5[T1, T2, T3, T4, T5], T5], + f1, + f2, + f3, + f4, + f5, + t, + ) + } +} + +// TraverseParTuple5 converts a [tuple.Tuple5[A1, A2, A3, A4, A5]] into a [ReaderIOEither[tuple.Tuple5[T1, T2, T3, T4, T5]]] +func TraverseParTuple5[F1 ~func(A1) ReaderIOEither[T1], F2 ~func(A2) ReaderIOEither[T2], F3 ~func(A3) ReaderIOEither[T3], F4 ~func(A4) ReaderIOEither[T4], F5 ~func(A5) ReaderIOEither[T5], T1, T2, T3, T4, T5, A1, A2, A3, A4, A5 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5) func(tuple.Tuple5[A1, A2, A3, A4, A5]) ReaderIOEither[tuple.Tuple5[T1, T2, T3, T4, T5]] { + return func(t tuple.Tuple5[A1, A2, A3, A4, A5]) ReaderIOEither[tuple.Tuple5[T1, T2, T3, T4, T5]] { + return apply.TraverseTuple5( + Map[T1, func(T2) func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5]], + ApPar[func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T2], + ApPar[func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T3], + ApPar[func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T4], + ApPar[tuple.Tuple5[T1, T2, T3, T4, T5], T5], + f1, + f2, + f3, + f4, + f5, + t, + ) + } +} + +// Eitherize6 converts a function with 6 parameters returning a tuple into a function with 6 parameters returning a [ReaderIOEither[R]] +// The inverse function is [Uneitherize6] +func Eitherize6[F ~func(context.Context, T0, T1, T2, T3, T4, T5) (R, error), T0, T1, T2, T3, T4, T5, R any](f F) func(T0, T1, T2, T3, T4, T5) ReaderIOEither[R] { + return G.Eitherize6[ReaderIOEither[R]](f) +} + +// Uneitherize6 converts a function with 7 parameters returning a [ReaderIOEither[R]] into a function with 6 parameters returning a tuple. +// The first parameter is considered to be the [context.Context]. +func Uneitherize6[F ~func(T0, T1, T2, T3, T4, T5) ReaderIOEither[R], T0, T1, T2, T3, T4, T5, R any](f F) func(context.Context, T0, T1, T2, T3, T4, T5) (R, error) { + return G.Uneitherize6[ReaderIOEither[R], func(context.Context, T0, T1, T2, T3, T4, T5)(R, error)](f) +} + +// SequenceT6 converts 6 [ReaderIOEither[T]] into a [ReaderIOEither[tuple.Tuple6[T1, T2, T3, T4, T5, T6]]] +func SequenceT6[T1, T2, T3, T4, T5, T6 any]( + t1 ReaderIOEither[T1], + t2 ReaderIOEither[T2], + t3 ReaderIOEither[T3], + t4 ReaderIOEither[T4], + t5 ReaderIOEither[T5], + t6 ReaderIOEither[T6], +) ReaderIOEither[tuple.Tuple6[T1, T2, T3, T4, T5, T6]] { + return apply.SequenceT6( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6]], + Ap[func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T2], + Ap[func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T3], + Ap[func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T4], + Ap[func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T5], + Ap[tuple.Tuple6[T1, T2, T3, T4, T5, T6], T6], + t1, + t2, + t3, + t4, + t5, + t6, + ) +} + +// SequenceSeqT6 converts 6 [ReaderIOEither[T]] into a [ReaderIOEither[tuple.Tuple6[T1, T2, T3, T4, T5, T6]]] +func SequenceSeqT6[T1, T2, T3, T4, T5, T6 any]( + t1 ReaderIOEither[T1], + t2 ReaderIOEither[T2], + t3 ReaderIOEither[T3], + t4 ReaderIOEither[T4], + t5 ReaderIOEither[T5], + t6 ReaderIOEither[T6], +) ReaderIOEither[tuple.Tuple6[T1, T2, T3, T4, T5, T6]] { + return apply.SequenceT6( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6]], + ApSeq[func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T2], + ApSeq[func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T3], + ApSeq[func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T4], + ApSeq[func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T5], + ApSeq[tuple.Tuple6[T1, T2, T3, T4, T5, T6], T6], + t1, + t2, + t3, + t4, + t5, + t6, + ) +} + +// SequenceParT6 converts 6 [ReaderIOEither[T]] into a [ReaderIOEither[tuple.Tuple6[T1, T2, T3, T4, T5, T6]]] +func SequenceParT6[T1, T2, T3, T4, T5, T6 any]( + t1 ReaderIOEither[T1], + t2 ReaderIOEither[T2], + t3 ReaderIOEither[T3], + t4 ReaderIOEither[T4], + t5 ReaderIOEither[T5], + t6 ReaderIOEither[T6], +) ReaderIOEither[tuple.Tuple6[T1, T2, T3, T4, T5, T6]] { + return apply.SequenceT6( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6]], + ApPar[func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T2], + ApPar[func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T3], + ApPar[func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T4], + ApPar[func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T5], + ApPar[tuple.Tuple6[T1, T2, T3, T4, T5, T6], T6], + t1, + t2, + t3, + t4, + t5, + t6, + ) +} + +// SequenceTuple6 converts a [tuple.Tuple6[ReaderIOEither[T]]] into a [ReaderIOEither[tuple.Tuple6[T1, T2, T3, T4, T5, T6]]] +func SequenceTuple6[T1, T2, T3, T4, T5, T6 any](t tuple.Tuple6[ReaderIOEither[T1], ReaderIOEither[T2], ReaderIOEither[T3], ReaderIOEither[T4], ReaderIOEither[T5], ReaderIOEither[T6]]) ReaderIOEither[tuple.Tuple6[T1, T2, T3, T4, T5, T6]] { + return apply.SequenceTuple6( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6]], + Ap[func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T2], + Ap[func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T3], + Ap[func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T4], + Ap[func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T5], + Ap[tuple.Tuple6[T1, T2, T3, T4, T5, T6], T6], + t, + ) +} + +// SequenceSeqTuple6 converts a [tuple.Tuple6[ReaderIOEither[T]]] into a [ReaderIOEither[tuple.Tuple6[T1, T2, T3, T4, T5, T6]]] +func SequenceSeqTuple6[T1, T2, T3, T4, T5, T6 any](t tuple.Tuple6[ReaderIOEither[T1], ReaderIOEither[T2], ReaderIOEither[T3], ReaderIOEither[T4], ReaderIOEither[T5], ReaderIOEither[T6]]) ReaderIOEither[tuple.Tuple6[T1, T2, T3, T4, T5, T6]] { + return apply.SequenceTuple6( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6]], + ApSeq[func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T2], + ApSeq[func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T3], + ApSeq[func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T4], + ApSeq[func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T5], + ApSeq[tuple.Tuple6[T1, T2, T3, T4, T5, T6], T6], + t, + ) +} + +// SequenceParTuple6 converts a [tuple.Tuple6[ReaderIOEither[T]]] into a [ReaderIOEither[tuple.Tuple6[T1, T2, T3, T4, T5, T6]]] +func SequenceParTuple6[T1, T2, T3, T4, T5, T6 any](t tuple.Tuple6[ReaderIOEither[T1], ReaderIOEither[T2], ReaderIOEither[T3], ReaderIOEither[T4], ReaderIOEither[T5], ReaderIOEither[T6]]) ReaderIOEither[tuple.Tuple6[T1, T2, T3, T4, T5, T6]] { + return apply.SequenceTuple6( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6]], + ApPar[func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T2], + ApPar[func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T3], + ApPar[func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T4], + ApPar[func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T5], + ApPar[tuple.Tuple6[T1, T2, T3, T4, T5, T6], T6], + t, + ) +} + +// TraverseTuple6 converts a [tuple.Tuple6[A1, A2, A3, A4, A5, A6]] into a [ReaderIOEither[tuple.Tuple6[T1, T2, T3, T4, T5, T6]]] +func TraverseTuple6[F1 ~func(A1) ReaderIOEither[T1], F2 ~func(A2) ReaderIOEither[T2], F3 ~func(A3) ReaderIOEither[T3], F4 ~func(A4) ReaderIOEither[T4], F5 ~func(A5) ReaderIOEither[T5], F6 ~func(A6) ReaderIOEither[T6], T1, T2, T3, T4, T5, T6, A1, A2, A3, A4, A5, A6 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6) func(tuple.Tuple6[A1, A2, A3, A4, A5, A6]) ReaderIOEither[tuple.Tuple6[T1, T2, T3, T4, T5, T6]] { + return func(t tuple.Tuple6[A1, A2, A3, A4, A5, A6]) ReaderIOEither[tuple.Tuple6[T1, T2, T3, T4, T5, T6]] { + return apply.TraverseTuple6( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6]], + Ap[func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T2], + Ap[func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T3], + Ap[func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T4], + Ap[func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T5], + Ap[tuple.Tuple6[T1, T2, T3, T4, T5, T6], T6], + f1, + f2, + f3, + f4, + f5, + f6, + t, + ) + } +} + +// TraverseSeqTuple6 converts a [tuple.Tuple6[A1, A2, A3, A4, A5, A6]] into a [ReaderIOEither[tuple.Tuple6[T1, T2, T3, T4, T5, T6]]] +func TraverseSeqTuple6[F1 ~func(A1) ReaderIOEither[T1], F2 ~func(A2) ReaderIOEither[T2], F3 ~func(A3) ReaderIOEither[T3], F4 ~func(A4) ReaderIOEither[T4], F5 ~func(A5) ReaderIOEither[T5], F6 ~func(A6) ReaderIOEither[T6], T1, T2, T3, T4, T5, T6, A1, A2, A3, A4, A5, A6 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6) func(tuple.Tuple6[A1, A2, A3, A4, A5, A6]) ReaderIOEither[tuple.Tuple6[T1, T2, T3, T4, T5, T6]] { + return func(t tuple.Tuple6[A1, A2, A3, A4, A5, A6]) ReaderIOEither[tuple.Tuple6[T1, T2, T3, T4, T5, T6]] { + return apply.TraverseTuple6( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6]], + ApSeq[func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T2], + ApSeq[func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T3], + ApSeq[func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T4], + ApSeq[func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T5], + ApSeq[tuple.Tuple6[T1, T2, T3, T4, T5, T6], T6], + f1, + f2, + f3, + f4, + f5, + f6, + t, + ) + } +} + +// TraverseParTuple6 converts a [tuple.Tuple6[A1, A2, A3, A4, A5, A6]] into a [ReaderIOEither[tuple.Tuple6[T1, T2, T3, T4, T5, T6]]] +func TraverseParTuple6[F1 ~func(A1) ReaderIOEither[T1], F2 ~func(A2) ReaderIOEither[T2], F3 ~func(A3) ReaderIOEither[T3], F4 ~func(A4) ReaderIOEither[T4], F5 ~func(A5) ReaderIOEither[T5], F6 ~func(A6) ReaderIOEither[T6], T1, T2, T3, T4, T5, T6, A1, A2, A3, A4, A5, A6 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6) func(tuple.Tuple6[A1, A2, A3, A4, A5, A6]) ReaderIOEither[tuple.Tuple6[T1, T2, T3, T4, T5, T6]] { + return func(t tuple.Tuple6[A1, A2, A3, A4, A5, A6]) ReaderIOEither[tuple.Tuple6[T1, T2, T3, T4, T5, T6]] { + return apply.TraverseTuple6( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6]], + ApPar[func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T2], + ApPar[func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T3], + ApPar[func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T4], + ApPar[func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T5], + ApPar[tuple.Tuple6[T1, T2, T3, T4, T5, T6], T6], + f1, + f2, + f3, + f4, + f5, + f6, + t, + ) + } +} + +// Eitherize7 converts a function with 7 parameters returning a tuple into a function with 7 parameters returning a [ReaderIOEither[R]] +// The inverse function is [Uneitherize7] +func Eitherize7[F ~func(context.Context, T0, T1, T2, T3, T4, T5, T6) (R, error), T0, T1, T2, T3, T4, T5, T6, R any](f F) func(T0, T1, T2, T3, T4, T5, T6) ReaderIOEither[R] { + return G.Eitherize7[ReaderIOEither[R]](f) +} + +// Uneitherize7 converts a function with 8 parameters returning a [ReaderIOEither[R]] into a function with 7 parameters returning a tuple. +// The first parameter is considered to be the [context.Context]. +func Uneitherize7[F ~func(T0, T1, T2, T3, T4, T5, T6) ReaderIOEither[R], T0, T1, T2, T3, T4, T5, T6, R any](f F) func(context.Context, T0, T1, T2, T3, T4, T5, T6) (R, error) { + return G.Uneitherize7[ReaderIOEither[R], func(context.Context, T0, T1, T2, T3, T4, T5, T6)(R, error)](f) +} + +// SequenceT7 converts 7 [ReaderIOEither[T]] into a [ReaderIOEither[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]]] +func SequenceT7[T1, T2, T3, T4, T5, T6, T7 any]( + t1 ReaderIOEither[T1], + t2 ReaderIOEither[T2], + t3 ReaderIOEither[T3], + t4 ReaderIOEither[T4], + t5 ReaderIOEither[T5], + t6 ReaderIOEither[T6], + t7 ReaderIOEither[T7], +) ReaderIOEither[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return apply.SequenceT7( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T2], + Ap[func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T3], + Ap[func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T4], + Ap[func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T5], + Ap[func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T6], + Ap[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T7], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + ) +} + +// SequenceSeqT7 converts 7 [ReaderIOEither[T]] into a [ReaderIOEither[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]]] +func SequenceSeqT7[T1, T2, T3, T4, T5, T6, T7 any]( + t1 ReaderIOEither[T1], + t2 ReaderIOEither[T2], + t3 ReaderIOEither[T3], + t4 ReaderIOEither[T4], + t5 ReaderIOEither[T5], + t6 ReaderIOEither[T6], + t7 ReaderIOEither[T7], +) ReaderIOEither[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return apply.SequenceT7( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]], + ApSeq[func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T2], + ApSeq[func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T3], + ApSeq[func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T4], + ApSeq[func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T5], + ApSeq[func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T6], + ApSeq[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T7], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + ) +} + +// SequenceParT7 converts 7 [ReaderIOEither[T]] into a [ReaderIOEither[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]]] +func SequenceParT7[T1, T2, T3, T4, T5, T6, T7 any]( + t1 ReaderIOEither[T1], + t2 ReaderIOEither[T2], + t3 ReaderIOEither[T3], + t4 ReaderIOEither[T4], + t5 ReaderIOEither[T5], + t6 ReaderIOEither[T6], + t7 ReaderIOEither[T7], +) ReaderIOEither[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return apply.SequenceT7( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]], + ApPar[func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T2], + ApPar[func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T3], + ApPar[func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T4], + ApPar[func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T5], + ApPar[func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T6], + ApPar[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T7], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + ) +} + +// SequenceTuple7 converts a [tuple.Tuple7[ReaderIOEither[T]]] into a [ReaderIOEither[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]]] +func SequenceTuple7[T1, T2, T3, T4, T5, T6, T7 any](t tuple.Tuple7[ReaderIOEither[T1], ReaderIOEither[T2], ReaderIOEither[T3], ReaderIOEither[T4], ReaderIOEither[T5], ReaderIOEither[T6], ReaderIOEither[T7]]) ReaderIOEither[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return apply.SequenceTuple7( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T2], + Ap[func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T3], + Ap[func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T4], + Ap[func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T5], + Ap[func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T6], + Ap[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T7], + t, + ) +} + +// SequenceSeqTuple7 converts a [tuple.Tuple7[ReaderIOEither[T]]] into a [ReaderIOEither[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]]] +func SequenceSeqTuple7[T1, T2, T3, T4, T5, T6, T7 any](t tuple.Tuple7[ReaderIOEither[T1], ReaderIOEither[T2], ReaderIOEither[T3], ReaderIOEither[T4], ReaderIOEither[T5], ReaderIOEither[T6], ReaderIOEither[T7]]) ReaderIOEither[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return apply.SequenceTuple7( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]], + ApSeq[func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T2], + ApSeq[func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T3], + ApSeq[func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T4], + ApSeq[func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T5], + ApSeq[func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T6], + ApSeq[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T7], + t, + ) +} + +// SequenceParTuple7 converts a [tuple.Tuple7[ReaderIOEither[T]]] into a [ReaderIOEither[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]]] +func SequenceParTuple7[T1, T2, T3, T4, T5, T6, T7 any](t tuple.Tuple7[ReaderIOEither[T1], ReaderIOEither[T2], ReaderIOEither[T3], ReaderIOEither[T4], ReaderIOEither[T5], ReaderIOEither[T6], ReaderIOEither[T7]]) ReaderIOEither[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return apply.SequenceTuple7( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]], + ApPar[func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T2], + ApPar[func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T3], + ApPar[func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T4], + ApPar[func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T5], + ApPar[func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T6], + ApPar[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T7], + t, + ) +} + +// TraverseTuple7 converts a [tuple.Tuple7[A1, A2, A3, A4, A5, A6, A7]] into a [ReaderIOEither[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]]] +func TraverseTuple7[F1 ~func(A1) ReaderIOEither[T1], F2 ~func(A2) ReaderIOEither[T2], F3 ~func(A3) ReaderIOEither[T3], F4 ~func(A4) ReaderIOEither[T4], F5 ~func(A5) ReaderIOEither[T5], F6 ~func(A6) ReaderIOEither[T6], F7 ~func(A7) ReaderIOEither[T7], T1, T2, T3, T4, T5, T6, T7, A1, A2, A3, A4, A5, A6, A7 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7) func(tuple.Tuple7[A1, A2, A3, A4, A5, A6, A7]) ReaderIOEither[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return func(t tuple.Tuple7[A1, A2, A3, A4, A5, A6, A7]) ReaderIOEither[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return apply.TraverseTuple7( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T2], + Ap[func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T3], + Ap[func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T4], + Ap[func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T5], + Ap[func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T6], + Ap[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T7], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + t, + ) + } +} + +// TraverseSeqTuple7 converts a [tuple.Tuple7[A1, A2, A3, A4, A5, A6, A7]] into a [ReaderIOEither[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]]] +func TraverseSeqTuple7[F1 ~func(A1) ReaderIOEither[T1], F2 ~func(A2) ReaderIOEither[T2], F3 ~func(A3) ReaderIOEither[T3], F4 ~func(A4) ReaderIOEither[T4], F5 ~func(A5) ReaderIOEither[T5], F6 ~func(A6) ReaderIOEither[T6], F7 ~func(A7) ReaderIOEither[T7], T1, T2, T3, T4, T5, T6, T7, A1, A2, A3, A4, A5, A6, A7 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7) func(tuple.Tuple7[A1, A2, A3, A4, A5, A6, A7]) ReaderIOEither[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return func(t tuple.Tuple7[A1, A2, A3, A4, A5, A6, A7]) ReaderIOEither[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return apply.TraverseTuple7( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]], + ApSeq[func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T2], + ApSeq[func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T3], + ApSeq[func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T4], + ApSeq[func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T5], + ApSeq[func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T6], + ApSeq[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T7], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + t, + ) + } +} + +// TraverseParTuple7 converts a [tuple.Tuple7[A1, A2, A3, A4, A5, A6, A7]] into a [ReaderIOEither[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]]] +func TraverseParTuple7[F1 ~func(A1) ReaderIOEither[T1], F2 ~func(A2) ReaderIOEither[T2], F3 ~func(A3) ReaderIOEither[T3], F4 ~func(A4) ReaderIOEither[T4], F5 ~func(A5) ReaderIOEither[T5], F6 ~func(A6) ReaderIOEither[T6], F7 ~func(A7) ReaderIOEither[T7], T1, T2, T3, T4, T5, T6, T7, A1, A2, A3, A4, A5, A6, A7 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7) func(tuple.Tuple7[A1, A2, A3, A4, A5, A6, A7]) ReaderIOEither[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return func(t tuple.Tuple7[A1, A2, A3, A4, A5, A6, A7]) ReaderIOEither[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return apply.TraverseTuple7( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]], + ApPar[func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T2], + ApPar[func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T3], + ApPar[func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T4], + ApPar[func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T5], + ApPar[func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T6], + ApPar[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T7], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + t, + ) + } +} + +// Eitherize8 converts a function with 8 parameters returning a tuple into a function with 8 parameters returning a [ReaderIOEither[R]] +// The inverse function is [Uneitherize8] +func Eitherize8[F ~func(context.Context, T0, T1, T2, T3, T4, T5, T6, T7) (R, error), T0, T1, T2, T3, T4, T5, T6, T7, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7) ReaderIOEither[R] { + return G.Eitherize8[ReaderIOEither[R]](f) +} + +// Uneitherize8 converts a function with 9 parameters returning a [ReaderIOEither[R]] into a function with 8 parameters returning a tuple. +// The first parameter is considered to be the [context.Context]. +func Uneitherize8[F ~func(T0, T1, T2, T3, T4, T5, T6, T7) ReaderIOEither[R], T0, T1, T2, T3, T4, T5, T6, T7, R any](f F) func(context.Context, T0, T1, T2, T3, T4, T5, T6, T7) (R, error) { + return G.Uneitherize8[ReaderIOEither[R], func(context.Context, T0, T1, T2, T3, T4, T5, T6, T7)(R, error)](f) +} + +// SequenceT8 converts 8 [ReaderIOEither[T]] into a [ReaderIOEither[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]]] +func SequenceT8[T1, T2, T3, T4, T5, T6, T7, T8 any]( + t1 ReaderIOEither[T1], + t2 ReaderIOEither[T2], + t3 ReaderIOEither[T3], + t4 ReaderIOEither[T4], + t5 ReaderIOEither[T5], + t6 ReaderIOEither[T6], + t7 ReaderIOEither[T7], + t8 ReaderIOEither[T8], +) ReaderIOEither[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return apply.SequenceT8( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T3], + Ap[func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T4], + Ap[func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T5], + Ap[func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T6], + Ap[func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T7], + Ap[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T8], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + t8, + ) +} + +// SequenceSeqT8 converts 8 [ReaderIOEither[T]] into a [ReaderIOEither[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]]] +func SequenceSeqT8[T1, T2, T3, T4, T5, T6, T7, T8 any]( + t1 ReaderIOEither[T1], + t2 ReaderIOEither[T2], + t3 ReaderIOEither[T3], + t4 ReaderIOEither[T4], + t5 ReaderIOEither[T5], + t6 ReaderIOEither[T6], + t7 ReaderIOEither[T7], + t8 ReaderIOEither[T8], +) ReaderIOEither[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return apply.SequenceT8( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]], + ApSeq[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T2], + ApSeq[func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T3], + ApSeq[func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T4], + ApSeq[func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T5], + ApSeq[func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T6], + ApSeq[func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T7], + ApSeq[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T8], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + t8, + ) +} + +// SequenceParT8 converts 8 [ReaderIOEither[T]] into a [ReaderIOEither[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]]] +func SequenceParT8[T1, T2, T3, T4, T5, T6, T7, T8 any]( + t1 ReaderIOEither[T1], + t2 ReaderIOEither[T2], + t3 ReaderIOEither[T3], + t4 ReaderIOEither[T4], + t5 ReaderIOEither[T5], + t6 ReaderIOEither[T6], + t7 ReaderIOEither[T7], + t8 ReaderIOEither[T8], +) ReaderIOEither[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return apply.SequenceT8( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]], + ApPar[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T2], + ApPar[func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T3], + ApPar[func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T4], + ApPar[func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T5], + ApPar[func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T6], + ApPar[func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T7], + ApPar[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T8], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + t8, + ) +} + +// SequenceTuple8 converts a [tuple.Tuple8[ReaderIOEither[T]]] into a [ReaderIOEither[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]]] +func SequenceTuple8[T1, T2, T3, T4, T5, T6, T7, T8 any](t tuple.Tuple8[ReaderIOEither[T1], ReaderIOEither[T2], ReaderIOEither[T3], ReaderIOEither[T4], ReaderIOEither[T5], ReaderIOEither[T6], ReaderIOEither[T7], ReaderIOEither[T8]]) ReaderIOEither[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return apply.SequenceTuple8( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T3], + Ap[func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T4], + Ap[func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T5], + Ap[func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T6], + Ap[func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T7], + Ap[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T8], + t, + ) +} + +// SequenceSeqTuple8 converts a [tuple.Tuple8[ReaderIOEither[T]]] into a [ReaderIOEither[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]]] +func SequenceSeqTuple8[T1, T2, T3, T4, T5, T6, T7, T8 any](t tuple.Tuple8[ReaderIOEither[T1], ReaderIOEither[T2], ReaderIOEither[T3], ReaderIOEither[T4], ReaderIOEither[T5], ReaderIOEither[T6], ReaderIOEither[T7], ReaderIOEither[T8]]) ReaderIOEither[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return apply.SequenceTuple8( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]], + ApSeq[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T2], + ApSeq[func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T3], + ApSeq[func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T4], + ApSeq[func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T5], + ApSeq[func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T6], + ApSeq[func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T7], + ApSeq[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T8], + t, + ) +} + +// SequenceParTuple8 converts a [tuple.Tuple8[ReaderIOEither[T]]] into a [ReaderIOEither[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]]] +func SequenceParTuple8[T1, T2, T3, T4, T5, T6, T7, T8 any](t tuple.Tuple8[ReaderIOEither[T1], ReaderIOEither[T2], ReaderIOEither[T3], ReaderIOEither[T4], ReaderIOEither[T5], ReaderIOEither[T6], ReaderIOEither[T7], ReaderIOEither[T8]]) ReaderIOEither[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return apply.SequenceTuple8( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]], + ApPar[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T2], + ApPar[func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T3], + ApPar[func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T4], + ApPar[func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T5], + ApPar[func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T6], + ApPar[func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T7], + ApPar[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T8], + t, + ) +} + +// TraverseTuple8 converts a [tuple.Tuple8[A1, A2, A3, A4, A5, A6, A7, A8]] into a [ReaderIOEither[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]]] +func TraverseTuple8[F1 ~func(A1) ReaderIOEither[T1], F2 ~func(A2) ReaderIOEither[T2], F3 ~func(A3) ReaderIOEither[T3], F4 ~func(A4) ReaderIOEither[T4], F5 ~func(A5) ReaderIOEither[T5], F6 ~func(A6) ReaderIOEither[T6], F7 ~func(A7) ReaderIOEither[T7], F8 ~func(A8) ReaderIOEither[T8], T1, T2, T3, T4, T5, T6, T7, T8, A1, A2, A3, A4, A5, A6, A7, A8 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8) func(tuple.Tuple8[A1, A2, A3, A4, A5, A6, A7, A8]) ReaderIOEither[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return func(t tuple.Tuple8[A1, A2, A3, A4, A5, A6, A7, A8]) ReaderIOEither[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return apply.TraverseTuple8( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T3], + Ap[func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T4], + Ap[func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T5], + Ap[func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T6], + Ap[func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T7], + Ap[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T8], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + f8, + t, + ) + } +} + +// TraverseSeqTuple8 converts a [tuple.Tuple8[A1, A2, A3, A4, A5, A6, A7, A8]] into a [ReaderIOEither[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]]] +func TraverseSeqTuple8[F1 ~func(A1) ReaderIOEither[T1], F2 ~func(A2) ReaderIOEither[T2], F3 ~func(A3) ReaderIOEither[T3], F4 ~func(A4) ReaderIOEither[T4], F5 ~func(A5) ReaderIOEither[T5], F6 ~func(A6) ReaderIOEither[T6], F7 ~func(A7) ReaderIOEither[T7], F8 ~func(A8) ReaderIOEither[T8], T1, T2, T3, T4, T5, T6, T7, T8, A1, A2, A3, A4, A5, A6, A7, A8 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8) func(tuple.Tuple8[A1, A2, A3, A4, A5, A6, A7, A8]) ReaderIOEither[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return func(t tuple.Tuple8[A1, A2, A3, A4, A5, A6, A7, A8]) ReaderIOEither[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return apply.TraverseTuple8( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]], + ApSeq[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T2], + ApSeq[func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T3], + ApSeq[func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T4], + ApSeq[func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T5], + ApSeq[func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T6], + ApSeq[func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T7], + ApSeq[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T8], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + f8, + t, + ) + } +} + +// TraverseParTuple8 converts a [tuple.Tuple8[A1, A2, A3, A4, A5, A6, A7, A8]] into a [ReaderIOEither[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]]] +func TraverseParTuple8[F1 ~func(A1) ReaderIOEither[T1], F2 ~func(A2) ReaderIOEither[T2], F3 ~func(A3) ReaderIOEither[T3], F4 ~func(A4) ReaderIOEither[T4], F5 ~func(A5) ReaderIOEither[T5], F6 ~func(A6) ReaderIOEither[T6], F7 ~func(A7) ReaderIOEither[T7], F8 ~func(A8) ReaderIOEither[T8], T1, T2, T3, T4, T5, T6, T7, T8, A1, A2, A3, A4, A5, A6, A7, A8 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8) func(tuple.Tuple8[A1, A2, A3, A4, A5, A6, A7, A8]) ReaderIOEither[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return func(t tuple.Tuple8[A1, A2, A3, A4, A5, A6, A7, A8]) ReaderIOEither[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return apply.TraverseTuple8( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]], + ApPar[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T2], + ApPar[func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T3], + ApPar[func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T4], + ApPar[func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T5], + ApPar[func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T6], + ApPar[func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T7], + ApPar[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T8], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + f8, + t, + ) + } +} + +// Eitherize9 converts a function with 9 parameters returning a tuple into a function with 9 parameters returning a [ReaderIOEither[R]] +// The inverse function is [Uneitherize9] +func Eitherize9[F ~func(context.Context, T0, T1, T2, T3, T4, T5, T6, T7, T8) (R, error), T0, T1, T2, T3, T4, T5, T6, T7, T8, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7, T8) ReaderIOEither[R] { + return G.Eitherize9[ReaderIOEither[R]](f) +} + +// Uneitherize9 converts a function with 10 parameters returning a [ReaderIOEither[R]] into a function with 9 parameters returning a tuple. +// The first parameter is considered to be the [context.Context]. +func Uneitherize9[F ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8) ReaderIOEither[R], T0, T1, T2, T3, T4, T5, T6, T7, T8, R any](f F) func(context.Context, T0, T1, T2, T3, T4, T5, T6, T7, T8) (R, error) { + return G.Uneitherize9[ReaderIOEither[R], func(context.Context, T0, T1, T2, T3, T4, T5, T6, T7, T8)(R, error)](f) +} + +// SequenceT9 converts 9 [ReaderIOEither[T]] into a [ReaderIOEither[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]]] +func SequenceT9[T1, T2, T3, T4, T5, T6, T7, T8, T9 any]( + t1 ReaderIOEither[T1], + t2 ReaderIOEither[T2], + t3 ReaderIOEither[T3], + t4 ReaderIOEither[T4], + t5 ReaderIOEither[T5], + t6 ReaderIOEither[T6], + t7 ReaderIOEither[T7], + t8 ReaderIOEither[T8], + t9 ReaderIOEither[T9], +) ReaderIOEither[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return apply.SequenceT9( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T3], + Ap[func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T4], + Ap[func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T5], + Ap[func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T6], + Ap[func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T7], + Ap[func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T8], + Ap[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T9], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + t8, + t9, + ) +} + +// SequenceSeqT9 converts 9 [ReaderIOEither[T]] into a [ReaderIOEither[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]]] +func SequenceSeqT9[T1, T2, T3, T4, T5, T6, T7, T8, T9 any]( + t1 ReaderIOEither[T1], + t2 ReaderIOEither[T2], + t3 ReaderIOEither[T3], + t4 ReaderIOEither[T4], + t5 ReaderIOEither[T5], + t6 ReaderIOEither[T6], + t7 ReaderIOEither[T7], + t8 ReaderIOEither[T8], + t9 ReaderIOEither[T9], +) ReaderIOEither[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return apply.SequenceT9( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]], + ApSeq[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T2], + ApSeq[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T3], + ApSeq[func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T4], + ApSeq[func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T5], + ApSeq[func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T6], + ApSeq[func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T7], + ApSeq[func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T8], + ApSeq[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T9], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + t8, + t9, + ) +} + +// SequenceParT9 converts 9 [ReaderIOEither[T]] into a [ReaderIOEither[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]]] +func SequenceParT9[T1, T2, T3, T4, T5, T6, T7, T8, T9 any]( + t1 ReaderIOEither[T1], + t2 ReaderIOEither[T2], + t3 ReaderIOEither[T3], + t4 ReaderIOEither[T4], + t5 ReaderIOEither[T5], + t6 ReaderIOEither[T6], + t7 ReaderIOEither[T7], + t8 ReaderIOEither[T8], + t9 ReaderIOEither[T9], +) ReaderIOEither[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return apply.SequenceT9( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]], + ApPar[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T2], + ApPar[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T3], + ApPar[func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T4], + ApPar[func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T5], + ApPar[func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T6], + ApPar[func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T7], + ApPar[func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T8], + ApPar[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T9], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + t8, + t9, + ) +} + +// SequenceTuple9 converts a [tuple.Tuple9[ReaderIOEither[T]]] into a [ReaderIOEither[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]]] +func SequenceTuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9 any](t tuple.Tuple9[ReaderIOEither[T1], ReaderIOEither[T2], ReaderIOEither[T3], ReaderIOEither[T4], ReaderIOEither[T5], ReaderIOEither[T6], ReaderIOEither[T7], ReaderIOEither[T8], ReaderIOEither[T9]]) ReaderIOEither[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return apply.SequenceTuple9( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T3], + Ap[func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T4], + Ap[func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T5], + Ap[func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T6], + Ap[func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T7], + Ap[func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T8], + Ap[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T9], + t, + ) +} + +// SequenceSeqTuple9 converts a [tuple.Tuple9[ReaderIOEither[T]]] into a [ReaderIOEither[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]]] +func SequenceSeqTuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9 any](t tuple.Tuple9[ReaderIOEither[T1], ReaderIOEither[T2], ReaderIOEither[T3], ReaderIOEither[T4], ReaderIOEither[T5], ReaderIOEither[T6], ReaderIOEither[T7], ReaderIOEither[T8], ReaderIOEither[T9]]) ReaderIOEither[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return apply.SequenceTuple9( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]], + ApSeq[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T2], + ApSeq[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T3], + ApSeq[func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T4], + ApSeq[func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T5], + ApSeq[func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T6], + ApSeq[func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T7], + ApSeq[func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T8], + ApSeq[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T9], + t, + ) +} + +// SequenceParTuple9 converts a [tuple.Tuple9[ReaderIOEither[T]]] into a [ReaderIOEither[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]]] +func SequenceParTuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9 any](t tuple.Tuple9[ReaderIOEither[T1], ReaderIOEither[T2], ReaderIOEither[T3], ReaderIOEither[T4], ReaderIOEither[T5], ReaderIOEither[T6], ReaderIOEither[T7], ReaderIOEither[T8], ReaderIOEither[T9]]) ReaderIOEither[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return apply.SequenceTuple9( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]], + ApPar[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T2], + ApPar[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T3], + ApPar[func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T4], + ApPar[func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T5], + ApPar[func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T6], + ApPar[func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T7], + ApPar[func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T8], + ApPar[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T9], + t, + ) +} + +// TraverseTuple9 converts a [tuple.Tuple9[A1, A2, A3, A4, A5, A6, A7, A8, A9]] into a [ReaderIOEither[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]]] +func TraverseTuple9[F1 ~func(A1) ReaderIOEither[T1], F2 ~func(A2) ReaderIOEither[T2], F3 ~func(A3) ReaderIOEither[T3], F4 ~func(A4) ReaderIOEither[T4], F5 ~func(A5) ReaderIOEither[T5], F6 ~func(A6) ReaderIOEither[T6], F7 ~func(A7) ReaderIOEither[T7], F8 ~func(A8) ReaderIOEither[T8], F9 ~func(A9) ReaderIOEither[T9], T1, T2, T3, T4, T5, T6, T7, T8, T9, A1, A2, A3, A4, A5, A6, A7, A8, A9 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9) func(tuple.Tuple9[A1, A2, A3, A4, A5, A6, A7, A8, A9]) ReaderIOEither[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return func(t tuple.Tuple9[A1, A2, A3, A4, A5, A6, A7, A8, A9]) ReaderIOEither[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return apply.TraverseTuple9( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T3], + Ap[func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T4], + Ap[func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T5], + Ap[func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T6], + Ap[func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T7], + Ap[func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T8], + Ap[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T9], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + f8, + f9, + t, + ) + } +} + +// TraverseSeqTuple9 converts a [tuple.Tuple9[A1, A2, A3, A4, A5, A6, A7, A8, A9]] into a [ReaderIOEither[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]]] +func TraverseSeqTuple9[F1 ~func(A1) ReaderIOEither[T1], F2 ~func(A2) ReaderIOEither[T2], F3 ~func(A3) ReaderIOEither[T3], F4 ~func(A4) ReaderIOEither[T4], F5 ~func(A5) ReaderIOEither[T5], F6 ~func(A6) ReaderIOEither[T6], F7 ~func(A7) ReaderIOEither[T7], F8 ~func(A8) ReaderIOEither[T8], F9 ~func(A9) ReaderIOEither[T9], T1, T2, T3, T4, T5, T6, T7, T8, T9, A1, A2, A3, A4, A5, A6, A7, A8, A9 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9) func(tuple.Tuple9[A1, A2, A3, A4, A5, A6, A7, A8, A9]) ReaderIOEither[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return func(t tuple.Tuple9[A1, A2, A3, A4, A5, A6, A7, A8, A9]) ReaderIOEither[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return apply.TraverseTuple9( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]], + ApSeq[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T2], + ApSeq[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T3], + ApSeq[func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T4], + ApSeq[func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T5], + ApSeq[func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T6], + ApSeq[func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T7], + ApSeq[func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T8], + ApSeq[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T9], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + f8, + f9, + t, + ) + } +} + +// TraverseParTuple9 converts a [tuple.Tuple9[A1, A2, A3, A4, A5, A6, A7, A8, A9]] into a [ReaderIOEither[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]]] +func TraverseParTuple9[F1 ~func(A1) ReaderIOEither[T1], F2 ~func(A2) ReaderIOEither[T2], F3 ~func(A3) ReaderIOEither[T3], F4 ~func(A4) ReaderIOEither[T4], F5 ~func(A5) ReaderIOEither[T5], F6 ~func(A6) ReaderIOEither[T6], F7 ~func(A7) ReaderIOEither[T7], F8 ~func(A8) ReaderIOEither[T8], F9 ~func(A9) ReaderIOEither[T9], T1, T2, T3, T4, T5, T6, T7, T8, T9, A1, A2, A3, A4, A5, A6, A7, A8, A9 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9) func(tuple.Tuple9[A1, A2, A3, A4, A5, A6, A7, A8, A9]) ReaderIOEither[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return func(t tuple.Tuple9[A1, A2, A3, A4, A5, A6, A7, A8, A9]) ReaderIOEither[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return apply.TraverseTuple9( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]], + ApPar[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T2], + ApPar[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T3], + ApPar[func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T4], + ApPar[func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T5], + ApPar[func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T6], + ApPar[func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T7], + ApPar[func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T8], + ApPar[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T9], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + f8, + f9, + t, + ) + } +} + +// Eitherize10 converts a function with 10 parameters returning a tuple into a function with 10 parameters returning a [ReaderIOEither[R]] +// The inverse function is [Uneitherize10] +func Eitherize10[F ~func(context.Context, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9) (R, error), T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9) ReaderIOEither[R] { + return G.Eitherize10[ReaderIOEither[R]](f) +} + +// Uneitherize10 converts a function with 11 parameters returning a [ReaderIOEither[R]] into a function with 10 parameters returning a tuple. +// The first parameter is considered to be the [context.Context]. +func Uneitherize10[F ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9) ReaderIOEither[R], T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, R any](f F) func(context.Context, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9) (R, error) { + return G.Uneitherize10[ReaderIOEither[R], func(context.Context, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9)(R, error)](f) +} + +// SequenceT10 converts 10 [ReaderIOEither[T]] into a [ReaderIOEither[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]]] +func SequenceT10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any]( + t1 ReaderIOEither[T1], + t2 ReaderIOEither[T2], + t3 ReaderIOEither[T3], + t4 ReaderIOEither[T4], + t5 ReaderIOEither[T5], + t6 ReaderIOEither[T6], + t7 ReaderIOEither[T7], + t8 ReaderIOEither[T8], + t9 ReaderIOEither[T9], + t10 ReaderIOEither[T10], +) ReaderIOEither[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return apply.SequenceT10( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T3], + Ap[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T4], + Ap[func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T5], + Ap[func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T6], + Ap[func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T7], + Ap[func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T8], + Ap[func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T9], + Ap[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T10], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + t8, + t9, + t10, + ) +} + +// SequenceSeqT10 converts 10 [ReaderIOEither[T]] into a [ReaderIOEither[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]]] +func SequenceSeqT10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any]( + t1 ReaderIOEither[T1], + t2 ReaderIOEither[T2], + t3 ReaderIOEither[T3], + t4 ReaderIOEither[T4], + t5 ReaderIOEither[T5], + t6 ReaderIOEither[T6], + t7 ReaderIOEither[T7], + t8 ReaderIOEither[T8], + t9 ReaderIOEither[T9], + t10 ReaderIOEither[T10], +) ReaderIOEither[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return apply.SequenceT10( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]], + ApSeq[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T2], + ApSeq[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T3], + ApSeq[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T4], + ApSeq[func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T5], + ApSeq[func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T6], + ApSeq[func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T7], + ApSeq[func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T8], + ApSeq[func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T9], + ApSeq[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T10], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + t8, + t9, + t10, + ) +} + +// SequenceParT10 converts 10 [ReaderIOEither[T]] into a [ReaderIOEither[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]]] +func SequenceParT10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any]( + t1 ReaderIOEither[T1], + t2 ReaderIOEither[T2], + t3 ReaderIOEither[T3], + t4 ReaderIOEither[T4], + t5 ReaderIOEither[T5], + t6 ReaderIOEither[T6], + t7 ReaderIOEither[T7], + t8 ReaderIOEither[T8], + t9 ReaderIOEither[T9], + t10 ReaderIOEither[T10], +) ReaderIOEither[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return apply.SequenceT10( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]], + ApPar[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T2], + ApPar[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T3], + ApPar[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T4], + ApPar[func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T5], + ApPar[func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T6], + ApPar[func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T7], + ApPar[func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T8], + ApPar[func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T9], + ApPar[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T10], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + t8, + t9, + t10, + ) +} + +// SequenceTuple10 converts a [tuple.Tuple10[ReaderIOEither[T]]] into a [ReaderIOEither[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]]] +func SequenceTuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any](t tuple.Tuple10[ReaderIOEither[T1], ReaderIOEither[T2], ReaderIOEither[T3], ReaderIOEither[T4], ReaderIOEither[T5], ReaderIOEither[T6], ReaderIOEither[T7], ReaderIOEither[T8], ReaderIOEither[T9], ReaderIOEither[T10]]) ReaderIOEither[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return apply.SequenceTuple10( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T3], + Ap[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T4], + Ap[func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T5], + Ap[func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T6], + Ap[func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T7], + Ap[func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T8], + Ap[func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T9], + Ap[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T10], + t, + ) +} + +// SequenceSeqTuple10 converts a [tuple.Tuple10[ReaderIOEither[T]]] into a [ReaderIOEither[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]]] +func SequenceSeqTuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any](t tuple.Tuple10[ReaderIOEither[T1], ReaderIOEither[T2], ReaderIOEither[T3], ReaderIOEither[T4], ReaderIOEither[T5], ReaderIOEither[T6], ReaderIOEither[T7], ReaderIOEither[T8], ReaderIOEither[T9], ReaderIOEither[T10]]) ReaderIOEither[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return apply.SequenceTuple10( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]], + ApSeq[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T2], + ApSeq[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T3], + ApSeq[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T4], + ApSeq[func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T5], + ApSeq[func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T6], + ApSeq[func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T7], + ApSeq[func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T8], + ApSeq[func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T9], + ApSeq[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T10], + t, + ) +} + +// SequenceParTuple10 converts a [tuple.Tuple10[ReaderIOEither[T]]] into a [ReaderIOEither[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]]] +func SequenceParTuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any](t tuple.Tuple10[ReaderIOEither[T1], ReaderIOEither[T2], ReaderIOEither[T3], ReaderIOEither[T4], ReaderIOEither[T5], ReaderIOEither[T6], ReaderIOEither[T7], ReaderIOEither[T8], ReaderIOEither[T9], ReaderIOEither[T10]]) ReaderIOEither[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return apply.SequenceTuple10( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]], + ApPar[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T2], + ApPar[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T3], + ApPar[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T4], + ApPar[func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T5], + ApPar[func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T6], + ApPar[func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T7], + ApPar[func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T8], + ApPar[func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T9], + ApPar[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T10], + t, + ) +} + +// TraverseTuple10 converts a [tuple.Tuple10[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10]] into a [ReaderIOEither[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]]] +func TraverseTuple10[F1 ~func(A1) ReaderIOEither[T1], F2 ~func(A2) ReaderIOEither[T2], F3 ~func(A3) ReaderIOEither[T3], F4 ~func(A4) ReaderIOEither[T4], F5 ~func(A5) ReaderIOEither[T5], F6 ~func(A6) ReaderIOEither[T6], F7 ~func(A7) ReaderIOEither[T7], F8 ~func(A8) ReaderIOEither[T8], F9 ~func(A9) ReaderIOEither[T9], F10 ~func(A10) ReaderIOEither[T10], T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10) func(tuple.Tuple10[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10]) ReaderIOEither[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return func(t tuple.Tuple10[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10]) ReaderIOEither[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return apply.TraverseTuple10( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T3], + Ap[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T4], + Ap[func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T5], + Ap[func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T6], + Ap[func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T7], + Ap[func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T8], + Ap[func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T9], + Ap[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T10], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + f8, + f9, + f10, + t, + ) + } +} + +// TraverseSeqTuple10 converts a [tuple.Tuple10[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10]] into a [ReaderIOEither[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]]] +func TraverseSeqTuple10[F1 ~func(A1) ReaderIOEither[T1], F2 ~func(A2) ReaderIOEither[T2], F3 ~func(A3) ReaderIOEither[T3], F4 ~func(A4) ReaderIOEither[T4], F5 ~func(A5) ReaderIOEither[T5], F6 ~func(A6) ReaderIOEither[T6], F7 ~func(A7) ReaderIOEither[T7], F8 ~func(A8) ReaderIOEither[T8], F9 ~func(A9) ReaderIOEither[T9], F10 ~func(A10) ReaderIOEither[T10], T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10) func(tuple.Tuple10[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10]) ReaderIOEither[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return func(t tuple.Tuple10[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10]) ReaderIOEither[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return apply.TraverseTuple10( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]], + ApSeq[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T2], + ApSeq[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T3], + ApSeq[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T4], + ApSeq[func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T5], + ApSeq[func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T6], + ApSeq[func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T7], + ApSeq[func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T8], + ApSeq[func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T9], + ApSeq[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T10], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + f8, + f9, + f10, + t, + ) + } +} + +// TraverseParTuple10 converts a [tuple.Tuple10[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10]] into a [ReaderIOEither[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]]] +func TraverseParTuple10[F1 ~func(A1) ReaderIOEither[T1], F2 ~func(A2) ReaderIOEither[T2], F3 ~func(A3) ReaderIOEither[T3], F4 ~func(A4) ReaderIOEither[T4], F5 ~func(A5) ReaderIOEither[T5], F6 ~func(A6) ReaderIOEither[T6], F7 ~func(A7) ReaderIOEither[T7], F8 ~func(A8) ReaderIOEither[T8], F9 ~func(A9) ReaderIOEither[T9], F10 ~func(A10) ReaderIOEither[T10], T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10) func(tuple.Tuple10[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10]) ReaderIOEither[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return func(t tuple.Tuple10[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10]) ReaderIOEither[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return apply.TraverseTuple10( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]], + ApPar[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T2], + ApPar[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T3], + ApPar[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T4], + ApPar[func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T5], + ApPar[func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T6], + ApPar[func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T7], + ApPar[func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T8], + ApPar[func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T9], + ApPar[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T10], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + f8, + f9, + f10, + t, + ) + } +} diff --git a/v2/context/readerioeither/generic/gen.go b/v2/context/readerioeither/generic/gen.go new file mode 100644 index 0000000..d4cbc1e --- /dev/null +++ b/v2/context/readerioeither/generic/gen.go @@ -0,0 +1,167 @@ +package generic + +// Code generated by go generate; DO NOT EDIT. +// This file was generated by robots at +// 2025-03-09 23:52:48.5677388 +0100 CET m=+0.002067301 + + +import ( + "context" + + E "github.com/IBM/fp-go/v2/either" + RE "github.com/IBM/fp-go/v2/readerioeither/generic" +) + +// Eitherize0 converts a function with 0 parameters returning a tuple into a function with 0 parameters returning a [GRA] +// The inverse function is [Uneitherize0] +func Eitherize0[GRA ~func(context.Context) GIOA, F ~func(context.Context) (R, error), GIOA ~func() E.Either[error, R], R any](f F) func() GRA { + return RE.Eitherize0[GRA](f) +} + +// Uneitherize0 converts a function with 0 parameters returning a [GRA] into a function with 0 parameters returning a tuple. +// The first parameter is considered to be the [context.Context]. +func Uneitherize0[GRA ~func(context.Context) GIOA, F ~func(context.Context) (R, error), GIOA ~func() E.Either[error, R], R any](f func() GRA) F { + return func(c context.Context) (R, error) { + return E.UnwrapError(f()(c)()) + } +} + +// Eitherize1 converts a function with 1 parameters returning a tuple into a function with 1 parameters returning a [GRA] +// The inverse function is [Uneitherize1] +func Eitherize1[GRA ~func(context.Context) GIOA, F ~func(context.Context, T0) (R, error), GIOA ~func() E.Either[error, R], T0, R any](f F) func(T0) GRA { + return RE.Eitherize1[GRA](f) +} + +// Uneitherize1 converts a function with 1 parameters returning a [GRA] into a function with 1 parameters returning a tuple. +// The first parameter is considered to be the [context.Context]. +func Uneitherize1[GRA ~func(context.Context) GIOA, F ~func(context.Context, T0) (R, error), GIOA ~func() E.Either[error, R], T0, R any](f func(T0) GRA) F { + return func(c context.Context, t0 T0) (R, error) { + return E.UnwrapError(f(t0)(c)()) + } +} + +// Eitherize2 converts a function with 2 parameters returning a tuple into a function with 2 parameters returning a [GRA] +// The inverse function is [Uneitherize2] +func Eitherize2[GRA ~func(context.Context) GIOA, F ~func(context.Context, T0, T1) (R, error), GIOA ~func() E.Either[error, R], T0, T1, R any](f F) func(T0, T1) GRA { + return RE.Eitherize2[GRA](f) +} + +// Uneitherize2 converts a function with 2 parameters returning a [GRA] into a function with 2 parameters returning a tuple. +// The first parameter is considered to be the [context.Context]. +func Uneitherize2[GRA ~func(context.Context) GIOA, F ~func(context.Context, T0, T1) (R, error), GIOA ~func() E.Either[error, R], T0, T1, R any](f func(T0, T1) GRA) F { + return func(c context.Context, t0 T0, t1 T1) (R, error) { + return E.UnwrapError(f(t0, t1)(c)()) + } +} + +// Eitherize3 converts a function with 3 parameters returning a tuple into a function with 3 parameters returning a [GRA] +// The inverse function is [Uneitherize3] +func Eitherize3[GRA ~func(context.Context) GIOA, F ~func(context.Context, T0, T1, T2) (R, error), GIOA ~func() E.Either[error, R], T0, T1, T2, R any](f F) func(T0, T1, T2) GRA { + return RE.Eitherize3[GRA](f) +} + +// Uneitherize3 converts a function with 3 parameters returning a [GRA] into a function with 3 parameters returning a tuple. +// The first parameter is considered to be the [context.Context]. +func Uneitherize3[GRA ~func(context.Context) GIOA, F ~func(context.Context, T0, T1, T2) (R, error), GIOA ~func() E.Either[error, R], T0, T1, T2, R any](f func(T0, T1, T2) GRA) F { + return func(c context.Context, t0 T0, t1 T1, t2 T2) (R, error) { + return E.UnwrapError(f(t0, t1, t2)(c)()) + } +} + +// Eitherize4 converts a function with 4 parameters returning a tuple into a function with 4 parameters returning a [GRA] +// The inverse function is [Uneitherize4] +func Eitherize4[GRA ~func(context.Context) GIOA, F ~func(context.Context, T0, T1, T2, T3) (R, error), GIOA ~func() E.Either[error, R], T0, T1, T2, T3, R any](f F) func(T0, T1, T2, T3) GRA { + return RE.Eitherize4[GRA](f) +} + +// Uneitherize4 converts a function with 4 parameters returning a [GRA] into a function with 4 parameters returning a tuple. +// The first parameter is considered to be the [context.Context]. +func Uneitherize4[GRA ~func(context.Context) GIOA, F ~func(context.Context, T0, T1, T2, T3) (R, error), GIOA ~func() E.Either[error, R], T0, T1, T2, T3, R any](f func(T0, T1, T2, T3) GRA) F { + return func(c context.Context, t0 T0, t1 T1, t2 T2, t3 T3) (R, error) { + return E.UnwrapError(f(t0, t1, t2, t3)(c)()) + } +} + +// Eitherize5 converts a function with 5 parameters returning a tuple into a function with 5 parameters returning a [GRA] +// The inverse function is [Uneitherize5] +func Eitherize5[GRA ~func(context.Context) GIOA, F ~func(context.Context, T0, T1, T2, T3, T4) (R, error), GIOA ~func() E.Either[error, R], T0, T1, T2, T3, T4, R any](f F) func(T0, T1, T2, T3, T4) GRA { + return RE.Eitherize5[GRA](f) +} + +// Uneitherize5 converts a function with 5 parameters returning a [GRA] into a function with 5 parameters returning a tuple. +// The first parameter is considered to be the [context.Context]. +func Uneitherize5[GRA ~func(context.Context) GIOA, F ~func(context.Context, T0, T1, T2, T3, T4) (R, error), GIOA ~func() E.Either[error, R], T0, T1, T2, T3, T4, R any](f func(T0, T1, T2, T3, T4) GRA) F { + return func(c context.Context, t0 T0, t1 T1, t2 T2, t3 T3, t4 T4) (R, error) { + return E.UnwrapError(f(t0, t1, t2, t3, t4)(c)()) + } +} + +// Eitherize6 converts a function with 6 parameters returning a tuple into a function with 6 parameters returning a [GRA] +// The inverse function is [Uneitherize6] +func Eitherize6[GRA ~func(context.Context) GIOA, F ~func(context.Context, T0, T1, T2, T3, T4, T5) (R, error), GIOA ~func() E.Either[error, R], T0, T1, T2, T3, T4, T5, R any](f F) func(T0, T1, T2, T3, T4, T5) GRA { + return RE.Eitherize6[GRA](f) +} + +// Uneitherize6 converts a function with 6 parameters returning a [GRA] into a function with 6 parameters returning a tuple. +// The first parameter is considered to be the [context.Context]. +func Uneitherize6[GRA ~func(context.Context) GIOA, F ~func(context.Context, T0, T1, T2, T3, T4, T5) (R, error), GIOA ~func() E.Either[error, R], T0, T1, T2, T3, T4, T5, R any](f func(T0, T1, T2, T3, T4, T5) GRA) F { + return func(c context.Context, t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5) (R, error) { + return E.UnwrapError(f(t0, t1, t2, t3, t4, t5)(c)()) + } +} + +// Eitherize7 converts a function with 7 parameters returning a tuple into a function with 7 parameters returning a [GRA] +// The inverse function is [Uneitherize7] +func Eitherize7[GRA ~func(context.Context) GIOA, F ~func(context.Context, T0, T1, T2, T3, T4, T5, T6) (R, error), GIOA ~func() E.Either[error, R], T0, T1, T2, T3, T4, T5, T6, R any](f F) func(T0, T1, T2, T3, T4, T5, T6) GRA { + return RE.Eitherize7[GRA](f) +} + +// Uneitherize7 converts a function with 7 parameters returning a [GRA] into a function with 7 parameters returning a tuple. +// The first parameter is considered to be the [context.Context]. +func Uneitherize7[GRA ~func(context.Context) GIOA, F ~func(context.Context, T0, T1, T2, T3, T4, T5, T6) (R, error), GIOA ~func() E.Either[error, R], T0, T1, T2, T3, T4, T5, T6, R any](f func(T0, T1, T2, T3, T4, T5, T6) GRA) F { + return func(c context.Context, t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6) (R, error) { + return E.UnwrapError(f(t0, t1, t2, t3, t4, t5, t6)(c)()) + } +} + +// Eitherize8 converts a function with 8 parameters returning a tuple into a function with 8 parameters returning a [GRA] +// The inverse function is [Uneitherize8] +func Eitherize8[GRA ~func(context.Context) GIOA, F ~func(context.Context, T0, T1, T2, T3, T4, T5, T6, T7) (R, error), GIOA ~func() E.Either[error, R], T0, T1, T2, T3, T4, T5, T6, T7, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7) GRA { + return RE.Eitherize8[GRA](f) +} + +// Uneitherize8 converts a function with 8 parameters returning a [GRA] into a function with 8 parameters returning a tuple. +// The first parameter is considered to be the [context.Context]. +func Uneitherize8[GRA ~func(context.Context) GIOA, F ~func(context.Context, T0, T1, T2, T3, T4, T5, T6, T7) (R, error), GIOA ~func() E.Either[error, R], T0, T1, T2, T3, T4, T5, T6, T7, R any](f func(T0, T1, T2, T3, T4, T5, T6, T7) GRA) F { + return func(c context.Context, t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7) (R, error) { + return E.UnwrapError(f(t0, t1, t2, t3, t4, t5, t6, t7)(c)()) + } +} + +// Eitherize9 converts a function with 9 parameters returning a tuple into a function with 9 parameters returning a [GRA] +// The inverse function is [Uneitherize9] +func Eitherize9[GRA ~func(context.Context) GIOA, F ~func(context.Context, T0, T1, T2, T3, T4, T5, T6, T7, T8) (R, error), GIOA ~func() E.Either[error, R], T0, T1, T2, T3, T4, T5, T6, T7, T8, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7, T8) GRA { + return RE.Eitherize9[GRA](f) +} + +// Uneitherize9 converts a function with 9 parameters returning a [GRA] into a function with 9 parameters returning a tuple. +// The first parameter is considered to be the [context.Context]. +func Uneitherize9[GRA ~func(context.Context) GIOA, F ~func(context.Context, T0, T1, T2, T3, T4, T5, T6, T7, T8) (R, error), GIOA ~func() E.Either[error, R], T0, T1, T2, T3, T4, T5, T6, T7, T8, R any](f func(T0, T1, T2, T3, T4, T5, T6, T7, T8) GRA) F { + return func(c context.Context, t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8) (R, error) { + return E.UnwrapError(f(t0, t1, t2, t3, t4, t5, t6, t7, t8)(c)()) + } +} + +// Eitherize10 converts a function with 10 parameters returning a tuple into a function with 10 parameters returning a [GRA] +// The inverse function is [Uneitherize10] +func Eitherize10[GRA ~func(context.Context) GIOA, F ~func(context.Context, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9) (R, error), GIOA ~func() E.Either[error, R], T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9) GRA { + return RE.Eitherize10[GRA](f) +} + +// Uneitherize10 converts a function with 10 parameters returning a [GRA] into a function with 10 parameters returning a tuple. +// The first parameter is considered to be the [context.Context]. +func Uneitherize10[GRA ~func(context.Context) GIOA, F ~func(context.Context, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9) (R, error), GIOA ~func() E.Either[error, R], T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, R any](f func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9) GRA) F { + return func(c context.Context, t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9) (R, error) { + return E.UnwrapError(f(t0, t1, t2, t3, t4, t5, t6, t7, t8, t9)(c)()) + } +} diff --git a/v2/context/readerioeither/http/builder/builder.go b/v2/context/readerioeither/http/builder/builder.go new file mode 100644 index 0000000..cbd3ec8 --- /dev/null +++ b/v2/context/readerioeither/http/builder/builder.go @@ -0,0 +1,72 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package builder + +import ( + "bytes" + "context" + "net/http" + "strconv" + + RIOE "github.com/IBM/fp-go/v2/context/readerioeither" + RIOEH "github.com/IBM/fp-go/v2/context/readerioeither/http" + E "github.com/IBM/fp-go/v2/either" + F "github.com/IBM/fp-go/v2/function" + R "github.com/IBM/fp-go/v2/http/builder" + H "github.com/IBM/fp-go/v2/http/headers" + LZ "github.com/IBM/fp-go/v2/lazy" + O "github.com/IBM/fp-go/v2/option" +) + +func Requester(builder *R.Builder) RIOEH.Requester { + + withBody := F.Curry3(func(data []byte, url string, method string) RIOE.ReaderIOEither[*http.Request] { + return RIOE.TryCatch(func(ctx context.Context) func() (*http.Request, error) { + return func() (*http.Request, error) { + req, err := http.NewRequestWithContext(ctx, method, url, bytes.NewReader(data)) + if err == nil { + req.Header.Set(H.ContentLength, strconv.Itoa(len(data))) + H.Monoid.Concat(req.Header, builder.GetHeaders()) + } + return req, err + } + }) + }) + + withoutBody := F.Curry2(func(url string, method string) RIOE.ReaderIOEither[*http.Request] { + return RIOE.TryCatch(func(ctx context.Context) func() (*http.Request, error) { + return func() (*http.Request, error) { + req, err := http.NewRequestWithContext(ctx, method, url, nil) + if err == nil { + H.Monoid.Concat(req.Header, builder.GetHeaders()) + } + return req, err + } + }) + }) + + return F.Pipe5( + builder.GetBody(), + O.Fold(LZ.Of(E.Of[error](withoutBody)), E.Map[error](withBody)), + E.Ap[func(string) RIOE.ReaderIOEither[*http.Request]](builder.GetTargetURL()), + E.Flap[error, RIOE.ReaderIOEither[*http.Request]](builder.GetMethod()), + E.GetOrElse(RIOE.Left[*http.Request]), + RIOE.Map(func(req *http.Request) *http.Request { + req.Header = H.Monoid.Concat(req.Header, builder.GetHeaders()) + return req + }), + ) +} diff --git a/v2/context/readerioeither/http/builder/builder_test.go b/v2/context/readerioeither/http/builder/builder_test.go new file mode 100644 index 0000000..311e719 --- /dev/null +++ b/v2/context/readerioeither/http/builder/builder_test.go @@ -0,0 +1,59 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package builder + +import ( + "context" + "net/http" + "net/url" + "testing" + + RIOE "github.com/IBM/fp-go/v2/context/readerioeither" + E "github.com/IBM/fp-go/v2/either" + F "github.com/IBM/fp-go/v2/function" + R "github.com/IBM/fp-go/v2/http/builder" + IO "github.com/IBM/fp-go/v2/io" + "github.com/stretchr/testify/assert" +) + +func TestBuilderWithQuery(t *testing.T) { + // add some query + withLimit := R.WithQueryArg("limit")("10") + withURL := R.WithURL("http://www.example.org?a=b") + + b := F.Pipe2( + R.Default, + withLimit, + withURL, + ) + + req := F.Pipe3( + b, + Requester, + RIOE.Map(func(r *http.Request) *url.URL { + return r.URL + }), + RIOE.ChainFirstIOK(func(u *url.URL) IO.IO[any] { + return IO.FromImpure(func() { + q := u.Query() + assert.Equal(t, "10", q.Get("limit")) + assert.Equal(t, "b", q.Get("a")) + }) + }), + ) + + assert.True(t, E.IsRight(req(context.Background())())) +} diff --git a/v2/context/readerioeither/http/request.go b/v2/context/readerioeither/http/request.go new file mode 100644 index 0000000..eea691c --- /dev/null +++ b/v2/context/readerioeither/http/request.go @@ -0,0 +1,129 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package http + +import ( + "io" + "net/http" + + B "github.com/IBM/fp-go/v2/bytes" + RIOE "github.com/IBM/fp-go/v2/context/readerioeither" + F "github.com/IBM/fp-go/v2/function" + H "github.com/IBM/fp-go/v2/http" + IOE "github.com/IBM/fp-go/v2/ioeither" + IOEF "github.com/IBM/fp-go/v2/ioeither/file" + J "github.com/IBM/fp-go/v2/json" + P "github.com/IBM/fp-go/v2/pair" +) + +type ( + // Requester is a reader that constructs a request + Requester = RIOE.ReaderIOEither[*http.Request] + + Client interface { + // Do can send an HTTP request considering a context + Do(Requester) RIOE.ReaderIOEither[*http.Response] + } + + client struct { + delegate *http.Client + doIOE func(*http.Request) IOE.IOEither[error, *http.Response] + } +) + +var ( + // MakeRequest is an eitherized version of [http.NewRequestWithContext] + MakeRequest = RIOE.Eitherize3(http.NewRequestWithContext) + makeRequest = F.Bind13of3(MakeRequest) + + // specialize + MakeGetRequest = makeRequest("GET", nil) +) + +func (client client) Do(req Requester) RIOE.ReaderIOEither[*http.Response] { + return F.Pipe1( + req, + RIOE.ChainIOEitherK(client.doIOE), + ) +} + +// MakeClient creates an HTTP client proxy +func MakeClient(httpClient *http.Client) Client { + return client{delegate: httpClient, doIOE: IOE.Eitherize1(httpClient.Do)} +} + +// ReadFullResponse sends a request, reads the response as a byte array and represents the result as a tuple +func ReadFullResponse(client Client) func(Requester) RIOE.ReaderIOEither[H.FullResponse] { + return func(req Requester) RIOE.ReaderIOEither[H.FullResponse] { + return F.Flow3( + client.Do(req), + IOE.ChainEitherK(H.ValidateResponse), + IOE.Chain(func(resp *http.Response) IOE.IOEither[error, H.FullResponse] { + return F.Pipe1( + F.Pipe3( + resp, + H.GetBody, + IOE.Of[error, io.ReadCloser], + IOEF.ReadAll[io.ReadCloser], + ), + IOE.Map[error](F.Bind1st(P.MakePair[*http.Response, []byte], resp)), + ) + }), + ) + } +} + +// ReadAll sends a request and reads the response as bytes +func ReadAll(client Client) func(Requester) RIOE.ReaderIOEither[[]byte] { + return F.Flow2( + ReadFullResponse(client), + RIOE.Map(H.Body), + ) +} + +// ReadText sends a request, reads the response and represents the response as a text string +func ReadText(client Client) func(Requester) RIOE.ReaderIOEither[string] { + return F.Flow2( + ReadAll(client), + RIOE.Map(B.ToString), + ) +} + +// ReadJson sends a request, reads the response and parses the response as JSON +// +// Deprecated: use [ReadJSON] instead +func ReadJson[A any](client Client) func(Requester) RIOE.ReaderIOEither[A] { + return ReadJSON[A](client) +} + +func readJSON(client Client) func(Requester) RIOE.ReaderIOEither[[]byte] { + return F.Flow3( + ReadFullResponse(client), + RIOE.ChainFirstEitherK(F.Flow2( + H.Response, + H.ValidateJSONResponse, + )), + RIOE.Map(H.Body), + ) +} + +// ReadJSON sends a request, reads the response and parses the response as JSON +func ReadJSON[A any](client Client) func(Requester) RIOE.ReaderIOEither[A] { + return F.Flow2( + readJSON(client), + RIOE.ChainEitherK(J.Unmarshal[A]), + ) +} diff --git a/v2/context/readerioeither/http/request_test.go b/v2/context/readerioeither/http/request_test.go new file mode 100644 index 0000000..2250d24 --- /dev/null +++ b/v2/context/readerioeither/http/request_test.go @@ -0,0 +1,157 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package http + +import ( + "context" + "fmt" + "testing" + + H "net/http" + + R "github.com/IBM/fp-go/v2/context/readerioeither" + E "github.com/IBM/fp-go/v2/either" + "github.com/IBM/fp-go/v2/errors" + F "github.com/IBM/fp-go/v2/function" + IOE "github.com/IBM/fp-go/v2/ioeither" + "github.com/stretchr/testify/assert" +) + +type PostItem struct { + UserID uint `json:"userId"` + Id uint `json:"id"` + Title string `json:"title"` + Body string `json:"body"` +} + +func getTitle(item PostItem) string { + return item.Title +} + +type simpleRequestBuilder struct { + method string + url string + headers H.Header +} + +func requestBuilder() simpleRequestBuilder { + return simpleRequestBuilder{method: "GET"} +} + +func (b simpleRequestBuilder) WithURL(url string) simpleRequestBuilder { + b.url = url + return b +} + +func (b simpleRequestBuilder) WithHeader(key, value string) simpleRequestBuilder { + if b.headers == nil { + b.headers = make(H.Header) + } else { + b.headers = b.headers.Clone() + } + b.headers.Set(key, value) + return b +} + +func (b simpleRequestBuilder) Build() R.ReaderIOEither[*H.Request] { + return func(ctx context.Context) IOE.IOEither[error, *H.Request] { + return IOE.TryCatchError(func() (*H.Request, error) { + req, err := H.NewRequestWithContext(ctx, b.method, b.url, nil) + if err == nil { + req.Header = b.headers + } + return req, err + }) + } +} + +func TestSendSingleRequest(t *testing.T) { + + client := MakeClient(H.DefaultClient) + + req1 := MakeGetRequest("https://jsonplaceholder.typicode.com/posts/1") + + readItem := ReadJSON[PostItem](client) + + resp1 := readItem(req1) + + resE := resp1(context.TODO())() + + fmt.Println(resE) +} + +// setHeaderUnsafe updates a header value in a request object by mutating the request object +func setHeaderUnsafe(key, value string) func(*H.Request) *H.Request { + return func(req *H.Request) *H.Request { + req.Header.Set(key, value) + return req + } +} + +func TestSendSingleRequestWithHeaderUnsafe(t *testing.T) { + + client := MakeClient(H.DefaultClient) + + // this is not safe from a puristic perspective, because the map call mutates the request object + req1 := F.Pipe2( + "https://jsonplaceholder.typicode.com/posts/1", + MakeGetRequest, + R.Map(setHeaderUnsafe("Content-Type", "text/html")), + ) + + readItem := ReadJSON[PostItem](client) + + resp1 := F.Pipe2( + req1, + readItem, + R.Map(getTitle), + ) + + res := F.Pipe1( + resp1(context.TODO())(), + E.GetOrElse(errors.ToString), + ) + + assert.Equal(t, "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", res) +} + +func TestSendSingleRequestWithHeaderSafe(t *testing.T) { + + client := MakeClient(H.DefaultClient) + + // the request builder assembles config values to construct + // the final http request. Each `With` step creates a copy of the settings + // so the flow is pure + request := requestBuilder(). + WithURL("https://jsonplaceholder.typicode.com/posts/1"). + WithHeader("Content-Type", "text/html"). + Build() + + readItem := ReadJSON[PostItem](client) + + response := F.Pipe2( + request, + readItem, + R.Map(getTitle), + ) + + res := F.Pipe1( + response(context.TODO())(), + E.GetOrElse(errors.ToString), + ) + + assert.Equal(t, "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", res) +} diff --git a/v2/context/readerioeither/monoid.go b/v2/context/readerioeither/monoid.go new file mode 100644 index 0000000..10976e6 --- /dev/null +++ b/v2/context/readerioeither/monoid.go @@ -0,0 +1,108 @@ +// Copyright (c) 2023 IBM Corp. +// All rights reserved. +// +// 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. + +package readerioeither + +import ( + "github.com/IBM/fp-go/v2/monoid" +) + +type ( + Monoid[A any] = monoid.Monoid[ReaderIOEither[A]] +) + +// ApplicativeMonoid returns a [Monoid] that concatenates [ReaderIOEither] instances via their applicative. +// This uses the default applicative behavior (parallel or sequential based on useParallel flag). +// +// The monoid combines two ReaderIOEither values by applying the underlying monoid's combine operation +// to their success values using applicative application. +// +// Parameters: +// - m: The underlying monoid for type A +// +// Returns a Monoid for ReaderIOEither[A]. +func ApplicativeMonoid[A any](m monoid.Monoid[A]) Monoid[A] { + return monoid.ApplicativeMonoid( + Of[A], + MonadMap[A, func(A) A], + MonadAp[A, A], + m, + ) +} + +// ApplicativeMonoidSeq returns a [Monoid] that concatenates [ReaderIOEither] instances via their applicative. +// This explicitly uses sequential execution for combining values. +// +// Parameters: +// - m: The underlying monoid for type A +// +// Returns a Monoid for ReaderIOEither[A] with sequential execution. +func ApplicativeMonoidSeq[A any](m monoid.Monoid[A]) Monoid[A] { + return monoid.ApplicativeMonoid( + Of[A], + MonadMap[A, func(A) A], + MonadApSeq[A, A], + m, + ) +} + +// ApplicativeMonoidPar returns a [Monoid] that concatenates [ReaderIOEither] instances via their applicative. +// This explicitly uses parallel execution for combining values. +// +// Parameters: +// - m: The underlying monoid for type A +// +// Returns a Monoid for ReaderIOEither[A] with parallel execution. +func ApplicativeMonoidPar[A any](m monoid.Monoid[A]) Monoid[A] { + return monoid.ApplicativeMonoid( + Of[A], + MonadMap[A, func(A) A], + MonadApPar[A, A], + m, + ) +} + +// AlternativeMonoid is the alternative [Monoid] for [ReaderIOEither]. +// This combines ReaderIOEither values using the alternative semantics, +// where the second value is only evaluated if the first fails. +// +// Parameters: +// - m: The underlying monoid for type A +// +// Returns a Monoid for ReaderIOEither[A] with alternative semantics. +func AlternativeMonoid[A any](m monoid.Monoid[A]) Monoid[A] { + return monoid.AlternativeMonoid( + Of[A], + MonadMap[A, func(A) A], + MonadAp[A, A], + MonadAlt[A], + m, + ) +} + +// AltMonoid is the alternative [Monoid] for a [ReaderIOEither]. +// This creates a monoid where the empty value is provided lazily, +// and combination uses the Alt operation (try first, fallback to second on failure). +// +// Parameters: +// - zero: Lazy computation that provides the empty/identity value +// +// Returns a Monoid for ReaderIOEither[A] with Alt-based combination. +func AltMonoid[A any](zero Lazy[ReaderIOEither[A]]) Monoid[A] { + return monoid.AltMonoid( + zero, + MonadAlt[A], + ) +} diff --git a/v2/context/readerioeither/reader.go b/v2/context/readerioeither/reader.go new file mode 100644 index 0000000..fade018 --- /dev/null +++ b/v2/context/readerioeither/reader.go @@ -0,0 +1,636 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readerioeither + +import ( + "context" + "time" + + "github.com/IBM/fp-go/v2/either" + "github.com/IBM/fp-go/v2/errors" + "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/io" + "github.com/IBM/fp-go/v2/ioeither" + "github.com/IBM/fp-go/v2/readerioeither" +) + +const ( + // useParallel is the feature flag to control if we use the parallel or the sequential implementation of ap + useParallel = true +) + +// FromEither converts an [Either] into a [ReaderIOEither]. +// The resulting computation ignores the context and immediately returns the Either value. +// +// Parameters: +// - e: The Either value to lift into ReaderIOEither +// +// Returns a ReaderIOEither that produces the given Either value. +func FromEither[A any](e Either[A]) ReaderIOEither[A] { + return readerioeither.FromEither[context.Context](e) +} + +// Left creates a [ReaderIOEither] that represents a failed computation with the given error. +// +// Parameters: +// - l: The error value +// +// Returns a ReaderIOEither that always fails with the given error. +func Left[A any](l error) ReaderIOEither[A] { + return readerioeither.Left[context.Context, A](l) +} + +// Right creates a [ReaderIOEither] that represents a successful computation with the given value. +// +// Parameters: +// - r: The success value +// +// Returns a ReaderIOEither that always succeeds with the given value. +func Right[A any](r A) ReaderIOEither[A] { + return readerioeither.Right[context.Context, error](r) +} + +// MonadMap transforms the success value of a [ReaderIOEither] using the provided function. +// If the computation fails, the error is propagated unchanged. +// +// Parameters: +// - fa: The ReaderIOEither to transform +// - f: The transformation function +// +// Returns a new ReaderIOEither with the transformed value. +func MonadMap[A, B any](fa ReaderIOEither[A], f func(A) B) ReaderIOEither[B] { + return readerioeither.MonadMap(fa, f) +} + +// Map transforms the success value of a [ReaderIOEither] using the provided function. +// This is the curried version of [MonadMap], useful for composition. +// +// Parameters: +// - f: The transformation function +// +// Returns a function that transforms a ReaderIOEither. +func Map[A, B any](f func(A) B) Operator[A, B] { + return readerioeither.Map[context.Context, error](f) +} + +// MonadMapTo replaces the success value of a [ReaderIOEither] with a constant value. +// If the computation fails, the error is propagated unchanged. +// +// Parameters: +// - fa: The ReaderIOEither to transform +// - b: The constant value to use +// +// Returns a new ReaderIOEither with the constant value. +func MonadMapTo[A, B any](fa ReaderIOEither[A], b B) ReaderIOEither[B] { + return readerioeither.MonadMapTo(fa, b) +} + +// MapTo replaces the success value of a [ReaderIOEither] with a constant value. +// This is the curried version of [MonadMapTo]. +// +// Parameters: +// - b: The constant value to use +// +// Returns a function that transforms a ReaderIOEither. +func MapTo[A, B any](b B) Operator[A, B] { + return readerioeither.MapTo[context.Context, error, A](b) +} + +// MonadChain sequences two [ReaderIOEither] computations, where the second depends on the result of the first. +// If the first computation fails, the second is not executed. +// +// Parameters: +// - ma: The first ReaderIOEither +// - f: Function that produces the second ReaderIOEither based on the first's result +// +// Returns a new ReaderIOEither representing the sequenced computation. +func MonadChain[A, B any](ma ReaderIOEither[A], f func(A) ReaderIOEither[B]) ReaderIOEither[B] { + return readerioeither.MonadChain(ma, f) +} + +// Chain sequences two [ReaderIOEither] computations, where the second depends on the result of the first. +// This is the curried version of [MonadChain], useful for composition. +// +// Parameters: +// - f: Function that produces the second ReaderIOEither based on the first's result +// +// Returns a function that sequences ReaderIOEither computations. +func Chain[A, B any](f func(A) ReaderIOEither[B]) Operator[A, B] { + return readerioeither.Chain(f) +} + +// MonadChainFirst sequences two [ReaderIOEither] computations but returns the result of the first. +// The second computation is executed for its side effects only. +// +// Parameters: +// - ma: The first ReaderIOEither +// - f: Function that produces the second ReaderIOEither +// +// Returns a ReaderIOEither with the result of the first computation. +func MonadChainFirst[A, B any](ma ReaderIOEither[A], f func(A) ReaderIOEither[B]) ReaderIOEither[A] { + return readerioeither.MonadChainFirst(ma, f) +} + +// ChainFirst sequences two [ReaderIOEither] computations but returns the result of the first. +// This is the curried version of [MonadChainFirst]. +// +// Parameters: +// - f: Function that produces the second ReaderIOEither +// +// Returns a function that sequences ReaderIOEither computations. +func ChainFirst[A, B any](f func(A) ReaderIOEither[B]) Operator[A, A] { + return readerioeither.ChainFirst(f) +} + +// Of creates a [ReaderIOEither] that always succeeds with the given value. +// This is the same as [Right] and represents the monadic return operation. +// +// Parameters: +// - a: The value to wrap +// +// Returns a ReaderIOEither that always succeeds with the given value. +func Of[A any](a A) ReaderIOEither[A] { + return readerioeither.Of[context.Context, error](a) +} + +func withCancelCauseFunc[A any](cancel context.CancelCauseFunc, ma IOEither[A]) IOEither[A] { + return function.Pipe3( + ma, + ioeither.Swap[error, A], + ioeither.ChainFirstIOK[A](func(err error) func() any { + return io.FromImpure(func() { cancel(err) }) + }), + ioeither.Swap[A, error], + ) +} + +// MonadApPar implements parallel applicative application for [ReaderIOEither]. +// It executes both computations in parallel and creates a sub-context that will be canceled +// if either operation fails. This provides automatic cancellation propagation. +// +// Parameters: +// - fab: ReaderIOEither containing a function +// - fa: ReaderIOEither containing a value +// +// Returns a ReaderIOEither with the function applied to the value. +func MonadApPar[B, A any](fab ReaderIOEither[func(A) B], fa ReaderIOEither[A]) ReaderIOEither[B] { + // context sensitive input + cfab := WithContext(fab) + cfa := WithContext(fa) + + return func(ctx context.Context) IOEither[B] { + // quick check for cancellation + if err := context.Cause(ctx); err != nil { + return ioeither.Left[B](err) + } + + return func() Either[B] { + // quick check for cancellation + if err := context.Cause(ctx); err != nil { + return either.Left[B](err) + } + + // create sub-contexts for fa and fab, so they can cancel one other + ctxSub, cancelSub := context.WithCancelCause(ctx) + defer cancelSub(nil) // cancel has to be called in all paths + + fabIOE := withCancelCauseFunc(cancelSub, cfab(ctxSub)) + faIOE := withCancelCauseFunc(cancelSub, cfa(ctxSub)) + + return ioeither.MonadApPar(fabIOE, faIOE)() + } + } +} + +// MonadAp implements applicative application for [ReaderIOEither]. +// By default, it uses parallel execution ([MonadApPar]) but can be configured to use +// sequential execution ([MonadApSeq]) via the useParallel constant. +// +// Parameters: +// - fab: ReaderIOEither containing a function +// - fa: ReaderIOEither containing a value +// +// Returns a ReaderIOEither with the function applied to the value. +func MonadAp[B, A any](fab ReaderIOEither[func(A) B], fa ReaderIOEither[A]) ReaderIOEither[B] { + // dispatch to the configured version + if useParallel { + return MonadApPar(fab, fa) + } + return MonadApSeq(fab, fa) +} + +// MonadApSeq implements sequential applicative application for [ReaderIOEither]. +// It executes the function computation first, then the value computation. +// +// Parameters: +// - fab: ReaderIOEither containing a function +// - fa: ReaderIOEither containing a value +// +// Returns a ReaderIOEither with the function applied to the value. +func MonadApSeq[B, A any](fab ReaderIOEither[func(A) B], fa ReaderIOEither[A]) ReaderIOEither[B] { + return readerioeither.MonadApSeq(fab, fa) +} + +// Ap applies a function wrapped in a [ReaderIOEither] to a value wrapped in a ReaderIOEither. +// This is the curried version of [MonadAp], using the default execution mode. +// +// Parameters: +// - fa: ReaderIOEither containing a value +// +// Returns a function that applies a ReaderIOEither function to the value. +func Ap[B, A any](fa ReaderIOEither[A]) Operator[func(A) B, B] { + return function.Bind2nd(MonadAp[B, A], fa) +} + +// ApSeq applies a function wrapped in a [ReaderIOEither] to a value sequentially. +// This is the curried version of [MonadApSeq]. +// +// Parameters: +// - fa: ReaderIOEither containing a value +// +// Returns a function that applies a ReaderIOEither function to the value sequentially. +func ApSeq[B, A any](fa ReaderIOEither[A]) Operator[func(A) B, B] { + return function.Bind2nd(MonadApSeq[B, A], fa) +} + +// ApPar applies a function wrapped in a [ReaderIOEither] to a value in parallel. +// This is the curried version of [MonadApPar]. +// +// Parameters: +// - fa: ReaderIOEither containing a value +// +// Returns a function that applies a ReaderIOEither function to the value in parallel. +func ApPar[B, A any](fa ReaderIOEither[A]) Operator[func(A) B, B] { + return function.Bind2nd(MonadApPar[B, A], fa) +} + +// FromPredicate creates a [ReaderIOEither] from a predicate function. +// If the predicate returns true, the value is wrapped in Right; otherwise, Left with the error from onFalse. +// +// Parameters: +// - pred: Predicate function to test the value +// - onFalse: Function to generate an error when predicate fails +// +// Returns a function that converts a value to ReaderIOEither based on the predicate. +func FromPredicate[A any](pred func(A) bool, onFalse func(A) error) func(A) ReaderIOEither[A] { + return readerioeither.FromPredicate[context.Context](pred, onFalse) +} + +// OrElse provides an alternative [ReaderIOEither] computation if the first one fails. +// The alternative is only executed if the first computation results in a Left (error). +// +// Parameters: +// - onLeft: Function that produces an alternative ReaderIOEither from the error +// +// Returns a function that provides fallback behavior for failed computations. +func OrElse[A any](onLeft func(error) ReaderIOEither[A]) Operator[A, A] { + return readerioeither.OrElse[context.Context](onLeft) +} + +// Ask returns a [ReaderIOEither] that provides access to the context. +// This is useful for accessing the [context.Context] within a computation. +// +// Returns a ReaderIOEither that produces the context. +func Ask() ReaderIOEither[context.Context] { + return readerioeither.Ask[context.Context, error]() +} + +// MonadChainEitherK chains a function that returns an [Either] into a [ReaderIOEither] computation. +// This is useful for integrating pure Either-returning functions into ReaderIOEither workflows. +// +// Parameters: +// - ma: The ReaderIOEither to chain from +// - f: Function that produces an Either +// +// Returns a new ReaderIOEither with the chained computation. +func MonadChainEitherK[A, B any](ma ReaderIOEither[A], f func(A) Either[B]) ReaderIOEither[B] { + return readerioeither.MonadChainEitherK[context.Context](ma, f) +} + +// ChainEitherK chains a function that returns an [Either] into a [ReaderIOEither] computation. +// This is the curried version of [MonadChainEitherK]. +// +// Parameters: +// - f: Function that produces an Either +// +// Returns a function that chains the Either-returning function. +func ChainEitherK[A, B any](f func(A) Either[B]) func(ma ReaderIOEither[A]) ReaderIOEither[B] { + return readerioeither.ChainEitherK[context.Context](f) +} + +// MonadChainFirstEitherK chains a function that returns an [Either] but keeps the original value. +// The Either-returning function is executed for its validation/side effects only. +// +// Parameters: +// - ma: The ReaderIOEither to chain from +// - f: Function that produces an Either +// +// Returns a ReaderIOEither with the original value if both computations succeed. +func MonadChainFirstEitherK[A, B any](ma ReaderIOEither[A], f func(A) Either[B]) ReaderIOEither[A] { + return readerioeither.MonadChainFirstEitherK[context.Context](ma, f) +} + +// ChainFirstEitherK chains a function that returns an [Either] but keeps the original value. +// This is the curried version of [MonadChainFirstEitherK]. +// +// Parameters: +// - f: Function that produces an Either +// +// Returns a function that chains the Either-returning function. +func ChainFirstEitherK[A, B any](f func(A) Either[B]) func(ma ReaderIOEither[A]) ReaderIOEither[A] { + return readerioeither.ChainFirstEitherK[context.Context](f) +} + +// ChainOptionK chains a function that returns an [Option] into a [ReaderIOEither] computation. +// If the Option is None, the provided error function is called. +// +// Parameters: +// - onNone: Function to generate an error when Option is None +// +// Returns a function that chains Option-returning functions into ReaderIOEither. +func ChainOptionK[A, B any](onNone func() error) func(func(A) Option[B]) Operator[A, B] { + return readerioeither.ChainOptionK[context.Context, A, B](onNone) +} + +// FromIOEither converts an [IOEither] into a [ReaderIOEither]. +// The resulting computation ignores the context. +// +// Parameters: +// - t: The IOEither to convert +// +// Returns a ReaderIOEither that executes the IOEither. +func FromIOEither[A any](t ioeither.IOEither[error, A]) ReaderIOEither[A] { + return readerioeither.FromIOEither[context.Context](t) +} + +// FromIO converts an [IO] into a [ReaderIOEither]. +// The IO computation always succeeds, so it's wrapped in Right. +// +// Parameters: +// - t: The IO to convert +// +// Returns a ReaderIOEither that executes the IO and wraps the result in Right. +func FromIO[A any](t IO[A]) ReaderIOEither[A] { + return readerioeither.FromIO[context.Context, error](t) +} + +// FromLazy converts a [Lazy] computation into a [ReaderIOEither]. +// The Lazy computation always succeeds, so it's wrapped in Right. +// This is an alias for [FromIO] since Lazy and IO have the same structure. +// +// Parameters: +// - t: The Lazy computation to convert +// +// Returns a ReaderIOEither that executes the Lazy computation and wraps the result in Right. +func FromLazy[A any](t Lazy[A]) ReaderIOEither[A] { + return readerioeither.FromIO[context.Context, error](t) +} + +// Never returns a [ReaderIOEither] that blocks indefinitely until the context is canceled. +// This is useful for creating computations that wait for external cancellation signals. +// +// Returns a ReaderIOEither that waits for context cancellation and returns the cancellation error. +func Never[A any]() ReaderIOEither[A] { + return func(ctx context.Context) IOEither[A] { + return func() Either[A] { + <-ctx.Done() + return either.Left[A](context.Cause(ctx)) + } + } +} + +// MonadChainIOK chains a function that returns an [IO] into a [ReaderIOEither] computation. +// The IO computation always succeeds, so it's wrapped in Right. +// +// Parameters: +// - ma: The ReaderIOEither to chain from +// - f: Function that produces an IO +// +// Returns a new ReaderIOEither with the chained IO computation. +func MonadChainIOK[A, B any](ma ReaderIOEither[A], f func(A) IO[B]) ReaderIOEither[B] { + return readerioeither.MonadChainIOK(ma, f) +} + +// ChainIOK chains a function that returns an [IO] into a [ReaderIOEither] computation. +// This is the curried version of [MonadChainIOK]. +// +// Parameters: +// - f: Function that produces an IO +// +// Returns a function that chains the IO-returning function. +func ChainIOK[A, B any](f func(A) IO[B]) func(ma ReaderIOEither[A]) ReaderIOEither[B] { + return readerioeither.ChainIOK[context.Context, error](f) +} + +// MonadChainFirstIOK chains a function that returns an [IO] but keeps the original value. +// The IO computation is executed for its side effects only. +// +// Parameters: +// - ma: The ReaderIOEither to chain from +// - f: Function that produces an IO +// +// Returns a ReaderIOEither with the original value after executing the IO. +func MonadChainFirstIOK[A, B any](ma ReaderIOEither[A], f func(A) IO[B]) ReaderIOEither[A] { + return readerioeither.MonadChainFirstIOK(ma, f) +} + +// ChainFirstIOK chains a function that returns an [IO] but keeps the original value. +// This is the curried version of [MonadChainFirstIOK]. +// +// Parameters: +// - f: Function that produces an IO +// +// Returns a function that chains the IO-returning function. +func ChainFirstIOK[A, B any](f func(A) IO[B]) func(ma ReaderIOEither[A]) ReaderIOEither[A] { + return readerioeither.ChainFirstIOK[context.Context, error](f) +} + +// ChainIOEitherK chains a function that returns an [IOEither] into a [ReaderIOEither] computation. +// This is useful for integrating IOEither-returning functions into ReaderIOEither workflows. +// +// Parameters: +// - f: Function that produces an IOEither +// +// Returns a function that chains the IOEither-returning function. +func ChainIOEitherK[A, B any](f func(A) ioeither.IOEither[error, B]) func(ma ReaderIOEither[A]) ReaderIOEither[B] { + return readerioeither.ChainIOEitherK[context.Context](f) +} + +// Delay creates an operation that delays execution by the specified duration. +// The computation waits for either the delay to expire or the context to be canceled. +// +// Parameters: +// - delay: The duration to wait before executing the computation +// +// Returns a function that delays a ReaderIOEither computation. +func Delay[A any](delay time.Duration) func(ma ReaderIOEither[A]) ReaderIOEither[A] { + return func(ma ReaderIOEither[A]) ReaderIOEither[A] { + return func(ctx context.Context) IOEither[A] { + return func() Either[A] { + // manage the timeout + timeoutCtx, cancelTimeout := context.WithTimeout(ctx, delay) + defer cancelTimeout() + // whatever comes first + select { + case <-timeoutCtx.Done(): + return ma(ctx)() + case <-ctx.Done(): + return either.Left[A](context.Cause(ctx)) + } + } + } + } +} + +// Timer returns the current time after waiting for the specified delay. +// This is useful for creating time-based computations. +// +// Parameters: +// - delay: The duration to wait before returning the time +// +// Returns a ReaderIOEither that produces the current time after the delay. +func Timer(delay time.Duration) ReaderIOEither[time.Time] { + return function.Pipe2( + io.Now, + FromIO[time.Time], + Delay[time.Time](delay), + ) +} + +// Defer creates a [ReaderIOEither] by lazily generating a new computation each time it's executed. +// This is useful for creating computations that should be re-evaluated on each execution. +// +// Parameters: +// - gen: Lazy generator function that produces a ReaderIOEither +// +// Returns a ReaderIOEither that generates a fresh computation on each execution. +func Defer[A any](gen Lazy[ReaderIOEither[A]]) ReaderIOEither[A] { + return readerioeither.Defer(gen) +} + +// TryCatch wraps a function that returns a tuple (value, error) into a [ReaderIOEither]. +// This is the standard way to convert Go error-returning functions into ReaderIOEither. +// +// Parameters: +// - f: Function that takes a context and returns a function producing (value, error) +// +// Returns a ReaderIOEither that wraps the error-returning function. +func TryCatch[A any](f func(context.Context) func() (A, error)) ReaderIOEither[A] { + return readerioeither.TryCatch(f, errors.IdentityError) +} + +// MonadAlt provides an alternative [ReaderIOEither] if the first one fails. +// The alternative is lazily evaluated only if needed. +// +// Parameters: +// - first: The primary ReaderIOEither to try +// - second: Lazy alternative ReaderIOEither to use if first fails +// +// Returns a ReaderIOEither that tries the first, then the second if first fails. +func MonadAlt[A any](first ReaderIOEither[A], second Lazy[ReaderIOEither[A]]) ReaderIOEither[A] { + return readerioeither.MonadAlt(first, second) +} + +// Alt provides an alternative [ReaderIOEither] if the first one fails. +// This is the curried version of [MonadAlt]. +// +// Parameters: +// - second: Lazy alternative ReaderIOEither to use if first fails +// +// Returns a function that provides fallback behavior. +func Alt[A any](second Lazy[ReaderIOEither[A]]) Operator[A, A] { + return readerioeither.Alt(second) +} + +// Memoize computes the value of the provided [ReaderIOEither] monad lazily but exactly once. +// The context used to compute the value is the context of the first call, so do not use this +// method if the value has a functional dependency on the content of the context. +// +// Parameters: +// - rdr: The ReaderIOEither to memoize +// +// Returns a ReaderIOEither that caches its result after the first execution. +func Memoize[A any](rdr ReaderIOEither[A]) ReaderIOEither[A] { + return readerioeither.Memoize(rdr) +} + +// Flatten converts a nested [ReaderIOEither] into a flat [ReaderIOEither]. +// This is equivalent to [MonadChain] with the identity function. +// +// Parameters: +// - rdr: The nested ReaderIOEither to flatten +// +// Returns a flattened ReaderIOEither. +func Flatten[A any](rdr ReaderIOEither[ReaderIOEither[A]]) ReaderIOEither[A] { + return readerioeither.Flatten(rdr) +} + +// MonadFlap applies a value to a function wrapped in a [ReaderIOEither]. +// This is the reverse of [MonadAp], useful in certain composition scenarios. +// +// Parameters: +// - fab: ReaderIOEither containing a function +// - a: The value to apply to the function +// +// Returns a ReaderIOEither with the function applied to the value. +func MonadFlap[B, A any](fab ReaderIOEither[func(A) B], a A) ReaderIOEither[B] { + return readerioeither.MonadFlap(fab, a) +} + +// Flap applies a value to a function wrapped in a [ReaderIOEither]. +// This is the curried version of [MonadFlap]. +// +// Parameters: +// - a: The value to apply to the function +// +// Returns a function that applies the value to a ReaderIOEither function. +func Flap[B, A any](a A) Operator[func(A) B, B] { + return readerioeither.Flap[context.Context, error, B](a) +} + +// Fold handles both success and error cases of a [ReaderIOEither] by providing handlers for each. +// Both handlers return ReaderIOEither, allowing for further composition. +// +// Parameters: +// - onLeft: Handler for error case +// - onRight: Handler for success case +// +// Returns a function that folds a ReaderIOEither into a new ReaderIOEither. +func Fold[A, B any](onLeft func(error) ReaderIOEither[B], onRight func(A) ReaderIOEither[B]) Operator[A, B] { + return readerioeither.Fold(onLeft, onRight) +} + +// GetOrElse extracts the value from a [ReaderIOEither], providing a default via a function if it fails. +// The result is a [ReaderIO] that always succeeds. +// +// Parameters: +// - onLeft: Function to provide a default value from the error +// +// Returns a function that converts a ReaderIOEither to a ReaderIO. +func GetOrElse[A any](onLeft func(error) ReaderIO[A]) func(ReaderIOEither[A]) ReaderIO[A] { + return readerioeither.GetOrElse(onLeft) +} + +// OrLeft transforms the error of a [ReaderIOEither] using the provided function. +// The success value is left unchanged. +// +// Parameters: +// - onLeft: Function to transform the error +// +// Returns a function that transforms the error of a ReaderIOEither. +func OrLeft[A any](onLeft func(error) ReaderIO[error]) Operator[A, A] { + return readerioeither.OrLeft[A](onLeft) +} diff --git a/v2/context/readerioeither/reader_extended_test.go b/v2/context/readerioeither/reader_extended_test.go new file mode 100644 index 0000000..5b3f2a9 --- /dev/null +++ b/v2/context/readerioeither/reader_extended_test.go @@ -0,0 +1,532 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readerioeither + +import ( + "context" + "errors" + "fmt" + "testing" + "time" + + E "github.com/IBM/fp-go/v2/either" + O "github.com/IBM/fp-go/v2/option" + "github.com/stretchr/testify/assert" +) + +func TestFromEither(t *testing.T) { + ctx := context.Background() + + // Test with Right + rightVal := E.Right[error](42) + result := FromEither(rightVal)(ctx)() + assert.Equal(t, E.Right[error](42), result) + + // Test with Left + err := errors.New("test error") + leftVal := E.Left[int](err) + result = FromEither(leftVal)(ctx)() + assert.Equal(t, E.Left[int](err), result) +} + +func TestLeftRight(t *testing.T) { + ctx := context.Background() + + // Test Left + err := errors.New("test error") + result := Left[int](err)(ctx)() + assert.True(t, E.IsLeft(result)) + + // Test Right + result = Right(42)(ctx)() + assert.True(t, E.IsRight(result)) + val, _ := E.Unwrap(result) + assert.Equal(t, 42, val) +} + +func TestOf(t *testing.T) { + ctx := context.Background() + result := Of(42)(ctx)() + assert.Equal(t, E.Right[error](42), result) +} + +func TestMonadMap(t *testing.T) { + ctx := context.Background() + + // Test with Right + result := MonadMap(Right(42), func(x int) int { return x * 2 })(ctx)() + assert.Equal(t, E.Right[error](84), result) + + // Test with Left + err := errors.New("test error") + result = MonadMap(Left[int](err), func(x int) int { return x * 2 })(ctx)() + assert.Equal(t, E.Left[int](err), result) +} + +func TestMonadMapTo(t *testing.T) { + ctx := context.Background() + + // Test with Right + result := MonadMapTo(Right(42), "hello")(ctx)() + assert.Equal(t, E.Right[error]("hello"), result) + + // Test with Left + err := errors.New("test error") + result = MonadMapTo(Left[int](err), "hello")(ctx)() + assert.Equal(t, E.Left[string](err), result) +} + +func TestMonadChain(t *testing.T) { + ctx := context.Background() + + // Test with Right + result := MonadChain(Right(42), func(x int) ReaderIOEither[int] { + return Right(x * 2) + })(ctx)() + assert.Equal(t, E.Right[error](84), result) + + // Test with Left + err := errors.New("test error") + result = MonadChain(Left[int](err), func(x int) ReaderIOEither[int] { + return Right(x * 2) + })(ctx)() + assert.Equal(t, E.Left[int](err), result) + + // Test where function returns Left + result = MonadChain(Right(42), func(x int) ReaderIOEither[int] { + return Left[int](errors.New("chain error")) + })(ctx)() + assert.True(t, E.IsLeft(result)) +} + +func TestMonadChainFirst(t *testing.T) { + ctx := context.Background() + + // Test with Right + result := MonadChainFirst(Right(42), func(x int) ReaderIOEither[string] { + return Right("ignored") + })(ctx)() + assert.Equal(t, E.Right[error](42), result) + + // Test with Left in first + err := errors.New("test error") + result = MonadChainFirst(Left[int](err), func(x int) ReaderIOEither[string] { + return Right("ignored") + })(ctx)() + assert.Equal(t, E.Left[int](err), result) + + // Test with Left in second + result = MonadChainFirst(Right(42), func(x int) ReaderIOEither[string] { + return Left[string](errors.New("chain error")) + })(ctx)() + assert.True(t, E.IsLeft(result)) +} + +func TestMonadApSeq(t *testing.T) { + ctx := context.Background() + + // Test with both Right + fct := Right(func(x int) int { return x * 2 }) + val := Right(42) + result := MonadApSeq(fct, val)(ctx)() + assert.Equal(t, E.Right[error](84), result) + + // Test with Left function + err := errors.New("function error") + fct = Left[func(int) int](err) + result = MonadApSeq(fct, val)(ctx)() + assert.Equal(t, E.Left[int](err), result) + + // Test with Left value + fct = Right(func(x int) int { return x * 2 }) + err = errors.New("value error") + val = Left[int](err) + result = MonadApSeq(fct, val)(ctx)() + assert.Equal(t, E.Left[int](err), result) +} + +func TestMonadApPar(t *testing.T) { + ctx := context.Background() + + // Test with both Right + fct := Right(func(x int) int { return x * 2 }) + val := Right(42) + result := MonadApPar(fct, val)(ctx)() + assert.Equal(t, E.Right[error](84), result) +} + +func TestFromPredicate(t *testing.T) { + ctx := context.Background() + + pred := func(x int) bool { return x > 0 } + onFalse := func(x int) error { return fmt.Errorf("value %d is not positive", x) } + + // Test with predicate true + result := FromPredicate(pred, onFalse)(42)(ctx)() + assert.Equal(t, E.Right[error](42), result) + + // Test with predicate false + result = FromPredicate(pred, onFalse)(-1)(ctx)() + assert.True(t, E.IsLeft(result)) +} + +func TestAsk(t *testing.T) { + ctx := context.WithValue(context.Background(), "key", "value") + result := Ask()(ctx)() + assert.True(t, E.IsRight(result)) + retrievedCtx, _ := E.Unwrap(result) + assert.Equal(t, "value", retrievedCtx.Value("key")) +} + +func TestMonadChainEitherK(t *testing.T) { + ctx := context.Background() + + // Test with Right + result := MonadChainEitherK(Right(42), func(x int) E.Either[error, int] { + return E.Right[error](x * 2) + })(ctx)() + assert.Equal(t, E.Right[error](84), result) + + // Test with Left in Either + result = MonadChainEitherK(Right(42), func(x int) E.Either[error, int] { + return E.Left[int](errors.New("either error")) + })(ctx)() + assert.True(t, E.IsLeft(result)) +} + +func TestMonadChainFirstEitherK(t *testing.T) { + ctx := context.Background() + + // Test with Right + result := MonadChainFirstEitherK(Right(42), func(x int) E.Either[error, string] { + return E.Right[error]("ignored") + })(ctx)() + assert.Equal(t, E.Right[error](42), result) + + // Test with Left in Either + result = MonadChainFirstEitherK(Right(42), func(x int) E.Either[error, string] { + return E.Left[string](errors.New("either error")) + })(ctx)() + assert.True(t, E.IsLeft(result)) +} + +func TestChainOptionKFunc(t *testing.T) { + ctx := context.Background() + + onNone := func() error { return errors.New("none error") } + + // Test with Some + chainFunc := ChainOptionK[int, int](onNone) + result := chainFunc(func(x int) O.Option[int] { + return O.Some(x * 2) + })(Right(42))(ctx)() + assert.Equal(t, E.Right[error](84), result) + + // Test with None + result = chainFunc(func(x int) O.Option[int] { + return O.None[int]() + })(Right(42))(ctx)() + assert.True(t, E.IsLeft(result)) +} + +func TestFromIOEither(t *testing.T) { + ctx := context.Background() + + // Test with Right + ioe := func() E.Either[error, int] { + return E.Right[error](42) + } + result := FromIOEither(ioe)(ctx)() + assert.Equal(t, E.Right[error](42), result) + + // Test with Left + err := errors.New("test error") + ioe = func() E.Either[error, int] { + return E.Left[int](err) + } + result = FromIOEither(ioe)(ctx)() + assert.Equal(t, E.Left[int](err), result) +} + +func TestFromIO(t *testing.T) { + ctx := context.Background() + + io := func() int { return 42 } + result := FromIO(io)(ctx)() + assert.Equal(t, E.Right[error](42), result) +} + +func TestFromLazy(t *testing.T) { + ctx := context.Background() + + lazy := func() int { return 42 } + result := FromLazy(lazy)(ctx)() + assert.Equal(t, E.Right[error](42), result) +} + +func TestNeverWithCancel(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + + // Start Never in a goroutine + done := make(chan E.Either[error, int]) + go func() { + done <- Never[int]()(ctx)() + }() + + // Cancel the context + cancel() + + // Should receive cancellation error + result := <-done + assert.True(t, E.IsLeft(result)) +} + +func TestMonadChainIOK(t *testing.T) { + ctx := context.Background() + + // Test with Right + result := MonadChainIOK(Right(42), func(x int) func() int { + return func() int { return x * 2 } + })(ctx)() + assert.Equal(t, E.Right[error](84), result) +} + +func TestMonadChainFirstIOK(t *testing.T) { + ctx := context.Background() + + // Test with Right + result := MonadChainFirstIOK(Right(42), func(x int) func() string { + return func() string { return "ignored" } + })(ctx)() + assert.Equal(t, E.Right[error](42), result) +} + +func TestDelayFunc(t *testing.T) { + ctx := context.Background() + delay := 100 * time.Millisecond + + start := time.Now() + delayFunc := Delay[int](delay) + result := delayFunc(Right(42))(ctx)() + elapsed := time.Since(start) + + assert.True(t, E.IsRight(result)) + assert.GreaterOrEqual(t, elapsed, delay) +} + +func TestDefer(t *testing.T) { + ctx := context.Background() + count := 0 + + gen := func() ReaderIOEither[int] { + count++ + return Right(count) + } + + deferred := Defer(gen) + + // First call + result1 := deferred(ctx)() + assert.Equal(t, E.Right[error](1), result1) + + // Second call should generate new value + result2 := deferred(ctx)() + assert.Equal(t, E.Right[error](2), result2) +} + +func TestTryCatch(t *testing.T) { + ctx := context.Background() + + // Test success + result := TryCatch(func(ctx context.Context) func() (int, error) { + return func() (int, error) { + return 42, nil + } + })(ctx)() + assert.Equal(t, E.Right[error](42), result) + + // Test error + err := errors.New("test error") + result = TryCatch(func(ctx context.Context) func() (int, error) { + return func() (int, error) { + return 0, err + } + })(ctx)() + assert.Equal(t, E.Left[int](err), result) +} + +func TestMonadAlt(t *testing.T) { + ctx := context.Background() + + // Test with Right (alternative not called) + result := MonadAlt(Right(42), func() ReaderIOEither[int] { + return Right(99) + })(ctx)() + assert.Equal(t, E.Right[error](42), result) + + // Test with Left (alternative called) + err := errors.New("test error") + result = MonadAlt(Left[int](err), func() ReaderIOEither[int] { + return Right(99) + })(ctx)() + assert.Equal(t, E.Right[error](99), result) +} + +func TestMemoize(t *testing.T) { + ctx := context.Background() + count := 0 + + rdr := Memoize(FromLazy(func() int { + count++ + return count + })) + + // First call + result1 := rdr(ctx)() + assert.Equal(t, E.Right[error](1), result1) + + // Second call should return memoized value + result2 := rdr(ctx)() + assert.Equal(t, E.Right[error](1), result2) +} + +func TestFlatten(t *testing.T) { + ctx := context.Background() + + nested := Right(Right(42)) + result := Flatten(nested)(ctx)() + assert.Equal(t, E.Right[error](42), result) +} + +func TestMonadFlap(t *testing.T) { + ctx := context.Background() + fab := Right(func(x int) int { return x * 2 }) + result := MonadFlap(fab, 42)(ctx)() + assert.Equal(t, E.Right[error](84), result) +} + +func TestWithContext(t *testing.T) { + // Test with non-canceled context + ctx := context.Background() + result := WithContext(Right(42))(ctx)() + assert.Equal(t, E.Right[error](42), result) + + // Test with canceled context + ctx, cancel := context.WithCancel(context.Background()) + cancel() + result = WithContext(Right(42))(ctx)() + assert.True(t, E.IsLeft(result)) +} + +func TestMonadAp(t *testing.T) { + ctx := context.Background() + + // Test with both Right + fct := Right(func(x int) int { return x * 2 }) + val := Right(42) + result := MonadAp(fct, val)(ctx)() + assert.Equal(t, E.Right[error](84), result) +} + +// Test traverse functions +func TestSequenceArray(t *testing.T) { + ctx := context.Background() + + // Test with all Right + arr := []ReaderIOEither[int]{Right(1), Right(2), Right(3)} + result := SequenceArray(arr)(ctx)() + assert.True(t, E.IsRight(result)) + vals, _ := E.Unwrap(result) + assert.Equal(t, []int{1, 2, 3}, vals) + + // Test with one Left + err := errors.New("test error") + arr = []ReaderIOEither[int]{Right(1), Left[int](err), Right(3)} + result = SequenceArray(arr)(ctx)() + assert.True(t, E.IsLeft(result)) +} + +func TestTraverseArray(t *testing.T) { + ctx := context.Background() + + // Test transformation + arr := []int{1, 2, 3} + result := TraverseArray(func(x int) ReaderIOEither[int] { + return Right(x * 2) + })(arr)(ctx)() + assert.True(t, E.IsRight(result)) + vals, _ := E.Unwrap(result) + assert.Equal(t, []int{2, 4, 6}, vals) +} + +func TestSequenceRecord(t *testing.T) { + ctx := context.Background() + + // Test with all Right + rec := map[string]ReaderIOEither[int]{ + "a": Right(1), + "b": Right(2), + } + result := SequenceRecord(rec)(ctx)() + assert.True(t, E.IsRight(result)) + vals, _ := E.Unwrap(result) + assert.Equal(t, 1, vals["a"]) + assert.Equal(t, 2, vals["b"]) +} + +func TestTraverseRecord(t *testing.T) { + ctx := context.Background() + + // Test transformation + rec := map[string]int{"a": 1, "b": 2} + result := TraverseRecord[string](func(x int) ReaderIOEither[int] { + return Right(x * 2) + })(rec)(ctx)() + assert.True(t, E.IsRight(result)) + vals, _ := E.Unwrap(result) + assert.Equal(t, 2, vals["a"]) + assert.Equal(t, 4, vals["b"]) +} + +// Test monoid functions +func TestAltSemigroup(t *testing.T) { + ctx := context.Background() + + sg := AltSemigroup[int]() + + // Test with Right (first succeeds) + result := sg.Concat(Right(42), Right(99))(ctx)() + assert.Equal(t, E.Right[error](42), result) + + // Test with Left then Right (fallback) + err := errors.New("test error") + result = sg.Concat(Left[int](err), Right(99))(ctx)() + assert.Equal(t, E.Right[error](99), result) +} + +// Test Do notation +func TestDo(t *testing.T) { + ctx := context.Background() + + type State struct { + Value int + } + + result := Do(State{Value: 42})(ctx)() + assert.True(t, E.IsRight(result)) + state, _ := E.Unwrap(result) + assert.Equal(t, 42, state.Value) +} diff --git a/v2/context/readerioeither/reader_test.go b/v2/context/readerioeither/reader_test.go new file mode 100644 index 0000000..a67ca32 --- /dev/null +++ b/v2/context/readerioeither/reader_test.go @@ -0,0 +1,286 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readerioeither + +import ( + "context" + "fmt" + "testing" + "time" + + E "github.com/IBM/fp-go/v2/either" + F "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/internal/utils" + "github.com/stretchr/testify/assert" +) + +func TestInnerContextCancelSemantics(t *testing.T) { + // start with a simple context + outer := context.Background() + + parent, parentCancel := context.WithCancel(outer) + defer parentCancel() + + inner, innerCancel := context.WithCancel(parent) + defer innerCancel() + + assert.NoError(t, parent.Err()) + assert.NoError(t, inner.Err()) + + innerCancel() + + assert.NoError(t, parent.Err()) + assert.Error(t, inner.Err()) + +} + +func TestOuterContextCancelSemantics(t *testing.T) { + // start with a simple context + outer := context.Background() + + parent, outerCancel := context.WithCancel(outer) + defer outerCancel() + + inner, innerCancel := context.WithCancel(parent) + defer innerCancel() + + assert.NoError(t, parent.Err()) + assert.NoError(t, inner.Err()) + + outerCancel() + + assert.Error(t, parent.Err()) + assert.Error(t, inner.Err()) + +} + +func TestOuterAndInnerContextCancelSemantics(t *testing.T) { + // start with a simple context + outer := context.Background() + + parent, outerCancel := context.WithCancel(outer) + defer outerCancel() + + inner, innerCancel := context.WithCancel(parent) + defer innerCancel() + + assert.NoError(t, parent.Err()) + assert.NoError(t, inner.Err()) + + outerCancel() + innerCancel() + + assert.Error(t, parent.Err()) + assert.Error(t, inner.Err()) + + outerCancel() + innerCancel() + + assert.Error(t, parent.Err()) + assert.Error(t, inner.Err()) +} + +func TestCancelCauseSemantics(t *testing.T) { + // start with a simple context + outer := context.Background() + + parent, outerCancel := context.WithCancelCause(outer) + defer outerCancel(nil) + + inner := context.WithValue(parent, "key", "value") + + assert.NoError(t, parent.Err()) + assert.NoError(t, inner.Err()) + + err := fmt.Errorf("test error") + + outerCancel(err) + + assert.Error(t, parent.Err()) + assert.Error(t, inner.Err()) + + assert.Equal(t, err, context.Cause(parent)) + assert.Equal(t, err, context.Cause(inner)) +} + +func TestTimer(t *testing.T) { + delta := 3 * time.Second + timer := Timer(delta) + ctx := context.Background() + + t0 := time.Now() + res := timer(ctx)() + t1 := time.Now() + + assert.WithinDuration(t, t0.Add(delta), t1, time.Second) + assert.True(t, E.IsRight(res)) +} + +func TestCanceledApply(t *testing.T) { + // our error + err := fmt.Errorf("TestCanceledApply") + // the actual apply value errors out after some time + errValue := F.Pipe1( + Left[string](err), + Delay[string](time.Second), + ) + // function never resolves + fct := Never[func(string) string]() + // apply the values, we expect an error after 1s + + applied := F.Pipe1( + fct, + Ap[string, string](errValue), + ) + + res := applied(context.Background())() + assert.Equal(t, E.Left[string](err), res) +} + +func TestRegularApply(t *testing.T) { + value := Of("Carsten") + fct := Of(utils.Upper) + + applied := F.Pipe1( + fct, + Ap[string, string](value), + ) + + res := applied(context.Background())() + assert.Equal(t, E.Of[error]("CARSTEN"), res) +} + +func TestWithResourceNoErrors(t *testing.T) { + var countAcquire, countBody, countRelease int + + acquire := FromLazy(func() int { + countAcquire++ + return countAcquire + }) + + release := func(int) ReaderIOEither[int] { + return FromLazy(func() int { + countRelease++ + return countRelease + }) + } + + body := func(int) ReaderIOEither[int] { + return FromLazy(func() int { + countBody++ + return countBody + }) + } + + resRIOE := WithResource[int](acquire, release)(body) + + res := resRIOE(context.Background())() + + assert.Equal(t, 1, countAcquire) + assert.Equal(t, 1, countBody) + assert.Equal(t, 1, countRelease) + assert.Equal(t, E.Of[error](1), res) +} + +func TestWithResourceErrorInBody(t *testing.T) { + var countAcquire, countBody, countRelease int + + acquire := FromLazy(func() int { + countAcquire++ + return countAcquire + }) + + release := func(int) ReaderIOEither[int] { + return FromLazy(func() int { + countRelease++ + return countRelease + }) + } + + err := fmt.Errorf("error in body") + body := func(int) ReaderIOEither[int] { + return Left[int](err) + } + + resRIOE := WithResource[int](acquire, release)(body) + + res := resRIOE(context.Background())() + + assert.Equal(t, 1, countAcquire) + assert.Equal(t, 0, countBody) + assert.Equal(t, 1, countRelease) + assert.Equal(t, E.Left[int](err), res) +} + +func TestWithResourceErrorInAcquire(t *testing.T) { + var countAcquire, countBody, countRelease int + + err := fmt.Errorf("error in acquire") + acquire := Left[int](err) + + release := func(int) ReaderIOEither[int] { + return FromLazy(func() int { + countRelease++ + return countRelease + }) + } + + body := func(int) ReaderIOEither[int] { + return FromLazy(func() int { + countBody++ + return countBody + }) + } + + resRIOE := WithResource[int](acquire, release)(body) + + res := resRIOE(context.Background())() + + assert.Equal(t, 0, countAcquire) + assert.Equal(t, 0, countBody) + assert.Equal(t, 0, countRelease) + assert.Equal(t, E.Left[int](err), res) +} + +func TestWithResourceErrorInRelease(t *testing.T) { + var countAcquire, countBody, countRelease int + + acquire := FromLazy(func() int { + countAcquire++ + return countAcquire + }) + + err := fmt.Errorf("error in release") + release := func(int) ReaderIOEither[int] { + return Left[int](err) + } + + body := func(int) ReaderIOEither[int] { + return FromLazy(func() int { + countBody++ + return countBody + }) + } + + resRIOE := WithResource[int](acquire, release)(body) + + res := resRIOE(context.Background())() + + assert.Equal(t, 1, countAcquire) + assert.Equal(t, 1, countBody) + assert.Equal(t, 0, countRelease) + assert.Equal(t, E.Left[int](err), res) +} diff --git a/v2/context/readerioeither/resource.go b/v2/context/readerioeither/resource.go new file mode 100644 index 0000000..1847868 --- /dev/null +++ b/v2/context/readerioeither/resource.go @@ -0,0 +1,63 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readerioeither + +import ( + "context" + + "github.com/IBM/fp-go/v2/function" + RIE "github.com/IBM/fp-go/v2/readerioeither" +) + +// WithResource constructs a function that creates a resource, then operates on it and then releases the resource. +// This implements the RAII (Resource Acquisition Is Initialization) pattern, ensuring that resources are +// properly released even if the operation fails or the context is canceled. +// +// The resource is created, used, and released in a safe manner: +// - onCreate: Creates the resource +// - The provided function uses the resource +// - onRelease: Releases the resource (always called, even on error) +// +// Parameters: +// - onCreate: ReaderIOEither that creates the resource +// - onRelease: Function to release the resource +// +// Returns a function that takes a resource-using function and returns a ReaderIOEither. +// +// Example: +// +// file := WithResource( +// openFile("data.txt"), +// func(f *os.File) ReaderIOEither[any] { +// return TryCatch(func(ctx context.Context) func() (any, error) { +// return func() (any, error) { return nil, f.Close() } +// }) +// }, +// ) +// result := file(func(f *os.File) ReaderIOEither[string] { +// return TryCatch(func(ctx context.Context) func() (string, error) { +// return func() (string, error) { +// data, err := io.ReadAll(f) +// return string(data), err +// } +// }) +// }) +func WithResource[A, R, ANY any](onCreate ReaderIOEither[R], onRelease func(R) ReaderIOEither[ANY]) func(func(R) ReaderIOEither[A]) ReaderIOEither[A] { + return function.Flow2( + function.Bind2nd(function.Flow2[func(R) ReaderIOEither[A], Operator[A, A], R, ReaderIOEither[A], ReaderIOEither[A]], WithContext[A]), + RIE.WithResource[A, context.Context, error, R](WithContext(onCreate), onRelease), + ) +} diff --git a/v2/context/readerioeither/resource_test.go b/v2/context/readerioeither/resource_test.go new file mode 100644 index 0000000..cb6127a --- /dev/null +++ b/v2/context/readerioeither/resource_test.go @@ -0,0 +1,79 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readerioeither + +import ( + "context" + "io" + "os" + + B "github.com/IBM/fp-go/v2/bytes" + F "github.com/IBM/fp-go/v2/function" + I "github.com/IBM/fp-go/v2/io" + IOE "github.com/IBM/fp-go/v2/ioeither" +) + +var ( + openFile = F.Flow3( + IOE.Eitherize1(os.Open), + FromIOEither[*os.File], + ChainFirstIOK(F.Flow2( + (*os.File).Name, + I.Logf[string]("Opened file [%s]"), + )), + ) +) + +func closeFile(f *os.File) ReaderIOEither[string] { + return F.Pipe1( + TryCatch(func(_ context.Context) func() (string, error) { + return func() (string, error) { + return f.Name(), f.Close() + } + }), + ChainFirstIOK(I.Logf[string]("Closed file [%s]")), + ) +} + +func ExampleWithResource() { + + stringReader := WithResource[string](openFile("data/file.txt"), closeFile) + + rdr := stringReader(func(f *os.File) ReaderIOEither[string] { + return F.Pipe2( + TryCatch(func(_ context.Context) func() ([]byte, error) { + return func() ([]byte, error) { + return io.ReadAll(f) + } + }), + ChainFirstIOK(F.Flow2( + B.Size, + I.Logf[int]("Read content of length [%d]"), + )), + Map(B.ToString), + ) + }) + + contentIOE := F.Pipe2( + context.Background(), + rdr, + IOE.ChainFirstIOK[error](I.Printf[string]("Content: %s")), + ) + + contentIOE() + + // Output: Content: Carsten +} diff --git a/v2/context/readerioeither/semigroup.go b/v2/context/readerioeither/semigroup.go new file mode 100644 index 0000000..c33c840 --- /dev/null +++ b/v2/context/readerioeither/semigroup.go @@ -0,0 +1,43 @@ +// Copyright (c) 2023 IBM Corp. +// All rights reserved. +// +// 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. + +package readerioeither + +import ( + "github.com/IBM/fp-go/v2/semigroup" +) + +type ( + Semigroup[A any] = semigroup.Semigroup[ReaderIOEither[A]] +) + +// AltSemigroup is a [Semigroup] that tries the first item and then the second one using an alternative. +// This creates a semigroup where combining two ReaderIOEither values means trying the first one, +// and if it fails, trying the second one. This is useful for implementing fallback behavior. +// +// Returns a Semigroup for ReaderIOEither[A] with Alt-based combination. +// +// Example: +// +// sg := AltSemigroup[int]() +// result := sg.Concat( +// Left[int](errors.New("first failed")), +// Right[int](42), +// ) // Returns Right(42) +func AltSemigroup[A any]() Semigroup[A] { + return semigroup.AltSemigroup( + MonadAlt[A], + ) +} diff --git a/v2/context/readerioeither/sync.go b/v2/context/readerioeither/sync.go new file mode 100644 index 0000000..3696e1c --- /dev/null +++ b/v2/context/readerioeither/sync.go @@ -0,0 +1,54 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readerioeither + +import ( + "context" + + "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/io" +) + +// WithLock executes the provided IO operation in the scope of a lock. +// This ensures that the operation is executed with mutual exclusion, preventing concurrent access. +// +// The lock is acquired before the operation and released after it completes (or fails). +// The lock parameter should return a CancelFunc that releases the lock when called. +// +// Parameters: +// - lock: ReaderIOEither that acquires a lock and returns a CancelFunc to release it +// +// Returns a function that wraps a ReaderIOEither with lock protection. +// +// Example: +// +// mutex := &sync.Mutex{} +// lock := TryCatch(func(ctx context.Context) func() (context.CancelFunc, error) { +// return func() (context.CancelFunc, error) { +// mutex.Lock() +// return func() { mutex.Unlock() }, nil +// } +// }) +// protectedOp := WithLock(lock)(myOperation) +func WithLock[A any](lock ReaderIOEither[context.CancelFunc]) Operator[A, A] { + return function.Flow2( + function.Constant1[context.CancelFunc, ReaderIOEither[A]], + WithResource[A](lock, function.Flow2( + io.FromImpure[context.CancelFunc], + FromIO[any], + )), + ) +} diff --git a/v2/context/readerioeither/traverse.go b/v2/context/readerioeither/traverse.go new file mode 100644 index 0000000..daa7f7f --- /dev/null +++ b/v2/context/readerioeither/traverse.go @@ -0,0 +1,306 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readerioeither + +import ( + "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/internal/array" + "github.com/IBM/fp-go/v2/internal/record" +) + +// TraverseArray transforms an array [[]A] into [[]ReaderIOEither[B]] and then resolves that into a [ReaderIOEither[[]B]]. +// This uses the default applicative behavior (parallel or sequential based on useParallel flag). +// +// Parameters: +// - f: Function that transforms each element into a ReaderIOEither +// +// Returns a function that transforms an array into a ReaderIOEither of an array. +func TraverseArray[A, B any](f func(A) ReaderIOEither[B]) func([]A) ReaderIOEither[[]B] { + return array.Traverse[[]A]( + Of[[]B], + Map[[]B, func(B) []B], + Ap[[]B, B], + + f, + ) +} + +// TraverseArrayWithIndex transforms an array [[]A] into [[]ReaderIOEither[B]] and then resolves that into a [ReaderIOEither[[]B]]. +// The transformation function receives both the index and the element. +// +// Parameters: +// - f: Function that transforms each element with its index into a ReaderIOEither +// +// Returns a function that transforms an array into a ReaderIOEither of an array. +func TraverseArrayWithIndex[A, B any](f func(int, A) ReaderIOEither[B]) func([]A) ReaderIOEither[[]B] { + return array.TraverseWithIndex[[]A]( + Of[[]B], + Map[[]B, func(B) []B], + Ap[[]B, B], + + f, + ) +} + +// SequenceArray converts a homogeneous sequence of ReaderIOEither into a ReaderIOEither of sequence. +// This is equivalent to TraverseArray with the identity function. +// +// Parameters: +// - ma: Array of ReaderIOEither values +// +// Returns a ReaderIOEither containing an array of values. +func SequenceArray[A any](ma []ReaderIOEither[A]) ReaderIOEither[[]A] { + return TraverseArray(function.Identity[ReaderIOEither[A]])(ma) +} + +// TraverseRecord transforms a record [map[K]A] into [map[K]ReaderIOEither[B]] and then resolves that into a [ReaderIOEither[map[K]B]]. +// +// Parameters: +// - f: Function that transforms each value into a ReaderIOEither +// +// Returns a function that transforms a map into a ReaderIOEither of a map. +func TraverseRecord[K comparable, A, B any](f func(A) ReaderIOEither[B]) func(map[K]A) ReaderIOEither[map[K]B] { + return record.Traverse[map[K]A]( + Of[map[K]B], + Map[map[K]B, func(B) map[K]B], + Ap[map[K]B, B], + + f, + ) +} + +// TraverseRecordWithIndex transforms a record [map[K]A] into [map[K]ReaderIOEither[B]] and then resolves that into a [ReaderIOEither[map[K]B]]. +// The transformation function receives both the key and the value. +// +// Parameters: +// - f: Function that transforms each key-value pair into a ReaderIOEither +// +// Returns a function that transforms a map into a ReaderIOEither of a map. +func TraverseRecordWithIndex[K comparable, A, B any](f func(K, A) ReaderIOEither[B]) func(map[K]A) ReaderIOEither[map[K]B] { + return record.TraverseWithIndex[map[K]A]( + Of[map[K]B], + Map[map[K]B, func(B) map[K]B], + Ap[map[K]B, B], + + f, + ) +} + +// SequenceRecord converts a homogeneous map of ReaderIOEither into a ReaderIOEither of map. +// +// Parameters: +// - ma: Map of ReaderIOEither values +// +// Returns a ReaderIOEither containing a map of values. +func SequenceRecord[K comparable, A any](ma map[K]ReaderIOEither[A]) ReaderIOEither[map[K]A] { + return TraverseRecord[K](function.Identity[ReaderIOEither[A]])(ma) +} + +// MonadTraverseArraySeq transforms an array [[]A] into [[]ReaderIOEither[B]] and then resolves that into a [ReaderIOEither[[]B]]. +// This explicitly uses sequential execution. +// +// Parameters: +// - as: The array to traverse +// - f: Function that transforms each element into a ReaderIOEither +// +// Returns a ReaderIOEither containing an array of transformed values. +func MonadTraverseArraySeq[A, B any](as []A, f func(A) ReaderIOEither[B]) ReaderIOEither[[]B] { + return array.MonadTraverse[[]A]( + Of[[]B], + Map[[]B, func(B) []B], + ApSeq[[]B, B], + as, + f, + ) +} + +// TraverseArraySeq transforms an array [[]A] into [[]ReaderIOEither[B]] and then resolves that into a [ReaderIOEither[[]B]]. +// This is the curried version of [MonadTraverseArraySeq] with sequential execution. +// +// Parameters: +// - f: Function that transforms each element into a ReaderIOEither +// +// Returns a function that transforms an array into a ReaderIOEither of an array. +func TraverseArraySeq[A, B any](f func(A) ReaderIOEither[B]) func([]A) ReaderIOEither[[]B] { + return array.Traverse[[]A]( + Of[[]B], + Map[[]B, func(B) []B], + ApSeq[[]B, B], + + f, + ) +} + +// TraverseArrayWithIndexSeq uses transforms an array [[]A] into [[]ReaderIOEither[B]] and then resolves that into a [ReaderIOEither[[]B]] +func TraverseArrayWithIndexSeq[A, B any](f func(int, A) ReaderIOEither[B]) func([]A) ReaderIOEither[[]B] { + return array.TraverseWithIndex[[]A]( + Of[[]B], + Map[[]B, func(B) []B], + ApSeq[[]B, B], + + f, + ) +} + +// SequenceArraySeq converts a homogeneous sequence of ReaderIOEither into a ReaderIOEither of sequence. +// This explicitly uses sequential execution. +// +// Parameters: +// - ma: Array of ReaderIOEither values +// +// Returns a ReaderIOEither containing an array of values. +func SequenceArraySeq[A any](ma []ReaderIOEither[A]) ReaderIOEither[[]A] { + return MonadTraverseArraySeq(ma, function.Identity[ReaderIOEither[A]]) +} + +// MonadTraverseRecordSeq uses transforms a record [map[K]A] into [map[K]ReaderIOEither[B]] and then resolves that into a [ReaderIOEither[map[K]B]] +func MonadTraverseRecordSeq[K comparable, A, B any](as map[K]A, f func(A) ReaderIOEither[B]) ReaderIOEither[map[K]B] { + return record.MonadTraverse[map[K]A]( + Of[map[K]B], + Map[map[K]B, func(B) map[K]B], + ApSeq[map[K]B, B], + as, + f, + ) +} + +// TraverseRecordSeq uses transforms a record [map[K]A] into [map[K]ReaderIOEither[B]] and then resolves that into a [ReaderIOEither[map[K]B]] +func TraverseRecordSeq[K comparable, A, B any](f func(A) ReaderIOEither[B]) func(map[K]A) ReaderIOEither[map[K]B] { + return record.Traverse[map[K]A]( + Of[map[K]B], + Map[map[K]B, func(B) map[K]B], + ApSeq[map[K]B, B], + + f, + ) +} + +// TraverseRecordWithIndexSeq uses transforms a record [map[K]A] into [map[K]ReaderIOEither[B]] and then resolves that into a [ReaderIOEither[map[K]B]] +func TraverseRecordWithIndexSeq[K comparable, A, B any](f func(K, A) ReaderIOEither[B]) func(map[K]A) ReaderIOEither[map[K]B] { + return record.TraverseWithIndex[map[K]A]( + Of[map[K]B], + Map[map[K]B, func(B) map[K]B], + ApSeq[map[K]B, B], + + f, + ) +} + +// SequenceRecordSeq converts a homogeneous sequence of either into an either of sequence +func SequenceRecordSeq[K comparable, A any](ma map[K]ReaderIOEither[A]) ReaderIOEither[map[K]A] { + return MonadTraverseRecordSeq(ma, function.Identity[ReaderIOEither[A]]) +} + +// MonadTraverseArrayPar transforms an array [[]A] into [[]ReaderIOEither[B]] and then resolves that into a [ReaderIOEither[[]B]]. +// This explicitly uses parallel execution. +// +// Parameters: +// - as: The array to traverse +// - f: Function that transforms each element into a ReaderIOEither +// +// Returns a ReaderIOEither containing an array of transformed values. +func MonadTraverseArrayPar[A, B any](as []A, f func(A) ReaderIOEither[B]) ReaderIOEither[[]B] { + return array.MonadTraverse[[]A]( + Of[[]B], + Map[[]B, func(B) []B], + ApPar[[]B, B], + as, + f, + ) +} + +// TraverseArrayPar transforms an array [[]A] into [[]ReaderIOEither[B]] and then resolves that into a [ReaderIOEither[[]B]]. +// This is the curried version of [MonadTraverseArrayPar] with parallel execution. +// +// Parameters: +// - f: Function that transforms each element into a ReaderIOEither +// +// Returns a function that transforms an array into a ReaderIOEither of an array. +func TraverseArrayPar[A, B any](f func(A) ReaderIOEither[B]) func([]A) ReaderIOEither[[]B] { + return array.Traverse[[]A]( + Of[[]B], + Map[[]B, func(B) []B], + ApPar[[]B, B], + + f, + ) +} + +// TraverseArrayWithIndexPar uses transforms an array [[]A] into [[]ReaderIOEither[B]] and then resolves that into a [ReaderIOEither[[]B]] +func TraverseArrayWithIndexPar[A, B any](f func(int, A) ReaderIOEither[B]) func([]A) ReaderIOEither[[]B] { + return array.TraverseWithIndex[[]A]( + Of[[]B], + Map[[]B, func(B) []B], + ApPar[[]B, B], + + f, + ) +} + +// SequenceArrayPar converts a homogeneous sequence of ReaderIOEither into a ReaderIOEither of sequence. +// This explicitly uses parallel execution. +// +// Parameters: +// - ma: Array of ReaderIOEither values +// +// Returns a ReaderIOEither containing an array of values. +func SequenceArrayPar[A any](ma []ReaderIOEither[A]) ReaderIOEither[[]A] { + return MonadTraverseArrayPar(ma, function.Identity[ReaderIOEither[A]]) +} + +// TraverseRecordPar uses transforms a record [map[K]A] into [map[K]ReaderIOEither[B]] and then resolves that into a [ReaderIOEither[map[K]B]] +func TraverseRecordPar[K comparable, A, B any](f func(A) ReaderIOEither[B]) func(map[K]A) ReaderIOEither[map[K]B] { + return record.Traverse[map[K]A]( + Of[map[K]B], + Map[map[K]B, func(B) map[K]B], + ApPar[map[K]B, B], + + f, + ) +} + +// TraverseRecordWithIndexPar uses transforms a record [map[K]A] into [map[K]ReaderIOEither[B]] and then resolves that into a [ReaderIOEither[map[K]B]] +func TraverseRecordWithIndexPar[K comparable, A, B any](f func(K, A) ReaderIOEither[B]) func(map[K]A) ReaderIOEither[map[K]B] { + return record.TraverseWithIndex[map[K]A]( + Of[map[K]B], + Map[map[K]B, func(B) map[K]B], + ApPar[map[K]B, B], + + f, + ) +} + +// MonadTraverseRecordPar uses transforms a record [map[K]A] into [map[K]ReaderIOEither[B]] and then resolves that into a [ReaderIOEither[map[K]B]] +func MonadTraverseRecordPar[K comparable, A, B any](as map[K]A, f func(A) ReaderIOEither[B]) ReaderIOEither[map[K]B] { + return record.MonadTraverse[map[K]A]( + Of[map[K]B], + Map[map[K]B, func(B) map[K]B], + ApPar[map[K]B, B], + as, + f, + ) +} + +// SequenceRecordPar converts a homogeneous map of ReaderIOEither into a ReaderIOEither of map. +// This explicitly uses parallel execution. +// +// Parameters: +// - ma: Map of ReaderIOEither values +// +// Returns a ReaderIOEither containing a map of values. +func SequenceRecordPar[K comparable, A any](ma map[K]ReaderIOEither[A]) ReaderIOEither[map[K]A] { + return MonadTraverseRecordPar(ma, function.Identity[ReaderIOEither[A]]) +} diff --git a/v2/context/readerioeither/type.go b/v2/context/readerioeither/type.go new file mode 100644 index 0000000..5311f40 --- /dev/null +++ b/v2/context/readerioeither/type.go @@ -0,0 +1,114 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readerioeither + +import ( + "context" + + "github.com/IBM/fp-go/v2/either" + "github.com/IBM/fp-go/v2/io" + "github.com/IBM/fp-go/v2/ioeither" + "github.com/IBM/fp-go/v2/lazy" + "github.com/IBM/fp-go/v2/option" + "github.com/IBM/fp-go/v2/reader" + "github.com/IBM/fp-go/v2/readerio" + "github.com/IBM/fp-go/v2/readerioeither" +) + +type ( + // Option represents an optional value that may or may not be present. + // It is used in operations that may not produce a value. + Option[A any] = option.Option[A] + + // Either represents a computation that can result in either an error or a success value. + // This is specialized to use [error] as the left (error) type, which is the standard + // error type in Go. + // + // Either[A] is equivalent to Either[error, A] from the either package. + Either[A any] = either.Either[error, A] + + // Lazy represents a deferred computation that produces a value of type A when executed. + // The computation is not executed until explicitly invoked. + Lazy[A any] = lazy.Lazy[A] + + // IO represents a side-effectful computation that produces a value of type A. + // The computation is deferred and only executed when invoked. + // + // IO[A] is equivalent to func() A + IO[A any] = io.IO[A] + + // IOEither represents a side-effectful computation that can fail with an error. + // This combines IO (side effects) with Either (error handling). + // + // IOEither[A] is equivalent to func() Either[error, A] + IOEither[A any] = ioeither.IOEither[error, A] + + // Reader represents a computation that depends on a context of type R. + // This is used for dependency injection and accessing shared context. + // + // Reader[R, A] is equivalent to func(R) A + Reader[R, A any] = reader.Reader[R, A] + + // ReaderIO represents a context-dependent computation that performs side effects. + // This is specialized to use [context.Context] as the context type. + // + // ReaderIO[A] is equivalent to func(context.Context) func() A + ReaderIO[A any] = readerio.ReaderIO[context.Context, A] + + // ReaderIOEither is the main type of this package. It represents a computation that: + // - Depends on a [context.Context] (Reader aspect) + // - Performs side effects (IO aspect) + // - Can fail with an [error] (Either aspect) + // - Produces a value of type A on success + // + // This is a specialization of [readerioeither.ReaderIOEither] with: + // - Context type fixed to [context.Context] + // - Error type fixed to [error] + // + // The type is defined as: + // ReaderIOEither[A] = func(context.Context) func() Either[error, A] + // + // Example usage: + // func fetchUser(id string) ReaderIOEither[User] { + // return func(ctx context.Context) func() Either[error, User] { + // return func() Either[error, User] { + // user, err := userService.Get(ctx, id) + // if err != nil { + // return either.Left[User](err) + // } + // return either.Right[error](user) + // } + // } + // } + // + // The computation is executed by providing a context and then invoking the result: + // ctx := context.Background() + // result := fetchUser("123")(ctx)() + ReaderIOEither[A any] = readerioeither.ReaderIOEither[context.Context, error, A] + + // Operator represents a transformation from one ReaderIOEither to another. + // This is useful for point-free style composition and building reusable transformations. + // + // Operator[A, B] is equivalent to func(ReaderIOEither[A]) ReaderIOEither[B] + // + // Example usage: + // // Define a reusable transformation + // var toUpper Operator[string, string] = Map(strings.ToUpper) + // + // // Apply the transformation + // result := toUpper(computation) + Operator[A, B any] = Reader[ReaderIOEither[A], ReaderIOEither[B]] +) diff --git a/v2/di/app.go b/v2/di/app.go new file mode 100644 index 0000000..62e52f1 --- /dev/null +++ b/v2/di/app.go @@ -0,0 +1,38 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package di + +import ( + DIE "github.com/IBM/fp-go/v2/di/erasure" + F "github.com/IBM/fp-go/v2/function" + IO "github.com/IBM/fp-go/v2/io" + IOE "github.com/IBM/fp-go/v2/ioeither" +) + +var ( + // InjMain is the [InjectionToken] for the main application + InjMain = MakeToken[any]("APP") + + // Main is the resolver for the main application + Main = Resolve(InjMain) +) + +// RunMain runs the main application from a set of [DIE.Provider]s +var RunMain = F.Flow3( + DIE.MakeInjector, + Main, + IOE.Fold(IO.Of[error], F.Constant1[any](IO.Of[error](nil))), +) diff --git a/v2/di/doc.go b/v2/di/doc.go new file mode 100644 index 0000000..a345862 --- /dev/null +++ b/v2/di/doc.go @@ -0,0 +1,325 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +/* +Package di implements functions and data types supporting dependency injection patterns. + +# Overview + +The dependency injection (DI) framework provides a type-safe way to manage dependencies +between components in your application. It ensures that all dependencies are resolved +correctly and that instances are created as singletons. + +# Core Concepts + +Dependency - An abstract concept representing a service, function, or value with a specific type. +Dependencies can be: + - Simple values (API URLs, configuration strings, numbers) + - Complex objects (HTTP clients, database connections) + - Service interfaces + - Functions + +InjectionToken - A unique identifier for a dependency that includes type information. +Created using MakeToken[T](name). + +Provider - The implementation of a dependency. Providers specify: + - Which dependency they provide (via an InjectionToken) + - What other dependencies they need + - How to create the instance + +InjectableFactory - The container that manages all providers and resolves dependencies. +All resolved instances are singletons. + +# Basic Usage + +Creating and using dependencies: + + import ( + "github.com/IBM/fp-go/v2/di" + IOE "github.com/IBM/fp-go/v2/ioeither" + ) + + // Define injection tokens + var ( + ConfigToken = di.MakeToken[Config]("Config") + DBToken = di.MakeToken[Database]("Database") + APIToken = di.MakeToken[APIService]("APIService") + ) + + // Create providers + configProvider := di.ConstProvider(ConfigToken, Config{Port: 8080}) + + dbProvider := di.MakeProvider1( + DBToken, + ConfigToken.Identity(), + func(cfg Config) IOE.IOEither[error, Database] { + return IOE.Of[error](NewDatabase(cfg)) + }, + ) + + apiProvider := di.MakeProvider2( + APIToken, + ConfigToken.Identity(), + DBToken.Identity(), + func(cfg Config, db Database) IOE.IOEither[error, APIService] { + return IOE.Of[error](NewAPIService(cfg, db)) + }, + ) + + // Create injector and resolve + injector := DIE.MakeInjector([]DIE.Provider{ + configProvider, + dbProvider, + apiProvider, + }) + + // Resolve the API service + resolver := di.Resolve(APIToken) + result := resolver(injector)() + +# Dependency Types + +Identity (Required) - The dependency must be resolved, or the injection fails: + + token.Identity() // Returns Dependency[T] + +Option (Optional) - The dependency is optional, returns Option[T]: + + token.Option() // Returns Dependency[Option[T]] + +IOEither (Lazy Required) - Lazy evaluation, memoized singleton: + + token.IOEither() // Returns Dependency[IOEither[error, T]] + +IOOption (Lazy Optional) - Lazy optional evaluation: + + token.IOOption() // Returns Dependency[IOOption[T]] + +# Provider Creation + +Providers are created using MakeProvider functions with suffixes indicating +the number of dependencies (0-15): + +MakeProvider0 - No dependencies: + + provider := di.MakeProvider0( + token, + IOE.Of[error](value), + ) + +MakeProvider1 - One dependency: + + provider := di.MakeProvider1( + resultToken, + dep1Token.Identity(), + func(dep1 Dep1Type) IOE.IOEither[error, ResultType] { + return IOE.Of[error](createResult(dep1)) + }, + ) + +MakeProvider2 - Two dependencies: + + provider := di.MakeProvider2( + resultToken, + dep1Token.Identity(), + dep2Token.Identity(), + func(dep1 Dep1Type, dep2 Dep2Type) IOE.IOEither[error, ResultType] { + return IOE.Of[error](createResult(dep1, dep2)) + }, + ) + +# Constant Providers + +For simple constant values: + + provider := di.ConstProvider(token, value) + +# Default Implementations + +Tokens can have default implementations that are used when no explicit +provider is registered: + + token := di.MakeTokenWithDefault0( + "ServiceName", + IOE.Of[error](defaultImplementation), + ) + + // Or with dependencies + token := di.MakeTokenWithDefault2( + "ServiceName", + dep1Token.Identity(), + dep2Token.Identity(), + func(dep1 Dep1Type, dep2 Dep2Type) IOE.IOEither[error, ResultType] { + return IOE.Of[error](createDefault(dep1, dep2)) + }, + ) + +# Multi-Value Dependencies + +For dependencies that can have multiple implementations: + + // Create a multi-token + loggersToken := di.MakeMultiToken[Logger]("Loggers") + + // Provide multiple items + consoleLogger := di.ConstProvider(loggersToken.Item(), ConsoleLogger{}) + fileLogger := di.ConstProvider(loggersToken.Item(), FileLogger{}) + + // Resolve all items as an array + resolver := di.Resolve(loggersToken.Container()) + loggers := resolver(injector)() // Returns []Logger + +# Lazy vs Eager Resolution + +Eager (Identity/Option) - Resolved immediately when the injector is created: + + dep1Token.Identity() // Resolved eagerly + dep2Token.Option() // Resolved eagerly + +Lazy (IOEither/IOOption) - Resolved only when accessed: + + dep3Token.IOEither() // Resolved lazily + dep4Token.IOOption() // Resolved lazily + +Lazy dependencies are memoized, so they're only created once. + +# Main Application Pattern + +The framework provides a convenient pattern for running applications: + + import ( + "github.com/IBM/fp-go/v2/di" + IOE "github.com/IBM/fp-go/v2/ioeither" + ) + + // Define your main application logic + mainProvider := di.MakeProvider1( + di.InjMain, + APIToken.Identity(), + func(api APIService) IOE.IOEither[error, any] { + return IOE.Of[error](api.Start()) + }, + ) + + // Run the application + err := di.RunMain([]DIE.Provider{ + configProvider, + dbProvider, + apiProvider, + mainProvider, + })() + +# Practical Examples + +Example 1: Configuration-based Service + + type Config struct { + APIKey string + Timeout int + } + + type HTTPClient struct { + config Config + } + + var ( + ConfigToken = di.MakeToken[Config]("Config") + ClientToken = di.MakeToken[HTTPClient]("HTTPClient") + ) + + configProvider := di.ConstProvider(ConfigToken, Config{ + APIKey: "secret", + Timeout: 30, + }) + + clientProvider := di.MakeProvider1( + ClientToken, + ConfigToken.Identity(), + func(cfg Config) IOE.IOEither[error, HTTPClient] { + return IOE.Of[error](HTTPClient{config: cfg}) + }, + ) + +Example 2: Optional Dependencies + + var ( + CacheToken = di.MakeToken[Cache]("Cache") + ServiceToken = di.MakeToken[Service]("Service") + ) + + // Service works with or without cache + serviceProvider := di.MakeProvider1( + ServiceToken, + CacheToken.Option(), // Optional dependency + func(cache O.Option[Cache]) IOE.IOEither[error, Service] { + return IOE.Of[error](NewService(cache)) + }, + ) + +Example 3: Lazy Dependencies + + var ( + DBToken = di.MakeToken[Database]("Database") + ReporterToken = di.MakeToken[Reporter]("Reporter") + ) + + // Reporter only connects to DB when needed + reporterProvider := di.MakeProvider1( + ReporterToken, + DBToken.IOEither(), // Lazy dependency + func(dbIO IOE.IOEither[error, Database]) IOE.IOEither[error, Reporter] { + return IOE.Of[error](NewReporter(dbIO)) + }, + ) + +# Function Reference + +Token Creation: + - MakeToken[T](name) InjectionToken[T] - Creates a unique injection token + - MakeTokenWithDefault[T](name, factory) InjectionToken[T] - Token with default implementation + - MakeTokenWithDefault0-15 - Token with default and N dependencies + - MakeMultiToken[T](name) MultiInjectionToken[T] - Token for multiple implementations + +Provider Creation: + - ConstProvider[T](token, value) Provider - Simple constant provider + - MakeProvider0[R](token, factory) Provider - Provider with no dependencies + - MakeProvider1-15 - Providers with 1-15 dependencies + - MakeProviderFactory0-15 - Lower-level factory creation + +Resolution: + - Resolve[T](token) ReaderIOEither[InjectableFactory, error, T] - Resolves a dependency + +Application: + - InjMain - Injection token for main application + - Main - Resolver for main application + - RunMain(providers) IO[error] - Runs the main application + +Utility: + - asDependency[T](t) Dependency - Converts to dependency interface + +# Related Packages + + - github.com/IBM/fp-go/v2/di/erasure - Type-erased DI implementation + - github.com/IBM/fp-go/v2/ioeither - IO operations with error handling + - github.com/IBM/fp-go/v2/option - Optional values + - github.com/IBM/fp-go/v2/either - Either monad for error handling + +[Provider]: [github.com/IBM/fp-go/v2/di/erasure.Provider] +[InjectableFactory]: [github.com/IBM/fp-go/v2/di/erasure.InjectableFactory] +[MakeInjector]: [github.com/IBM/fp-go/v2/di/erasure.MakeInjector] +*/ +package di + +//go:generate go run .. di --count 15 --filename gen.go diff --git a/v2/di/erasure/injector.go b/v2/di/erasure/injector.go new file mode 100644 index 0000000..407e12a --- /dev/null +++ b/v2/di/erasure/injector.go @@ -0,0 +1,169 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package erasure + +import ( + A "github.com/IBM/fp-go/v2/array" + "github.com/IBM/fp-go/v2/errors" + F "github.com/IBM/fp-go/v2/function" + I "github.com/IBM/fp-go/v2/identity" + IOE "github.com/IBM/fp-go/v2/ioeither" + L "github.com/IBM/fp-go/v2/lazy" + O "github.com/IBM/fp-go/v2/option" + R "github.com/IBM/fp-go/v2/record" + T "github.com/IBM/fp-go/v2/tuple" + + "sync" +) + +func providerToEntry(p Provider) T.Tuple2[string, ProviderFactory] { + return T.MakeTuple2(p.Provides().Id(), p.Factory()) +} + +func itemProviderToMap(p Provider) map[string][]ProviderFactory { + return R.Singleton(p.Provides().Id(), A.Of(p.Factory())) +} + +var ( + // missingProviderError returns a [ProviderFactory] that fails due to a missing dependency + missingProviderError = F.Flow4( + Dependency.String, + errors.OnSome[string]("no provider for dependency [%s]"), + IOE.Left[any, error], + F.Constant1[InjectableFactory, IOE.IOEither[error, any]], + ) + + // missingProviderErrorOrDefault returns the default [ProviderFactory] or an error + missingProviderErrorOrDefault = F.Flow3( + T.Replicate2[Dependency], + T.Map2(Dependency.ProviderFactory, F.Flow2(missingProviderError, F.Constant[ProviderFactory])), + T.Tupled2(O.MonadGetOrElse[ProviderFactory]), + ) + + emptyMulti any = A.Empty[any]() + + // emptyMultiDependency returns a [ProviderFactory] for an empty, multi dependency + emptyMultiDependency = F.Constant1[Dependency](F.Constant1[InjectableFactory](IOE.Of[error](emptyMulti))) + + // handleMissingProvider covers the case of a missing provider. It either + // returns an error or an empty multi value provider + handleMissingProvider = F.Flow2( + F.Ternary(isMultiDependency, emptyMultiDependency, missingProviderErrorOrDefault), + F.Constant[ProviderFactory], + ) + + // mergeItemProviders is a monoid for item provider factories + mergeItemProviders = R.UnionMonoid[string](A.Semigroup[ProviderFactory]()) + + // mergeProviders is a monoid for provider factories + mergeProviders = R.UnionLastMonoid[string, ProviderFactory]() + + // collectItemProviders create a provider map for item providers + collectItemProviders = F.Flow2( + A.FoldMap[Provider](mergeItemProviders)(itemProviderToMap), + R.Map[string](itemProviderFactory), + ) + + // collectProviders collects non-item providers + collectProviders = F.Flow2( + A.Map(providerToEntry), + R.FromEntries[string, ProviderFactory], + ) + + // assembleProviders constructs the provider map for item and non-item providers + assembleProviders = F.Flow3( + A.Partition(isItemProvider), + T.Map2(collectProviders, collectItemProviders), + T.Tupled2(mergeProviders.Concat), + ) +) + +// isMultiDependency tests if a dependency is a container dependency +func isMultiDependency(dep Dependency) bool { + return dep.Flag()&Multi == Multi +} + +// isItemProvider tests if a provivder provides a single item +func isItemProvider(provider Provider) bool { + return provider.Provides().Flag()&Item == Item +} + +// itemProviderFactory combines multiple factories into one, returning an array +func itemProviderFactory(fcts []ProviderFactory) ProviderFactory { + return func(inj InjectableFactory) IOE.IOEither[error, any] { + return F.Pipe2( + fcts, + IOE.TraverseArray(I.Flap[IOE.IOEither[error, any]](inj)), + IOE.Map[error](F.ToAny[[]any]), + ) + } +} + +// MakeInjector creates an [InjectableFactory] based on a set of [Provider]s +// +// The resulting [InjectableFactory] can then be used to retrieve service instances given their [Dependency]. The implementation +// makes sure to transitively resolve the required dependencies. +func MakeInjector(providers []Provider) InjectableFactory { + + type Result = IOE.IOEither[error, any] + type LazyResult = L.Lazy[Result] + + // resolved stores the values resolved so far, key is the string ID + // of the token, value is a lazy result + var resolved sync.Map + + // provide a mapping for all providers + factoryByID := assembleProviders(providers) + + // the actual factory, we need lazy initialization + var injFct InjectableFactory + + // lazy initialization, so we can cross reference it + injFct = func(token Dependency) Result { + + key := token.Id() + + // according to https://github.com/golang/go/issues/44159 this + // is the best way to use the sync map + actual, loaded := resolved.Load(key) + if !loaded { + + computeResult := func() Result { + return F.Pipe5( + token, + T.Replicate2[Dependency], + T.Map2(F.Flow3( + Dependency.Id, + R.Lookup[ProviderFactory, string], + I.Ap[O.Option[ProviderFactory]](factoryByID), + ), handleMissingProvider), + T.Tupled2(O.MonadGetOrElse[ProviderFactory]), + I.Ap[IOE.IOEither[error, any]](injFct), + IOE.Memoize[error, any], + ) + } + + actual, _ = resolved.LoadOrStore(key, F.Pipe1( + computeResult, + L.Memoize[Result], + )) + } + + return actual.(LazyResult)() + } + + return injFct +} diff --git a/v2/di/erasure/provider.go b/v2/di/erasure/provider.go new file mode 100644 index 0000000..864abb4 --- /dev/null +++ b/v2/di/erasure/provider.go @@ -0,0 +1,180 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package erasure + +import ( + "fmt" + + A "github.com/IBM/fp-go/v2/array" + E "github.com/IBM/fp-go/v2/either" + F "github.com/IBM/fp-go/v2/function" + I "github.com/IBM/fp-go/v2/identity" + IO "github.com/IBM/fp-go/v2/io" + IOE "github.com/IBM/fp-go/v2/ioeither" + IOO "github.com/IBM/fp-go/v2/iooption" + Int "github.com/IBM/fp-go/v2/number/integer" + O "github.com/IBM/fp-go/v2/option" + R "github.com/IBM/fp-go/v2/record" +) + +type ( + // InjectableFactory is a factory function that can create an untyped instance of a service based on its [Dependency] identifier + InjectableFactory = func(Dependency) IOE.IOEither[error, any] + ProviderFactory = func(InjectableFactory) IOE.IOEither[error, any] + + paramIndex = map[int]int + paramValue = map[int]any + handler = func(paramIndex) func([]IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] + mapping = map[int]paramIndex + + Provider interface { + fmt.Stringer + // Provides returns the [Dependency] implemented by this provider + Provides() Dependency + // Factory returns s function that can create an instance of the dependency based on an [InjectableFactory] + Factory() ProviderFactory + } + + provider struct { + provides Dependency + factory ProviderFactory + } +) + +func (p *provider) Provides() Dependency { + return p.provides +} + +func (p *provider) Factory() ProviderFactory { + return p.factory +} + +func (p *provider) String() string { + return fmt.Sprintf("Provider for [%s]", p.provides) +} + +func MakeProvider(token Dependency, fct ProviderFactory) Provider { + return &provider{token, fct} +} + +func mapFromToken(idx int, token Dependency) map[int]paramIndex { + return R.Singleton(token.Flag()&BehaviourMask, R.Singleton(idx, idx)) +} + +var ( + // Empty is the empty array of providers + Empty = A.Empty[Provider]() + + mergeTokenMaps = R.UnionMonoid[int](R.UnionLastSemigroup[int, int]()) + foldDeps = A.FoldMapWithIndex[Dependency](mergeTokenMaps)(mapFromToken) + mergeMaps = R.UnionLastMonoid[int, any]() + collectParams = R.CollectOrd[any, any](Int.Ord)(F.SK[int, any]) + + mapDeps = F.Curry2(A.MonadMap[Dependency, IOE.IOEither[error, any]]) + + handlers = map[int]handler{ + Identity: func(mp paramIndex) func([]IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] { + return func(res []IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] { + return F.Pipe1( + mp, + IOE.TraverseRecord[int](getAt(res)), + ) + } + }, + Option: func(mp paramIndex) func([]IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] { + return func(res []IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] { + return F.Pipe3( + mp, + IO.TraverseRecord[int](getAt(res)), + IO.Map(R.Map[int](F.Flow2( + E.ToOption[error, any], + F.ToAny[O.Option[any]], + ))), + IOE.FromIO[error, paramValue], + ) + } + }, + IOEither: func(mp paramIndex) func([]IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] { + return func(res []IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] { + return F.Pipe2( + mp, + R.Map[int](F.Flow2( + getAt(res), + F.ToAny[IOE.IOEither[error, any]], + )), + IOE.Of[error, paramValue], + ) + } + }, + IOOption: func(mp paramIndex) func([]IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] { + return func(res []IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] { + return F.Pipe2( + mp, + R.Map[int](F.Flow3( + getAt(res), + IOE.ToIOOption[error, any], + F.ToAny[IOO.IOOption[any]], + )), + IOE.Of[error, paramValue], + ) + } + }, + } +) + +func getAt[T any](ar []T) func(idx int) T { + return func(idx int) T { + return ar[idx] + } +} + +func handleMapping(mp mapping) func(res []IOE.IOEither[error, any]) IOE.IOEither[error, []any] { + preFct := F.Pipe1( + mp, + R.Collect(func(idx int, p paramIndex) func([]IOE.IOEither[error, any]) IOE.IOEither[error, paramValue] { + return handlers[idx](p) + }), + ) + doFct := F.Flow2( + I.Flap[IOE.IOEither[error, paramValue], []IOE.IOEither[error, any]], + IOE.TraverseArray[error, func([]IOE.IOEither[error, any]) IOE.IOEither[error, paramValue], paramValue], + ) + postFct := IOE.Map[error](F.Flow2( + A.Fold(mergeMaps), + collectParams, + )) + + return func(res []IOE.IOEither[error, any]) IOE.IOEither[error, []any] { + return F.Pipe2( + preFct, + doFct(res), + postFct, + ) + } +} + +// MakeProviderFactory constructs a [ProviderFactory] based on a set of [Dependency]s and +// a function that accepts the resolved dependencies to return a result +func MakeProviderFactory( + deps []Dependency, + fct func(param ...any) IOE.IOEither[error, any]) ProviderFactory { + + return F.Flow3( + mapDeps(deps), + handleMapping(foldDeps(deps)), + IOE.Chain(F.Unvariadic0(fct)), + ) +} diff --git a/v2/di/erasure/token.go b/v2/di/erasure/token.go new file mode 100644 index 0000000..7b9043e --- /dev/null +++ b/v2/di/erasure/token.go @@ -0,0 +1,45 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package erasure + +import ( + "fmt" + + O "github.com/IBM/fp-go/v2/option" +) + +const ( + BehaviourMask = 0x0f + Identity = 0 // required dependency + Option = 1 // optional dependency + IOEither = 2 // lazy and required + IOOption = 3 // lazy and optional + + TypeMask = 0xf0 + Multi = 1 << 4 // array of implementations + Item = 2 << 4 // item of a multi token +) + +// Dependency describes the relationship to a service +type Dependency interface { + fmt.Stringer + // Id returns a unique identifier for a token that can be used as a cache key + Id() string + // Flag returns a tag that identifies the behaviour of the dependency + Flag() int + // ProviderFactory optionally returns an attached [ProviderFactory] that represents the default for this dependency + ProviderFactory() O.Option[ProviderFactory] +} diff --git a/v2/di/gen.go b/v2/di/gen.go new file mode 100644 index 0000000..789bfe6 --- /dev/null +++ b/v2/di/gen.go @@ -0,0 +1,1889 @@ +// Code generated by go generate; DO NOT EDIT. +// This file was generated by robots at +// 2025-03-09 23:52:50.1589225 +0100 CET m=+0.002639401 + +package di + + +import ( + E "github.com/IBM/fp-go/v2/either" + IOE "github.com/IBM/fp-go/v2/ioeither" + T "github.com/IBM/fp-go/v2/tuple" + A "github.com/IBM/fp-go/v2/array" + DIE "github.com/IBM/fp-go/v2/di/erasure" +) + +// eraseProviderFactory1 creates a function that takes a variadic number of untyped arguments and from a function of 1 strongly typed arguments and 1 dependencies +func eraseProviderFactory1[T1 any, R any]( + d1 Dependency[T1], + f func(T1) IOE.IOEither[error, R]) func(params ...any) IOE.IOEither[error, any] { + ft := eraseTuple(T.Tupled1(f)) + t1 := lookupAt[T1](0, d1) + return func(params ...any) IOE.IOEither[error, any] { + return ft(E.SequenceT1( + t1(params), + )) + } +} + +// MakeProviderFactory1 creates a [DIE.ProviderFactory] from a function with 1 arguments and 1 dependencies +func MakeProviderFactory1[T1 any, R any]( + d1 Dependency[T1], + f func(T1) IOE.IOEither[error, R], +) DIE.ProviderFactory { + return DIE.MakeProviderFactory( + A.From[DIE.Dependency]( + d1, + ), + eraseProviderFactory1( + d1, + f, + ), + ) +} + +// MakeTokenWithDefault1 creates an [InjectionToken] with a default implementation with 1 dependencies +func MakeTokenWithDefault1[T1 any, R any]( + name string, + d1 Dependency[T1], + f func(T1) IOE.IOEither[error, R], +) InjectionToken[R] { + return MakeTokenWithDefault[R](name, MakeProviderFactory1( + d1, + f, + )) +} + +// MakeProvider1 creates a [DIE.Provider] for an [InjectionToken] from a function with 1 dependencies +func MakeProvider1[T1 any, R any]( + token InjectionToken[R], + d1 Dependency[T1], + f func(T1) IOE.IOEither[error, R], +) DIE.Provider { + return DIE.MakeProvider( + token, + MakeProviderFactory1( + d1, + f, + )) +} + +// eraseProviderFactory2 creates a function that takes a variadic number of untyped arguments and from a function of 2 strongly typed arguments and 2 dependencies +func eraseProviderFactory2[T1, T2 any, R any]( + d1 Dependency[T1], + d2 Dependency[T2], + f func(T1, T2) IOE.IOEither[error, R]) func(params ...any) IOE.IOEither[error, any] { + ft := eraseTuple(T.Tupled2(f)) + t1 := lookupAt[T1](0, d1) + t2 := lookupAt[T2](1, d2) + return func(params ...any) IOE.IOEither[error, any] { + return ft(E.SequenceT2( + t1(params), + t2(params), + )) + } +} + +// MakeProviderFactory2 creates a [DIE.ProviderFactory] from a function with 2 arguments and 2 dependencies +func MakeProviderFactory2[T1, T2 any, R any]( + d1 Dependency[T1], + d2 Dependency[T2], + f func(T1, T2) IOE.IOEither[error, R], +) DIE.ProviderFactory { + return DIE.MakeProviderFactory( + A.From[DIE.Dependency]( + d1, + d2, + ), + eraseProviderFactory2( + d1, + d2, + f, + ), + ) +} + +// MakeTokenWithDefault2 creates an [InjectionToken] with a default implementation with 2 dependencies +func MakeTokenWithDefault2[T1, T2 any, R any]( + name string, + d1 Dependency[T1], + d2 Dependency[T2], + f func(T1, T2) IOE.IOEither[error, R], +) InjectionToken[R] { + return MakeTokenWithDefault[R](name, MakeProviderFactory2( + d1, + d2, + f, + )) +} + +// MakeProvider2 creates a [DIE.Provider] for an [InjectionToken] from a function with 2 dependencies +func MakeProvider2[T1, T2 any, R any]( + token InjectionToken[R], + d1 Dependency[T1], + d2 Dependency[T2], + f func(T1, T2) IOE.IOEither[error, R], +) DIE.Provider { + return DIE.MakeProvider( + token, + MakeProviderFactory2( + d1, + d2, + f, + )) +} + +// eraseProviderFactory3 creates a function that takes a variadic number of untyped arguments and from a function of 3 strongly typed arguments and 3 dependencies +func eraseProviderFactory3[T1, T2, T3 any, R any]( + d1 Dependency[T1], + d2 Dependency[T2], + d3 Dependency[T3], + f func(T1, T2, T3) IOE.IOEither[error, R]) func(params ...any) IOE.IOEither[error, any] { + ft := eraseTuple(T.Tupled3(f)) + t1 := lookupAt[T1](0, d1) + t2 := lookupAt[T2](1, d2) + t3 := lookupAt[T3](2, d3) + return func(params ...any) IOE.IOEither[error, any] { + return ft(E.SequenceT3( + t1(params), + t2(params), + t3(params), + )) + } +} + +// MakeProviderFactory3 creates a [DIE.ProviderFactory] from a function with 3 arguments and 3 dependencies +func MakeProviderFactory3[T1, T2, T3 any, R any]( + d1 Dependency[T1], + d2 Dependency[T2], + d3 Dependency[T3], + f func(T1, T2, T3) IOE.IOEither[error, R], +) DIE.ProviderFactory { + return DIE.MakeProviderFactory( + A.From[DIE.Dependency]( + d1, + d2, + d3, + ), + eraseProviderFactory3( + d1, + d2, + d3, + f, + ), + ) +} + +// MakeTokenWithDefault3 creates an [InjectionToken] with a default implementation with 3 dependencies +func MakeTokenWithDefault3[T1, T2, T3 any, R any]( + name string, + d1 Dependency[T1], + d2 Dependency[T2], + d3 Dependency[T3], + f func(T1, T2, T3) IOE.IOEither[error, R], +) InjectionToken[R] { + return MakeTokenWithDefault[R](name, MakeProviderFactory3( + d1, + d2, + d3, + f, + )) +} + +// MakeProvider3 creates a [DIE.Provider] for an [InjectionToken] from a function with 3 dependencies +func MakeProvider3[T1, T2, T3 any, R any]( + token InjectionToken[R], + d1 Dependency[T1], + d2 Dependency[T2], + d3 Dependency[T3], + f func(T1, T2, T3) IOE.IOEither[error, R], +) DIE.Provider { + return DIE.MakeProvider( + token, + MakeProviderFactory3( + d1, + d2, + d3, + f, + )) +} + +// eraseProviderFactory4 creates a function that takes a variadic number of untyped arguments and from a function of 4 strongly typed arguments and 4 dependencies +func eraseProviderFactory4[T1, T2, T3, T4 any, R any]( + d1 Dependency[T1], + d2 Dependency[T2], + d3 Dependency[T3], + d4 Dependency[T4], + f func(T1, T2, T3, T4) IOE.IOEither[error, R]) func(params ...any) IOE.IOEither[error, any] { + ft := eraseTuple(T.Tupled4(f)) + t1 := lookupAt[T1](0, d1) + t2 := lookupAt[T2](1, d2) + t3 := lookupAt[T3](2, d3) + t4 := lookupAt[T4](3, d4) + return func(params ...any) IOE.IOEither[error, any] { + return ft(E.SequenceT4( + t1(params), + t2(params), + t3(params), + t4(params), + )) + } +} + +// MakeProviderFactory4 creates a [DIE.ProviderFactory] from a function with 4 arguments and 4 dependencies +func MakeProviderFactory4[T1, T2, T3, T4 any, R any]( + d1 Dependency[T1], + d2 Dependency[T2], + d3 Dependency[T3], + d4 Dependency[T4], + f func(T1, T2, T3, T4) IOE.IOEither[error, R], +) DIE.ProviderFactory { + return DIE.MakeProviderFactory( + A.From[DIE.Dependency]( + d1, + d2, + d3, + d4, + ), + eraseProviderFactory4( + d1, + d2, + d3, + d4, + f, + ), + ) +} + +// MakeTokenWithDefault4 creates an [InjectionToken] with a default implementation with 4 dependencies +func MakeTokenWithDefault4[T1, T2, T3, T4 any, R any]( + name string, + d1 Dependency[T1], + d2 Dependency[T2], + d3 Dependency[T3], + d4 Dependency[T4], + f func(T1, T2, T3, T4) IOE.IOEither[error, R], +) InjectionToken[R] { + return MakeTokenWithDefault[R](name, MakeProviderFactory4( + d1, + d2, + d3, + d4, + f, + )) +} + +// MakeProvider4 creates a [DIE.Provider] for an [InjectionToken] from a function with 4 dependencies +func MakeProvider4[T1, T2, T3, T4 any, R any]( + token InjectionToken[R], + d1 Dependency[T1], + d2 Dependency[T2], + d3 Dependency[T3], + d4 Dependency[T4], + f func(T1, T2, T3, T4) IOE.IOEither[error, R], +) DIE.Provider { + return DIE.MakeProvider( + token, + MakeProviderFactory4( + d1, + d2, + d3, + d4, + f, + )) +} + +// eraseProviderFactory5 creates a function that takes a variadic number of untyped arguments and from a function of 5 strongly typed arguments and 5 dependencies +func eraseProviderFactory5[T1, T2, T3, T4, T5 any, R any]( + d1 Dependency[T1], + d2 Dependency[T2], + d3 Dependency[T3], + d4 Dependency[T4], + d5 Dependency[T5], + f func(T1, T2, T3, T4, T5) IOE.IOEither[error, R]) func(params ...any) IOE.IOEither[error, any] { + ft := eraseTuple(T.Tupled5(f)) + t1 := lookupAt[T1](0, d1) + t2 := lookupAt[T2](1, d2) + t3 := lookupAt[T3](2, d3) + t4 := lookupAt[T4](3, d4) + t5 := lookupAt[T5](4, d5) + return func(params ...any) IOE.IOEither[error, any] { + return ft(E.SequenceT5( + t1(params), + t2(params), + t3(params), + t4(params), + t5(params), + )) + } +} + +// MakeProviderFactory5 creates a [DIE.ProviderFactory] from a function with 5 arguments and 5 dependencies +func MakeProviderFactory5[T1, T2, T3, T4, T5 any, R any]( + d1 Dependency[T1], + d2 Dependency[T2], + d3 Dependency[T3], + d4 Dependency[T4], + d5 Dependency[T5], + f func(T1, T2, T3, T4, T5) IOE.IOEither[error, R], +) DIE.ProviderFactory { + return DIE.MakeProviderFactory( + A.From[DIE.Dependency]( + d1, + d2, + d3, + d4, + d5, + ), + eraseProviderFactory5( + d1, + d2, + d3, + d4, + d5, + f, + ), + ) +} + +// MakeTokenWithDefault5 creates an [InjectionToken] with a default implementation with 5 dependencies +func MakeTokenWithDefault5[T1, T2, T3, T4, T5 any, R any]( + name string, + d1 Dependency[T1], + d2 Dependency[T2], + d3 Dependency[T3], + d4 Dependency[T4], + d5 Dependency[T5], + f func(T1, T2, T3, T4, T5) IOE.IOEither[error, R], +) InjectionToken[R] { + return MakeTokenWithDefault[R](name, MakeProviderFactory5( + d1, + d2, + d3, + d4, + d5, + f, + )) +} + +// MakeProvider5 creates a [DIE.Provider] for an [InjectionToken] from a function with 5 dependencies +func MakeProvider5[T1, T2, T3, T4, T5 any, R any]( + token InjectionToken[R], + d1 Dependency[T1], + d2 Dependency[T2], + d3 Dependency[T3], + d4 Dependency[T4], + d5 Dependency[T5], + f func(T1, T2, T3, T4, T5) IOE.IOEither[error, R], +) DIE.Provider { + return DIE.MakeProvider( + token, + MakeProviderFactory5( + d1, + d2, + d3, + d4, + d5, + f, + )) +} + +// eraseProviderFactory6 creates a function that takes a variadic number of untyped arguments and from a function of 6 strongly typed arguments and 6 dependencies +func eraseProviderFactory6[T1, T2, T3, T4, T5, T6 any, R any]( + d1 Dependency[T1], + d2 Dependency[T2], + d3 Dependency[T3], + d4 Dependency[T4], + d5 Dependency[T5], + d6 Dependency[T6], + f func(T1, T2, T3, T4, T5, T6) IOE.IOEither[error, R]) func(params ...any) IOE.IOEither[error, any] { + ft := eraseTuple(T.Tupled6(f)) + t1 := lookupAt[T1](0, d1) + t2 := lookupAt[T2](1, d2) + t3 := lookupAt[T3](2, d3) + t4 := lookupAt[T4](3, d4) + t5 := lookupAt[T5](4, d5) + t6 := lookupAt[T6](5, d6) + return func(params ...any) IOE.IOEither[error, any] { + return ft(E.SequenceT6( + t1(params), + t2(params), + t3(params), + t4(params), + t5(params), + t6(params), + )) + } +} + +// MakeProviderFactory6 creates a [DIE.ProviderFactory] from a function with 6 arguments and 6 dependencies +func MakeProviderFactory6[T1, T2, T3, T4, T5, T6 any, R any]( + d1 Dependency[T1], + d2 Dependency[T2], + d3 Dependency[T3], + d4 Dependency[T4], + d5 Dependency[T5], + d6 Dependency[T6], + f func(T1, T2, T3, T4, T5, T6) IOE.IOEither[error, R], +) DIE.ProviderFactory { + return DIE.MakeProviderFactory( + A.From[DIE.Dependency]( + d1, + d2, + d3, + d4, + d5, + d6, + ), + eraseProviderFactory6( + d1, + d2, + d3, + d4, + d5, + d6, + f, + ), + ) +} + +// MakeTokenWithDefault6 creates an [InjectionToken] with a default implementation with 6 dependencies +func MakeTokenWithDefault6[T1, T2, T3, T4, T5, T6 any, R any]( + name string, + d1 Dependency[T1], + d2 Dependency[T2], + d3 Dependency[T3], + d4 Dependency[T4], + d5 Dependency[T5], + d6 Dependency[T6], + f func(T1, T2, T3, T4, T5, T6) IOE.IOEither[error, R], +) InjectionToken[R] { + return MakeTokenWithDefault[R](name, MakeProviderFactory6( + d1, + d2, + d3, + d4, + d5, + d6, + f, + )) +} + +// MakeProvider6 creates a [DIE.Provider] for an [InjectionToken] from a function with 6 dependencies +func MakeProvider6[T1, T2, T3, T4, T5, T6 any, R any]( + token InjectionToken[R], + d1 Dependency[T1], + d2 Dependency[T2], + d3 Dependency[T3], + d4 Dependency[T4], + d5 Dependency[T5], + d6 Dependency[T6], + f func(T1, T2, T3, T4, T5, T6) IOE.IOEither[error, R], +) DIE.Provider { + return DIE.MakeProvider( + token, + MakeProviderFactory6( + d1, + d2, + d3, + d4, + d5, + d6, + f, + )) +} + +// eraseProviderFactory7 creates a function that takes a variadic number of untyped arguments and from a function of 7 strongly typed arguments and 7 dependencies +func eraseProviderFactory7[T1, T2, T3, T4, T5, T6, T7 any, R any]( + d1 Dependency[T1], + d2 Dependency[T2], + d3 Dependency[T3], + d4 Dependency[T4], + d5 Dependency[T5], + d6 Dependency[T6], + d7 Dependency[T7], + f func(T1, T2, T3, T4, T5, T6, T7) IOE.IOEither[error, R]) func(params ...any) IOE.IOEither[error, any] { + ft := eraseTuple(T.Tupled7(f)) + t1 := lookupAt[T1](0, d1) + t2 := lookupAt[T2](1, d2) + t3 := lookupAt[T3](2, d3) + t4 := lookupAt[T4](3, d4) + t5 := lookupAt[T5](4, d5) + t6 := lookupAt[T6](5, d6) + t7 := lookupAt[T7](6, d7) + return func(params ...any) IOE.IOEither[error, any] { + return ft(E.SequenceT7( + t1(params), + t2(params), + t3(params), + t4(params), + t5(params), + t6(params), + t7(params), + )) + } +} + +// MakeProviderFactory7 creates a [DIE.ProviderFactory] from a function with 7 arguments and 7 dependencies +func MakeProviderFactory7[T1, T2, T3, T4, T5, T6, T7 any, R any]( + d1 Dependency[T1], + d2 Dependency[T2], + d3 Dependency[T3], + d4 Dependency[T4], + d5 Dependency[T5], + d6 Dependency[T6], + d7 Dependency[T7], + f func(T1, T2, T3, T4, T5, T6, T7) IOE.IOEither[error, R], +) DIE.ProviderFactory { + return DIE.MakeProviderFactory( + A.From[DIE.Dependency]( + d1, + d2, + d3, + d4, + d5, + d6, + d7, + ), + eraseProviderFactory7( + d1, + d2, + d3, + d4, + d5, + d6, + d7, + f, + ), + ) +} + +// MakeTokenWithDefault7 creates an [InjectionToken] with a default implementation with 7 dependencies +func MakeTokenWithDefault7[T1, T2, T3, T4, T5, T6, T7 any, R any]( + name string, + d1 Dependency[T1], + d2 Dependency[T2], + d3 Dependency[T3], + d4 Dependency[T4], + d5 Dependency[T5], + d6 Dependency[T6], + d7 Dependency[T7], + f func(T1, T2, T3, T4, T5, T6, T7) IOE.IOEither[error, R], +) InjectionToken[R] { + return MakeTokenWithDefault[R](name, MakeProviderFactory7( + d1, + d2, + d3, + d4, + d5, + d6, + d7, + f, + )) +} + +// MakeProvider7 creates a [DIE.Provider] for an [InjectionToken] from a function with 7 dependencies +func MakeProvider7[T1, T2, T3, T4, T5, T6, T7 any, R any]( + token InjectionToken[R], + d1 Dependency[T1], + d2 Dependency[T2], + d3 Dependency[T3], + d4 Dependency[T4], + d5 Dependency[T5], + d6 Dependency[T6], + d7 Dependency[T7], + f func(T1, T2, T3, T4, T5, T6, T7) IOE.IOEither[error, R], +) DIE.Provider { + return DIE.MakeProvider( + token, + MakeProviderFactory7( + d1, + d2, + d3, + d4, + d5, + d6, + d7, + f, + )) +} + +// eraseProviderFactory8 creates a function that takes a variadic number of untyped arguments and from a function of 8 strongly typed arguments and 8 dependencies +func eraseProviderFactory8[T1, T2, T3, T4, T5, T6, T7, T8 any, R any]( + d1 Dependency[T1], + d2 Dependency[T2], + d3 Dependency[T3], + d4 Dependency[T4], + d5 Dependency[T5], + d6 Dependency[T6], + d7 Dependency[T7], + d8 Dependency[T8], + f func(T1, T2, T3, T4, T5, T6, T7, T8) IOE.IOEither[error, R]) func(params ...any) IOE.IOEither[error, any] { + ft := eraseTuple(T.Tupled8(f)) + t1 := lookupAt[T1](0, d1) + t2 := lookupAt[T2](1, d2) + t3 := lookupAt[T3](2, d3) + t4 := lookupAt[T4](3, d4) + t5 := lookupAt[T5](4, d5) + t6 := lookupAt[T6](5, d6) + t7 := lookupAt[T7](6, d7) + t8 := lookupAt[T8](7, d8) + return func(params ...any) IOE.IOEither[error, any] { + return ft(E.SequenceT8( + t1(params), + t2(params), + t3(params), + t4(params), + t5(params), + t6(params), + t7(params), + t8(params), + )) + } +} + +// MakeProviderFactory8 creates a [DIE.ProviderFactory] from a function with 8 arguments and 8 dependencies +func MakeProviderFactory8[T1, T2, T3, T4, T5, T6, T7, T8 any, R any]( + d1 Dependency[T1], + d2 Dependency[T2], + d3 Dependency[T3], + d4 Dependency[T4], + d5 Dependency[T5], + d6 Dependency[T6], + d7 Dependency[T7], + d8 Dependency[T8], + f func(T1, T2, T3, T4, T5, T6, T7, T8) IOE.IOEither[error, R], +) DIE.ProviderFactory { + return DIE.MakeProviderFactory( + A.From[DIE.Dependency]( + d1, + d2, + d3, + d4, + d5, + d6, + d7, + d8, + ), + eraseProviderFactory8( + d1, + d2, + d3, + d4, + d5, + d6, + d7, + d8, + f, + ), + ) +} + +// MakeTokenWithDefault8 creates an [InjectionToken] with a default implementation with 8 dependencies +func MakeTokenWithDefault8[T1, T2, T3, T4, T5, T6, T7, T8 any, R any]( + name string, + d1 Dependency[T1], + d2 Dependency[T2], + d3 Dependency[T3], + d4 Dependency[T4], + d5 Dependency[T5], + d6 Dependency[T6], + d7 Dependency[T7], + d8 Dependency[T8], + f func(T1, T2, T3, T4, T5, T6, T7, T8) IOE.IOEither[error, R], +) InjectionToken[R] { + return MakeTokenWithDefault[R](name, MakeProviderFactory8( + d1, + d2, + d3, + d4, + d5, + d6, + d7, + d8, + f, + )) +} + +// MakeProvider8 creates a [DIE.Provider] for an [InjectionToken] from a function with 8 dependencies +func MakeProvider8[T1, T2, T3, T4, T5, T6, T7, T8 any, R any]( + token InjectionToken[R], + d1 Dependency[T1], + d2 Dependency[T2], + d3 Dependency[T3], + d4 Dependency[T4], + d5 Dependency[T5], + d6 Dependency[T6], + d7 Dependency[T7], + d8 Dependency[T8], + f func(T1, T2, T3, T4, T5, T6, T7, T8) IOE.IOEither[error, R], +) DIE.Provider { + return DIE.MakeProvider( + token, + MakeProviderFactory8( + d1, + d2, + d3, + d4, + d5, + d6, + d7, + d8, + f, + )) +} + +// eraseProviderFactory9 creates a function that takes a variadic number of untyped arguments and from a function of 9 strongly typed arguments and 9 dependencies +func eraseProviderFactory9[T1, T2, T3, T4, T5, T6, T7, T8, T9 any, R any]( + d1 Dependency[T1], + d2 Dependency[T2], + d3 Dependency[T3], + d4 Dependency[T4], + d5 Dependency[T5], + d6 Dependency[T6], + d7 Dependency[T7], + d8 Dependency[T8], + d9 Dependency[T9], + f func(T1, T2, T3, T4, T5, T6, T7, T8, T9) IOE.IOEither[error, R]) func(params ...any) IOE.IOEither[error, any] { + ft := eraseTuple(T.Tupled9(f)) + t1 := lookupAt[T1](0, d1) + t2 := lookupAt[T2](1, d2) + t3 := lookupAt[T3](2, d3) + t4 := lookupAt[T4](3, d4) + t5 := lookupAt[T5](4, d5) + t6 := lookupAt[T6](5, d6) + t7 := lookupAt[T7](6, d7) + t8 := lookupAt[T8](7, d8) + t9 := lookupAt[T9](8, d9) + return func(params ...any) IOE.IOEither[error, any] { + return ft(E.SequenceT9( + t1(params), + t2(params), + t3(params), + t4(params), + t5(params), + t6(params), + t7(params), + t8(params), + t9(params), + )) + } +} + +// MakeProviderFactory9 creates a [DIE.ProviderFactory] from a function with 9 arguments and 9 dependencies +func MakeProviderFactory9[T1, T2, T3, T4, T5, T6, T7, T8, T9 any, R any]( + d1 Dependency[T1], + d2 Dependency[T2], + d3 Dependency[T3], + d4 Dependency[T4], + d5 Dependency[T5], + d6 Dependency[T6], + d7 Dependency[T7], + d8 Dependency[T8], + d9 Dependency[T9], + f func(T1, T2, T3, T4, T5, T6, T7, T8, T9) IOE.IOEither[error, R], +) DIE.ProviderFactory { + return DIE.MakeProviderFactory( + A.From[DIE.Dependency]( + d1, + d2, + d3, + d4, + d5, + d6, + d7, + d8, + d9, + ), + eraseProviderFactory9( + d1, + d2, + d3, + d4, + d5, + d6, + d7, + d8, + d9, + f, + ), + ) +} + +// MakeTokenWithDefault9 creates an [InjectionToken] with a default implementation with 9 dependencies +func MakeTokenWithDefault9[T1, T2, T3, T4, T5, T6, T7, T8, T9 any, R any]( + name string, + d1 Dependency[T1], + d2 Dependency[T2], + d3 Dependency[T3], + d4 Dependency[T4], + d5 Dependency[T5], + d6 Dependency[T6], + d7 Dependency[T7], + d8 Dependency[T8], + d9 Dependency[T9], + f func(T1, T2, T3, T4, T5, T6, T7, T8, T9) IOE.IOEither[error, R], +) InjectionToken[R] { + return MakeTokenWithDefault[R](name, MakeProviderFactory9( + d1, + d2, + d3, + d4, + d5, + d6, + d7, + d8, + d9, + f, + )) +} + +// MakeProvider9 creates a [DIE.Provider] for an [InjectionToken] from a function with 9 dependencies +func MakeProvider9[T1, T2, T3, T4, T5, T6, T7, T8, T9 any, R any]( + token InjectionToken[R], + d1 Dependency[T1], + d2 Dependency[T2], + d3 Dependency[T3], + d4 Dependency[T4], + d5 Dependency[T5], + d6 Dependency[T6], + d7 Dependency[T7], + d8 Dependency[T8], + d9 Dependency[T9], + f func(T1, T2, T3, T4, T5, T6, T7, T8, T9) IOE.IOEither[error, R], +) DIE.Provider { + return DIE.MakeProvider( + token, + MakeProviderFactory9( + d1, + d2, + d3, + d4, + d5, + d6, + d7, + d8, + d9, + f, + )) +} + +// eraseProviderFactory10 creates a function that takes a variadic number of untyped arguments and from a function of 10 strongly typed arguments and 10 dependencies +func eraseProviderFactory10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any, R any]( + d1 Dependency[T1], + d2 Dependency[T2], + d3 Dependency[T3], + d4 Dependency[T4], + d5 Dependency[T5], + d6 Dependency[T6], + d7 Dependency[T7], + d8 Dependency[T8], + d9 Dependency[T9], + d10 Dependency[T10], + f func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) IOE.IOEither[error, R]) func(params ...any) IOE.IOEither[error, any] { + ft := eraseTuple(T.Tupled10(f)) + t1 := lookupAt[T1](0, d1) + t2 := lookupAt[T2](1, d2) + t3 := lookupAt[T3](2, d3) + t4 := lookupAt[T4](3, d4) + t5 := lookupAt[T5](4, d5) + t6 := lookupAt[T6](5, d6) + t7 := lookupAt[T7](6, d7) + t8 := lookupAt[T8](7, d8) + t9 := lookupAt[T9](8, d9) + t10 := lookupAt[T10](9, d10) + return func(params ...any) IOE.IOEither[error, any] { + return ft(E.SequenceT10( + t1(params), + t2(params), + t3(params), + t4(params), + t5(params), + t6(params), + t7(params), + t8(params), + t9(params), + t10(params), + )) + } +} + +// MakeProviderFactory10 creates a [DIE.ProviderFactory] from a function with 10 arguments and 10 dependencies +func MakeProviderFactory10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any, R any]( + d1 Dependency[T1], + d2 Dependency[T2], + d3 Dependency[T3], + d4 Dependency[T4], + d5 Dependency[T5], + d6 Dependency[T6], + d7 Dependency[T7], + d8 Dependency[T8], + d9 Dependency[T9], + d10 Dependency[T10], + f func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) IOE.IOEither[error, R], +) DIE.ProviderFactory { + return DIE.MakeProviderFactory( + A.From[DIE.Dependency]( + d1, + d2, + d3, + d4, + d5, + d6, + d7, + d8, + d9, + d10, + ), + eraseProviderFactory10( + d1, + d2, + d3, + d4, + d5, + d6, + d7, + d8, + d9, + d10, + f, + ), + ) +} + +// MakeTokenWithDefault10 creates an [InjectionToken] with a default implementation with 10 dependencies +func MakeTokenWithDefault10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any, R any]( + name string, + d1 Dependency[T1], + d2 Dependency[T2], + d3 Dependency[T3], + d4 Dependency[T4], + d5 Dependency[T5], + d6 Dependency[T6], + d7 Dependency[T7], + d8 Dependency[T8], + d9 Dependency[T9], + d10 Dependency[T10], + f func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) IOE.IOEither[error, R], +) InjectionToken[R] { + return MakeTokenWithDefault[R](name, MakeProviderFactory10( + d1, + d2, + d3, + d4, + d5, + d6, + d7, + d8, + d9, + d10, + f, + )) +} + +// MakeProvider10 creates a [DIE.Provider] for an [InjectionToken] from a function with 10 dependencies +func MakeProvider10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any, R any]( + token InjectionToken[R], + d1 Dependency[T1], + d2 Dependency[T2], + d3 Dependency[T3], + d4 Dependency[T4], + d5 Dependency[T5], + d6 Dependency[T6], + d7 Dependency[T7], + d8 Dependency[T8], + d9 Dependency[T9], + d10 Dependency[T10], + f func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) IOE.IOEither[error, R], +) DIE.Provider { + return DIE.MakeProvider( + token, + MakeProviderFactory10( + d1, + d2, + d3, + d4, + d5, + d6, + d7, + d8, + d9, + d10, + f, + )) +} + +// eraseProviderFactory11 creates a function that takes a variadic number of untyped arguments and from a function of 11 strongly typed arguments and 11 dependencies +func eraseProviderFactory11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11 any, R any]( + d1 Dependency[T1], + d2 Dependency[T2], + d3 Dependency[T3], + d4 Dependency[T4], + d5 Dependency[T5], + d6 Dependency[T6], + d7 Dependency[T7], + d8 Dependency[T8], + d9 Dependency[T9], + d10 Dependency[T10], + d11 Dependency[T11], + f func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11) IOE.IOEither[error, R]) func(params ...any) IOE.IOEither[error, any] { + ft := eraseTuple(T.Tupled11(f)) + t1 := lookupAt[T1](0, d1) + t2 := lookupAt[T2](1, d2) + t3 := lookupAt[T3](2, d3) + t4 := lookupAt[T4](3, d4) + t5 := lookupAt[T5](4, d5) + t6 := lookupAt[T6](5, d6) + t7 := lookupAt[T7](6, d7) + t8 := lookupAt[T8](7, d8) + t9 := lookupAt[T9](8, d9) + t10 := lookupAt[T10](9, d10) + t11 := lookupAt[T11](10, d11) + return func(params ...any) IOE.IOEither[error, any] { + return ft(E.SequenceT11( + t1(params), + t2(params), + t3(params), + t4(params), + t5(params), + t6(params), + t7(params), + t8(params), + t9(params), + t10(params), + t11(params), + )) + } +} + +// MakeProviderFactory11 creates a [DIE.ProviderFactory] from a function with 11 arguments and 11 dependencies +func MakeProviderFactory11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11 any, R any]( + d1 Dependency[T1], + d2 Dependency[T2], + d3 Dependency[T3], + d4 Dependency[T4], + d5 Dependency[T5], + d6 Dependency[T6], + d7 Dependency[T7], + d8 Dependency[T8], + d9 Dependency[T9], + d10 Dependency[T10], + d11 Dependency[T11], + f func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11) IOE.IOEither[error, R], +) DIE.ProviderFactory { + return DIE.MakeProviderFactory( + A.From[DIE.Dependency]( + d1, + d2, + d3, + d4, + d5, + d6, + d7, + d8, + d9, + d10, + d11, + ), + eraseProviderFactory11( + d1, + d2, + d3, + d4, + d5, + d6, + d7, + d8, + d9, + d10, + d11, + f, + ), + ) +} + +// MakeTokenWithDefault11 creates an [InjectionToken] with a default implementation with 11 dependencies +func MakeTokenWithDefault11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11 any, R any]( + name string, + d1 Dependency[T1], + d2 Dependency[T2], + d3 Dependency[T3], + d4 Dependency[T4], + d5 Dependency[T5], + d6 Dependency[T6], + d7 Dependency[T7], + d8 Dependency[T8], + d9 Dependency[T9], + d10 Dependency[T10], + d11 Dependency[T11], + f func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11) IOE.IOEither[error, R], +) InjectionToken[R] { + return MakeTokenWithDefault[R](name, MakeProviderFactory11( + d1, + d2, + d3, + d4, + d5, + d6, + d7, + d8, + d9, + d10, + d11, + f, + )) +} + +// MakeProvider11 creates a [DIE.Provider] for an [InjectionToken] from a function with 11 dependencies +func MakeProvider11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11 any, R any]( + token InjectionToken[R], + d1 Dependency[T1], + d2 Dependency[T2], + d3 Dependency[T3], + d4 Dependency[T4], + d5 Dependency[T5], + d6 Dependency[T6], + d7 Dependency[T7], + d8 Dependency[T8], + d9 Dependency[T9], + d10 Dependency[T10], + d11 Dependency[T11], + f func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11) IOE.IOEither[error, R], +) DIE.Provider { + return DIE.MakeProvider( + token, + MakeProviderFactory11( + d1, + d2, + d3, + d4, + d5, + d6, + d7, + d8, + d9, + d10, + d11, + f, + )) +} + +// eraseProviderFactory12 creates a function that takes a variadic number of untyped arguments and from a function of 12 strongly typed arguments and 12 dependencies +func eraseProviderFactory12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12 any, R any]( + d1 Dependency[T1], + d2 Dependency[T2], + d3 Dependency[T3], + d4 Dependency[T4], + d5 Dependency[T5], + d6 Dependency[T6], + d7 Dependency[T7], + d8 Dependency[T8], + d9 Dependency[T9], + d10 Dependency[T10], + d11 Dependency[T11], + d12 Dependency[T12], + f func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12) IOE.IOEither[error, R]) func(params ...any) IOE.IOEither[error, any] { + ft := eraseTuple(T.Tupled12(f)) + t1 := lookupAt[T1](0, d1) + t2 := lookupAt[T2](1, d2) + t3 := lookupAt[T3](2, d3) + t4 := lookupAt[T4](3, d4) + t5 := lookupAt[T5](4, d5) + t6 := lookupAt[T6](5, d6) + t7 := lookupAt[T7](6, d7) + t8 := lookupAt[T8](7, d8) + t9 := lookupAt[T9](8, d9) + t10 := lookupAt[T10](9, d10) + t11 := lookupAt[T11](10, d11) + t12 := lookupAt[T12](11, d12) + return func(params ...any) IOE.IOEither[error, any] { + return ft(E.SequenceT12( + t1(params), + t2(params), + t3(params), + t4(params), + t5(params), + t6(params), + t7(params), + t8(params), + t9(params), + t10(params), + t11(params), + t12(params), + )) + } +} + +// MakeProviderFactory12 creates a [DIE.ProviderFactory] from a function with 12 arguments and 12 dependencies +func MakeProviderFactory12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12 any, R any]( + d1 Dependency[T1], + d2 Dependency[T2], + d3 Dependency[T3], + d4 Dependency[T4], + d5 Dependency[T5], + d6 Dependency[T6], + d7 Dependency[T7], + d8 Dependency[T8], + d9 Dependency[T9], + d10 Dependency[T10], + d11 Dependency[T11], + d12 Dependency[T12], + f func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12) IOE.IOEither[error, R], +) DIE.ProviderFactory { + return DIE.MakeProviderFactory( + A.From[DIE.Dependency]( + d1, + d2, + d3, + d4, + d5, + d6, + d7, + d8, + d9, + d10, + d11, + d12, + ), + eraseProviderFactory12( + d1, + d2, + d3, + d4, + d5, + d6, + d7, + d8, + d9, + d10, + d11, + d12, + f, + ), + ) +} + +// MakeTokenWithDefault12 creates an [InjectionToken] with a default implementation with 12 dependencies +func MakeTokenWithDefault12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12 any, R any]( + name string, + d1 Dependency[T1], + d2 Dependency[T2], + d3 Dependency[T3], + d4 Dependency[T4], + d5 Dependency[T5], + d6 Dependency[T6], + d7 Dependency[T7], + d8 Dependency[T8], + d9 Dependency[T9], + d10 Dependency[T10], + d11 Dependency[T11], + d12 Dependency[T12], + f func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12) IOE.IOEither[error, R], +) InjectionToken[R] { + return MakeTokenWithDefault[R](name, MakeProviderFactory12( + d1, + d2, + d3, + d4, + d5, + d6, + d7, + d8, + d9, + d10, + d11, + d12, + f, + )) +} + +// MakeProvider12 creates a [DIE.Provider] for an [InjectionToken] from a function with 12 dependencies +func MakeProvider12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12 any, R any]( + token InjectionToken[R], + d1 Dependency[T1], + d2 Dependency[T2], + d3 Dependency[T3], + d4 Dependency[T4], + d5 Dependency[T5], + d6 Dependency[T6], + d7 Dependency[T7], + d8 Dependency[T8], + d9 Dependency[T9], + d10 Dependency[T10], + d11 Dependency[T11], + d12 Dependency[T12], + f func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12) IOE.IOEither[error, R], +) DIE.Provider { + return DIE.MakeProvider( + token, + MakeProviderFactory12( + d1, + d2, + d3, + d4, + d5, + d6, + d7, + d8, + d9, + d10, + d11, + d12, + f, + )) +} + +// eraseProviderFactory13 creates a function that takes a variadic number of untyped arguments and from a function of 13 strongly typed arguments and 13 dependencies +func eraseProviderFactory13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13 any, R any]( + d1 Dependency[T1], + d2 Dependency[T2], + d3 Dependency[T3], + d4 Dependency[T4], + d5 Dependency[T5], + d6 Dependency[T6], + d7 Dependency[T7], + d8 Dependency[T8], + d9 Dependency[T9], + d10 Dependency[T10], + d11 Dependency[T11], + d12 Dependency[T12], + d13 Dependency[T13], + f func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13) IOE.IOEither[error, R]) func(params ...any) IOE.IOEither[error, any] { + ft := eraseTuple(T.Tupled13(f)) + t1 := lookupAt[T1](0, d1) + t2 := lookupAt[T2](1, d2) + t3 := lookupAt[T3](2, d3) + t4 := lookupAt[T4](3, d4) + t5 := lookupAt[T5](4, d5) + t6 := lookupAt[T6](5, d6) + t7 := lookupAt[T7](6, d7) + t8 := lookupAt[T8](7, d8) + t9 := lookupAt[T9](8, d9) + t10 := lookupAt[T10](9, d10) + t11 := lookupAt[T11](10, d11) + t12 := lookupAt[T12](11, d12) + t13 := lookupAt[T13](12, d13) + return func(params ...any) IOE.IOEither[error, any] { + return ft(E.SequenceT13( + t1(params), + t2(params), + t3(params), + t4(params), + t5(params), + t6(params), + t7(params), + t8(params), + t9(params), + t10(params), + t11(params), + t12(params), + t13(params), + )) + } +} + +// MakeProviderFactory13 creates a [DIE.ProviderFactory] from a function with 13 arguments and 13 dependencies +func MakeProviderFactory13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13 any, R any]( + d1 Dependency[T1], + d2 Dependency[T2], + d3 Dependency[T3], + d4 Dependency[T4], + d5 Dependency[T5], + d6 Dependency[T6], + d7 Dependency[T7], + d8 Dependency[T8], + d9 Dependency[T9], + d10 Dependency[T10], + d11 Dependency[T11], + d12 Dependency[T12], + d13 Dependency[T13], + f func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13) IOE.IOEither[error, R], +) DIE.ProviderFactory { + return DIE.MakeProviderFactory( + A.From[DIE.Dependency]( + d1, + d2, + d3, + d4, + d5, + d6, + d7, + d8, + d9, + d10, + d11, + d12, + d13, + ), + eraseProviderFactory13( + d1, + d2, + d3, + d4, + d5, + d6, + d7, + d8, + d9, + d10, + d11, + d12, + d13, + f, + ), + ) +} + +// MakeTokenWithDefault13 creates an [InjectionToken] with a default implementation with 13 dependencies +func MakeTokenWithDefault13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13 any, R any]( + name string, + d1 Dependency[T1], + d2 Dependency[T2], + d3 Dependency[T3], + d4 Dependency[T4], + d5 Dependency[T5], + d6 Dependency[T6], + d7 Dependency[T7], + d8 Dependency[T8], + d9 Dependency[T9], + d10 Dependency[T10], + d11 Dependency[T11], + d12 Dependency[T12], + d13 Dependency[T13], + f func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13) IOE.IOEither[error, R], +) InjectionToken[R] { + return MakeTokenWithDefault[R](name, MakeProviderFactory13( + d1, + d2, + d3, + d4, + d5, + d6, + d7, + d8, + d9, + d10, + d11, + d12, + d13, + f, + )) +} + +// MakeProvider13 creates a [DIE.Provider] for an [InjectionToken] from a function with 13 dependencies +func MakeProvider13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13 any, R any]( + token InjectionToken[R], + d1 Dependency[T1], + d2 Dependency[T2], + d3 Dependency[T3], + d4 Dependency[T4], + d5 Dependency[T5], + d6 Dependency[T6], + d7 Dependency[T7], + d8 Dependency[T8], + d9 Dependency[T9], + d10 Dependency[T10], + d11 Dependency[T11], + d12 Dependency[T12], + d13 Dependency[T13], + f func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13) IOE.IOEither[error, R], +) DIE.Provider { + return DIE.MakeProvider( + token, + MakeProviderFactory13( + d1, + d2, + d3, + d4, + d5, + d6, + d7, + d8, + d9, + d10, + d11, + d12, + d13, + f, + )) +} + +// eraseProviderFactory14 creates a function that takes a variadic number of untyped arguments and from a function of 14 strongly typed arguments and 14 dependencies +func eraseProviderFactory14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14 any, R any]( + d1 Dependency[T1], + d2 Dependency[T2], + d3 Dependency[T3], + d4 Dependency[T4], + d5 Dependency[T5], + d6 Dependency[T6], + d7 Dependency[T7], + d8 Dependency[T8], + d9 Dependency[T9], + d10 Dependency[T10], + d11 Dependency[T11], + d12 Dependency[T12], + d13 Dependency[T13], + d14 Dependency[T14], + f func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14) IOE.IOEither[error, R]) func(params ...any) IOE.IOEither[error, any] { + ft := eraseTuple(T.Tupled14(f)) + t1 := lookupAt[T1](0, d1) + t2 := lookupAt[T2](1, d2) + t3 := lookupAt[T3](2, d3) + t4 := lookupAt[T4](3, d4) + t5 := lookupAt[T5](4, d5) + t6 := lookupAt[T6](5, d6) + t7 := lookupAt[T7](6, d7) + t8 := lookupAt[T8](7, d8) + t9 := lookupAt[T9](8, d9) + t10 := lookupAt[T10](9, d10) + t11 := lookupAt[T11](10, d11) + t12 := lookupAt[T12](11, d12) + t13 := lookupAt[T13](12, d13) + t14 := lookupAt[T14](13, d14) + return func(params ...any) IOE.IOEither[error, any] { + return ft(E.SequenceT14( + t1(params), + t2(params), + t3(params), + t4(params), + t5(params), + t6(params), + t7(params), + t8(params), + t9(params), + t10(params), + t11(params), + t12(params), + t13(params), + t14(params), + )) + } +} + +// MakeProviderFactory14 creates a [DIE.ProviderFactory] from a function with 14 arguments and 14 dependencies +func MakeProviderFactory14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14 any, R any]( + d1 Dependency[T1], + d2 Dependency[T2], + d3 Dependency[T3], + d4 Dependency[T4], + d5 Dependency[T5], + d6 Dependency[T6], + d7 Dependency[T7], + d8 Dependency[T8], + d9 Dependency[T9], + d10 Dependency[T10], + d11 Dependency[T11], + d12 Dependency[T12], + d13 Dependency[T13], + d14 Dependency[T14], + f func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14) IOE.IOEither[error, R], +) DIE.ProviderFactory { + return DIE.MakeProviderFactory( + A.From[DIE.Dependency]( + d1, + d2, + d3, + d4, + d5, + d6, + d7, + d8, + d9, + d10, + d11, + d12, + d13, + d14, + ), + eraseProviderFactory14( + d1, + d2, + d3, + d4, + d5, + d6, + d7, + d8, + d9, + d10, + d11, + d12, + d13, + d14, + f, + ), + ) +} + +// MakeTokenWithDefault14 creates an [InjectionToken] with a default implementation with 14 dependencies +func MakeTokenWithDefault14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14 any, R any]( + name string, + d1 Dependency[T1], + d2 Dependency[T2], + d3 Dependency[T3], + d4 Dependency[T4], + d5 Dependency[T5], + d6 Dependency[T6], + d7 Dependency[T7], + d8 Dependency[T8], + d9 Dependency[T9], + d10 Dependency[T10], + d11 Dependency[T11], + d12 Dependency[T12], + d13 Dependency[T13], + d14 Dependency[T14], + f func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14) IOE.IOEither[error, R], +) InjectionToken[R] { + return MakeTokenWithDefault[R](name, MakeProviderFactory14( + d1, + d2, + d3, + d4, + d5, + d6, + d7, + d8, + d9, + d10, + d11, + d12, + d13, + d14, + f, + )) +} + +// MakeProvider14 creates a [DIE.Provider] for an [InjectionToken] from a function with 14 dependencies +func MakeProvider14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14 any, R any]( + token InjectionToken[R], + d1 Dependency[T1], + d2 Dependency[T2], + d3 Dependency[T3], + d4 Dependency[T4], + d5 Dependency[T5], + d6 Dependency[T6], + d7 Dependency[T7], + d8 Dependency[T8], + d9 Dependency[T9], + d10 Dependency[T10], + d11 Dependency[T11], + d12 Dependency[T12], + d13 Dependency[T13], + d14 Dependency[T14], + f func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14) IOE.IOEither[error, R], +) DIE.Provider { + return DIE.MakeProvider( + token, + MakeProviderFactory14( + d1, + d2, + d3, + d4, + d5, + d6, + d7, + d8, + d9, + d10, + d11, + d12, + d13, + d14, + f, + )) +} + +// eraseProviderFactory15 creates a function that takes a variadic number of untyped arguments and from a function of 15 strongly typed arguments and 15 dependencies +func eraseProviderFactory15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15 any, R any]( + d1 Dependency[T1], + d2 Dependency[T2], + d3 Dependency[T3], + d4 Dependency[T4], + d5 Dependency[T5], + d6 Dependency[T6], + d7 Dependency[T7], + d8 Dependency[T8], + d9 Dependency[T9], + d10 Dependency[T10], + d11 Dependency[T11], + d12 Dependency[T12], + d13 Dependency[T13], + d14 Dependency[T14], + d15 Dependency[T15], + f func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15) IOE.IOEither[error, R]) func(params ...any) IOE.IOEither[error, any] { + ft := eraseTuple(T.Tupled15(f)) + t1 := lookupAt[T1](0, d1) + t2 := lookupAt[T2](1, d2) + t3 := lookupAt[T3](2, d3) + t4 := lookupAt[T4](3, d4) + t5 := lookupAt[T5](4, d5) + t6 := lookupAt[T6](5, d6) + t7 := lookupAt[T7](6, d7) + t8 := lookupAt[T8](7, d8) + t9 := lookupAt[T9](8, d9) + t10 := lookupAt[T10](9, d10) + t11 := lookupAt[T11](10, d11) + t12 := lookupAt[T12](11, d12) + t13 := lookupAt[T13](12, d13) + t14 := lookupAt[T14](13, d14) + t15 := lookupAt[T15](14, d15) + return func(params ...any) IOE.IOEither[error, any] { + return ft(E.SequenceT15( + t1(params), + t2(params), + t3(params), + t4(params), + t5(params), + t6(params), + t7(params), + t8(params), + t9(params), + t10(params), + t11(params), + t12(params), + t13(params), + t14(params), + t15(params), + )) + } +} + +// MakeProviderFactory15 creates a [DIE.ProviderFactory] from a function with 15 arguments and 15 dependencies +func MakeProviderFactory15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15 any, R any]( + d1 Dependency[T1], + d2 Dependency[T2], + d3 Dependency[T3], + d4 Dependency[T4], + d5 Dependency[T5], + d6 Dependency[T6], + d7 Dependency[T7], + d8 Dependency[T8], + d9 Dependency[T9], + d10 Dependency[T10], + d11 Dependency[T11], + d12 Dependency[T12], + d13 Dependency[T13], + d14 Dependency[T14], + d15 Dependency[T15], + f func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15) IOE.IOEither[error, R], +) DIE.ProviderFactory { + return DIE.MakeProviderFactory( + A.From[DIE.Dependency]( + d1, + d2, + d3, + d4, + d5, + d6, + d7, + d8, + d9, + d10, + d11, + d12, + d13, + d14, + d15, + ), + eraseProviderFactory15( + d1, + d2, + d3, + d4, + d5, + d6, + d7, + d8, + d9, + d10, + d11, + d12, + d13, + d14, + d15, + f, + ), + ) +} + +// MakeTokenWithDefault15 creates an [InjectionToken] with a default implementation with 15 dependencies +func MakeTokenWithDefault15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15 any, R any]( + name string, + d1 Dependency[T1], + d2 Dependency[T2], + d3 Dependency[T3], + d4 Dependency[T4], + d5 Dependency[T5], + d6 Dependency[T6], + d7 Dependency[T7], + d8 Dependency[T8], + d9 Dependency[T9], + d10 Dependency[T10], + d11 Dependency[T11], + d12 Dependency[T12], + d13 Dependency[T13], + d14 Dependency[T14], + d15 Dependency[T15], + f func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15) IOE.IOEither[error, R], +) InjectionToken[R] { + return MakeTokenWithDefault[R](name, MakeProviderFactory15( + d1, + d2, + d3, + d4, + d5, + d6, + d7, + d8, + d9, + d10, + d11, + d12, + d13, + d14, + d15, + f, + )) +} + +// MakeProvider15 creates a [DIE.Provider] for an [InjectionToken] from a function with 15 dependencies +func MakeProvider15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15 any, R any]( + token InjectionToken[R], + d1 Dependency[T1], + d2 Dependency[T2], + d3 Dependency[T3], + d4 Dependency[T4], + d5 Dependency[T5], + d6 Dependency[T6], + d7 Dependency[T7], + d8 Dependency[T8], + d9 Dependency[T9], + d10 Dependency[T10], + d11 Dependency[T11], + d12 Dependency[T12], + d13 Dependency[T13], + d14 Dependency[T14], + d15 Dependency[T15], + f func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15) IOE.IOEither[error, R], +) DIE.Provider { + return DIE.MakeProvider( + token, + MakeProviderFactory15( + d1, + d2, + d3, + d4, + d5, + d6, + d7, + d8, + d9, + d10, + d11, + d12, + d13, + d14, + d15, + f, + )) +} diff --git a/v2/di/injector.go b/v2/di/injector.go new file mode 100644 index 0000000..63773e7 --- /dev/null +++ b/v2/di/injector.go @@ -0,0 +1,32 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package di + +import ( + DIE "github.com/IBM/fp-go/v2/di/erasure" + F "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/identity" + IOE "github.com/IBM/fp-go/v2/ioeither" + RIOE "github.com/IBM/fp-go/v2/readerioeither" +) + +// Resolve performs a type safe resolution of a dependency +func Resolve[T any](token InjectionToken[T]) RIOE.ReaderIOEither[DIE.InjectableFactory, error, T] { + return F.Flow2( + identity.Ap[IOE.IOEither[error, any]](asDependency(token)), + IOE.ChainEitherK(token.Unerase), + ) +} diff --git a/v2/di/provider.go b/v2/di/provider.go new file mode 100644 index 0000000..96361e1 --- /dev/null +++ b/v2/di/provider.go @@ -0,0 +1,79 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package di + +import ( + A "github.com/IBM/fp-go/v2/array" + DIE "github.com/IBM/fp-go/v2/di/erasure" + E "github.com/IBM/fp-go/v2/either" + "github.com/IBM/fp-go/v2/errors" + F "github.com/IBM/fp-go/v2/function" + IOE "github.com/IBM/fp-go/v2/ioeither" +) + +func lookupAt[T any](idx int, token Dependency[T]) func(params []any) E.Either[error, T] { + return F.Flow3( + A.Lookup[any](idx), + E.FromOption[any](errors.OnNone("No parameter at position %d", idx)), + E.Chain(token.Unerase), + ) +} + +func eraseTuple[A, R any](f func(A) IOE.IOEither[error, R]) func(E.Either[error, A]) IOE.IOEither[error, any] { + return F.Flow3( + IOE.FromEither[error, A], + IOE.Chain(f), + IOE.Map[error](F.ToAny[R]), + ) +} + +func eraseProviderFactory0[R any](f IOE.IOEither[error, R]) func(params ...any) IOE.IOEither[error, any] { + return func(_ ...any) IOE.IOEither[error, any] { + return F.Pipe1( + f, + IOE.Map[error](F.ToAny[R]), + ) + } +} + +func MakeProviderFactory0[R any]( + fct IOE.IOEither[error, R], +) DIE.ProviderFactory { + return DIE.MakeProviderFactory( + A.Empty[DIE.Dependency](), + eraseProviderFactory0(fct), + ) +} + +// MakeTokenWithDefault0 creates a unique [InjectionToken] for a specific type with an attached default [DIE.Provider] +func MakeTokenWithDefault0[R any](name string, fct IOE.IOEither[error, R]) InjectionToken[R] { + return MakeTokenWithDefault[R](name, MakeProviderFactory0(fct)) +} + +func MakeProvider0[R any]( + token InjectionToken[R], + fct IOE.IOEither[error, R], +) DIE.Provider { + return DIE.MakeProvider( + token, + MakeProviderFactory0(fct), + ) +} + +// ConstProvider simple implementation for a provider with a constant value +func ConstProvider[R any](token InjectionToken[R], value R) DIE.Provider { + return MakeProvider0[R](token, IOE.Of[error](value)) +} diff --git a/v2/di/provider_test.go b/v2/di/provider_test.go new file mode 100644 index 0000000..e4b742f --- /dev/null +++ b/v2/di/provider_test.go @@ -0,0 +1,346 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package di + +import ( + "fmt" + "testing" + "time" + + A "github.com/IBM/fp-go/v2/array" + DIE "github.com/IBM/fp-go/v2/di/erasure" + E "github.com/IBM/fp-go/v2/either" + F "github.com/IBM/fp-go/v2/function" + IOE "github.com/IBM/fp-go/v2/ioeither" + O "github.com/IBM/fp-go/v2/option" + "github.com/stretchr/testify/assert" +) + +var ( + INJ_KEY2 = MakeToken[string]("INJ_KEY2") + INJ_KEY1 = MakeToken[string]("INJ_KEY1") + INJ_KEY3 = MakeToken[string]("INJ_KEY3") +) + +func TestSimpleProvider(t *testing.T) { + + var staticCount int + + staticValue := func(value string) IOE.IOEither[error, string] { + return func() E.Either[error, string] { + staticCount++ + return E.Of[error](fmt.Sprintf("Static based on [%s], at [%s]", value, time.Now())) + } + } + + var dynamicCount int + + dynamicValue := func(value string) IOE.IOEither[error, string] { + return func() E.Either[error, string] { + dynamicCount++ + return E.Of[error](fmt.Sprintf("Dynamic based on [%s] at [%s]", value, time.Now())) + } + } + + p1 := MakeProvider0(INJ_KEY1, staticValue("Carsten")) + p2 := MakeProvider1(INJ_KEY2, INJ_KEY1.Identity(), dynamicValue) + + inj := DIE.MakeInjector(A.From(p1, p2)) + + i1 := Resolve(INJ_KEY1) + i2 := Resolve(INJ_KEY2) + + res := IOE.SequenceT4( + i2(inj), + i1(inj), + i2(inj), + i1(inj), + ) + + r := res() + + assert.True(t, E.IsRight(r)) + assert.Equal(t, 1, staticCount) + assert.Equal(t, 1, dynamicCount) +} + +func TestOptionalProvider(t *testing.T) { + + var staticCount int + + staticValue := func(value string) IOE.IOEither[error, string] { + return func() E.Either[error, string] { + staticCount++ + return E.Of[error](fmt.Sprintf("Static based on [%s], at [%s]", value, time.Now())) + } + } + + var dynamicCount int + + dynamicValue := func(value O.Option[string]) IOE.IOEither[error, string] { + return func() E.Either[error, string] { + dynamicCount++ + return E.Of[error](fmt.Sprintf("Dynamic based on [%s] at [%s]", value, time.Now())) + } + } + + p1 := MakeProvider0(INJ_KEY1, staticValue("Carsten")) + p2 := MakeProvider1(INJ_KEY2, INJ_KEY1.Option(), dynamicValue) + + inj := DIE.MakeInjector(A.From(p1, p2)) + + i1 := Resolve(INJ_KEY1) + i2 := Resolve(INJ_KEY2) + + res := IOE.SequenceT4( + i2(inj), + i1(inj), + i2(inj), + i1(inj), + ) + + r := res() + + assert.True(t, E.IsRight(r)) + assert.Equal(t, 1, staticCount) + assert.Equal(t, 1, dynamicCount) +} + +func TestOptionalProviderMissingDependency(t *testing.T) { + + var dynamicCount int + + dynamicValue := func(value O.Option[string]) IOE.IOEither[error, string] { + return func() E.Either[error, string] { + dynamicCount++ + return E.Of[error](fmt.Sprintf("Dynamic based on [%s] at [%s]", value, time.Now())) + } + } + + p2 := MakeProvider1(INJ_KEY2, INJ_KEY1.Option(), dynamicValue) + + inj := DIE.MakeInjector(A.From(p2)) + + i2 := Resolve(INJ_KEY2) + + res := IOE.SequenceT2( + i2(inj), + i2(inj), + ) + + r := res() + + assert.True(t, E.IsRight(r)) + assert.Equal(t, 1, dynamicCount) +} + +func TestProviderMissingDependency(t *testing.T) { + + var dynamicCount int + + dynamicValue := func(value string) IOE.IOEither[error, string] { + return func() E.Either[error, string] { + dynamicCount++ + return E.Of[error](fmt.Sprintf("Dynamic based on [%s] at [%s]", value, time.Now())) + } + } + + p2 := MakeProvider1(INJ_KEY2, INJ_KEY1.Identity(), dynamicValue) + + inj := DIE.MakeInjector(A.From(p2)) + + i2 := Resolve(INJ_KEY2) + + res := IOE.SequenceT2( + i2(inj), + i2(inj), + ) + + r := res() + + assert.True(t, E.IsLeft(r)) + assert.Equal(t, 0, dynamicCount) +} + +func TestEagerAndLazyProvider(t *testing.T) { + + var staticCount int + + staticValue := func(value string) IOE.IOEither[error, string] { + return func() E.Either[error, string] { + staticCount++ + return E.Of[error](fmt.Sprintf("Static based on [%s], at [%s]", value, time.Now())) + } + } + + var dynamicCount int + + dynamicValue := func(value string) IOE.IOEither[error, string] { + return func() E.Either[error, string] { + dynamicCount++ + return E.Of[error](fmt.Sprintf("Dynamic based on [%s] at [%s]", value, time.Now())) + } + } + + var lazyEagerCount int + + lazyEager := func(laz IOE.IOEither[error, string], eager string) IOE.IOEither[error, string] { + return F.Pipe1( + laz, + IOE.Chain(func(lazValue string) IOE.IOEither[error, string] { + return func() E.Either[error, string] { + lazyEagerCount++ + return E.Of[error](fmt.Sprintf("Dynamic based on [%s], [%s] at [%s]", lazValue, eager, time.Now())) + } + }), + ) + } + + p1 := MakeProvider0(INJ_KEY1, staticValue("Carsten")) + p2 := MakeProvider1(INJ_KEY2, INJ_KEY1.Identity(), dynamicValue) + p3 := MakeProvider2(INJ_KEY3, INJ_KEY2.IOEither(), INJ_KEY1.Identity(), lazyEager) + + inj := DIE.MakeInjector(A.From(p1, p2, p3)) + + i3 := Resolve(INJ_KEY3) + + r := i3(inj)() + + fmt.Println(r) + + assert.True(t, E.IsRight(r)) + assert.Equal(t, 1, staticCount) + assert.Equal(t, 1, dynamicCount) + assert.Equal(t, 1, lazyEagerCount) +} + +func TestItemProvider(t *testing.T) { + // define a multi token + injMulti := MakeMultiToken[string]("configs") + + // provide some values + v1 := ConstProvider(injMulti.Item(), "Value1") + v2 := ConstProvider(injMulti.Item(), "Value2") + // mix in non-multi values + p1 := ConstProvider(INJ_KEY1, "Value3") + p2 := ConstProvider(INJ_KEY2, "Value4") + + // populate the injector + inj := DIE.MakeInjector(A.From(p1, v1, p2, v2)) + + // access the multi value + multi := Resolve(injMulti.Container()) + + multiInj := multi(inj) + + value := multiInj() + + assert.Equal(t, E.Of[error](A.From("Value1", "Value2")), value) +} + +func TestEmptyItemProvider(t *testing.T) { + // define a multi token + injMulti := MakeMultiToken[string]("configs") + + // mix in non-multi values + p1 := ConstProvider(INJ_KEY1, "Value3") + p2 := ConstProvider(INJ_KEY2, "Value4") + + // populate the injector + inj := DIE.MakeInjector(A.From(p1, p2)) + + // access the multi value + multi := Resolve(injMulti.Container()) + + multiInj := multi(inj) + + value := multiInj() + + assert.Equal(t, E.Of[error](A.Empty[string]()), value) +} + +func TestDependencyOnMultiProvider(t *testing.T) { + // define a multi token + injMulti := MakeMultiToken[string]("configs") + + // provide some values + v1 := ConstProvider(injMulti.Item(), "Value1") + v2 := ConstProvider(injMulti.Item(), "Value2") + // mix in non-multi values + p1 := ConstProvider(INJ_KEY1, "Value3") + p2 := ConstProvider(INJ_KEY2, "Value4") + + fromMulti := func(val string, multi []string) IOE.IOEither[error, string] { + return IOE.Of[error](fmt.Sprintf("Val: %s, Multi: %s", val, multi)) + } + p3 := MakeProvider2(INJ_KEY3, INJ_KEY1.Identity(), injMulti.Container().Identity(), fromMulti) + + // populate the injector + inj := DIE.MakeInjector(A.From(p1, p2, v1, v2, p3)) + + r3 := Resolve(INJ_KEY3) + + v := r3(inj)() + + assert.Equal(t, E.Of[error]("Val: Value3, Multi: [Value1 Value2]"), v) +} + +func TestTokenWithDefaultProvider(t *testing.T) { + // token without a default + injToken1 := MakeToken[string]("Token1") + // token with a default + injToken2 := MakeTokenWithDefault0("Token2", IOE.Of[error]("Carsten")) + // dependency + injToken3 := MakeToken[string]("Token3") + + p3 := MakeProvider1(injToken3, injToken2.Identity(), func(data string) IOE.IOEither[error, string] { + return IOE.Of[error](fmt.Sprintf("Token: %s", data)) + }) + + // populate the injector + inj := DIE.MakeInjector(A.From(p3)) + + // resolving injToken3 should work and use the default provider for injToken2 + r1 := Resolve(injToken1) + r3 := Resolve(injToken3) + + // inj1 should not be available + assert.True(t, E.IsLeft(r1(inj)())) + // r3 should work + assert.Equal(t, E.Of[error]("Token: Carsten"), r3(inj)()) +} + +func TestTokenWithDefaultProviderAndOverride(t *testing.T) { + // token with a default + injToken2 := MakeTokenWithDefault0("Token2", IOE.Of[error]("Carsten")) + // dependency + injToken3 := MakeToken[string]("Token3") + + p2 := ConstProvider(injToken2, "Override") + + p3 := MakeProvider1(injToken3, injToken2.Identity(), func(data string) IOE.IOEither[error, string] { + return IOE.Of[error](fmt.Sprintf("Token: %s", data)) + }) + + // populate the injector + inj := DIE.MakeInjector(A.From(p2, p3)) + + // resolving injToken3 should work and use the default provider for injToken2 + r3 := Resolve(injToken3) + + // r3 should work + assert.Equal(t, E.Of[error]("Token: Override"), r3(inj)()) +} diff --git a/v2/di/token.go b/v2/di/token.go new file mode 100644 index 0000000..28024ba --- /dev/null +++ b/v2/di/token.go @@ -0,0 +1,204 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package di + +import ( + "fmt" + "strconv" + "sync/atomic" + + DIE "github.com/IBM/fp-go/v2/di/erasure" + E "github.com/IBM/fp-go/v2/either" + IO "github.com/IBM/fp-go/v2/io" + IOE "github.com/IBM/fp-go/v2/ioeither" + IOO "github.com/IBM/fp-go/v2/iooption" + O "github.com/IBM/fp-go/v2/option" +) + +// Dependency describes the relationship to a service, that has a type and +// a behaviour such as required, option or lazy +type Dependency[T any] interface { + DIE.Dependency + // Unerase converts a value with erased type signature into a strongly typed value + Unerase(val any) E.Either[error, T] +} + +// InjectionToken uniquely identifies a dependency by giving it an Id, Type and name +type InjectionToken[T any] interface { + Dependency[T] + // Identity idenifies this dependency as a mandatory, required dependency, it will be resolved eagerly and injected as `T`. + // If the dependency cannot be resolved, the resolution process fails + Identity() Dependency[T] + // Option identifies this dependency as optional, it will be resolved eagerly and injected as [O.Option[T]]. + // If the dependency cannot be resolved, the resolution process continues and the dependency is represented as [O.None[T]] + Option() Dependency[O.Option[T]] + // IOEither identifies this dependency as mandatory but it will be resolved lazily as a [IOE.IOEither[error, T]]. This + // value is memoized to make sure the dependency is a singleton. + // If the dependency cannot be resolved, the resolution process fails + IOEither() Dependency[IOE.IOEither[error, T]] + // IOOption identifies this dependency as optional but it will be resolved lazily as a [IOO.IOOption[T]]. This + // value is memoized to make sure the dependency is a singleton. + // If the dependency cannot be resolved, the resolution process continues and the dependency is represented as the none value. + IOOption() Dependency[IOO.IOOption[T]] +} + +// MultiInjectionToken uniquely identifies a dependency by giving it an Id, Type and name that can have multiple implementations. +// Implementations are provided via the [MultiInjectionToken.Item] injection token. +type MultiInjectionToken[T any] interface { + // Container returns the injection token used to request an array of all provided items + Container() InjectionToken[[]T] + // Item returns the injection token used to provide an item + Item() InjectionToken[T] +} + +// makeID creates a generator of unique string IDs +func makeID() IO.IO[string] { + var count atomic.Int64 + return func() string { + return strconv.FormatInt(count.Add(1), 16) + } +} + +// genID is the common generator of unique string IDs +var genID = makeID() + +type tokenBase struct { + name string + id string + flag int + providerFactory O.Option[DIE.ProviderFactory] +} + +type token[T any] struct { + base *tokenBase + toType func(val any) E.Either[error, T] +} + +func (t *token[T]) Id() string { + return t.base.id +} + +func (t *token[T]) Flag() int { + return t.base.flag +} + +func (t *token[T]) String() string { + return t.base.name +} + +func (t *token[T]) Unerase(val any) E.Either[error, T] { + return t.toType(val) +} + +func (t *token[T]) ProviderFactory() O.Option[DIE.ProviderFactory] { + return t.base.providerFactory +} +func makeTokenBase(name string, id string, typ int, providerFactory O.Option[DIE.ProviderFactory]) *tokenBase { + return &tokenBase{name, id, typ, providerFactory} +} + +func makeToken[T any](name string, id string, typ int, unerase func(val any) E.Either[error, T], providerFactory O.Option[DIE.ProviderFactory]) Dependency[T] { + return &token[T]{makeTokenBase(name, id, typ, providerFactory), unerase} +} + +type injectionToken[T any] struct { + token[T] + option Dependency[O.Option[T]] + ioeither Dependency[IOE.IOEither[error, T]] + iooption Dependency[IOO.IOOption[T]] +} + +type multiInjectionToken[T any] struct { + container *injectionToken[[]T] + item *injectionToken[T] +} + +func (i *injectionToken[T]) Identity() Dependency[T] { + return i +} + +func (i *injectionToken[T]) Option() Dependency[O.Option[T]] { + return i.option +} + +func (i *injectionToken[T]) IOEither() Dependency[IOE.IOEither[error, T]] { + return i.ioeither +} + +func (i *injectionToken[T]) IOOption() Dependency[IOO.IOOption[T]] { + return i.iooption +} + +func (i *injectionToken[T]) ProviderFactory() O.Option[DIE.ProviderFactory] { + return i.base.providerFactory +} + +func (m *multiInjectionToken[T]) Container() InjectionToken[[]T] { + return m.container +} + +func (m *multiInjectionToken[T]) Item() InjectionToken[T] { + return m.item +} + +// makeToken create a unique [InjectionToken] for a specific type +func makeInjectionToken[T any](name string, providerFactory O.Option[DIE.ProviderFactory]) InjectionToken[T] { + id := genID() + toIdentity := toType[T]() + return &injectionToken[T]{ + token[T]{makeTokenBase(name, id, DIE.Identity, providerFactory), toIdentity}, + makeToken[O.Option[T]](fmt.Sprintf("Option[%s]", name), id, DIE.Option, toOptionType(toIdentity), providerFactory), + makeToken[IOE.IOEither[error, T]](fmt.Sprintf("IOEither[%s]", name), id, DIE.IOEither, toIOEitherType(toIdentity), providerFactory), + makeToken[IOO.IOOption[T]](fmt.Sprintf("IOOption[%s]", name), id, DIE.IOOption, toIOOptionType(toIdentity), providerFactory), + } +} + +// MakeToken create a unique [InjectionToken] for a specific type +func MakeToken[T any](name string) InjectionToken[T] { + return makeInjectionToken[T](name, O.None[DIE.ProviderFactory]()) +} + +// MakeToken create a unique [InjectionToken] for a specific type +func MakeTokenWithDefault[T any](name string, providerFactory DIE.ProviderFactory) InjectionToken[T] { + return makeInjectionToken[T](name, O.Of(providerFactory)) +} + +// MakeMultiToken creates a [MultiInjectionToken] +func MakeMultiToken[T any](name string) MultiInjectionToken[T] { + id := genID() + toItem := toType[T]() + toContainer := toArrayType(toItem) + containerName := fmt.Sprintf("Container[%s]", name) + itemName := fmt.Sprintf("Item[%s]", name) + // empty factory + providerFactory := O.None[DIE.ProviderFactory]() + // container + container := &injectionToken[[]T]{ + token[[]T]{makeTokenBase(containerName, id, DIE.Multi|DIE.Identity, providerFactory), toContainer}, + makeToken[O.Option[[]T]](fmt.Sprintf("Option[%s]", containerName), id, DIE.Multi|DIE.Option, toOptionType(toContainer), providerFactory), + makeToken[IOE.IOEither[error, []T]](fmt.Sprintf("IOEither[%s]", containerName), id, DIE.Multi|DIE.IOEither, toIOEitherType(toContainer), providerFactory), + makeToken[IOO.IOOption[[]T]](fmt.Sprintf("IOOption[%s]", containerName), id, DIE.Multi|DIE.IOOption, toIOOptionType(toContainer), providerFactory), + } + // item + item := &injectionToken[T]{ + token[T]{makeTokenBase(itemName, id, DIE.Item|DIE.Identity, providerFactory), toItem}, + makeToken[O.Option[T]](fmt.Sprintf("Option[%s]", itemName), id, DIE.Item|DIE.Option, toOptionType(toItem), providerFactory), + makeToken[IOE.IOEither[error, T]](fmt.Sprintf("IOEither[%s]", itemName), id, DIE.Item|DIE.IOEither, toIOEitherType(toItem), providerFactory), + makeToken[IOO.IOOption[T]](fmt.Sprintf("IOOption[%s]", itemName), id, DIE.Item|DIE.IOOption, toIOOptionType(toItem), providerFactory), + } + // returns the token + return &multiInjectionToken[T]{container, item} +} diff --git a/v2/di/token_test.go b/v2/di/token_test.go new file mode 100644 index 0000000..1aa4028 --- /dev/null +++ b/v2/di/token_test.go @@ -0,0 +1,269 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package di + +import ( + "errors" + "testing" + + E "github.com/IBM/fp-go/v2/either" + F "github.com/IBM/fp-go/v2/function" + IOE "github.com/IBM/fp-go/v2/ioeither" + IOO "github.com/IBM/fp-go/v2/iooption" + O "github.com/IBM/fp-go/v2/option" + "github.com/stretchr/testify/assert" +) + +func TestMakeToken(t *testing.T) { + token := MakeToken[string]("TestToken") + + assert.NotNil(t, token) + assert.Equal(t, "TestToken", token.String()) + assert.NotEmpty(t, token.Id()) +} + +func TestTokenIdentity(t *testing.T) { + token := MakeToken[int]("IntToken") + identity := token.Identity() + + assert.NotNil(t, identity) + assert.Equal(t, token.Id(), identity.Id()) + assert.Equal(t, token.String(), identity.String()) +} + +func TestTokenOption(t *testing.T) { + token := MakeToken[int]("IntToken") + option := token.Option() + + assert.NotNil(t, option) + assert.Contains(t, option.String(), "Option") + assert.Equal(t, token.Id(), option.Id()) +} + +func TestTokenIOEither(t *testing.T) { + token := MakeToken[int]("IntToken") + ioeither := token.IOEither() + + assert.NotNil(t, ioeither) + assert.Contains(t, ioeither.String(), "IOEither") + assert.Equal(t, token.Id(), ioeither.Id()) +} + +func TestTokenIOOption(t *testing.T) { + token := MakeToken[int]("IntToken") + iooption := token.IOOption() + + assert.NotNil(t, iooption) + assert.Contains(t, iooption.String(), "IOOption") + assert.Equal(t, token.Id(), iooption.Id()) +} + +func TestTokenUnerase(t *testing.T) { + token := MakeToken[int]("IntToken") + + // Test successful unerase + result := token.Unerase(42) + assert.True(t, E.IsRight(result)) + assert.Equal(t, E.Of[error](42), result) + + // Test failed unerase (wrong type) + result2 := token.Unerase("not an int") + assert.True(t, E.IsLeft(result2)) +} + +func TestTokenFlag(t *testing.T) { + token := MakeToken[int]("IntToken") + + // Flags should be set for different dependency types + optionFlag := token.Option().Flag() + ioeitherFlag := token.IOEither().Flag() + iooptionFlag := token.IOOption().Flag() + + // Flags should be different for different types + assert.NotEqual(t, optionFlag, ioeitherFlag) + assert.NotEqual(t, optionFlag, iooptionFlag) + assert.NotEqual(t, ioeitherFlag, iooptionFlag) +} + +func TestTokenProviderFactory(t *testing.T) { + // Token without default + token1 := MakeToken[int]("Token1") + assert.True(t, O.IsNone(token1.ProviderFactory())) + + // Token with default + token2 := MakeTokenWithDefault0("Token2", IOE.Of[error](42)) + assert.True(t, O.IsSome(token2.ProviderFactory())) +} + +func TestMakeMultiToken(t *testing.T) { + multiToken := MakeMultiToken[string]("MultiToken") + + assert.NotNil(t, multiToken) + assert.NotNil(t, multiToken.Container()) + assert.NotNil(t, multiToken.Item()) + + // Container and Item should share the same ID + assert.Equal(t, multiToken.Container().Id(), multiToken.Item().Id()) + + // But have different names + assert.Contains(t, multiToken.Container().String(), "Container") + assert.Contains(t, multiToken.Item().String(), "Item") +} + +func TestMultiTokenFlags(t *testing.T) { + multiToken := MakeMultiToken[string]("MultiToken") + + // Container should have Multi flag + containerFlag := multiToken.Container().Flag() + assert.NotZero(t, containerFlag) + + // Item should have Item flag + itemFlag := multiToken.Item().Flag() + assert.NotZero(t, itemFlag) +} + +func TestTokenUniqueness(t *testing.T) { + token1 := MakeToken[int]("Token") + token2 := MakeToken[int]("Token") + + // Even with same name, IDs should be different + assert.NotEqual(t, token1.Id(), token2.Id()) +} + +func TestOptionTokenUnerase(t *testing.T) { + token := MakeToken[int]("IntToken") + optionToken := token.Option() + + // Test successful unerase with Some + result := optionToken.Unerase(O.Of[any](42)) + assert.True(t, E.IsRight(result)) + + // Test successful unerase with None + noneResult := optionToken.Unerase(O.None[any]()) + assert.True(t, E.IsRight(noneResult)) + assert.Equal(t, E.Of[error](O.None[int]()), noneResult) + + // Test failed unerase (wrong type) + badResult := optionToken.Unerase(42) // Not an Option + assert.True(t, E.IsLeft(badResult)) +} + +func TestIOEitherTokenUnerase(t *testing.T) { + token := MakeToken[int]("IntToken") + ioeitherToken := token.IOEither() + + // Test successful unerase + ioValue := IOE.Of[error](any(42)) + result := ioeitherToken.Unerase(ioValue) + assert.True(t, E.IsRight(result)) + + // Execute the IOEither to verify it works + if E.IsRight(result) { + ioe := E.ToOption(result) + if O.IsSome(ioe) { + executed := O.GetOrElse(F.Constant(IOE.Left[int](errors.New("fail"))))(ioe)() + assert.True(t, E.IsRight(executed)) + } + } + + // Test failed unerase (wrong type) + badResult := ioeitherToken.Unerase(42) + assert.True(t, E.IsLeft(badResult)) +} + +func TestIOOptionTokenUnerase(t *testing.T) { + token := MakeToken[int]("IntToken") + iooptionToken := token.IOOption() + + // Test successful unerase + ioValue := IOO.Of(any(42)) + result := iooptionToken.Unerase(ioValue) + assert.True(t, E.IsRight(result)) + + // Test failed unerase (wrong type) + badResult := iooptionToken.Unerase(42) + assert.True(t, E.IsLeft(badResult)) +} + +func TestMultiTokenContainerUnerase(t *testing.T) { + multiToken := MakeMultiToken[int]("MultiToken") + container := multiToken.Container() + + // Test successful unerase with array + arrayValue := []any{1, 2, 3} + result := container.Unerase(arrayValue) + assert.True(t, E.IsRight(result)) + + if E.IsRight(result) { + arr := E.ToOption(result) + if O.IsSome(arr) { + values := O.GetOrElse(F.Constant([]int{}))(arr) + assert.Equal(t, []int{1, 2, 3}, values) + } + } + + // Test failed unerase (wrong type in array) + badArray := []any{1, "not an int", 3} + badResult := container.Unerase(badArray) + assert.True(t, E.IsLeft(badResult)) +} + +func TestMakeTokenWithDefault(t *testing.T) { + factory := MakeProviderFactory0(IOE.Of[error](42)) + token := MakeTokenWithDefault[int]("TokenWithDefault", factory) + + assert.NotNil(t, token) + assert.True(t, O.IsSome(token.ProviderFactory())) +} + +func TestTokenStringRepresentation(t *testing.T) { + token := MakeToken[int]("MyToken") + + assert.Equal(t, "MyToken", token.String()) + assert.Contains(t, token.Option().String(), "Option[MyToken]") + assert.Contains(t, token.IOEither().String(), "IOEither[MyToken]") + assert.Contains(t, token.IOOption().String(), "IOOption[MyToken]") +} + +func TestMultiTokenStringRepresentation(t *testing.T) { + multiToken := MakeMultiToken[int]("MyMulti") + + assert.Contains(t, multiToken.Container().String(), "Container[MyMulti]") + assert.Contains(t, multiToken.Item().String(), "Item[MyMulti]") +} + +// Benchmark tests +func BenchmarkMakeToken(b *testing.B) { + for i := 0; i < b.N; i++ { + MakeToken[int]("BenchToken") + } +} + +func BenchmarkTokenUnerase(b *testing.B) { + token := MakeToken[int]("BenchToken") + value := any(42) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + token.Unerase(value) + } +} + +func BenchmarkMakeMultiToken(b *testing.B) { + for i := 0; i < b.N; i++ { + MakeMultiToken[int]("BenchMulti") + } +} diff --git a/v2/di/utils.go b/v2/di/utils.go new file mode 100644 index 0000000..e23ffdd --- /dev/null +++ b/v2/di/utils.go @@ -0,0 +1,84 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package di + +import ( + DIE "github.com/IBM/fp-go/v2/di/erasure" + E "github.com/IBM/fp-go/v2/either" + "github.com/IBM/fp-go/v2/errors" + F "github.com/IBM/fp-go/v2/function" + IOE "github.com/IBM/fp-go/v2/ioeither" + IOO "github.com/IBM/fp-go/v2/iooption" + O "github.com/IBM/fp-go/v2/option" +) + +var ( + toOptionAny = toType[O.Option[any]]() + toIOEitherAny = toType[IOE.IOEither[error, any]]() + toIOOptionAny = toType[IOO.IOOption[any]]() + toArrayAny = toType[[]any]() +) + +// asDependency converts a generic type to a [DIE.Dependency] +func asDependency[T DIE.Dependency](t T) DIE.Dependency { + return t +} + +// toType converts an any to a T +func toType[T any]() func(t any) E.Either[error, T] { + return E.ToType[T](errors.OnSome[any]("Value of type [%T] cannot be converted.")) +} + +// toOptionType converts an any to an Option[any] and then to an Option[T] +func toOptionType[T any](item func(any) E.Either[error, T]) func(t any) E.Either[error, O.Option[T]] { + return F.Flow2( + toOptionAny, + E.Chain(O.Fold( + F.Nullary2(O.None[T], E.Of[error, O.Option[T]]), + F.Flow2( + item, + E.Map[error](O.Of[T]), + ), + )), + ) +} + +// toIOEitherType converts an any to an IOEither[error, any] and then to an IOEither[error, T] +func toIOEitherType[T any](item func(any) E.Either[error, T]) func(t any) E.Either[error, IOE.IOEither[error, T]] { + return F.Flow2( + toIOEitherAny, + E.Map[error](IOE.ChainEitherK(item)), + ) +} + +// toIOOptionType converts an any to an IOOption[any] and then to an IOOption[T] +func toIOOptionType[T any](item func(any) E.Either[error, T]) func(t any) E.Either[error, IOO.IOOption[T]] { + return F.Flow2( + toIOOptionAny, + E.Map[error](IOO.ChainOptionK(F.Flow2( + item, + E.ToOption[error, T], + ))), + ) +} + +// toArrayType converts an any to a []T +func toArrayType[T any](item func(any) E.Either[error, T]) func(t any) E.Either[error, []T] { + return F.Flow2( + toArrayAny, + E.Chain(E.TraverseArray(item)), + ) +} diff --git a/v2/di/utils_test.go b/v2/di/utils_test.go new file mode 100644 index 0000000..3b861b1 --- /dev/null +++ b/v2/di/utils_test.go @@ -0,0 +1,84 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package di + +import ( + "testing" + + A "github.com/IBM/fp-go/v2/array" + E "github.com/IBM/fp-go/v2/either" + F "github.com/IBM/fp-go/v2/function" + IOE "github.com/IBM/fp-go/v2/ioeither" + O "github.com/IBM/fp-go/v2/option" + "github.com/stretchr/testify/assert" +) + +var ( + toInt = toType[int]() + toString = toType[string]() +) + +func TestToType(t *testing.T) { + // good cases + assert.Equal(t, E.Of[error](10), toInt(any(10))) + assert.Equal(t, E.Of[error]("Carsten"), toString(any("Carsten"))) + assert.Equal(t, E.Of[error](O.Of("Carsten")), toType[O.Option[string]]()(any(O.Of("Carsten")))) + assert.Equal(t, E.Of[error](O.Of(any("Carsten"))), toType[O.Option[any]]()(any(O.Of(any("Carsten"))))) + // failure + assert.False(t, E.IsRight(toInt(any("Carsten")))) + assert.False(t, E.IsRight(toType[O.Option[string]]()(O.Of(any("Carsten"))))) +} + +func TestToOptionType(t *testing.T) { + // shortcuts + toOptInt := toOptionType(toInt) + toOptString := toOptionType(toString) + // good cases + assert.Equal(t, E.Of[error](O.Of(10)), toOptInt(any(O.Of(any(10))))) + assert.Equal(t, E.Of[error](O.Of("Carsten")), toOptString(any(O.Of(any("Carsten"))))) + // bad cases + assert.False(t, E.IsRight(toOptInt(any(10)))) + assert.False(t, E.IsRight(toOptInt(any(O.Of(10))))) +} + +func invokeIOEither[T any](e E.Either[error, IOE.IOEither[error, T]]) E.Either[error, T] { + return F.Pipe1( + e, + E.Chain(func(ioe IOE.IOEither[error, T]) E.Either[error, T] { + return ioe() + }), + ) +} + +func TestToIOEitherType(t *testing.T) { + // shortcuts + toIOEitherInt := toIOEitherType(toInt) + toIOEitherString := toIOEitherType(toString) + // good cases + assert.Equal(t, E.Of[error](10), invokeIOEither(toIOEitherInt(any(IOE.Of[error](any(10)))))) + assert.Equal(t, E.Of[error]("Carsten"), invokeIOEither(toIOEitherString(any(IOE.Of[error](any("Carsten")))))) + // bad cases + assert.False(t, E.IsRight(invokeIOEither(toIOEitherString(any(IOE.Of[error](any(10))))))) + assert.False(t, E.IsRight(invokeIOEither(toIOEitherString(any(IOE.Of[error]("Carsten")))))) + assert.False(t, E.IsRight(invokeIOEither(toIOEitherString(any("Carsten"))))) +} + +func TestToArrayType(t *testing.T) { + // shortcuts + toArrayString := toArrayType(toString) + // good cases + assert.Equal(t, E.Of[error](A.From("a", "b")), toArrayString(any(A.From(any("a"), any("b"))))) +} diff --git a/v2/either/apply.go b/v2/either/apply.go new file mode 100644 index 0000000..9667d44 --- /dev/null +++ b/v2/either/apply.go @@ -0,0 +1,46 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package either + +import ( + M "github.com/IBM/fp-go/v2/monoid" + S "github.com/IBM/fp-go/v2/semigroup" +) + +// ApplySemigroup lifts a Semigroup over the Right values of Either. +// Combines two Right values using the provided Semigroup. +// If either value is Left, returns the first Left encountered. +// +// Example: +// +// intAdd := semigroup.MakeSemigroup(func(a, b int) int { return a + b }) +// eitherSemi := either.ApplySemigroup[error](intAdd) +// result := eitherSemi.Concat(either.Right[error](2), either.Right[error](3)) // Right(5) +func ApplySemigroup[E, A any](s S.Semigroup[A]) S.Semigroup[Either[E, A]] { + return S.ApplySemigroup(MonadMap[E, A, func(A) A], MonadAp[A, E, A], s) +} + +// ApplicativeMonoid returns a Monoid that concatenates Either instances via their applicative. +// Provides an empty Either (Right with monoid's empty value) and combines Right values using the monoid. +// +// Example: +// +// intAddMonoid := monoid.MakeMonoid(0, func(a, b int) int { return a + b }) +// eitherMon := either.ApplicativeMonoid[error](intAddMonoid) +// empty := eitherMon.Empty() // Right(0) +func ApplicativeMonoid[E, A any](m M.Monoid[A]) M.Monoid[Either[E, A]] { + return M.ApplicativeMonoid(Of[E, A], MonadMap[E, A, func(A) A], MonadAp[A, E, A], m) +} diff --git a/v2/either/apply_test.go b/v2/either/apply_test.go new file mode 100644 index 0000000..9b6f382 --- /dev/null +++ b/v2/either/apply_test.go @@ -0,0 +1,63 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package either + +import ( + "testing" + + M "github.com/IBM/fp-go/v2/monoid/testing" + N "github.com/IBM/fp-go/v2/number" + "github.com/stretchr/testify/assert" +) + +func TestApplySemigroup(t *testing.T) { + + sg := ApplySemigroup[string](N.SemigroupSum[int]()) + + la := Left[int]("a") + lb := Left[int]("b") + r1 := Right[string](1) + r2 := Right[string](2) + r3 := Right[string](3) + + assert.Equal(t, la, sg.Concat(la, lb)) + assert.Equal(t, lb, sg.Concat(r1, lb)) + assert.Equal(t, la, sg.Concat(la, r2)) + assert.Equal(t, lb, sg.Concat(r1, lb)) + assert.Equal(t, r3, sg.Concat(r1, r2)) +} + +func TestApplicativeMonoid(t *testing.T) { + + m := ApplicativeMonoid[string](N.MonoidSum[int]()) + + la := Left[int]("a") + lb := Left[int]("b") + r1 := Right[string](1) + r2 := Right[string](2) + r3 := Right[string](3) + + assert.Equal(t, la, m.Concat(la, m.Empty())) + assert.Equal(t, lb, m.Concat(m.Empty(), lb)) + assert.Equal(t, r1, m.Concat(r1, m.Empty())) + assert.Equal(t, r2, m.Concat(m.Empty(), r2)) + assert.Equal(t, r3, m.Concat(r1, r2)) +} + +func TestApplicativeMonoidLaws(t *testing.T) { + m := ApplicativeMonoid[string](N.MonoidSum[int]()) + M.AssertLaws(t, m)([]Either[string, int]{Left[int]("a"), Right[string](1)}) +} diff --git a/v2/either/array.go b/v2/either/array.go new file mode 100644 index 0000000..c72d711 --- /dev/null +++ b/v2/either/array.go @@ -0,0 +1,164 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package either + +import ( + F "github.com/IBM/fp-go/v2/function" + RA "github.com/IBM/fp-go/v2/internal/array" +) + +// TraverseArrayG transforms an array by applying a function that returns an Either to each element. +// If any element produces a Left, the entire result is that Left (short-circuits). +// Otherwise, returns Right containing the array of all Right values. +// The G suffix indicates support for generic slice types. +// +// Example: +// +// parse := func(s string) either.Either[error, int] { +// v, err := strconv.Atoi(s) +// return either.FromError(v, err) +// } +// result := either.TraverseArrayG[[]string, []int](parse)([]string{"1", "2", "3"}) +// // result is Right([]int{1, 2, 3}) +func TraverseArrayG[GA ~[]A, GB ~[]B, E, A, B any](f func(A) Either[E, B]) func(GA) Either[E, GB] { + return RA.Traverse[GA]( + Of[E, GB], + Map[E, GB, func(B) GB], + Ap[GB, E, B], + + f, + ) +} + +// TraverseArray transforms an array by applying a function that returns an Either to each element. +// If any element produces a Left, the entire result is that Left (short-circuits). +// Otherwise, returns Right containing the array of all Right values. +// +// Example: +// +// parse := func(s string) either.Either[error, int] { +// v, err := strconv.Atoi(s) +// return either.FromError(v, err) +// } +// result := either.TraverseArray(parse)([]string{"1", "2", "3"}) +// // result is Right([]int{1, 2, 3}) +func TraverseArray[E, A, B any](f func(A) Either[E, B]) func([]A) Either[E, []B] { + return TraverseArrayG[[]A, []B](f) +} + +// TraverseArrayWithIndexG transforms an array by applying an indexed function that returns an Either. +// The function receives both the index and the element. +// If any element produces a Left, the entire result is that Left (short-circuits). +// The G suffix indicates support for generic slice types. +// +// Example: +// +// validate := func(i int, s string) either.Either[error, string] { +// if len(s) > 0 { +// return either.Right[error](fmt.Sprintf("%d:%s", i, s)) +// } +// return either.Left[string](fmt.Errorf("empty at index %d", i)) +// } +// result := either.TraverseArrayWithIndexG[[]string, []string](validate)([]string{"a", "b"}) +// // result is Right([]string{"0:a", "1:b"}) +func TraverseArrayWithIndexG[GA ~[]A, GB ~[]B, E, A, B any](f func(int, A) Either[E, B]) func(GA) Either[E, GB] { + return RA.TraverseWithIndex[GA]( + Of[E, GB], + Map[E, GB, func(B) GB], + Ap[GB, E, B], + + f, + ) +} + +// TraverseArrayWithIndex transforms an array by applying an indexed function that returns an Either. +// The function receives both the index and the element. +// If any element produces a Left, the entire result is that Left (short-circuits). +// +// Example: +// +// validate := func(i int, s string) either.Either[error, string] { +// if len(s) > 0 { +// return either.Right[error](fmt.Sprintf("%d:%s", i, s)) +// } +// return either.Left[string](fmt.Errorf("empty at index %d", i)) +// } +// result := either.TraverseArrayWithIndex(validate)([]string{"a", "b"}) +// // result is Right([]string{"0:a", "1:b"}) +func TraverseArrayWithIndex[E, A, B any](f func(int, A) Either[E, B]) func([]A) Either[E, []B] { + return TraverseArrayWithIndexG[[]A, []B](f) +} + +//go:inline +func SequenceArrayG[GA ~[]A, GOA ~[]Either[E, A], E, A any](ma GOA) Either[E, GA] { + return TraverseArrayG[GOA, GA](F.Identity[Either[E, A]])(ma) +} + +// SequenceArray converts a homogeneous sequence of Either into an Either of sequence. +// If any element is Left, returns that Left (short-circuits). +// Otherwise, returns Right containing all the Right values. +// +// Example: +// +// eithers := []either.Either[error, int]{ +// either.Right[error](1), +// either.Right[error](2), +// either.Right[error](3), +// } +// result := either.SequenceArray(eithers) +// // result is Right([]int{1, 2, 3}) +// +//go:inline +func SequenceArray[E, A any](ma []Either[E, A]) Either[E, []A] { + return SequenceArrayG[[]A](ma) +} + +// CompactArrayG discards all Left values and keeps only the Right values. +// The G suffix indicates support for generic slice types. +// +// Example: +// +// eithers := []either.Either[error, int]{ +// either.Right[error](1), +// either.Left[int](errors.New("error")), +// either.Right[error](3), +// } +// result := either.CompactArrayG[[]either.Either[error, int], []int](eithers) +// // result is []int{1, 3} +// +//go:inline +func CompactArrayG[A1 ~[]Either[E, A], A2 ~[]A, E, A any](fa A1) A2 { + return RA.Reduce(fa, func(out A2, value Either[E, A]) A2 { + return MonadFold(value, F.Constant1[E](out), F.Bind1st(RA.Append[A2, A], out)) + }, make(A2, 0, len(fa))) +} + +// CompactArray discards all Left values and keeps only the Right values. +// +// Example: +// +// eithers := []either.Either[error, int]{ +// either.Right[error](1), +// either.Left[int](errors.New("error")), +// either.Right[error](3), +// } +// result := either.CompactArray(eithers) +// // result is []int{1, 3} +// +//go:inline +func CompactArray[E, A any](fa []Either[E, A]) []A { + return CompactArrayG[[]Either[E, A], []A](fa) +} diff --git a/v2/either/array_test.go b/v2/either/array_test.go new file mode 100644 index 0000000..3d4e8a2 --- /dev/null +++ b/v2/either/array_test.go @@ -0,0 +1,50 @@ +package either + +import ( + "fmt" + "testing" + + TST "github.com/IBM/fp-go/v2/internal/testing" + "github.com/stretchr/testify/assert" +) + +func TestCompactArray(t *testing.T) { + ar := []Either[string, string]{ + Of[string]("ok"), + Left[string]("err"), + Of[string]("ok"), + } + + res := CompactArray(ar) + assert.Equal(t, 2, len(res)) +} + +func TestSequenceArray(t *testing.T) { + + s := TST.SequenceArrayTest( + FromStrictEquals[error, bool](), + Pointed[error, string](), + Pointed[error, bool](), + Functor[error, []string, bool](), + SequenceArray[error, string], + ) + + for i := 0; i < 10; i++ { + t.Run(fmt.Sprintf("TestSequenceArray %d", i), s(i)) + } +} + +func TestSequenceArrayError(t *testing.T) { + + s := TST.SequenceArrayErrorTest( + FromStrictEquals[error, bool](), + Left[string, error], + Left[bool, error], + Pointed[error, string](), + Pointed[error, bool](), + Functor[error, []string, bool](), + SequenceArray[error, string], + ) + // run across four bits + s(4)(t) +} diff --git a/v2/either/bind.go b/v2/either/bind.go new file mode 100644 index 0000000..4daad87 --- /dev/null +++ b/v2/either/bind.go @@ -0,0 +1,161 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package either + +import ( + A "github.com/IBM/fp-go/v2/internal/apply" + C "github.com/IBM/fp-go/v2/internal/chain" + F "github.com/IBM/fp-go/v2/internal/functor" +) + +// Do creates an empty context of type S to be used with the Bind operation. +// This is the starting point for do-notation style computations. +// +// Example: +// +// type State struct { x, y int } +// result := either.Do[error](State{}) +func Do[E, S any]( + empty S, +) Either[E, S] { + return Of[E](empty) +} + +// Bind attaches the result of a computation to a context S1 to produce a context S2. +// This enables building up complex computations in a pipeline. +// +// Example: +// +// type State struct { value int } +// result := F.Pipe2( +// either.Do[error](State{}), +// either.Bind( +// func(v int) func(State) State { +// return func(s State) State { return State{value: v} } +// }, +// func(s State) either.Either[error, int] { +// return either.Right[error](42) +// }, +// ), +// ) +func Bind[E, S1, S2, T any]( + setter func(T) func(S1) S2, + f func(S1) Either[E, T], +) func(Either[E, S1]) Either[E, S2] { + return C.Bind( + Chain[E, S1, S2], + Map[E, T, S2], + setter, + f, + ) +} + +// Let attaches the result of a pure computation to a context S1 to produce a context S2. +// Similar to Bind but for pure (non-Either) computations. +// +// Example: +// +// type State struct { value int } +// result := F.Pipe2( +// either.Right[error](State{value: 10}), +// either.Let( +// func(v int) func(State) State { +// return func(s State) State { return State{value: s.value + v} } +// }, +// func(s State) int { return 32 }, +// ), +// ) // Right(State{value: 42}) +func Let[E, S1, S2, T any]( + key func(T) func(S1) S2, + f func(S1) T, +) func(Either[E, S1]) Either[E, S2] { + return F.Let( + Map[E, S1, S2], + key, + f, + ) +} + +// LetTo attaches a constant value to a context S1 to produce a context S2. +// +// Example: +// +// type State struct { name string } +// result := F.Pipe2( +// either.Right[error](State{}), +// either.LetTo( +// func(n string) func(State) State { +// return func(s State) State { return State{name: n} } +// }, +// "Alice", +// ), +// ) // Right(State{name: "Alice"}) +func LetTo[E, S1, S2, T any]( + key func(T) func(S1) S2, + b T, +) func(Either[E, S1]) Either[E, S2] { + return F.LetTo( + Map[E, S1, S2], + key, + b, + ) +} + +// BindTo initializes a new state S1 from a value T. +// This is typically used to start a bind chain. +// +// Example: +// +// type State struct { value int } +// result := F.Pipe2( +// either.Right[error](42), +// either.BindTo(func(v int) State { return State{value: v} }), +// ) // Right(State{value: 42}) +func BindTo[E, S1, T any]( + setter func(T) S1, +) func(Either[E, T]) Either[E, S1] { + return C.BindTo( + Map[E, T, S1], + setter, + ) +} + +// ApS attaches a value to a context S1 to produce a context S2 by considering the context and the value concurrently. +// Uses applicative semantics rather than monadic sequencing. +// +// Example: +// +// type State struct { x, y int } +// result := F.Pipe2( +// either.Right[error](State{x: 10}), +// either.ApS( +// func(y int) func(State) State { +// return func(s State) State { return State{x: s.x, y: y} } +// }, +// either.Right[error](32), +// ), +// ) // Right(State{x: 10, y: 32}) +func ApS[E, S1, S2, T any]( + setter func(T) func(S1) S2, + fa Either[E, T], +) func(Either[E, S1]) Either[E, S2] { + return A.ApS( + Ap[S2, E, T], + Map[E, S1, func(T) S2], + setter, + fa, + ) +} diff --git a/v2/either/bind_test.go b/v2/either/bind_test.go new file mode 100644 index 0000000..3ff5886 --- /dev/null +++ b/v2/either/bind_test.go @@ -0,0 +1,56 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package either + +import ( + "testing" + + F "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/internal/utils" + "github.com/stretchr/testify/assert" +) + +func getLastName(s utils.Initial) Either[error, string] { + return Of[error]("Doe") +} + +func getGivenName(s utils.WithLastName) Either[error, string] { + return Of[error]("John") +} + +func TestBind(t *testing.T) { + + res := F.Pipe3( + Do[error](utils.Empty), + Bind(utils.SetLastName, getLastName), + Bind(utils.SetGivenName, getGivenName), + Map[error](utils.GetFullName), + ) + + assert.Equal(t, res, Of[error]("John Doe")) +} + +func TestApS(t *testing.T) { + + res := F.Pipe3( + Do[error](utils.Empty), + ApS(utils.SetLastName, Of[error]("Doe")), + ApS(utils.SetGivenName, Of[error]("John")), + Map[error](utils.GetFullName), + ) + + assert.Equal(t, res, Of[error]("John Doe")) +} diff --git a/v2/either/core.go b/v2/either/core.go new file mode 100644 index 0000000..8964887 --- /dev/null +++ b/v2/either/core.go @@ -0,0 +1,153 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package either + +import ( + "fmt" +) + +type ( + either struct { + isLeft bool + value any + } + + // Either defines a data structure that logically holds either an E or an A. The flag discriminates the cases + Either[E, A any] either +) + +// String prints some debug info for the object +// +//go:noinline +func eitherString(s *either) string { + if s.isLeft { + return fmt.Sprintf("Left[%T](%v)", s.value, s.value) + } + return fmt.Sprintf("Right[%T](%v)", s.value, s.value) +} + +// Format prints some debug info for the object +// +//go:noinline +func eitherFormat(e *either, f fmt.State, c rune) { + switch c { + case 's': + fmt.Fprint(f, eitherString(e)) + default: + fmt.Fprint(f, eitherString(e)) + } +} + +// String prints some debug info for the object +func (s Either[E, A]) String() string { + return eitherString((*either)(&s)) +} + +// Format prints some debug info for the object +func (s Either[E, A]) Format(f fmt.State, c rune) { + eitherFormat((*either)(&s), f, c) +} + +// IsLeft tests if the Either is a Left value. +// Rather use [Fold] or [MonadFold] if you need to access the values. +// Inverse is [IsRight]. +// +// Example: +// +// either.IsLeft(either.Left[int](errors.New("err"))) // true +// either.IsLeft(either.Right[error](42)) // false +// +//go:inline +func IsLeft[E, A any](val Either[E, A]) bool { + return val.isLeft +} + +// IsRight tests if the Either is a Right value. +// Rather use [Fold] or [MonadFold] if you need to access the values. +// Inverse is [IsLeft]. +// +// Example: +// +// either.IsRight(either.Right[error](42)) // true +// either.IsRight(either.Left[int](errors.New("err"))) // false +// +//go:inline +func IsRight[E, A any](val Either[E, A]) bool { + return !val.isLeft +} + +// Left creates a new Either representing a Left (error/failure) value. +// By convention, Left represents the error case. +// +// Example: +// +// result := either.Left[int](errors.New("something went wrong")) +// +//go:inline +func Left[A, E any](value E) Either[E, A] { + return Either[E, A]{true, value} +} + +// Right creates a new Either representing a Right (success) value. +// By convention, Right represents the success case. +// +// Example: +// +// result := either.Right[error](42) +// +//go:inline +func Right[E, A any](value A) Either[E, A] { + return Either[E, A]{false, value} +} + +// MonadFold extracts the value from an Either by providing handlers for both cases. +// This is the fundamental pattern matching operation for Either. +// +// Example: +// +// result := either.MonadFold( +// either.Right[error](42), +// func(err error) string { return "Error: " + err.Error() }, +// func(n int) string { return fmt.Sprintf("Value: %d", n) }, +// ) // "Value: 42" +// +//go:inline +func MonadFold[E, A, B any](ma Either[E, A], onLeft func(e E) B, onRight func(a A) B) B { + if ma.isLeft { + return onLeft(ma.value.(E)) + } + return onRight(ma.value.(A)) +} + +// Unwrap converts an Either into the idiomatic Go tuple (value, error). +// For Right values, returns (value, zero-error). +// For Left values, returns (zero-value, error). +// +// Example: +// +// val, err := either.Unwrap(either.Right[error](42)) // 42, nil +// val, err := either.Unwrap(either.Left[int](errors.New("fail"))) // 0, error +// +//go:inline +func Unwrap[E, A any](ma Either[E, A]) (A, E) { + if ma.isLeft { + var a A + return a, ma.value.(E) + } else { + var e E + return ma.value.(A), e + } +} diff --git a/v2/either/curry.go b/v2/either/curry.go new file mode 100644 index 0000000..1e4e43d --- /dev/null +++ b/v2/either/curry.go @@ -0,0 +1,117 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package either + +import ( + F "github.com/IBM/fp-go/v2/function" +) + +// Curry0 converts a Go function that returns (R, error) into a curried version that returns Either[error, R]. +// +// Example: +// +// getConfig := func() (string, error) { return "config", nil } +// curried := either.Curry0(getConfig) +// result := curried() // Right("config") +func Curry0[R any](f func() (R, error)) func() Either[error, R] { + return Eitherize0(f) +} + +// Curry1 converts a Go function that returns (R, error) into a curried version that returns Either[error, R]. +// +// Example: +// +// parse := func(s string) (int, error) { return strconv.Atoi(s) } +// curried := either.Curry1(parse) +// result := curried("42") // Right(42) +func Curry1[T1, R any](f func(T1) (R, error)) func(T1) Either[error, R] { + return Eitherize1(f) +} + +// Curry2 converts a 2-argument Go function that returns (R, error) into a curried version. +// +// Example: +// +// divide := func(a, b int) (int, error) { +// if b == 0 { return 0, errors.New("div by zero") } +// return a / b, nil +// } +// curried := either.Curry2(divide) +// result := curried(10)(2) // Right(5) +func Curry2[T1, T2, R any](f func(T1, T2) (R, error)) func(T1) func(T2) Either[error, R] { + return F.Curry2(Eitherize2(f)) +} + +// Curry3 converts a 3-argument Go function that returns (R, error) into a curried version. +func Curry3[T1, T2, T3, R any](f func(T1, T2, T3) (R, error)) func(T1) func(T2) func(T3) Either[error, R] { + return F.Curry3(Eitherize3(f)) +} + +// Curry4 converts a 4-argument Go function that returns (R, error) into a curried version. +func Curry4[T1, T2, T3, T4, R any](f func(T1, T2, T3, T4) (R, error)) func(T1) func(T2) func(T3) func(T4) Either[error, R] { + return F.Curry4(Eitherize4(f)) +} + +// Uncurry0 converts a function returning Either[error, R] back to Go's (R, error) style. +// +// Example: +// +// curried := func() either.Either[error, string] { return either.Right[error]("value") } +// uncurried := either.Uncurry0(curried) +// result, err := uncurried() // "value", nil +func Uncurry0[R any](f func() Either[error, R]) func() (R, error) { + return func() (R, error) { + return UnwrapError(f()) + } +} + +// Uncurry1 converts a function returning Either[error, R] back to Go's (R, error) style. +// +// Example: +// +// curried := func(x int) either.Either[error, string] { return either.Right[error](strconv.Itoa(x)) } +// uncurried := either.Uncurry1(curried) +// result, err := uncurried(42) // "42", nil +func Uncurry1[T1, R any](f func(T1) Either[error, R]) func(T1) (R, error) { + uc := F.Uncurry1(f) + return func(t1 T1) (R, error) { + return UnwrapError(uc(t1)) + } +} + +// Uncurry2 converts a curried function returning Either[error, R] back to Go's (R, error) style. +func Uncurry2[T1, T2, R any](f func(T1) func(T2) Either[error, R]) func(T1, T2) (R, error) { + uc := F.Uncurry2(f) + return func(t1 T1, t2 T2) (R, error) { + return UnwrapError(uc(t1, t2)) + } +} + +// Uncurry3 converts a curried function returning Either[error, R] back to Go's (R, error) style. +func Uncurry3[T1, T2, T3, R any](f func(T1) func(T2) func(T3) Either[error, R]) func(T1, T2, T3) (R, error) { + uc := F.Uncurry3(f) + return func(t1 T1, t2 T2, t3 T3) (R, error) { + return UnwrapError(uc(t1, t2, t3)) + } +} + +// Uncurry4 converts a curried function returning Either[error, R] back to Go's (R, error) style. +func Uncurry4[T1, T2, T3, T4, R any](f func(T1) func(T2) func(T3) func(T4) Either[error, R]) func(T1, T2, T3, T4) (R, error) { + uc := F.Uncurry4(f) + return func(t1 T1, t2 T2, t3 T3, t4 T4) (R, error) { + return UnwrapError(uc(t1, t2, t3, t4)) + } +} diff --git a/v2/either/doc.go b/v2/either/doc.go new file mode 100644 index 0000000..adcefbb --- /dev/null +++ b/v2/either/doc.go @@ -0,0 +1,65 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +// Package either provides the Either monad, a data structure representing a value of one of two possible types. +// +// Either is commonly used for error handling, where by convention: +// - Left represents an error or failure case (type E) +// - Right represents a success case (type A) +// +// # Core Concepts +// +// The Either type is a discriminated union that can hold either a Left value (typically an error) +// or a Right value (typically a successful result). This makes it ideal for computations that may fail. +// +// # Basic Usage +// +// // Creating Either values +// success := either.Right[error](42) // Right value +// failure := either.Left[int](errors.New("oops")) // Left value +// +// // Pattern matching with Fold +// result := either.Fold( +// func(err error) string { return "Error: " + err.Error() }, +// func(n int) string { return fmt.Sprintf("Success: %d", n) }, +// )(success) +// +// // Chaining operations (short-circuits on Left) +// result := either.Chain(func(n int) either.Either[error, int] { +// return either.Right[error](n * 2) +// })(success) +// +// # Monadic Operations +// +// Either implements the Monad interface, providing: +// - Map: Transform the Right value +// - Chain (FlatMap): Chain computations that may fail +// - Ap: Apply a function wrapped in Either +// +// # Error Handling +// +// Either provides utilities for working with Go's error type: +// - TryCatchError: Convert (value, error) tuples to Either +// - UnwrapError: Convert Either back to (value, error) tuple +// - FromError: Create Either from error-returning functions +// +// # Subpackages +// +// - either/exec: Execute system commands returning Either +// - either/http: HTTP request builders returning Either +// - either/testing: Testing utilities for Either laws +package either + +//go:generate go run .. either --count 15 --filename gen.go diff --git a/v2/either/either.go b/v2/either/either.go new file mode 100644 index 0000000..8b31558 --- /dev/null +++ b/v2/either/either.go @@ -0,0 +1,529 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +// package either implements the Either monad +// +// A data type that can be of either of two types but not both. This is +// typically used to carry an error or a return value +package either + +import ( + E "github.com/IBM/fp-go/v2/errors" + F "github.com/IBM/fp-go/v2/function" + C "github.com/IBM/fp-go/v2/internal/chain" + FC "github.com/IBM/fp-go/v2/internal/functor" + L "github.com/IBM/fp-go/v2/lazy" + O "github.com/IBM/fp-go/v2/option" +) + +// Of constructs a Right value containing the given value. +// This is the monadic return/pure operation for Either. +// Equivalent to [Right]. +// +// Example: +// +// result := either.Of[error](42) // Right(42) +// +//go:inline +func Of[E, A any](value A) Either[E, A] { + return F.Pipe1(value, Right[E, A]) +} + +// FromIO executes an IO operation and wraps the result in a Right value. +// This is useful for lifting pure IO operations into the Either context. +// +// Example: +// +// getValue := func() int { return 42 } +// result := either.FromIO[error](getValue) // Right(42) +func FromIO[E any, IO ~func() A, A any](f IO) Either[E, A] { + return F.Pipe1(f(), Right[E, A]) +} + +// MonadAp applies a function wrapped in Either to a value wrapped in Either. +// If either the function or the value is Left, returns Left. +// This is the applicative apply operation. +// +// Example: +// +// fab := either.Right[error](func(x int) int { return x * 2 }) +// fa := either.Right[error](21) +// result := either.MonadAp(fab, fa) // Right(42) +func MonadAp[B, E, A any](fab Either[E, func(a A) B], fa Either[E, A]) Either[E, B] { + return MonadFold(fab, Left[B, E], func(ab func(A) B) Either[E, B] { + return MonadFold(fa, Left[B, E], F.Flow2(ab, Right[E, B])) + }) +} + +// Ap is the curried version of [MonadAp]. +// Returns a function that applies a wrapped function to the given wrapped value. +func Ap[B, E, A any](fa Either[E, A]) func(fab Either[E, func(a A) B]) Either[E, B] { + return F.Bind2nd(MonadAp[B, E, A], fa) +} + +// MonadMap transforms the Right value using the provided function. +// If the Either is Left, returns Left unchanged. +// This is the functor map operation. +// +// Example: +// +// result := either.MonadMap( +// either.Right[error](21), +// func(x int) int { return x * 2 }, +// ) // Right(42) +// +//go:inline +func MonadMap[E, A, B any](fa Either[E, A], f func(a A) B) Either[E, B] { + return MonadChain(fa, F.Flow2(f, Right[E, B])) +} + +// MonadBiMap applies two functions: one to transform a Left value, another to transform a Right value. +// This allows transforming both channels of the Either simultaneously. +// +// Example: +// +// result := either.MonadBiMap( +// either.Left[int](errors.New("error")), +// func(e error) string { return e.Error() }, +// func(n int) string { return fmt.Sprint(n) }, +// ) // Left("error") +func MonadBiMap[E1, E2, A, B any](fa Either[E1, A], f func(E1) E2, g func(a A) B) Either[E2, B] { + return MonadFold(fa, F.Flow2(f, Left[B, E2]), F.Flow2(g, Right[E2, B])) +} + +// BiMap is the curried version of [MonadBiMap]. +// Maps a pair of functions over the two type arguments of the bifunctor. +func BiMap[E1, E2, A, B any](f func(E1) E2, g func(a A) B) func(Either[E1, A]) Either[E2, B] { + return Fold(F.Flow2(f, Left[B, E2]), F.Flow2(g, Right[E2, B])) +} + +// MonadMapTo replaces the Right value with a constant value. +// If the Either is Left, returns Left unchanged. +// +// Example: +// +// result := either.MonadMapTo(either.Right[error](21), "success") // Right("success") +func MonadMapTo[E, A, B any](fa Either[E, A], b B) Either[E, B] { + return MonadMap(fa, F.Constant1[A](b)) +} + +// MapTo is the curried version of [MonadMapTo]. +func MapTo[E, A, B any](b B) func(Either[E, A]) Either[E, B] { + return Map[E](F.Constant1[A](b)) +} + +// MonadMapLeft applies a transformation function to the Left (error) value. +// If the Either is Right, returns Right unchanged. +// +// Example: +// +// result := either.MonadMapLeft( +// either.Left[int](errors.New("error")), +// func(e error) string { return e.Error() }, +// ) // Left("error") +func MonadMapLeft[E1, A, E2 any](fa Either[E1, A], f func(E1) E2) Either[E2, A] { + return MonadFold(fa, F.Flow2(f, Left[A, E2]), Right[E2, A]) +} + +// Map is the curried version of [MonadMap]. +// Transforms the Right value using the provided function. +func Map[E, A, B any](f func(a A) B) func(fa Either[E, A]) Either[E, B] { + return Chain(F.Flow2(f, Right[E, B])) +} + +// MapLeft is the curried version of [MonadMapLeft]. +// Applies a mapping function to the Left (error) channel. +func MapLeft[A, E1, E2 any](f func(E1) E2) func(fa Either[E1, A]) Either[E2, A] { + return Fold(F.Flow2(f, Left[A, E2]), Right[E2, A]) +} + +// MonadChain sequences two computations, where the second depends on the result of the first. +// If the first Either is Left, returns Left without executing the second computation. +// This is the monadic bind operation (also known as flatMap). +// +// Example: +// +// result := either.MonadChain( +// either.Right[error](21), +// func(x int) either.Either[error, int] { +// return either.Right[error](x * 2) +// }, +// ) // Right(42) +// +//go:inline +func MonadChain[E, A, B any](fa Either[E, A], f func(a A) Either[E, B]) Either[E, B] { + return MonadFold(fa, Left[B, E], f) +} + +// MonadChainFirst executes a side-effect computation but returns the original value. +// Useful for performing actions (like logging) without changing the value. +// +// Example: +// +// result := either.MonadChainFirst( +// either.Right[error](42), +// func(x int) either.Either[error, string] { +// fmt.Println(x) // side effect +// return either.Right[error]("logged") +// }, +// ) // Right(42) - original value preserved +func MonadChainFirst[E, A, B any](ma Either[E, A], f func(a A) Either[E, B]) Either[E, A] { + return C.MonadChainFirst( + MonadChain[E, A, A], + MonadMap[E, B, A], + ma, + f, + ) +} + +// MonadChainTo ignores the first Either and returns the second. +// Useful for sequencing operations where you don't need the first result. +func MonadChainTo[A, E, B any](_ Either[E, A], mb Either[E, B]) Either[E, B] { + return mb +} + +// MonadChainOptionK chains a function that returns an Option, converting None to Left. +// +// Example: +// +// result := either.MonadChainOptionK( +// func() error { return errors.New("not found") }, +// either.Right[error](42), +// func(x int) option.Option[string] { +// if x > 0 { return option.Some("positive") } +// return option.None[string]() +// }, +// ) // Right("positive") +func MonadChainOptionK[A, B, E any](onNone func() E, ma Either[E, A], f func(A) Option[B]) Either[E, B] { + return MonadChain(ma, F.Flow2(f, FromOption[B](onNone))) +} + +// ChainOptionK is the curried version of [MonadChainOptionK]. +func ChainOptionK[A, B, E any](onNone func() E) func(func(A) Option[B]) func(Either[E, A]) Either[E, B] { + from := FromOption[B](onNone) + return func(f func(A) Option[B]) func(Either[E, A]) Either[E, B] { + return Chain(F.Flow2(f, from)) + } +} + +// ChainTo is the curried version of [MonadChainTo]. +func ChainTo[A, E, B any](mb Either[E, B]) func(Either[E, A]) Either[E, B] { + return F.Constant1[Either[E, A]](mb) +} + +// Chain is the curried version of [MonadChain]. +// Sequences two computations where the second depends on the first. +func Chain[E, A, B any](f func(a A) Either[E, B]) func(Either[E, A]) Either[E, B] { + return Fold(Left[B, E], f) +} + +// ChainFirst is the curried version of [MonadChainFirst]. +func ChainFirst[E, A, B any](f func(a A) Either[E, B]) func(Either[E, A]) Either[E, A] { + return C.ChainFirst( + Chain[E, A, A], + Map[E, B, A], + f, + ) +} + +// Flatten removes one level of nesting from a nested Either. +// +// Example: +// +// nested := either.Right[error](either.Right[error](42)) +// result := either.Flatten(nested) // Right(42) +func Flatten[E, A any](mma Either[E, Either[E, A]]) Either[E, A] { + return MonadChain(mma, F.Identity[Either[E, A]]) +} + +// TryCatch converts a (value, error) tuple into an Either, applying a transformation to the error. +// +// Example: +// +// result := either.TryCatch( +// 42, nil, +// func(err error) string { return err.Error() }, +// ) // Right(42) +func TryCatch[FE func(error) E, E, A any](val A, err error, onThrow FE) Either[E, A] { + if err != nil { + return F.Pipe2(err, onThrow, Left[A, E]) + } + return F.Pipe1(val, Right[E, A]) +} + +// TryCatchError is a specialized version of [TryCatch] for error types. +// Converts a (value, error) tuple into Either[error, A]. +// +// Example: +// +// result := either.TryCatchError(42, nil) // Right(42) +// result := either.TryCatchError(0, errors.New("fail")) // Left(error) +func TryCatchError[A any](val A, err error) Either[error, A] { + return TryCatch(val, err, E.IdentityError) +} + +// Sequence2 sequences two Either values using a combining function. +// Short-circuits on the first Left encountered. +func Sequence2[E, T1, T2, R any](f func(T1, T2) Either[E, R]) func(Either[E, T1], Either[E, T2]) Either[E, R] { + return func(e1 Either[E, T1], e2 Either[E, T2]) Either[E, R] { + return MonadSequence2(e1, e2, f) + } +} + +// Sequence3 sequences three Either values using a combining function. +// Short-circuits on the first Left encountered. +func Sequence3[E, T1, T2, T3, R any](f func(T1, T2, T3) Either[E, R]) func(Either[E, T1], Either[E, T2], Either[E, T3]) Either[E, R] { + return func(e1 Either[E, T1], e2 Either[E, T2], e3 Either[E, T3]) Either[E, R] { + return MonadSequence3(e1, e2, e3, f) + } +} + +// FromOption converts an Option to an Either, using the provided function to generate a Left value for None. +// +// Example: +// +// opt := option.Some(42) +// result := either.FromOption[int](func() error { return errors.New("none") })(opt) // Right(42) +func FromOption[A, E any](onNone func() E) func(Option[A]) Either[E, A] { + return O.Fold(F.Nullary2(onNone, Left[A, E]), Right[E, A]) +} + +// ToOption converts an Either to an Option, discarding the Left value. +// +// Example: +// +// result := either.ToOption(either.Right[error](42)) // Some(42) +// result := either.ToOption(either.Left[int](errors.New("err"))) // None +// +//go:inline +func ToOption[E, A any](ma Either[E, A]) Option[A] { + return MonadFold(ma, F.Ignore1of1[E](O.None[A]), O.Some[A]) +} + +// FromError creates an Either from a function that may return an error. +// +// Example: +// +// validate := func(x int) error { +// if x < 0 { return errors.New("negative") } +// return nil +// } +// toEither := either.FromError(validate) +// result := toEither(42) // Right(42) +func FromError[A any](f func(a A) error) func(A) Either[error, A] { + return func(a A) Either[error, A] { + return TryCatchError(a, f(a)) + } +} + +// ToError converts an Either[error, A] to an error, returning nil for Right values. +// +// Example: +// +// err := either.ToError(either.Left[int](errors.New("fail"))) // error +// err := either.ToError(either.Right[error](42)) // nil +func ToError[A any](e Either[error, A]) error { + return MonadFold(e, E.IdentityError, F.Constant1[A, error](nil)) +} + +// Fold is the curried version of [MonadFold]. +// Extracts the value from an Either by providing handlers for both cases. +// +// Example: +// +// result := either.Fold( +// func(err error) string { return "Error: " + err.Error() }, +// func(n int) string { return fmt.Sprintf("Value: %d", n) }, +// )(either.Right[error](42)) // "Value: 42" +func Fold[E, A, B any](onLeft func(E) B, onRight func(A) B) func(Either[E, A]) B { + return func(ma Either[E, A]) B { + return MonadFold(ma, onLeft, onRight) + } +} + +// UnwrapError converts an Either[error, A] into the idiomatic Go tuple (A, error). +// +// Example: +// +// val, err := either.UnwrapError(either.Right[error](42)) // 42, nil +// val, err := either.UnwrapError(either.Left[int](errors.New("fail"))) // zero, error +// +//go:inline +func UnwrapError[A any](ma Either[error, A]) (A, error) { + return Unwrap[error](ma) +} + +// FromPredicate creates an Either based on a predicate. +// If the predicate returns true, creates a Right; otherwise creates a Left using onFalse. +// +// Example: +// +// isPositive := either.FromPredicate( +// func(x int) bool { return x > 0 }, +// func(x int) error { return errors.New("not positive") }, +// ) +// result := isPositive(42) // Right(42) +// result := isPositive(-1) // Left(error) +func FromPredicate[E, A any](pred func(A) bool, onFalse func(A) E) func(A) Either[E, A] { + return func(a A) Either[E, A] { + if pred(a) { + return Right[E](a) + } + return Left[A, E](onFalse(a)) + } +} + +// FromNillable creates an Either from a pointer, using the provided error for nil pointers. +// +// Example: +// +// var ptr *int = nil +// result := either.FromNillable[int](errors.New("nil"))(ptr) // Left(error) +// val := 42 +// result := either.FromNillable[int](errors.New("nil"))(&val) // Right(&42) +func FromNillable[A, E any](e E) func(*A) Either[E, *A] { + return FromPredicate(F.IsNonNil[A], F.Constant1[*A](e)) +} + +// GetOrElse extracts the Right value or computes a default from the Left value. +// +// Example: +// +// result := either.GetOrElse(func(err error) int { return 0 })(either.Right[error](42)) // 42 +// result := either.GetOrElse(func(err error) int { return 0 })(either.Left[int](err)) // 0 +func GetOrElse[E, A any](onLeft func(E) A) func(Either[E, A]) A { + return Fold(onLeft, F.Identity[A]) +} + +// Reduce folds an Either into a single value using a reducer function. +// Returns the initial value for Left, or applies the reducer to the Right value. +func Reduce[E, A, B any](f func(B, A) B, initial B) func(Either[E, A]) B { + return Fold( + F.Constant1[E](initial), + F.Bind1st(f, initial), + ) +} + +// AltW provides an alternative Either if the first is Left, allowing different error types. +// The 'W' suffix indicates "widening" of the error type. +// +// Example: +// +// alternative := either.AltW[error, string](func() either.Either[string, int] { +// return either.Right[string](99) +// }) +// result := alternative(either.Left[int](errors.New("fail"))) // Right(99) +func AltW[E, E1, A any](that L.Lazy[Either[E1, A]]) func(Either[E, A]) Either[E1, A] { + return Fold(F.Ignore1of1[E](that), Right[E1, A]) +} + +// Alt provides an alternative Either if the first is Left. +// +// Example: +// +// alternative := either.Alt[error](func() either.Either[error, int] { +// return either.Right[error](99) +// }) +// result := alternative(either.Left[int](errors.New("fail"))) // Right(99) +func Alt[E, A any](that L.Lazy[Either[E, A]]) func(Either[E, A]) Either[E, A] { + return AltW[E](that) +} + +// OrElse recovers from a Left by providing an alternative computation. +// +// Example: +// +// recover := either.OrElse(func(err error) either.Either[error, int] { +// return either.Right[error](0) // default value +// }) +// result := recover(either.Left[int](errors.New("fail"))) // Right(0) +func OrElse[E, A any](onLeft func(e E) Either[E, A]) func(Either[E, A]) Either[E, A] { + return Fold(onLeft, Of[E, A]) +} + +// ToType attempts to convert an any value to a specific type, returning Either. +// +// Example: +// +// convert := either.ToType[int](func(v any) error { +// return fmt.Errorf("cannot convert %v to int", v) +// }) +// result := convert(42) // Right(42) +// result := convert("string") // Left(error) +func ToType[A, E any](onError func(any) E) func(any) Either[E, A] { + return func(value any) Either[E, A] { + return F.Pipe2( + value, + O.ToType[A], + O.Fold(F.Nullary3(F.Constant(value), onError, Left[A, E]), Right[E, A]), + ) + } +} + +// Memoize returns the Either unchanged (Either values are already memoized). +func Memoize[E, A any](val Either[E, A]) Either[E, A] { + return val +} + +// MonadSequence2 sequences two Either values using a combining function. +// Short-circuits on the first Left encountered. +func MonadSequence2[E, T1, T2, R any](e1 Either[E, T1], e2 Either[E, T2], f func(T1, T2) Either[E, R]) Either[E, R] { + return MonadFold(e1, Left[R, E], func(t1 T1) Either[E, R] { + return MonadFold(e2, Left[R, E], func(t2 T2) Either[E, R] { + return f(t1, t2) + }) + }) +} + +// MonadSequence3 sequences three Either values using a combining function. +// Short-circuits on the first Left encountered. +func MonadSequence3[E, T1, T2, T3, R any](e1 Either[E, T1], e2 Either[E, T2], e3 Either[E, T3], f func(T1, T2, T3) Either[E, R]) Either[E, R] { + return MonadFold(e1, Left[R, E], func(t1 T1) Either[E, R] { + return MonadFold(e2, Left[R, E], func(t2 T2) Either[E, R] { + return MonadFold(e3, Left[R, E], func(t3 T3) Either[E, R] { + return f(t1, t2, t3) + }) + }) + }) +} + +// Swap exchanges the Left and Right type parameters. +// +// Example: +// +// result := either.Swap(either.Right[error](42)) // Left(42) +// result := either.Swap(either.Left[int](errors.New("err"))) // Right(error) +// +//go:inline +func Swap[E, A any](val Either[E, A]) Either[A, E] { + return MonadFold(val, Right[A, E], Left[E, A]) +} + +// MonadFlap applies a value to a function wrapped in Either. +// This is the reverse of [MonadAp]. +func MonadFlap[E, B, A any](fab Either[E, func(A) B], a A) Either[E, B] { + return FC.MonadFlap(MonadMap[E, func(A) B, B], fab, a) +} + +// Flap is the curried version of [MonadFlap]. +func Flap[E, B, A any](a A) func(Either[E, func(A) B]) Either[E, B] { + return FC.Flap(Map[E, func(A) B, B], a) +} + +// MonadAlt provides an alternative Either if the first is Left. +// This is the monadic version of [Alt]. +func MonadAlt[E, A any](fa Either[E, A], that L.Lazy[Either[E, A]]) Either[E, A] { + return MonadFold(fa, F.Ignore1of1[E](that), Of[E, A]) +} diff --git a/v2/either/either_coverage_test.go b/v2/either/either_coverage_test.go new file mode 100644 index 0000000..8dabf47 --- /dev/null +++ b/v2/either/either_coverage_test.go @@ -0,0 +1,702 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package either + +import ( + "errors" + "fmt" + "strconv" + "testing" + + F "github.com/IBM/fp-go/v2/function" + M "github.com/IBM/fp-go/v2/monoid" + O "github.com/IBM/fp-go/v2/option" + "github.com/stretchr/testify/assert" +) + +// Test BiMap +func TestBiMap(t *testing.T) { + errToStr := func(e error) string { return e.Error() } + intToStr := func(i int) string { return strconv.Itoa(i) } + + // Test Right case + result := BiMap(errToStr, intToStr)(Right[error](42)) + assert.Equal(t, Right[string]("42"), result) + + // Test Left case + result = BiMap(errToStr, intToStr)(Left[int](errors.New("error"))) + assert.Equal(t, Left[string]("error"), result) +} + +// Test MonadBiMap +func TestMonadBiMap(t *testing.T) { + errToStr := func(e error) string { return e.Error() } + intToStr := func(i int) string { return strconv.Itoa(i) } + + result := MonadBiMap(Right[error](42), errToStr, intToStr) + assert.Equal(t, Right[string]("42"), result) + + result = MonadBiMap(Left[int](errors.New("error")), errToStr, intToStr) + assert.Equal(t, Left[string]("error"), result) +} + +// Test MapLeft +func TestMapLeft(t *testing.T) { + errToStr := func(e error) string { return e.Error() } + + result := MapLeft[int](errToStr)(Left[int](errors.New("error"))) + assert.Equal(t, Left[int]("error"), result) + + result = MapLeft[int](errToStr)(Right[error](42)) + assert.Equal(t, Right[string](42), result) +} + +// Test MonadMapLeft +func TestMonadMapLeft(t *testing.T) { + errToStr := func(e error) string { return e.Error() } + + result := MonadMapLeft(Left[int](errors.New("error")), errToStr) + assert.Equal(t, Left[int]("error"), result) + + result = MonadMapLeft(Right[error](42), errToStr) + assert.Equal(t, Right[string](42), result) +} + +// Test MonadMapTo +func TestMonadMapTo(t *testing.T) { + result := MonadMapTo(Right[error](42), "success") + assert.Equal(t, Right[error]("success"), result) + + result = MonadMapTo(Left[int](errors.New("error")), "success") + assert.Equal(t, Left[string](errors.New("error")), result) +} + +// Test MonadChainFirst +func TestMonadChainFirst(t *testing.T) { + f := func(x int) Either[error, string] { + return Right[error](strconv.Itoa(x)) + } + + result := MonadChainFirst(Right[error](42), f) + assert.Equal(t, Right[error](42), result) + + result = MonadChainFirst(Left[int](errors.New("error")), f) + assert.Equal(t, Left[int](errors.New("error")), result) +} + +// Test MonadChainTo +func TestMonadChainTo(t *testing.T) { + result := MonadChainTo(Right[error](42), Right[error]("hello")) + assert.Equal(t, Right[error]("hello"), result) + + result = MonadChainTo(Left[int](errors.New("error")), Right[error]("hello")) + assert.Equal(t, Right[error]("hello"), result) +} + +// Test Flatten +func TestFlatten(t *testing.T) { + nested := Right[error](Right[error](42)) + result := Flatten(nested) + assert.Equal(t, Right[error](42), result) + + nestedLeft := Right[error](Left[int](errors.New("error"))) + result = Flatten(nestedLeft) + assert.Equal(t, Left[int](errors.New("error")), result) + + outerLeft := Left[Either[error, int]](errors.New("outer error")) + result = Flatten(outerLeft) + assert.Equal(t, Left[int](errors.New("outer error")), result) +} + +// Test ToOption +func TestToOption(t *testing.T) { + result := ToOption(Right[error](42)) + assert.Equal(t, O.Some(42), result) + + result = ToOption(Left[int](errors.New("error"))) + assert.Equal(t, O.None[int](), result) +} + +// Test FromError +func TestFromError(t *testing.T) { + validate := func(x int) error { + if x < 0 { + return errors.New("negative") + } + return nil + } + + toEither := FromError(validate) + result := toEither(42) + assert.Equal(t, Right[error](42), result) + + result = toEither(-1) + assert.True(t, IsLeft(result)) +} + +// Test ToError +func TestToError(t *testing.T) { + err := ToError(Left[int](errors.New("error"))) + assert.Error(t, err) + assert.Equal(t, "error", err.Error()) + + err = ToError(Right[error](42)) + assert.NoError(t, err) +} + +// Test OrElse +func TestOrElse(t *testing.T) { + recover := OrElse(func(e error) Either[error, int] { + return Right[error](0) + }) + + result := recover(Left[int](errors.New("error"))) + assert.Equal(t, Right[error](0), result) + + result = recover(Right[error](42)) + assert.Equal(t, Right[error](42), result) +} + +// Test ToType +func TestToType(t *testing.T) { + convert := ToType[int](func(v any) error { + return fmt.Errorf("cannot convert %v to int", v) + }) + + result := convert(42) + assert.Equal(t, Right[error](42), result) + + result = convert("string") + assert.True(t, IsLeft(result)) +} + +// Test Memoize +func TestMemoize(t *testing.T) { + val := Right[error](42) + result := Memoize(val) + assert.Equal(t, val, result) +} + +// Test Swap +func TestSwap(t *testing.T) { + result := Swap(Right[error](42)) + assert.Equal(t, Left[error](42), result) + + result = Swap(Left[int](errors.New("error"))) + assert.Equal(t, Right[int](errors.New("error")), result) +} + +// Test MonadFlap and Flap +func TestFlap(t *testing.T) { + fab := Right[error](func(x int) string { return strconv.Itoa(x) }) + result := MonadFlap(fab, 42) + assert.Equal(t, Right[error]("42"), result) + + result = Flap[error, string](42)(fab) + assert.Equal(t, Right[error]("42"), result) + + fabLeft := Left[func(int) string](errors.New("error")) + result = MonadFlap(fabLeft, 42) + assert.Equal(t, Left[string](errors.New("error")), result) +} + +// Test Sequence2 and MonadSequence2 +func TestSequence2(t *testing.T) { + f := func(a int, b int) Either[error, int] { + return Right[error](a + b) + } + + result := Sequence2(f)(Right[error](1), Right[error](2)) + assert.Equal(t, Right[error](3), result) + + result = Sequence2(f)(Left[int](errors.New("error")), Right[error](2)) + assert.Equal(t, Left[int](errors.New("error")), result) + + result = MonadSequence2(Right[error](1), Right[error](2), f) + assert.Equal(t, Right[error](3), result) +} + +// Test Sequence3 and MonadSequence3 +func TestSequence3(t *testing.T) { + f := func(a, b, c int) Either[error, int] { + return Right[error](a + b + c) + } + + result := Sequence3(f)(Right[error](1), Right[error](2), Right[error](3)) + assert.Equal(t, Right[error](6), result) + + result = Sequence3(f)(Left[int](errors.New("error")), Right[error](2), Right[error](3)) + assert.Equal(t, Left[int](errors.New("error")), result) + + result = MonadSequence3(Right[error](1), Right[error](2), Right[error](3), f) + assert.Equal(t, Right[error](6), result) +} + +// Test Let +func TestLet(t *testing.T) { + type State struct{ value int } + result := F.Pipe2( + Right[error](State{value: 10}), + Let[error]( + func(v int) func(State) State { + return func(s State) State { return State{value: s.value + v} } + }, + func(s State) int { return 32 }, + ), + Map[error](F.Identity[State]), + ) + assert.Equal(t, Right[error](State{value: 42}), result) +} + +// Test LetTo +func TestLetTo(t *testing.T) { + type State struct{ name string } + result := F.Pipe2( + Right[error](State{}), + LetTo[error]( + func(n string) func(State) State { + return func(s State) State { return State{name: n} } + }, + "Alice", + ), + Map[error](F.Identity[State]), + ) + assert.Equal(t, Right[error](State{name: "Alice"}), result) +} + +// Test BindTo +func TestBindTo(t *testing.T) { + type State struct{ value int } + result := F.Pipe2( + Right[error](42), + BindTo[error](func(v int) State { return State{value: v} }), + Map[error](F.Identity[State]), + ) + assert.Equal(t, Right[error](State{value: 42}), result) +} + +// Test TraverseArray +func TestTraverseArray(t *testing.T) { + parse := func(s string) Either[error, int] { + v, err := strconv.Atoi(s) + return TryCatchError(v, err) + } + + result := TraverseArray(parse)([]string{"1", "2", "3"}) + assert.Equal(t, Right[error]([]int{1, 2, 3}), result) + + result = TraverseArray(parse)([]string{"1", "bad", "3"}) + assert.True(t, IsLeft(result)) +} + +// Test TraverseArrayWithIndex +func TestTraverseArrayWithIndex(t *testing.T) { + validate := func(i int, s string) Either[error, string] { + if len(s) > 0 { + return Right[error](fmt.Sprintf("%d:%s", i, s)) + } + return Left[string](fmt.Errorf("empty at index %d", i)) + } + + result := TraverseArrayWithIndex(validate)([]string{"a", "b"}) + assert.Equal(t, Right[error]([]string{"0:a", "1:b"}), result) + + result = TraverseArrayWithIndex(validate)([]string{"a", ""}) + assert.True(t, IsLeft(result)) +} + +// Test TraverseRecord +func TestTraverseRecord(t *testing.T) { + parse := func(s string) Either[error, int] { + v, err := strconv.Atoi(s) + return TryCatchError(v, err) + } + + input := map[string]string{"a": "1", "b": "2"} + result := TraverseRecord[string](parse)(input) + expected := Right[error](map[string]int{"a": 1, "b": 2}) + assert.Equal(t, expected, result) +} + +// Test TraverseRecordWithIndex +func TestTraverseRecordWithIndex(t *testing.T) { + validate := func(k string, v string) Either[error, string] { + if len(v) > 0 { + return Right[error](k + ":" + v) + } + return Left[string](fmt.Errorf("empty value for key %s", k)) + } + + input := map[string]string{"a": "1"} + result := TraverseRecordWithIndex[string](validate)(input) + expected := Right[error](map[string]string{"a": "a:1"}) + assert.Equal(t, expected, result) +} + +// Test SequenceRecord +func TestSequenceRecord(t *testing.T) { + eithers := map[string]Either[error, int]{ + "a": Right[error](1), + "b": Right[error](2), + } + result := SequenceRecord(eithers) + expected := Right[error](map[string]int{"a": 1, "b": 2}) + assert.Equal(t, expected, result) + + eithersWithError := map[string]Either[error, int]{ + "a": Right[error](1), + "b": Left[int](errors.New("error")), + } + result = SequenceRecord(eithersWithError) + assert.True(t, IsLeft(result)) +} + +// Test Curry functions +func TestCurry0(t *testing.T) { + getConfig := func() (string, error) { return "config", nil } + curried := Curry0(getConfig) + result := curried() + assert.Equal(t, Right[error]("config"), result) +} + +func TestCurry1(t *testing.T) { + parse := func(s string) (int, error) { return strconv.Atoi(s) } + curried := Curry1(parse) + result := curried("42") + assert.Equal(t, Right[error](42), result) + + result = curried("bad") + assert.True(t, IsLeft(result)) +} + +func TestCurry2(t *testing.T) { + divide := func(a, b int) (int, error) { + if b == 0 { + return 0, errors.New("div by zero") + } + return a / b, nil + } + curried := Curry2(divide) + result := curried(10)(2) + assert.Equal(t, Right[error](5), result) + + result = curried(10)(0) + assert.True(t, IsLeft(result)) +} + +func TestCurry3(t *testing.T) { + sum3 := func(a, b, c int) (int, error) { + return a + b + c, nil + } + curried := Curry3(sum3) + result := curried(1)(2)(3) + assert.Equal(t, Right[error](6), result) +} + +func TestCurry4(t *testing.T) { + sum4 := func(a, b, c, d int) (int, error) { + return a + b + c + d, nil + } + curried := Curry4(sum4) + result := curried(1)(2)(3)(4) + assert.Equal(t, Right[error](10), result) +} + +// Test Uncurry functions +func TestUncurry0(t *testing.T) { + curried := func() Either[error, string] { return Right[error]("value") } + uncurried := Uncurry0(curried) + result, err := uncurried() + assert.NoError(t, err) + assert.Equal(t, "value", result) +} + +func TestUncurry1(t *testing.T) { + curried := func(x int) Either[error, string] { return Right[error](strconv.Itoa(x)) } + uncurried := Uncurry1(curried) + result, err := uncurried(42) + assert.NoError(t, err) + assert.Equal(t, "42", result) +} + +func TestUncurry2(t *testing.T) { + curried := func(a int) func(int) Either[error, int] { + return func(b int) Either[error, int] { + return Right[error](a + b) + } + } + uncurried := Uncurry2(curried) + result, err := uncurried(1, 2) + assert.NoError(t, err) + assert.Equal(t, 3, result) +} + +func TestUncurry3(t *testing.T) { + curried := func(a int) func(int) func(int) Either[error, int] { + return func(b int) func(int) Either[error, int] { + return func(c int) Either[error, int] { + return Right[error](a + b + c) + } + } + } + uncurried := Uncurry3(curried) + result, err := uncurried(1, 2, 3) + assert.NoError(t, err) + assert.Equal(t, 6, result) +} + +func TestUncurry4(t *testing.T) { + curried := func(a int) func(int) func(int) func(int) Either[error, int] { + return func(b int) func(int) func(int) Either[error, int] { + return func(c int) func(int) Either[error, int] { + return func(d int) Either[error, int] { + return Right[error](a + b + c + d) + } + } + } + } + uncurried := Uncurry4(curried) + result, err := uncurried(1, 2, 3, 4) + assert.NoError(t, err) + assert.Equal(t, 10, result) +} + +// Test Variadic functions +func TestVariadic0(t *testing.T) { + sum := func(nums []int) (int, error) { + total := 0 + for _, n := range nums { + total += n + } + return total, nil + } + variadicSum := Variadic0(sum) + result := variadicSum(1, 2, 3) + assert.Equal(t, Right[error](6), result) +} + +func TestVariadic1(t *testing.T) { + multiply := func(factor int, nums []int) ([]int, error) { + result := make([]int, len(nums)) + for i, n := range nums { + result[i] = n * factor + } + return result, nil + } + variadicMultiply := Variadic1(multiply) + result := variadicMultiply(2, 1, 2, 3) + assert.Equal(t, Right[error]([]int{2, 4, 6}), result) +} + +func TestVariadic2(t *testing.T) { + combine := func(a, b int, nums []int) (int, error) { + total := a + b + for _, n := range nums { + total += n + } + return total, nil + } + variadicCombine := Variadic2(combine) + result := variadicCombine(1, 2, 3, 4) + assert.Equal(t, Right[error](10), result) +} + +func TestVariadic3(t *testing.T) { + combine := func(a, b, c int, nums []int) (int, error) { + total := a + b + c + for _, n := range nums { + total += n + } + return total, nil + } + variadicCombine := Variadic3(combine) + result := variadicCombine(1, 2, 3, 4, 5) + assert.Equal(t, Right[error](15), result) +} + +func TestVariadic4(t *testing.T) { + combine := func(a, b, c, d int, nums []int) (int, error) { + total := a + b + c + d + for _, n := range nums { + total += n + } + return total, nil + } + variadicCombine := Variadic4(combine) + result := variadicCombine(1, 2, 3, 4, 5, 6) + assert.Equal(t, Right[error](21), result) +} + +// Test Unvariadic functions +func TestUnvariadic0(t *testing.T) { + variadic := func(nums ...int) (int, error) { + total := 0 + for _, n := range nums { + total += n + } + return total, nil + } + unvariadic := Unvariadic0(variadic) + result := unvariadic([]int{1, 2, 3}) + assert.Equal(t, Right[error](6), result) +} + +func TestUnvariadic1(t *testing.T) { + variadic := func(factor int, nums ...int) ([]int, error) { + result := make([]int, len(nums)) + for i, n := range nums { + result[i] = n * factor + } + return result, nil + } + unvariadic := Unvariadic1(variadic) + result := unvariadic(2, []int{1, 2, 3}) + assert.Equal(t, Right[error]([]int{2, 4, 6}), result) +} + +func TestUnvariadic2(t *testing.T) { + variadic := func(a, b int, nums ...int) (int, error) { + total := a + b + for _, n := range nums { + total += n + } + return total, nil + } + unvariadic := Unvariadic2(variadic) + result := unvariadic(1, 2, []int{3, 4}) + assert.Equal(t, Right[error](10), result) +} + +func TestUnvariadic3(t *testing.T) { + variadic := func(a, b, c int, nums ...int) (int, error) { + total := a + b + c + for _, n := range nums { + total += n + } + return total, nil + } + unvariadic := Unvariadic3(variadic) + result := unvariadic(1, 2, 3, []int{4, 5}) + assert.Equal(t, Right[error](15), result) +} + +func TestUnvariadic4(t *testing.T) { + variadic := func(a, b, c, d int, nums ...int) (int, error) { + total := a + b + c + d + for _, n := range nums { + total += n + } + return total, nil + } + unvariadic := Unvariadic4(variadic) + result := unvariadic(1, 2, 3, 4, []int{5, 6}) + assert.Equal(t, Right[error](21), result) +} + +// Test Monad +func TestMonad(t *testing.T) { + m := Monad[error, int, string]() + + // Test Of + result := m.Of(42) + assert.Equal(t, Right[error](42), result) + + // Test Map + mapFn := m.Map(func(x int) string { return strconv.Itoa(x) }) + result2 := mapFn(Right[error](42)) + assert.Equal(t, Right[error]("42"), result2) + + // Test Chain + chainFn := m.Chain(func(x int) Either[error, string] { + return Right[error](strconv.Itoa(x)) + }) + result3 := chainFn(Right[error](42)) + assert.Equal(t, Right[error]("42"), result3) + + // Test Ap + apFn := m.Ap(Right[error](42)) + result4 := apFn(Right[error](func(x int) string { return strconv.Itoa(x) })) + assert.Equal(t, Right[error]("42"), result4) +} + +// Test AltSemigroup +func TestAltSemigroup(t *testing.T) { + sg := AltSemigroup[error, int]() + + result := sg.Concat(Left[int](errors.New("error")), Right[error](42)) + assert.Equal(t, Right[error](42), result) + + result = sg.Concat(Right[error](1), Right[error](2)) + assert.Equal(t, Right[error](1), result) +} + +// Test AlternativeMonoid +func TestAlternativeMonoid(t *testing.T) { + intAdd := M.MakeMonoid(func(a, b int) int { return a + b }, 0) + m := AlternativeMonoid[error](intAdd) + + result := m.Concat(Right[error](1), Right[error](2)) + assert.Equal(t, Right[error](3), result) + + empty := m.Empty() + assert.Equal(t, Right[error](0), empty) +} + +// Test AltMonoid +func TestAltMonoid(t *testing.T) { + zero := func() Either[error, int] { return Left[int](errors.New("empty")) } + m := AltMonoid[error, int](zero) + + result := m.Concat(Left[int](errors.New("err1")), Right[error](42)) + assert.Equal(t, Right[error](42), result) + + empty := m.Empty() + assert.Equal(t, Left[int](errors.New("empty")), empty) +} + +// Test core.go Format +func TestFormat(t *testing.T) { + e := Right[error](42) + formatted := fmt.Sprintf("%s", e) + assert.Contains(t, formatted, "Right") + assert.Contains(t, formatted, "42") + + e2 := Left[int](errors.New("error")) + formatted2 := fmt.Sprintf("%v", e2) + assert.Contains(t, formatted2, "Left") +} + +// Test TryCatch with error +func TestTryCatchWithError(t *testing.T) { + result := TryCatch(0, errors.New("error"), func(err error) string { + return err.Error() + }) + assert.Equal(t, Left[int]("error"), result) +} + +// Test Unwrap with else branch +func TestUnwrapElseBranch(t *testing.T) { + val, err := Unwrap(Right[error](42)) + assert.Equal(t, 42, val) + assert.Equal(t, error(nil), err) +} + +// Test eitherFormat with different rune +func TestEitherFormatDifferentRune(t *testing.T) { + e := Right[error](42) + formatted := fmt.Sprintf("%v", e) + assert.Contains(t, formatted, "Right") +} diff --git a/v2/either/either_test.go b/v2/either/either_test.go new file mode 100644 index 0000000..7529d40 --- /dev/null +++ b/v2/either/either_test.go @@ -0,0 +1,129 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package either + +import ( + "errors" + "fmt" + "testing" + + F "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/internal/utils" + IO "github.com/IBM/fp-go/v2/io" + O "github.com/IBM/fp-go/v2/option" + S "github.com/IBM/fp-go/v2/string" + "github.com/stretchr/testify/assert" +) + +func TestIsLeft(t *testing.T) { + err := errors.New("Some error") + withError := Left[string](err) + + assert.True(t, IsLeft(withError)) + assert.False(t, IsRight(withError)) +} + +func TestIsRight(t *testing.T) { + noError := Right[error]("Carsten") + + assert.True(t, IsRight(noError)) + assert.False(t, IsLeft(noError)) +} + +func TestMapEither(t *testing.T) { + + assert.Equal(t, F.Pipe1(Right[error]("abc"), Map[error](utils.StringLen)), Right[error](3)) + + val2 := F.Pipe1(Left[string, string]("s"), Map[string](utils.StringLen)) + exp2 := Left[int]("s") + + assert.Equal(t, val2, exp2) +} + +func TestUnwrapError(t *testing.T) { + a := "" + err := errors.New("Some error") + withError := Left[string](err) + + res, extracted := UnwrapError(withError) + assert.Equal(t, a, res) + assert.Equal(t, extracted, err) + +} + +func TestReduce(t *testing.T) { + + s := S.Semigroup() + + assert.Equal(t, "foobar", F.Pipe1(Right[string]("bar"), Reduce[string](s.Concat, "foo"))) + assert.Equal(t, "foo", F.Pipe1(Left[string, string]("bar"), Reduce[string](s.Concat, "foo"))) + +} +func TestAp(t *testing.T) { + f := S.Size + + assert.Equal(t, Right[string](3), F.Pipe1(Right[string](f), Ap[int, string, string](Right[string]("abc")))) + assert.Equal(t, Left[int]("maError"), F.Pipe1(Right[string](f), Ap[int, string, string](Left[string, string]("maError")))) + assert.Equal(t, Left[int]("mabError"), F.Pipe1(Left[func(string) int]("mabError"), Ap[int, string, string](Left[string, string]("maError")))) +} + +func TestAlt(t *testing.T) { + assert.Equal(t, Right[string](1), F.Pipe1(Right[string](1), Alt(F.Constant(Right[string](2))))) + assert.Equal(t, Right[string](1), F.Pipe1(Right[string](1), Alt(F.Constant(Left[int]("a"))))) + assert.Equal(t, Right[string](2), F.Pipe1(Left[int]("b"), Alt(F.Constant(Right[string](2))))) + assert.Equal(t, Left[int]("b"), F.Pipe1(Left[int]("a"), Alt(F.Constant(Left[int]("b"))))) +} + +func TestChainFirst(t *testing.T) { + f := F.Flow2(S.Size, Right[string, int]) + + assert.Equal(t, Right[string]("abc"), F.Pipe1(Right[string]("abc"), ChainFirst(f))) + assert.Equal(t, Left[string, string]("maError"), F.Pipe1(Left[string, string]("maError"), ChainFirst(f))) +} + +func TestChainOptionK(t *testing.T) { + f := ChainOptionK[int, int](F.Constant("a"))(func(n int) Option[int] { + if n > 0 { + return O.Some(n) + } + return O.None[int]() + }) + assert.Equal(t, Right[string](1), f(Right[string](1))) + assert.Equal(t, Left[int]("a"), f(Right[string](-1))) + assert.Equal(t, Left[int]("b"), f(Left[int]("b"))) +} + +func TestFromOption(t *testing.T) { + assert.Equal(t, Left[int]("none"), FromOption[int](F.Constant("none"))(O.None[int]())) + assert.Equal(t, Right[string](1), FromOption[int](F.Constant("none"))(O.Some(1))) +} + +func TestStringer(t *testing.T) { + e := Of[error]("foo") + exp := "Right[string](foo)" + + assert.Equal(t, exp, e.String()) + + var s fmt.Stringer = e + assert.Equal(t, exp, s.String()) +} + +func TestFromIO(t *testing.T) { + f := IO.Of("abc") + e := FromIO[error](f) + + assert.Equal(t, Right[error]("abc"), e) +} diff --git a/v2/either/eq.go b/v2/either/eq.go new file mode 100644 index 0000000..b84484c --- /dev/null +++ b/v2/either/eq.go @@ -0,0 +1,58 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package either + +import ( + EQ "github.com/IBM/fp-go/v2/eq" + F "github.com/IBM/fp-go/v2/function" +) + +// Eq constructs an equality predicate for Either values. +// Two Either values are equal if they are both Left with equal error values, +// or both Right with equal success values. +// +// Parameters: +// - e: Equality predicate for the Left (error) type +// - a: Equality predicate for the Right (success) type +// +// Example: +// +// eq := either.Eq(eq.FromStrictEquals[error](), eq.FromStrictEquals[int]()) +// result := eq.Equals(either.Right[error](42), either.Right[error](42)) // true +// result2 := eq.Equals(either.Right[error](42), either.Right[error](43)) // false +func Eq[E, A any](e EQ.Eq[E], a EQ.Eq[A]) EQ.Eq[Either[E, A]] { + // some convenient shortcuts + eqa := F.Curry2(a.Equals) + eqe := F.Curry2(e.Equals) + + fca := F.Bind2nd(Fold[E, A, bool], F.Constant1[A](false)) + fce := F.Bind1st(Fold[E, A, bool], F.Constant1[E](false)) + + fld := Fold(F.Flow2(eqe, fca), F.Flow2(eqa, fce)) + + return EQ.FromEquals(F.Uncurry2(fld)) +} + +// FromStrictEquals constructs an equality predicate using Go's == operator. +// Both the Left and Right types must be comparable. +// +// Example: +// +// eq := either.FromStrictEquals[error, int]() +// result := eq.Equals(either.Right[error](42), either.Right[error](42)) // true +func FromStrictEquals[E, A comparable]() EQ.Eq[Either[E, A]] { + return Eq(EQ.FromStrictEquals[E](), EQ.FromStrictEquals[A]()) +} diff --git a/v2/either/eq_test.go b/v2/either/eq_test.go new file mode 100644 index 0000000..a702658 --- /dev/null +++ b/v2/either/eq_test.go @@ -0,0 +1,45 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package either + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestEq(t *testing.T) { + + r1 := Of[string](1) + r2 := Of[string](1) + r3 := Of[string](2) + + e1 := Left[int]("a") + e2 := Left[int]("a") + e3 := Left[int]("b") + + eq := FromStrictEquals[string, int]() + + assert.True(t, eq.Equals(r1, r1)) + assert.True(t, eq.Equals(r1, r2)) + assert.False(t, eq.Equals(r1, r3)) + assert.False(t, eq.Equals(r1, e1)) + + assert.True(t, eq.Equals(e1, e1)) + assert.True(t, eq.Equals(e1, e2)) + assert.False(t, eq.Equals(e1, e3)) + assert.False(t, eq.Equals(e2, r2)) +} diff --git a/v2/either/examples_create_test.go b/v2/either/examples_create_test.go new file mode 100644 index 0000000..b8e2749 --- /dev/null +++ b/v2/either/examples_create_test.go @@ -0,0 +1,58 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package either + +import ( + "fmt" + + "github.com/IBM/fp-go/v2/errors" +) + +func ExampleEither_creation() { + // Build an Either + leftValue := Left[string](fmt.Errorf("some error")) + rightValue := Right[error]("value") + + // Build from a value + fromNillable := FromNillable[string](fmt.Errorf("value was nil")) + leftFromNil := fromNillable(nil) + value := "value" + rightFromPointer := fromNillable(&value) + + // some predicate + isEven := func(num int) bool { + return num%2 == 0 + } + fromEven := FromPredicate(isEven, errors.OnSome[int]("%d is an odd number")) + leftFromPred := fromEven(3) + rightFromPred := fromEven(4) + + fmt.Println(leftValue) + fmt.Println(rightValue) + fmt.Println(leftFromNil) + fmt.Println(IsRight(rightFromPointer)) + fmt.Println(leftFromPred) + fmt.Println(rightFromPred) + + // Output: + // Left[*errors.errorString](some error) + // Right[string](value) + // Left[*errors.errorString](value was nil) + // true + // Left[*errors.errorString](3 is an odd number) + // Right[int](4) + +} diff --git a/v2/either/examples_extract_test.go b/v2/either/examples_extract_test.go new file mode 100644 index 0000000..9b75fa0 --- /dev/null +++ b/v2/either/examples_extract_test.go @@ -0,0 +1,64 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package either + +import ( + "fmt" + + F "github.com/IBM/fp-go/v2/function" + N "github.com/IBM/fp-go/v2/number" +) + +func ExampleEither_extraction() { + leftValue := Left[int](fmt.Errorf("Division by Zero!")) + rightValue := Right[error](10) + + // Convert Either[E, A] to A with a default value + leftWithDefault := GetOrElse(F.Constant1[error](0))(leftValue) // 0 + rightWithDefault := GetOrElse(F.Constant1[error](0))(rightValue) // 10 + + // Apply a different function on Left(...)/Right(...) + doubleOrZero := Fold(F.Constant1[error](0), N.Mul(2)) // func(Either[error, int]) int + doubleFromLeft := doubleOrZero(leftValue) // 0 + doubleFromRight := doubleOrZero(rightValue) // 20 + + // Pro-tip: Fold is short for the following: + doubleOrZeroBis := F.Flow2( + Map[error](N.Mul(2)), + GetOrElse(F.Constant1[error](0)), + ) + doubleFromLeftBis := doubleOrZeroBis(leftValue) // 0 + doubleFromRightBis := doubleOrZeroBis(rightValue) // 20 + + fmt.Println(leftValue) + fmt.Println(rightValue) + fmt.Println(leftWithDefault) + fmt.Println(rightWithDefault) + fmt.Println(doubleFromLeft) + fmt.Println(doubleFromRight) + fmt.Println(doubleFromLeftBis) + fmt.Println(doubleFromRightBis) + + // Output: + // Left[*errors.errorString](Division by Zero!) + // Right[int](10) + // 0 + // 10 + // 0 + // 20 + // 0 + // 20 +} diff --git a/v2/either/exec/exec.go b/v2/either/exec/exec.go new file mode 100644 index 0000000..d64d0f8 --- /dev/null +++ b/v2/either/exec/exec.go @@ -0,0 +1,50 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +// Package exec provides utilities for executing system commands with Either-based error handling. +package exec + +import ( + "context" + + E "github.com/IBM/fp-go/v2/either" + "github.com/IBM/fp-go/v2/exec" + F "github.com/IBM/fp-go/v2/function" + GE "github.com/IBM/fp-go/v2/internal/exec" +) + +var ( + // Command executes a system command and returns the result as an Either. + // Use this version if the command does not produce any side effects, + // i.e., if the output is uniquely determined by the input. + // For commands with side effects, typically you'd use the IOEither version instead. + // + // Parameters (curried): + // - name: The command name/path + // - args: Command arguments + // - in: Input bytes to send to the command's stdin + // + // Returns Either[error, CommandOutput] containing the command's output or an error. + // + // Example: + // + // result := exec.Command("echo")( []string{"hello"})([]byte{}) + // // result is Right(CommandOutput{Stdout: "hello\n", ...}) + Command = F.Curry3(command) +) + +func command(name string, args []string, in []byte) E.Either[error, exec.CommandOutput] { + return E.TryCatchError(GE.Exec(context.Background(), name, args, in)) +} diff --git a/v2/either/functor.go b/v2/either/functor.go new file mode 100644 index 0000000..8e07fd0 --- /dev/null +++ b/v2/either/functor.go @@ -0,0 +1,39 @@ +// Copyright (c) 2024 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package either + +import ( + "github.com/IBM/fp-go/v2/internal/functor" +) + +type eitherFunctor[E, A, B any] struct{} + +func (o *eitherFunctor[E, A, B]) Map(f func(A) B) func(Either[E, A]) Either[E, B] { + return Map[E, A, B](f) +} + +// Functor implements the functoric operations for Either. +// A functor provides the Map operation that transforms values inside a context +// while preserving the structure. +// +// Example: +// +// f := either.Functor[error, int, string]() +// result := f.Map(strconv.Itoa)(either.Right[error](42)) +// // result is Right("42") +func Functor[E, A, B any]() functor.Functor[A, B, Either[E, A], Either[E, B]] { + return &eitherFunctor[E, A, B]{} +} diff --git a/v2/either/gen.go b/v2/either/gen.go new file mode 100644 index 0000000..340aee7 --- /dev/null +++ b/v2/either/gen.go @@ -0,0 +1,1204 @@ +// Code generated by go generate; DO NOT EDIT. +// This file was generated by robots at +// 2025-03-09 23:52:50.4396159 +0100 CET m=+0.001548701 +// +// This file contains generated functions for converting between Either and tuple-based functions. +// It provides Eitherize/Uneitherize functions for functions with 0-15 parameters, +// as well as SequenceT/SequenceTuple/TraverseTuple functions for working with tuples of Either values. + +// Code generated by go generate; DO NOT EDIT. +// This file was generated by robots at +// 2025-03-09 23:52:50.4396159 +0100 CET m=+0.001548701 + +package either + +import ( + A "github.com/IBM/fp-go/v2/internal/apply" + T "github.com/IBM/fp-go/v2/tuple" +) + +// Eitherize0 converts a function with 0 parameters returning a tuple into a function with 0 parameters returning an Either +// The inverse function is [Uneitherize0] +func Eitherize0[F ~func() (R, error), R any](f F) func() Either[error, R] { + return func() Either[error, R] { + return TryCatchError(f()) + } +} + +// Uneitherize0 converts a function with 0 parameters returning an Either into a function with 0 parameters returning a tuple +// The inverse function is [Eitherize0] +func Uneitherize0[F ~func() Either[error, R], R any](f F) func() (R, error) { + return func() (R, error) { + return UnwrapError(f()) + } +} + +// Eitherize1 converts a function with 1 parameters returning a tuple into a function with 1 parameters returning an Either +// The inverse function is [Uneitherize1] +func Eitherize1[F ~func(T0) (R, error), T0, R any](f F) func(T0) Either[error, R] { + return func(t0 T0) Either[error, R] { + return TryCatchError(f(t0)) + } +} + +// Uneitherize1 converts a function with 1 parameters returning an Either into a function with 1 parameters returning a tuple +// The inverse function is [Eitherize1] +func Uneitherize1[F ~func(T0) Either[error, R], T0, R any](f F) func(T0) (R, error) { + return func(t0 T0) (R, error) { + return UnwrapError(f(t0)) + } +} + +// SequenceT1 converts 1 parameters of [Either[E, T]] into a [Either[E, Tuple1]]. +func SequenceT1[E, T1 any](t1 Either[E, T1]) Either[E, T.Tuple1[T1]] { + return A.SequenceT1( + Map[E, T1, T.Tuple1[T1]], + t1, + ) +} + +// SequenceTuple1 converts a [Tuple1] of [Either[E, T]] into an [Either[E, Tuple1]]. +func SequenceTuple1[E, T1 any](t T.Tuple1[Either[E, T1]]) Either[E, T.Tuple1[T1]] { + return A.SequenceTuple1( + Map[E, T1, T.Tuple1[T1]], + t, + ) +} + +// TraverseTuple1 converts a [Tuple1] of [A] via transformation functions transforming [A] to [Either[E, A]] into a [Either[E, Tuple1]]. +func TraverseTuple1[F1 ~func(A1) Either[E, T1], E, A1, T1 any](f1 F1) func(T.Tuple1[A1]) Either[E, T.Tuple1[T1]] { + return func(t T.Tuple1[A1]) Either[E, T.Tuple1[T1]] { + return A.TraverseTuple1( + Map[E, T1, T.Tuple1[T1]], + f1, + t, + ) + } +} + +// Eitherize2 converts a function with 2 parameters returning a tuple into a function with 2 parameters returning an Either +// The inverse function is [Uneitherize2] +func Eitherize2[F ~func(T0, T1) (R, error), T0, T1, R any](f F) func(T0, T1) Either[error, R] { + return func(t0 T0, t1 T1) Either[error, R] { + return TryCatchError(f(t0, t1)) + } +} + +// Uneitherize2 converts a function with 2 parameters returning an Either into a function with 2 parameters returning a tuple +// The inverse function is [Eitherize2] +func Uneitherize2[F ~func(T0, T1) Either[error, R], T0, T1, R any](f F) func(T0, T1) (R, error) { + return func(t0 T0, t1 T1) (R, error) { + return UnwrapError(f(t0, t1)) + } +} + +// SequenceT2 converts 2 parameters of [Either[E, T]] into a [Either[E, Tuple2]]. +func SequenceT2[E, T1, T2 any](t1 Either[E, T1], t2 Either[E, T2]) Either[E, T.Tuple2[T1, T2]] { + return A.SequenceT2( + Map[E, T1, func(T2) T.Tuple2[T1, T2]], + Ap[T.Tuple2[T1, T2], E, T2], + t1, + t2, + ) +} + +// SequenceTuple2 converts a [Tuple2] of [Either[E, T]] into an [Either[E, Tuple2]]. +func SequenceTuple2[E, T1, T2 any](t T.Tuple2[Either[E, T1], Either[E, T2]]) Either[E, T.Tuple2[T1, T2]] { + return A.SequenceTuple2( + Map[E, T1, func(T2) T.Tuple2[T1, T2]], + Ap[T.Tuple2[T1, T2], E, T2], + t, + ) +} + +// TraverseTuple2 converts a [Tuple2] of [A] via transformation functions transforming [A] to [Either[E, A]] into a [Either[E, Tuple2]]. +func TraverseTuple2[F1 ~func(A1) Either[E, T1], F2 ~func(A2) Either[E, T2], E, A1, T1, A2, T2 any](f1 F1, f2 F2) func(T.Tuple2[A1, A2]) Either[E, T.Tuple2[T1, T2]] { + return func(t T.Tuple2[A1, A2]) Either[E, T.Tuple2[T1, T2]] { + return A.TraverseTuple2( + Map[E, T1, func(T2) T.Tuple2[T1, T2]], + Ap[T.Tuple2[T1, T2], E, T2], + f1, + f2, + t, + ) + } +} + +// Eitherize3 converts a function with 3 parameters returning a tuple into a function with 3 parameters returning an Either +// The inverse function is [Uneitherize3] +func Eitherize3[F ~func(T0, T1, T2) (R, error), T0, T1, T2, R any](f F) func(T0, T1, T2) Either[error, R] { + return func(t0 T0, t1 T1, t2 T2) Either[error, R] { + return TryCatchError(f(t0, t1, t2)) + } +} + +// Uneitherize3 converts a function with 3 parameters returning an Either into a function with 3 parameters returning a tuple +// The inverse function is [Eitherize3] +func Uneitherize3[F ~func(T0, T1, T2) Either[error, R], T0, T1, T2, R any](f F) func(T0, T1, T2) (R, error) { + return func(t0 T0, t1 T1, t2 T2) (R, error) { + return UnwrapError(f(t0, t1, t2)) + } +} + +// SequenceT3 converts 3 parameters of [Either[E, T]] into a [Either[E, Tuple3]]. +func SequenceT3[E, T1, T2, T3 any](t1 Either[E, T1], t2 Either[E, T2], t3 Either[E, T3]) Either[E, T.Tuple3[T1, T2, T3]] { + return A.SequenceT3( + Map[E, T1, func(T2) func(T3) T.Tuple3[T1, T2, T3]], + Ap[func(T3) T.Tuple3[T1, T2, T3], E, T2], + Ap[T.Tuple3[T1, T2, T3], E, T3], + t1, + t2, + t3, + ) +} + +// SequenceTuple3 converts a [Tuple3] of [Either[E, T]] into an [Either[E, Tuple3]]. +func SequenceTuple3[E, T1, T2, T3 any](t T.Tuple3[Either[E, T1], Either[E, T2], Either[E, T3]]) Either[E, T.Tuple3[T1, T2, T3]] { + return A.SequenceTuple3( + Map[E, T1, func(T2) func(T3) T.Tuple3[T1, T2, T3]], + Ap[func(T3) T.Tuple3[T1, T2, T3], E, T2], + Ap[T.Tuple3[T1, T2, T3], E, T3], + t, + ) +} + +// TraverseTuple3 converts a [Tuple3] of [A] via transformation functions transforming [A] to [Either[E, A]] into a [Either[E, Tuple3]]. +func TraverseTuple3[F1 ~func(A1) Either[E, T1], F2 ~func(A2) Either[E, T2], F3 ~func(A3) Either[E, T3], E, A1, T1, A2, T2, A3, T3 any](f1 F1, f2 F2, f3 F3) func(T.Tuple3[A1, A2, A3]) Either[E, T.Tuple3[T1, T2, T3]] { + return func(t T.Tuple3[A1, A2, A3]) Either[E, T.Tuple3[T1, T2, T3]] { + return A.TraverseTuple3( + Map[E, T1, func(T2) func(T3) T.Tuple3[T1, T2, T3]], + Ap[func(T3) T.Tuple3[T1, T2, T3], E, T2], + Ap[T.Tuple3[T1, T2, T3], E, T3], + f1, + f2, + f3, + t, + ) + } +} + +// Eitherize4 converts a function with 4 parameters returning a tuple into a function with 4 parameters returning an Either +// The inverse function is [Uneitherize4] +func Eitherize4[F ~func(T0, T1, T2, T3) (R, error), T0, T1, T2, T3, R any](f F) func(T0, T1, T2, T3) Either[error, R] { + return func(t0 T0, t1 T1, t2 T2, t3 T3) Either[error, R] { + return TryCatchError(f(t0, t1, t2, t3)) + } +} + +// Uneitherize4 converts a function with 4 parameters returning an Either into a function with 4 parameters returning a tuple +// The inverse function is [Eitherize4] +func Uneitherize4[F ~func(T0, T1, T2, T3) Either[error, R], T0, T1, T2, T3, R any](f F) func(T0, T1, T2, T3) (R, error) { + return func(t0 T0, t1 T1, t2 T2, t3 T3) (R, error) { + return UnwrapError(f(t0, t1, t2, t3)) + } +} + +// SequenceT4 converts 4 parameters of [Either[E, T]] into a [Either[E, Tuple4]]. +func SequenceT4[E, T1, T2, T3, T4 any](t1 Either[E, T1], t2 Either[E, T2], t3 Either[E, T3], t4 Either[E, T4]) Either[E, T.Tuple4[T1, T2, T3, T4]] { + return A.SequenceT4( + Map[E, T1, func(T2) func(T3) func(T4) T.Tuple4[T1, T2, T3, T4]], + Ap[func(T3) func(T4) T.Tuple4[T1, T2, T3, T4], E, T2], + Ap[func(T4) T.Tuple4[T1, T2, T3, T4], E, T3], + Ap[T.Tuple4[T1, T2, T3, T4], E, T4], + t1, + t2, + t3, + t4, + ) +} + +// SequenceTuple4 converts a [Tuple4] of [Either[E, T]] into an [Either[E, Tuple4]]. +func SequenceTuple4[E, T1, T2, T3, T4 any](t T.Tuple4[Either[E, T1], Either[E, T2], Either[E, T3], Either[E, T4]]) Either[E, T.Tuple4[T1, T2, T3, T4]] { + return A.SequenceTuple4( + Map[E, T1, func(T2) func(T3) func(T4) T.Tuple4[T1, T2, T3, T4]], + Ap[func(T3) func(T4) T.Tuple4[T1, T2, T3, T4], E, T2], + Ap[func(T4) T.Tuple4[T1, T2, T3, T4], E, T3], + Ap[T.Tuple4[T1, T2, T3, T4], E, T4], + t, + ) +} + +// TraverseTuple4 converts a [Tuple4] of [A] via transformation functions transforming [A] to [Either[E, A]] into a [Either[E, Tuple4]]. +func TraverseTuple4[F1 ~func(A1) Either[E, T1], F2 ~func(A2) Either[E, T2], F3 ~func(A3) Either[E, T3], F4 ~func(A4) Either[E, T4], E, A1, T1, A2, T2, A3, T3, A4, T4 any](f1 F1, f2 F2, f3 F3, f4 F4) func(T.Tuple4[A1, A2, A3, A4]) Either[E, T.Tuple4[T1, T2, T3, T4]] { + return func(t T.Tuple4[A1, A2, A3, A4]) Either[E, T.Tuple4[T1, T2, T3, T4]] { + return A.TraverseTuple4( + Map[E, T1, func(T2) func(T3) func(T4) T.Tuple4[T1, T2, T3, T4]], + Ap[func(T3) func(T4) T.Tuple4[T1, T2, T3, T4], E, T2], + Ap[func(T4) T.Tuple4[T1, T2, T3, T4], E, T3], + Ap[T.Tuple4[T1, T2, T3, T4], E, T4], + f1, + f2, + f3, + f4, + t, + ) + } +} + +// Eitherize5 converts a function with 5 parameters returning a tuple into a function with 5 parameters returning an Either +// The inverse function is [Uneitherize5] +func Eitherize5[F ~func(T0, T1, T2, T3, T4) (R, error), T0, T1, T2, T3, T4, R any](f F) func(T0, T1, T2, T3, T4) Either[error, R] { + return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4) Either[error, R] { + return TryCatchError(f(t0, t1, t2, t3, t4)) + } +} + +// Uneitherize5 converts a function with 5 parameters returning an Either into a function with 5 parameters returning a tuple +// The inverse function is [Eitherize5] +func Uneitherize5[F ~func(T0, T1, T2, T3, T4) Either[error, R], T0, T1, T2, T3, T4, R any](f F) func(T0, T1, T2, T3, T4) (R, error) { + return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4) (R, error) { + return UnwrapError(f(t0, t1, t2, t3, t4)) + } +} + +// SequenceT5 converts 5 parameters of [Either[E, T]] into a [Either[E, Tuple5]]. +func SequenceT5[E, T1, T2, T3, T4, T5 any](t1 Either[E, T1], t2 Either[E, T2], t3 Either[E, T3], t4 Either[E, T4], t5 Either[E, T5]) Either[E, T.Tuple5[T1, T2, T3, T4, T5]] { + return A.SequenceT5( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) T.Tuple5[T1, T2, T3, T4, T5]], + Ap[func(T3) func(T4) func(T5) T.Tuple5[T1, T2, T3, T4, T5], E, T2], + Ap[func(T4) func(T5) T.Tuple5[T1, T2, T3, T4, T5], E, T3], + Ap[func(T5) T.Tuple5[T1, T2, T3, T4, T5], E, T4], + Ap[T.Tuple5[T1, T2, T3, T4, T5], E, T5], + t1, + t2, + t3, + t4, + t5, + ) +} + +// SequenceTuple5 converts a [Tuple5] of [Either[E, T]] into an [Either[E, Tuple5]]. +func SequenceTuple5[E, T1, T2, T3, T4, T5 any](t T.Tuple5[Either[E, T1], Either[E, T2], Either[E, T3], Either[E, T4], Either[E, T5]]) Either[E, T.Tuple5[T1, T2, T3, T4, T5]] { + return A.SequenceTuple5( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) T.Tuple5[T1, T2, T3, T4, T5]], + Ap[func(T3) func(T4) func(T5) T.Tuple5[T1, T2, T3, T4, T5], E, T2], + Ap[func(T4) func(T5) T.Tuple5[T1, T2, T3, T4, T5], E, T3], + Ap[func(T5) T.Tuple5[T1, T2, T3, T4, T5], E, T4], + Ap[T.Tuple5[T1, T2, T3, T4, T5], E, T5], + t, + ) +} + +// TraverseTuple5 converts a [Tuple5] of [A] via transformation functions transforming [A] to [Either[E, A]] into a [Either[E, Tuple5]]. +func TraverseTuple5[F1 ~func(A1) Either[E, T1], F2 ~func(A2) Either[E, T2], F3 ~func(A3) Either[E, T3], F4 ~func(A4) Either[E, T4], F5 ~func(A5) Either[E, T5], E, A1, T1, A2, T2, A3, T3, A4, T4, A5, T5 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5) func(T.Tuple5[A1, A2, A3, A4, A5]) Either[E, T.Tuple5[T1, T2, T3, T4, T5]] { + return func(t T.Tuple5[A1, A2, A3, A4, A5]) Either[E, T.Tuple5[T1, T2, T3, T4, T5]] { + return A.TraverseTuple5( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) T.Tuple5[T1, T2, T3, T4, T5]], + Ap[func(T3) func(T4) func(T5) T.Tuple5[T1, T2, T3, T4, T5], E, T2], + Ap[func(T4) func(T5) T.Tuple5[T1, T2, T3, T4, T5], E, T3], + Ap[func(T5) T.Tuple5[T1, T2, T3, T4, T5], E, T4], + Ap[T.Tuple5[T1, T2, T3, T4, T5], E, T5], + f1, + f2, + f3, + f4, + f5, + t, + ) + } +} + +// Eitherize6 converts a function with 6 parameters returning a tuple into a function with 6 parameters returning an Either +// The inverse function is [Uneitherize6] +func Eitherize6[F ~func(T0, T1, T2, T3, T4, T5) (R, error), T0, T1, T2, T3, T4, T5, R any](f F) func(T0, T1, T2, T3, T4, T5) Either[error, R] { + return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5) Either[error, R] { + return TryCatchError(f(t0, t1, t2, t3, t4, t5)) + } +} + +// Uneitherize6 converts a function with 6 parameters returning an Either into a function with 6 parameters returning a tuple +// The inverse function is [Eitherize6] +func Uneitherize6[F ~func(T0, T1, T2, T3, T4, T5) Either[error, R], T0, T1, T2, T3, T4, T5, R any](f F) func(T0, T1, T2, T3, T4, T5) (R, error) { + return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5) (R, error) { + return UnwrapError(f(t0, t1, t2, t3, t4, t5)) + } +} + +// SequenceT6 converts 6 parameters of [Either[E, T]] into a [Either[E, Tuple6]]. +func SequenceT6[E, T1, T2, T3, T4, T5, T6 any](t1 Either[E, T1], t2 Either[E, T2], t3 Either[E, T3], t4 Either[E, T4], t5 Either[E, T5], t6 Either[E, T6]) Either[E, T.Tuple6[T1, T2, T3, T4, T5, T6]] { + return A.SequenceT6( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6]], + Ap[func(T3) func(T4) func(T5) func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6], E, T2], + Ap[func(T4) func(T5) func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6], E, T3], + Ap[func(T5) func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6], E, T4], + Ap[func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6], E, T5], + Ap[T.Tuple6[T1, T2, T3, T4, T5, T6], E, T6], + t1, + t2, + t3, + t4, + t5, + t6, + ) +} + +// SequenceTuple6 converts a [Tuple6] of [Either[E, T]] into an [Either[E, Tuple6]]. +func SequenceTuple6[E, T1, T2, T3, T4, T5, T6 any](t T.Tuple6[Either[E, T1], Either[E, T2], Either[E, T3], Either[E, T4], Either[E, T5], Either[E, T6]]) Either[E, T.Tuple6[T1, T2, T3, T4, T5, T6]] { + return A.SequenceTuple6( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6]], + Ap[func(T3) func(T4) func(T5) func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6], E, T2], + Ap[func(T4) func(T5) func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6], E, T3], + Ap[func(T5) func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6], E, T4], + Ap[func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6], E, T5], + Ap[T.Tuple6[T1, T2, T3, T4, T5, T6], E, T6], + t, + ) +} + +// TraverseTuple6 converts a [Tuple6] of [A] via transformation functions transforming [A] to [Either[E, A]] into a [Either[E, Tuple6]]. +func TraverseTuple6[F1 ~func(A1) Either[E, T1], F2 ~func(A2) Either[E, T2], F3 ~func(A3) Either[E, T3], F4 ~func(A4) Either[E, T4], F5 ~func(A5) Either[E, T5], F6 ~func(A6) Either[E, T6], E, A1, T1, A2, T2, A3, T3, A4, T4, A5, T5, A6, T6 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6) func(T.Tuple6[A1, A2, A3, A4, A5, A6]) Either[E, T.Tuple6[T1, T2, T3, T4, T5, T6]] { + return func(t T.Tuple6[A1, A2, A3, A4, A5, A6]) Either[E, T.Tuple6[T1, T2, T3, T4, T5, T6]] { + return A.TraverseTuple6( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6]], + Ap[func(T3) func(T4) func(T5) func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6], E, T2], + Ap[func(T4) func(T5) func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6], E, T3], + Ap[func(T5) func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6], E, T4], + Ap[func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6], E, T5], + Ap[T.Tuple6[T1, T2, T3, T4, T5, T6], E, T6], + f1, + f2, + f3, + f4, + f5, + f6, + t, + ) + } +} + +// Eitherize7 converts a function with 7 parameters returning a tuple into a function with 7 parameters returning an Either +// The inverse function is [Uneitherize7] +func Eitherize7[F ~func(T0, T1, T2, T3, T4, T5, T6) (R, error), T0, T1, T2, T3, T4, T5, T6, R any](f F) func(T0, T1, T2, T3, T4, T5, T6) Either[error, R] { + return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6) Either[error, R] { + return TryCatchError(f(t0, t1, t2, t3, t4, t5, t6)) + } +} + +// Uneitherize7 converts a function with 7 parameters returning an Either into a function with 7 parameters returning a tuple +// The inverse function is [Eitherize7] +func Uneitherize7[F ~func(T0, T1, T2, T3, T4, T5, T6) Either[error, R], T0, T1, T2, T3, T4, T5, T6, R any](f F) func(T0, T1, T2, T3, T4, T5, T6) (R, error) { + return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6) (R, error) { + return UnwrapError(f(t0, t1, t2, t3, t4, t5, t6)) + } +} + +// SequenceT7 converts 7 parameters of [Either[E, T]] into a [Either[E, Tuple7]]. +func SequenceT7[E, T1, T2, T3, T4, T5, T6, T7 any](t1 Either[E, T1], t2 Either[E, T2], t3 Either[E, T3], t4 Either[E, T4], t5 Either[E, T5], t6 Either[E, T6], t7 Either[E, T7]) Either[E, T.Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return A.SequenceT7( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T2], + Ap[func(T4) func(T5) func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T3], + Ap[func(T5) func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T4], + Ap[func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T5], + Ap[func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T6], + Ap[T.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T7], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + ) +} + +// SequenceTuple7 converts a [Tuple7] of [Either[E, T]] into an [Either[E, Tuple7]]. +func SequenceTuple7[E, T1, T2, T3, T4, T5, T6, T7 any](t T.Tuple7[Either[E, T1], Either[E, T2], Either[E, T3], Either[E, T4], Either[E, T5], Either[E, T6], Either[E, T7]]) Either[E, T.Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return A.SequenceTuple7( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T2], + Ap[func(T4) func(T5) func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T3], + Ap[func(T5) func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T4], + Ap[func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T5], + Ap[func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T6], + Ap[T.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T7], + t, + ) +} + +// TraverseTuple7 converts a [Tuple7] of [A] via transformation functions transforming [A] to [Either[E, A]] into a [Either[E, Tuple7]]. +func TraverseTuple7[F1 ~func(A1) Either[E, T1], F2 ~func(A2) Either[E, T2], F3 ~func(A3) Either[E, T3], F4 ~func(A4) Either[E, T4], F5 ~func(A5) Either[E, T5], F6 ~func(A6) Either[E, T6], F7 ~func(A7) Either[E, T7], E, A1, T1, A2, T2, A3, T3, A4, T4, A5, T5, A6, T6, A7, T7 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7) func(T.Tuple7[A1, A2, A3, A4, A5, A6, A7]) Either[E, T.Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return func(t T.Tuple7[A1, A2, A3, A4, A5, A6, A7]) Either[E, T.Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return A.TraverseTuple7( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T2], + Ap[func(T4) func(T5) func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T3], + Ap[func(T5) func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T4], + Ap[func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T5], + Ap[func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T6], + Ap[T.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T7], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + t, + ) + } +} + +// Eitherize8 converts a function with 8 parameters returning a tuple into a function with 8 parameters returning an Either +// The inverse function is [Uneitherize8] +func Eitherize8[F ~func(T0, T1, T2, T3, T4, T5, T6, T7) (R, error), T0, T1, T2, T3, T4, T5, T6, T7, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7) Either[error, R] { + return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7) Either[error, R] { + return TryCatchError(f(t0, t1, t2, t3, t4, t5, t6, t7)) + } +} + +// Uneitherize8 converts a function with 8 parameters returning an Either into a function with 8 parameters returning a tuple +// The inverse function is [Eitherize8] +func Uneitherize8[F ~func(T0, T1, T2, T3, T4, T5, T6, T7) Either[error, R], T0, T1, T2, T3, T4, T5, T6, T7, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7) (R, error) { + return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7) (R, error) { + return UnwrapError(f(t0, t1, t2, t3, t4, t5, t6, t7)) + } +} + +// SequenceT8 converts 8 parameters of [Either[E, T]] into a [Either[E, Tuple8]]. +func SequenceT8[E, T1, T2, T3, T4, T5, T6, T7, T8 any](t1 Either[E, T1], t2 Either[E, T2], t3 Either[E, T3], t4 Either[E, T4], t5 Either[E, T5], t6 Either[E, T6], t7 Either[E, T7], t8 Either[E, T8]) Either[E, T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return A.SequenceT8( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T3], + Ap[func(T5) func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T4], + Ap[func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T5], + Ap[func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T6], + Ap[func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T7], + Ap[T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T8], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + t8, + ) +} + +// SequenceTuple8 converts a [Tuple8] of [Either[E, T]] into an [Either[E, Tuple8]]. +func SequenceTuple8[E, T1, T2, T3, T4, T5, T6, T7, T8 any](t T.Tuple8[Either[E, T1], Either[E, T2], Either[E, T3], Either[E, T4], Either[E, T5], Either[E, T6], Either[E, T7], Either[E, T8]]) Either[E, T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return A.SequenceTuple8( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T3], + Ap[func(T5) func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T4], + Ap[func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T5], + Ap[func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T6], + Ap[func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T7], + Ap[T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T8], + t, + ) +} + +// TraverseTuple8 converts a [Tuple8] of [A] via transformation functions transforming [A] to [Either[E, A]] into a [Either[E, Tuple8]]. +func TraverseTuple8[F1 ~func(A1) Either[E, T1], F2 ~func(A2) Either[E, T2], F3 ~func(A3) Either[E, T3], F4 ~func(A4) Either[E, T4], F5 ~func(A5) Either[E, T5], F6 ~func(A6) Either[E, T6], F7 ~func(A7) Either[E, T7], F8 ~func(A8) Either[E, T8], E, A1, T1, A2, T2, A3, T3, A4, T4, A5, T5, A6, T6, A7, T7, A8, T8 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8) func(T.Tuple8[A1, A2, A3, A4, A5, A6, A7, A8]) Either[E, T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return func(t T.Tuple8[A1, A2, A3, A4, A5, A6, A7, A8]) Either[E, T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return A.TraverseTuple8( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T3], + Ap[func(T5) func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T4], + Ap[func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T5], + Ap[func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T6], + Ap[func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T7], + Ap[T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T8], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + f8, + t, + ) + } +} + +// Eitherize9 converts a function with 9 parameters returning a tuple into a function with 9 parameters returning an Either +// The inverse function is [Uneitherize9] +func Eitherize9[F ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8) (R, error), T0, T1, T2, T3, T4, T5, T6, T7, T8, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7, T8) Either[error, R] { + return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8) Either[error, R] { + return TryCatchError(f(t0, t1, t2, t3, t4, t5, t6, t7, t8)) + } +} + +// Uneitherize9 converts a function with 9 parameters returning an Either into a function with 9 parameters returning a tuple +// The inverse function is [Eitherize9] +func Uneitherize9[F ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8) Either[error, R], T0, T1, T2, T3, T4, T5, T6, T7, T8, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7, T8) (R, error) { + return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8) (R, error) { + return UnwrapError(f(t0, t1, t2, t3, t4, t5, t6, t7, t8)) + } +} + +// SequenceT9 converts 9 parameters of [Either[E, T]] into a [Either[E, Tuple9]]. +func SequenceT9[E, T1, T2, T3, T4, T5, T6, T7, T8, T9 any](t1 Either[E, T1], t2 Either[E, T2], t3 Either[E, T3], t4 Either[E, T4], t5 Either[E, T5], t6 Either[E, T6], t7 Either[E, T7], t8 Either[E, T8], t9 Either[E, T9]) Either[E, T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return A.SequenceT9( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T3], + Ap[func(T5) func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T4], + Ap[func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T5], + Ap[func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T6], + Ap[func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T7], + Ap[func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T8], + Ap[T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T9], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + t8, + t9, + ) +} + +// SequenceTuple9 converts a [Tuple9] of [Either[E, T]] into an [Either[E, Tuple9]]. +func SequenceTuple9[E, T1, T2, T3, T4, T5, T6, T7, T8, T9 any](t T.Tuple9[Either[E, T1], Either[E, T2], Either[E, T3], Either[E, T4], Either[E, T5], Either[E, T6], Either[E, T7], Either[E, T8], Either[E, T9]]) Either[E, T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return A.SequenceTuple9( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T3], + Ap[func(T5) func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T4], + Ap[func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T5], + Ap[func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T6], + Ap[func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T7], + Ap[func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T8], + Ap[T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T9], + t, + ) +} + +// TraverseTuple9 converts a [Tuple9] of [A] via transformation functions transforming [A] to [Either[E, A]] into a [Either[E, Tuple9]]. +func TraverseTuple9[F1 ~func(A1) Either[E, T1], F2 ~func(A2) Either[E, T2], F3 ~func(A3) Either[E, T3], F4 ~func(A4) Either[E, T4], F5 ~func(A5) Either[E, T5], F6 ~func(A6) Either[E, T6], F7 ~func(A7) Either[E, T7], F8 ~func(A8) Either[E, T8], F9 ~func(A9) Either[E, T9], E, A1, T1, A2, T2, A3, T3, A4, T4, A5, T5, A6, T6, A7, T7, A8, T8, A9, T9 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9) func(T.Tuple9[A1, A2, A3, A4, A5, A6, A7, A8, A9]) Either[E, T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return func(t T.Tuple9[A1, A2, A3, A4, A5, A6, A7, A8, A9]) Either[E, T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return A.TraverseTuple9( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T3], + Ap[func(T5) func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T4], + Ap[func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T5], + Ap[func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T6], + Ap[func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T7], + Ap[func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T8], + Ap[T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T9], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + f8, + f9, + t, + ) + } +} + +// Eitherize10 converts a function with 10 parameters returning a tuple into a function with 10 parameters returning an Either +// The inverse function is [Uneitherize10] +func Eitherize10[F ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9) (R, error), T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9) Either[error, R] { + return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9) Either[error, R] { + return TryCatchError(f(t0, t1, t2, t3, t4, t5, t6, t7, t8, t9)) + } +} + +// Uneitherize10 converts a function with 10 parameters returning an Either into a function with 10 parameters returning a tuple +// The inverse function is [Eitherize10] +func Uneitherize10[F ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9) Either[error, R], T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9) (R, error) { + return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9) (R, error) { + return UnwrapError(f(t0, t1, t2, t3, t4, t5, t6, t7, t8, t9)) + } +} + +// SequenceT10 converts 10 parameters of [Either[E, T]] into a [Either[E, Tuple10]]. +func SequenceT10[E, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any](t1 Either[E, T1], t2 Either[E, T2], t3 Either[E, T3], t4 Either[E, T4], t5 Either[E, T5], t6 Either[E, T6], t7 Either[E, T7], t8 Either[E, T8], t9 Either[E, T9], t10 Either[E, T10]) Either[E, T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return A.SequenceT10( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T3], + Ap[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T4], + Ap[func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T5], + Ap[func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T6], + Ap[func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T7], + Ap[func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T8], + Ap[func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T9], + Ap[T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T10], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + t8, + t9, + t10, + ) +} + +// SequenceTuple10 converts a [Tuple10] of [Either[E, T]] into an [Either[E, Tuple10]]. +func SequenceTuple10[E, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any](t T.Tuple10[Either[E, T1], Either[E, T2], Either[E, T3], Either[E, T4], Either[E, T5], Either[E, T6], Either[E, T7], Either[E, T8], Either[E, T9], Either[E, T10]]) Either[E, T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return A.SequenceTuple10( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T3], + Ap[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T4], + Ap[func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T5], + Ap[func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T6], + Ap[func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T7], + Ap[func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T8], + Ap[func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T9], + Ap[T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T10], + t, + ) +} + +// TraverseTuple10 converts a [Tuple10] of [A] via transformation functions transforming [A] to [Either[E, A]] into a [Either[E, Tuple10]]. +func TraverseTuple10[F1 ~func(A1) Either[E, T1], F2 ~func(A2) Either[E, T2], F3 ~func(A3) Either[E, T3], F4 ~func(A4) Either[E, T4], F5 ~func(A5) Either[E, T5], F6 ~func(A6) Either[E, T6], F7 ~func(A7) Either[E, T7], F8 ~func(A8) Either[E, T8], F9 ~func(A9) Either[E, T9], F10 ~func(A10) Either[E, T10], E, A1, T1, A2, T2, A3, T3, A4, T4, A5, T5, A6, T6, A7, T7, A8, T8, A9, T9, A10, T10 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10) func(T.Tuple10[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10]) Either[E, T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return func(t T.Tuple10[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10]) Either[E, T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return A.TraverseTuple10( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T3], + Ap[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T4], + Ap[func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T5], + Ap[func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T6], + Ap[func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T7], + Ap[func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T8], + Ap[func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T9], + Ap[T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T10], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + f8, + f9, + f10, + t, + ) + } +} + +// Eitherize11 converts a function with 11 parameters returning a tuple into a function with 11 parameters returning an Either +// The inverse function is [Uneitherize11] +func Eitherize11[F ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) (R, error), T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) Either[error, R] { + return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10) Either[error, R] { + return TryCatchError(f(t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10)) + } +} + +// Uneitherize11 converts a function with 11 parameters returning an Either into a function with 11 parameters returning a tuple +// The inverse function is [Eitherize11] +func Uneitherize11[F ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) Either[error, R], T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) (R, error) { + return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10) (R, error) { + return UnwrapError(f(t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10)) + } +} + +// SequenceT11 converts 11 parameters of [Either[E, T]] into a [Either[E, Tuple11]]. +func SequenceT11[E, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11 any](t1 Either[E, T1], t2 Either[E, T2], t3 Either[E, T3], t4 Either[E, T4], t5 Either[E, T5], t6 Either[E, T6], t7 Either[E, T7], t8 Either[E, T8], t9 Either[E, T9], t10 Either[E, T10], t11 Either[E, T11]) Either[E, T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]] { + return A.SequenceT11( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11], E, T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11], E, T3], + Ap[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11], E, T4], + Ap[func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11], E, T5], + Ap[func(T7) func(T8) func(T9) func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11], E, T6], + Ap[func(T8) func(T9) func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11], E, T7], + Ap[func(T9) func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11], E, T8], + Ap[func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11], E, T9], + Ap[func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11], E, T10], + Ap[T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11], E, T11], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + t8, + t9, + t10, + t11, + ) +} + +// SequenceTuple11 converts a [Tuple11] of [Either[E, T]] into an [Either[E, Tuple11]]. +func SequenceTuple11[E, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11 any](t T.Tuple11[Either[E, T1], Either[E, T2], Either[E, T3], Either[E, T4], Either[E, T5], Either[E, T6], Either[E, T7], Either[E, T8], Either[E, T9], Either[E, T10], Either[E, T11]]) Either[E, T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]] { + return A.SequenceTuple11( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11], E, T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11], E, T3], + Ap[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11], E, T4], + Ap[func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11], E, T5], + Ap[func(T7) func(T8) func(T9) func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11], E, T6], + Ap[func(T8) func(T9) func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11], E, T7], + Ap[func(T9) func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11], E, T8], + Ap[func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11], E, T9], + Ap[func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11], E, T10], + Ap[T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11], E, T11], + t, + ) +} + +// TraverseTuple11 converts a [Tuple11] of [A] via transformation functions transforming [A] to [Either[E, A]] into a [Either[E, Tuple11]]. +func TraverseTuple11[F1 ~func(A1) Either[E, T1], F2 ~func(A2) Either[E, T2], F3 ~func(A3) Either[E, T3], F4 ~func(A4) Either[E, T4], F5 ~func(A5) Either[E, T5], F6 ~func(A6) Either[E, T6], F7 ~func(A7) Either[E, T7], F8 ~func(A8) Either[E, T8], F9 ~func(A9) Either[E, T9], F10 ~func(A10) Either[E, T10], F11 ~func(A11) Either[E, T11], E, A1, T1, A2, T2, A3, T3, A4, T4, A5, T5, A6, T6, A7, T7, A8, T8, A9, T9, A10, T10, A11, T11 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10, f11 F11) func(T.Tuple11[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11]) Either[E, T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]] { + return func(t T.Tuple11[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11]) Either[E, T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]] { + return A.TraverseTuple11( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11], E, T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11], E, T3], + Ap[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11], E, T4], + Ap[func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11], E, T5], + Ap[func(T7) func(T8) func(T9) func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11], E, T6], + Ap[func(T8) func(T9) func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11], E, T7], + Ap[func(T9) func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11], E, T8], + Ap[func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11], E, T9], + Ap[func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11], E, T10], + Ap[T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11], E, T11], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + f8, + f9, + f10, + f11, + t, + ) + } +} + +// Eitherize12 converts a function with 12 parameters returning a tuple into a function with 12 parameters returning an Either +// The inverse function is [Uneitherize12] +func Eitherize12[F ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11) (R, error), T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11) Either[error, R] { + return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11) Either[error, R] { + return TryCatchError(f(t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11)) + } +} + +// Uneitherize12 converts a function with 12 parameters returning an Either into a function with 12 parameters returning a tuple +// The inverse function is [Eitherize12] +func Uneitherize12[F ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11) Either[error, R], T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11) (R, error) { + return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11) (R, error) { + return UnwrapError(f(t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11)) + } +} + +// SequenceT12 converts 12 parameters of [Either[E, T]] into a [Either[E, Tuple12]]. +func SequenceT12[E, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12 any](t1 Either[E, T1], t2 Either[E, T2], t3 Either[E, T3], t4 Either[E, T4], t5 Either[E, T5], t6 Either[E, T6], t7 Either[E, T7], t8 Either[E, T8], t9 Either[E, T9], t10 Either[E, T10], t11 Either[E, T11], t12 Either[E, T12]) Either[E, T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]] { + return A.SequenceT12( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12], E, T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12], E, T3], + Ap[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12], E, T4], + Ap[func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12], E, T5], + Ap[func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12], E, T6], + Ap[func(T8) func(T9) func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12], E, T7], + Ap[func(T9) func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12], E, T8], + Ap[func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12], E, T9], + Ap[func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12], E, T10], + Ap[func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12], E, T11], + Ap[T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12], E, T12], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + t8, + t9, + t10, + t11, + t12, + ) +} + +// SequenceTuple12 converts a [Tuple12] of [Either[E, T]] into an [Either[E, Tuple12]]. +func SequenceTuple12[E, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12 any](t T.Tuple12[Either[E, T1], Either[E, T2], Either[E, T3], Either[E, T4], Either[E, T5], Either[E, T6], Either[E, T7], Either[E, T8], Either[E, T9], Either[E, T10], Either[E, T11], Either[E, T12]]) Either[E, T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]] { + return A.SequenceTuple12( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12], E, T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12], E, T3], + Ap[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12], E, T4], + Ap[func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12], E, T5], + Ap[func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12], E, T6], + Ap[func(T8) func(T9) func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12], E, T7], + Ap[func(T9) func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12], E, T8], + Ap[func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12], E, T9], + Ap[func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12], E, T10], + Ap[func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12], E, T11], + Ap[T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12], E, T12], + t, + ) +} + +// TraverseTuple12 converts a [Tuple12] of [A] via transformation functions transforming [A] to [Either[E, A]] into a [Either[E, Tuple12]]. +func TraverseTuple12[F1 ~func(A1) Either[E, T1], F2 ~func(A2) Either[E, T2], F3 ~func(A3) Either[E, T3], F4 ~func(A4) Either[E, T4], F5 ~func(A5) Either[E, T5], F6 ~func(A6) Either[E, T6], F7 ~func(A7) Either[E, T7], F8 ~func(A8) Either[E, T8], F9 ~func(A9) Either[E, T9], F10 ~func(A10) Either[E, T10], F11 ~func(A11) Either[E, T11], F12 ~func(A12) Either[E, T12], E, A1, T1, A2, T2, A3, T3, A4, T4, A5, T5, A6, T6, A7, T7, A8, T8, A9, T9, A10, T10, A11, T11, A12, T12 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10, f11 F11, f12 F12) func(T.Tuple12[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12]) Either[E, T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]] { + return func(t T.Tuple12[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12]) Either[E, T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]] { + return A.TraverseTuple12( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12], E, T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12], E, T3], + Ap[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12], E, T4], + Ap[func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12], E, T5], + Ap[func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12], E, T6], + Ap[func(T8) func(T9) func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12], E, T7], + Ap[func(T9) func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12], E, T8], + Ap[func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12], E, T9], + Ap[func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12], E, T10], + Ap[func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12], E, T11], + Ap[T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12], E, T12], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + f8, + f9, + f10, + f11, + f12, + t, + ) + } +} + +// Eitherize13 converts a function with 13 parameters returning a tuple into a function with 13 parameters returning an Either +// The inverse function is [Uneitherize13] +func Eitherize13[F ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12) (R, error), T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12) Either[error, R] { + return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11, t12 T12) Either[error, R] { + return TryCatchError(f(t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12)) + } +} + +// Uneitherize13 converts a function with 13 parameters returning an Either into a function with 13 parameters returning a tuple +// The inverse function is [Eitherize13] +func Uneitherize13[F ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12) Either[error, R], T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12) (R, error) { + return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11, t12 T12) (R, error) { + return UnwrapError(f(t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12)) + } +} + +// SequenceT13 converts 13 parameters of [Either[E, T]] into a [Either[E, Tuple13]]. +func SequenceT13[E, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13 any](t1 Either[E, T1], t2 Either[E, T2], t3 Either[E, T3], t4 Either[E, T4], t5 Either[E, T5], t6 Either[E, T6], t7 Either[E, T7], t8 Either[E, T8], t9 Either[E, T9], t10 Either[E, T10], t11 Either[E, T11], t12 Either[E, T12], t13 Either[E, T13]) Either[E, T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]] { + return A.SequenceT13( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13], E, T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13], E, T3], + Ap[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13], E, T4], + Ap[func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13], E, T5], + Ap[func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13], E, T6], + Ap[func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13], E, T7], + Ap[func(T9) func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13], E, T8], + Ap[func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13], E, T9], + Ap[func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13], E, T10], + Ap[func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13], E, T11], + Ap[func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13], E, T12], + Ap[T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13], E, T13], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + t8, + t9, + t10, + t11, + t12, + t13, + ) +} + +// SequenceTuple13 converts a [Tuple13] of [Either[E, T]] into an [Either[E, Tuple13]]. +func SequenceTuple13[E, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13 any](t T.Tuple13[Either[E, T1], Either[E, T2], Either[E, T3], Either[E, T4], Either[E, T5], Either[E, T6], Either[E, T7], Either[E, T8], Either[E, T9], Either[E, T10], Either[E, T11], Either[E, T12], Either[E, T13]]) Either[E, T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]] { + return A.SequenceTuple13( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13], E, T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13], E, T3], + Ap[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13], E, T4], + Ap[func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13], E, T5], + Ap[func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13], E, T6], + Ap[func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13], E, T7], + Ap[func(T9) func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13], E, T8], + Ap[func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13], E, T9], + Ap[func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13], E, T10], + Ap[func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13], E, T11], + Ap[func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13], E, T12], + Ap[T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13], E, T13], + t, + ) +} + +// TraverseTuple13 converts a [Tuple13] of [A] via transformation functions transforming [A] to [Either[E, A]] into a [Either[E, Tuple13]]. +func TraverseTuple13[F1 ~func(A1) Either[E, T1], F2 ~func(A2) Either[E, T2], F3 ~func(A3) Either[E, T3], F4 ~func(A4) Either[E, T4], F5 ~func(A5) Either[E, T5], F6 ~func(A6) Either[E, T6], F7 ~func(A7) Either[E, T7], F8 ~func(A8) Either[E, T8], F9 ~func(A9) Either[E, T9], F10 ~func(A10) Either[E, T10], F11 ~func(A11) Either[E, T11], F12 ~func(A12) Either[E, T12], F13 ~func(A13) Either[E, T13], E, A1, T1, A2, T2, A3, T3, A4, T4, A5, T5, A6, T6, A7, T7, A8, T8, A9, T9, A10, T10, A11, T11, A12, T12, A13, T13 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10, f11 F11, f12 F12, f13 F13) func(T.Tuple13[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13]) Either[E, T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]] { + return func(t T.Tuple13[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13]) Either[E, T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]] { + return A.TraverseTuple13( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13], E, T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13], E, T3], + Ap[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13], E, T4], + Ap[func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13], E, T5], + Ap[func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13], E, T6], + Ap[func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13], E, T7], + Ap[func(T9) func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13], E, T8], + Ap[func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13], E, T9], + Ap[func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13], E, T10], + Ap[func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13], E, T11], + Ap[func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13], E, T12], + Ap[T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13], E, T13], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + f8, + f9, + f10, + f11, + f12, + f13, + t, + ) + } +} + +// Eitherize14 converts a function with 14 parameters returning a tuple into a function with 14 parameters returning an Either +// The inverse function is [Uneitherize14] +func Eitherize14[F ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13) (R, error), T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13) Either[error, R] { + return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11, t12 T12, t13 T13) Either[error, R] { + return TryCatchError(f(t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13)) + } +} + +// Uneitherize14 converts a function with 14 parameters returning an Either into a function with 14 parameters returning a tuple +// The inverse function is [Eitherize14] +func Uneitherize14[F ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13) Either[error, R], T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13) (R, error) { + return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11, t12 T12, t13 T13) (R, error) { + return UnwrapError(f(t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13)) + } +} + +// SequenceT14 converts 14 parameters of [Either[E, T]] into a [Either[E, Tuple14]]. +func SequenceT14[E, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14 any](t1 Either[E, T1], t2 Either[E, T2], t3 Either[E, T3], t4 Either[E, T4], t5 Either[E, T5], t6 Either[E, T6], t7 Either[E, T7], t8 Either[E, T8], t9 Either[E, T9], t10 Either[E, T10], t11 Either[E, T11], t12 Either[E, T12], t13 Either[E, T13], t14 Either[E, T14]) Either[E, T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]] { + return A.SequenceT14( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14], E, T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14], E, T3], + Ap[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14], E, T4], + Ap[func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14], E, T5], + Ap[func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14], E, T6], + Ap[func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14], E, T7], + Ap[func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14], E, T8], + Ap[func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14], E, T9], + Ap[func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14], E, T10], + Ap[func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14], E, T11], + Ap[func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14], E, T12], + Ap[func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14], E, T13], + Ap[T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14], E, T14], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + t8, + t9, + t10, + t11, + t12, + t13, + t14, + ) +} + +// SequenceTuple14 converts a [Tuple14] of [Either[E, T]] into an [Either[E, Tuple14]]. +func SequenceTuple14[E, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14 any](t T.Tuple14[Either[E, T1], Either[E, T2], Either[E, T3], Either[E, T4], Either[E, T5], Either[E, T6], Either[E, T7], Either[E, T8], Either[E, T9], Either[E, T10], Either[E, T11], Either[E, T12], Either[E, T13], Either[E, T14]]) Either[E, T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]] { + return A.SequenceTuple14( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14], E, T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14], E, T3], + Ap[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14], E, T4], + Ap[func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14], E, T5], + Ap[func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14], E, T6], + Ap[func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14], E, T7], + Ap[func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14], E, T8], + Ap[func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14], E, T9], + Ap[func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14], E, T10], + Ap[func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14], E, T11], + Ap[func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14], E, T12], + Ap[func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14], E, T13], + Ap[T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14], E, T14], + t, + ) +} + +// TraverseTuple14 converts a [Tuple14] of [A] via transformation functions transforming [A] to [Either[E, A]] into a [Either[E, Tuple14]]. +func TraverseTuple14[F1 ~func(A1) Either[E, T1], F2 ~func(A2) Either[E, T2], F3 ~func(A3) Either[E, T3], F4 ~func(A4) Either[E, T4], F5 ~func(A5) Either[E, T5], F6 ~func(A6) Either[E, T6], F7 ~func(A7) Either[E, T7], F8 ~func(A8) Either[E, T8], F9 ~func(A9) Either[E, T9], F10 ~func(A10) Either[E, T10], F11 ~func(A11) Either[E, T11], F12 ~func(A12) Either[E, T12], F13 ~func(A13) Either[E, T13], F14 ~func(A14) Either[E, T14], E, A1, T1, A2, T2, A3, T3, A4, T4, A5, T5, A6, T6, A7, T7, A8, T8, A9, T9, A10, T10, A11, T11, A12, T12, A13, T13, A14, T14 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10, f11 F11, f12 F12, f13 F13, f14 F14) func(T.Tuple14[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14]) Either[E, T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]] { + return func(t T.Tuple14[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14]) Either[E, T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]] { + return A.TraverseTuple14( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14], E, T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14], E, T3], + Ap[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14], E, T4], + Ap[func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14], E, T5], + Ap[func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14], E, T6], + Ap[func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14], E, T7], + Ap[func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14], E, T8], + Ap[func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14], E, T9], + Ap[func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14], E, T10], + Ap[func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14], E, T11], + Ap[func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14], E, T12], + Ap[func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14], E, T13], + Ap[T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14], E, T14], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + f8, + f9, + f10, + f11, + f12, + f13, + f14, + t, + ) + } +} + +// Eitherize15 converts a function with 15 parameters returning a tuple into a function with 15 parameters returning an Either +// The inverse function is [Uneitherize15] +func Eitherize15[F ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14) (R, error), T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14) Either[error, R] { + return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11, t12 T12, t13 T13, t14 T14) Either[error, R] { + return TryCatchError(f(t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14)) + } +} + +// Uneitherize15 converts a function with 15 parameters returning an Either into a function with 15 parameters returning a tuple +// The inverse function is [Eitherize15] +func Uneitherize15[F ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14) Either[error, R], T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14) (R, error) { + return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11, t12 T12, t13 T13, t14 T14) (R, error) { + return UnwrapError(f(t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14)) + } +} + +// SequenceT15 converts 15 parameters of [Either[E, T]] into a [Either[E, Tuple15]]. +func SequenceT15[E, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15 any](t1 Either[E, T1], t2 Either[E, T2], t3 Either[E, T3], t4 Either[E, T4], t5 Either[E, T5], t6 Either[E, T6], t7 Either[E, T7], t8 Either[E, T8], t9 Either[E, T9], t10 Either[E, T10], t11 Either[E, T11], t12 Either[E, T12], t13 Either[E, T13], t14 Either[E, T14], t15 Either[E, T15]) Either[E, T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] { + return A.SequenceT15( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15], E, T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15], E, T3], + Ap[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15], E, T4], + Ap[func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15], E, T5], + Ap[func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15], E, T6], + Ap[func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15], E, T7], + Ap[func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15], E, T8], + Ap[func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15], E, T9], + Ap[func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15], E, T10], + Ap[func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15], E, T11], + Ap[func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15], E, T12], + Ap[func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15], E, T13], + Ap[func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15], E, T14], + Ap[T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15], E, T15], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + t8, + t9, + t10, + t11, + t12, + t13, + t14, + t15, + ) +} + +// SequenceTuple15 converts a [Tuple15] of [Either[E, T]] into an [Either[E, Tuple15]]. +func SequenceTuple15[E, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15 any](t T.Tuple15[Either[E, T1], Either[E, T2], Either[E, T3], Either[E, T4], Either[E, T5], Either[E, T6], Either[E, T7], Either[E, T8], Either[E, T9], Either[E, T10], Either[E, T11], Either[E, T12], Either[E, T13], Either[E, T14], Either[E, T15]]) Either[E, T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] { + return A.SequenceTuple15( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15], E, T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15], E, T3], + Ap[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15], E, T4], + Ap[func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15], E, T5], + Ap[func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15], E, T6], + Ap[func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15], E, T7], + Ap[func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15], E, T8], + Ap[func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15], E, T9], + Ap[func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15], E, T10], + Ap[func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15], E, T11], + Ap[func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15], E, T12], + Ap[func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15], E, T13], + Ap[func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15], E, T14], + Ap[T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15], E, T15], + t, + ) +} + +// TraverseTuple15 converts a [Tuple15] of [A] via transformation functions transforming [A] to [Either[E, A]] into a [Either[E, Tuple15]]. +func TraverseTuple15[F1 ~func(A1) Either[E, T1], F2 ~func(A2) Either[E, T2], F3 ~func(A3) Either[E, T3], F4 ~func(A4) Either[E, T4], F5 ~func(A5) Either[E, T5], F6 ~func(A6) Either[E, T6], F7 ~func(A7) Either[E, T7], F8 ~func(A8) Either[E, T8], F9 ~func(A9) Either[E, T9], F10 ~func(A10) Either[E, T10], F11 ~func(A11) Either[E, T11], F12 ~func(A12) Either[E, T12], F13 ~func(A13) Either[E, T13], F14 ~func(A14) Either[E, T14], F15 ~func(A15) Either[E, T15], E, A1, T1, A2, T2, A3, T3, A4, T4, A5, T5, A6, T6, A7, T7, A8, T8, A9, T9, A10, T10, A11, T11, A12, T12, A13, T13, A14, T14, A15, T15 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10, f11 F11, f12 F12, f13 F13, f14 F14, f15 F15) func(T.Tuple15[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15]) Either[E, T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] { + return func(t T.Tuple15[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15]) Either[E, T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] { + return A.TraverseTuple15( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15], E, T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15], E, T3], + Ap[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15], E, T4], + Ap[func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15], E, T5], + Ap[func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15], E, T6], + Ap[func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15], E, T7], + Ap[func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15], E, T8], + Ap[func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15], E, T9], + Ap[func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15], E, T10], + Ap[func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15], E, T11], + Ap[func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15], E, T12], + Ap[func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15], E, T13], + Ap[func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15], E, T14], + Ap[T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15], E, T15], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + f8, + f9, + f10, + f11, + f12, + f13, + f14, + f15, + t, + ) + } +} diff --git a/v2/either/gen_coverage_test.go b/v2/either/gen_coverage_test.go new file mode 100644 index 0000000..0ffcb35 --- /dev/null +++ b/v2/either/gen_coverage_test.go @@ -0,0 +1,558 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package either + +import ( + "errors" + "testing" + + O "github.com/IBM/fp-go/v2/option" + T "github.com/IBM/fp-go/v2/tuple" + "github.com/stretchr/testify/assert" +) + +// Test MonadChainOptionK +func TestMonadChainOptionK(t *testing.T) { + f := func(n int) O.Option[int] { + if n > 0 { + return O.Some(n * 2) + } + return O.None[int]() + } + + onNone := func() error { return errors.New("none") } + + result := MonadChainOptionK(onNone, Right[error](5), f) + assert.Equal(t, Right[error](10), result) + + result = MonadChainOptionK(onNone, Right[error](-1), f) + assert.Equal(t, Left[int](errors.New("none")), result) + + result = MonadChainOptionK(onNone, Left[int](errors.New("error")), f) + assert.Equal(t, Left[int](errors.New("error")), result) +} + +// Test Uneitherize1 +func TestUneitherize1(t *testing.T) { + f := func(x int) Either[error, string] { + if x > 0 { + return Right[error]("positive") + } + return Left[string](errors.New("negative")) + } + + uneitherized := Uneitherize1(f) + result, err := uneitherized(5) + assert.NoError(t, err) + assert.Equal(t, "positive", result) + + result, err = uneitherized(-1) + assert.Error(t, err) +} + +// Test Uneitherize2 +func TestUneitherize2(t *testing.T) { + f := func(x, y int) Either[error, int] { + if x > 0 && y > 0 { + return Right[error](x + y) + } + return Left[int](errors.New("invalid")) + } + + uneitherized := Uneitherize2(f) + result, err := uneitherized(5, 3) + assert.NoError(t, err) + assert.Equal(t, 8, result) + + result, err = uneitherized(-1, 3) + assert.Error(t, err) +} + +// Test Uneitherize3 +func TestUneitherize3(t *testing.T) { + f := func(x, y, z int) Either[error, int] { + return Right[error](x + y + z) + } + + uneitherized := Uneitherize3(f) + result, err := uneitherized(1, 2, 3) + assert.NoError(t, err) + assert.Equal(t, 6, result) +} + +// Test Uneitherize4 +func TestUneitherize4(t *testing.T) { + f := func(a, b, c, d int) Either[error, int] { + return Right[error](a + b + c + d) + } + + uneitherized := Uneitherize4(f) + result, err := uneitherized(1, 2, 3, 4) + assert.NoError(t, err) + assert.Equal(t, 10, result) +} + +// Test SequenceT1 +func TestSequenceT1(t *testing.T) { + result := SequenceT1( + Right[error](1), + ) + expected := Right[error](T.MakeTuple1(1)) + assert.Equal(t, expected, result) + + result = SequenceT1( + Left[int](errors.New("error")), + ) + assert.True(t, IsLeft(result)) +} + +// Test SequenceT2 +func TestSequenceT2(t *testing.T) { + result := SequenceT2( + Right[error](1), + Right[error]("hello"), + ) + expected := Right[error](T.MakeTuple2(1, "hello")) + assert.Equal(t, expected, result) + + result = SequenceT2( + Left[int](errors.New("error")), + Right[error]("hello"), + ) + assert.True(t, IsLeft(result)) +} + +// Test SequenceT3 +func TestSequenceT3(t *testing.T) { + result := SequenceT3( + Right[error](1), + Right[error]("hello"), + Right[error](true), + ) + expected := Right[error](T.MakeTuple3(1, "hello", true)) + assert.Equal(t, expected, result) +} + +// Test SequenceT4 +func TestSequenceT4(t *testing.T) { + result := SequenceT4( + Right[error](1), + Right[error](2), + Right[error](3), + Right[error](4), + ) + expected := Right[error](T.MakeTuple4(1, 2, 3, 4)) + assert.Equal(t, expected, result) +} + +// Test SequenceTuple1 +func TestSequenceTuple1(t *testing.T) { + tuple := T.MakeTuple1(Right[error](42)) + result := SequenceTuple1(tuple) + expected := Right[error](T.MakeTuple1(42)) + assert.Equal(t, expected, result) +} + +// Test SequenceTuple2 +func TestSequenceTuple2(t *testing.T) { + tuple := T.MakeTuple2(Right[error](1), Right[error]("hello")) + result := SequenceTuple2(tuple) + expected := Right[error](T.MakeTuple2(1, "hello")) + assert.Equal(t, expected, result) +} + +// Test SequenceTuple3 +func TestSequenceTuple3(t *testing.T) { + tuple := T.MakeTuple3(Right[error](1), Right[error](2), Right[error](3)) + result := SequenceTuple3(tuple) + expected := Right[error](T.MakeTuple3(1, 2, 3)) + assert.Equal(t, expected, result) +} + +// Test SequenceTuple4 +func TestSequenceTuple4(t *testing.T) { + tuple := T.MakeTuple4(Right[error](1), Right[error](2), Right[error](3), Right[error](4)) + result := SequenceTuple4(tuple) + expected := Right[error](T.MakeTuple4(1, 2, 3, 4)) + assert.Equal(t, expected, result) +} + +// Test TraverseTuple1 +func TestTraverseTuple1(t *testing.T) { + f := func(x int) Either[error, string] { + if x > 0 { + return Right[error]("positive") + } + return Left[string](errors.New("negative")) + } + + tuple := T.MakeTuple1(5) + result := TraverseTuple1(f)(tuple) + expected := Right[error](T.MakeTuple1("positive")) + assert.Equal(t, expected, result) +} + +// Test TraverseTuple2 +func TestTraverseTuple2(t *testing.T) { + f1 := func(x int) Either[error, int] { + return Right[error](x * 2) + } + f2 := func(x int) Either[error, int] { + return Right[error](x * 2) + } + + tuple := T.MakeTuple2(1, 2) + result := TraverseTuple2(f1, f2)(tuple) + expected := Right[error](T.MakeTuple2(2, 4)) + assert.Equal(t, expected, result) +} + +// Test TraverseTuple3 +func TestTraverseTuple3(t *testing.T) { + f1 := func(x int) Either[error, int] { + return Right[error](x * 2) + } + f2 := func(x int) Either[error, int] { + return Right[error](x * 2) + } + f3 := func(x int) Either[error, int] { + return Right[error](x * 2) + } + + tuple := T.MakeTuple3(1, 2, 3) + result := TraverseTuple3(f1, f2, f3)(tuple) + expected := Right[error](T.MakeTuple3(2, 4, 6)) + assert.Equal(t, expected, result) +} + +// Test TraverseTuple4 +func TestTraverseTuple4(t *testing.T) { + f1 := func(x int) Either[error, int] { + return Right[error](x * 2) + } + f2 := func(x int) Either[error, int] { + return Right[error](x * 2) + } + f3 := func(x int) Either[error, int] { + return Right[error](x * 2) + } + f4 := func(x int) Either[error, int] { + return Right[error](x * 2) + } + + tuple := T.MakeTuple4(1, 2, 3, 4) + result := TraverseTuple4(f1, f2, f3, f4)(tuple) + expected := Right[error](T.MakeTuple4(2, 4, 6, 8)) + assert.Equal(t, expected, result) +} + +// Test Eitherize5 +func TestEitherize5(t *testing.T) { + f := func(a, b, c, d, e int) (int, error) { + return a + b + c + d + e, nil + } + + eitherized := Eitherize5(f) + result := eitherized(1, 2, 3, 4, 5) + assert.Equal(t, Right[error](15), result) +} + +// Test Uneitherize5 +func TestUneitherize5(t *testing.T) { + f := func(a, b, c, d, e int) Either[error, int] { + return Right[error](a + b + c + d + e) + } + + uneitherized := Uneitherize5(f) + result, err := uneitherized(1, 2, 3, 4, 5) + assert.NoError(t, err) + assert.Equal(t, 15, result) +} + +// Test SequenceT5 +func TestSequenceT5(t *testing.T) { + result := SequenceT5( + Right[error](1), + Right[error](2), + Right[error](3), + Right[error](4), + Right[error](5), + ) + expected := Right[error](T.MakeTuple5(1, 2, 3, 4, 5)) + assert.Equal(t, expected, result) +} + +// Test SequenceTuple5 +func TestSequenceTuple5(t *testing.T) { + tuple := T.MakeTuple5( + Right[error](1), + Right[error](2), + Right[error](3), + Right[error](4), + Right[error](5), + ) + result := SequenceTuple5(tuple) + expected := Right[error](T.MakeTuple5(1, 2, 3, 4, 5)) + assert.Equal(t, expected, result) +} + +// Test TraverseTuple5 +func TestTraverseTuple5(t *testing.T) { + f1 := func(x int) Either[error, int] { + return Right[error](x * 2) + } + f2 := func(x int) Either[error, int] { + return Right[error](x * 2) + } + f3 := func(x int) Either[error, int] { + return Right[error](x * 2) + } + f4 := func(x int) Either[error, int] { + return Right[error](x * 2) + } + f5 := func(x int) Either[error, int] { + return Right[error](x * 2) + } + + tuple := T.MakeTuple5(1, 2, 3, 4, 5) + result := TraverseTuple5(f1, f2, f3, f4, f5)(tuple) + expected := Right[error](T.MakeTuple5(2, 4, 6, 8, 10)) + assert.Equal(t, expected, result) +} + +// Test higher arity functions (6-10) - sample tests +func TestEitherize6(t *testing.T) { + f := func(a, b, c, d, e, f int) (int, error) { + return a + b + c + d + e + f, nil + } + + eitherized := Eitherize6(f) + result := eitherized(1, 2, 3, 4, 5, 6) + assert.Equal(t, Right[error](21), result) +} + +func TestSequenceT6(t *testing.T) { + result := SequenceT6( + Right[error](1), + Right[error](2), + Right[error](3), + Right[error](4), + Right[error](5), + Right[error](6), + ) + assert.True(t, IsRight(result)) +} + +func TestEitherize7(t *testing.T) { + f := func(a, b, c, d, e, f, g int) (int, error) { + return a + b + c + d + e + f + g, nil + } + + eitherized := Eitherize7(f) + result := eitherized(1, 2, 3, 4, 5, 6, 7) + assert.Equal(t, Right[error](28), result) +} + +func TestEitherize8(t *testing.T) { + f := func(a, b, c, d, e, f, g, h int) (int, error) { + return a + b + c + d + e + f + g + h, nil + } + + eitherized := Eitherize8(f) + result := eitherized(1, 2, 3, 4, 5, 6, 7, 8) + assert.Equal(t, Right[error](36), result) +} + +func TestEitherize9(t *testing.T) { + f := func(a, b, c, d, e, f, g, h, i int) (int, error) { + return a + b + c + d + e + f + g + h + i, nil + } + + eitherized := Eitherize9(f) + result := eitherized(1, 2, 3, 4, 5, 6, 7, 8, 9) + assert.Equal(t, Right[error](45), result) +} + +func TestEitherize10(t *testing.T) { + f := func(a, b, c, d, e, f, g, h, i, j int) (int, error) { + return a + b + c + d + e + f + g + h + i + j, nil + } + + eitherized := Eitherize10(f) + result := eitherized(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + assert.Equal(t, Right[error](55), result) +} + +func TestEitherize11(t *testing.T) { + f := func(a, b, c, d, e, f, g, h, i, j, k int) (int, error) { + return a + b + c + d + e + f + g + h + i + j + k, nil + } + + eitherized := Eitherize11(f) + result := eitherized(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11) + assert.Equal(t, Right[error](66), result) +} + +func TestEitherize12(t *testing.T) { + f := func(a, b, c, d, e, f, g, h, i, j, k, l int) (int, error) { + return a + b + c + d + e + f + g + h + i + j + k + l, nil + } + + eitherized := Eitherize12(f) + result := eitherized(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12) + assert.Equal(t, Right[error](78), result) +} + +func TestEitherize13(t *testing.T) { + f := func(a, b, c, d, e, f, g, h, i, j, k, l, m int) (int, error) { + return a + b + c + d + e + f + g + h + i + j + k + l + m, nil + } + + eitherized := Eitherize13(f) + result := eitherized(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13) + assert.Equal(t, Right[error](91), result) +} + +func TestEitherize14(t *testing.T) { + f := func(a, b, c, d, e, f, g, h, i, j, k, l, m, n int) (int, error) { + return a + b + c + d + e + f + g + h + i + j + k + l + m + n, nil + } + + eitherized := Eitherize14(f) + result := eitherized(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14) + assert.Equal(t, Right[error](105), result) +} + +func TestEitherize15(t *testing.T) { + f := func(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o int) (int, error) { + return a + b + c + d + e + f + g + h + i + j + k + l + m + n + o, nil + } + + eitherized := Eitherize15(f) + result := eitherized(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15) + assert.Equal(t, Right[error](120), result) +} + +// Test Uneitherize functions for higher arities +func TestUneitherize6(t *testing.T) { + f := func(a, b, c, d, e, f int) Either[error, int] { + return Right[error](a + b + c + d + e + f) + } + + uneitherized := Uneitherize6(f) + result, err := uneitherized(1, 2, 3, 4, 5, 6) + assert.NoError(t, err) + assert.Equal(t, 21, result) +} + +func TestUneitherize7(t *testing.T) { + f := func(a, b, c, d, e, f, g int) Either[error, int] { + return Right[error](a + b + c + d + e + f + g) + } + + uneitherized := Uneitherize7(f) + result, err := uneitherized(1, 2, 3, 4, 5, 6, 7) + assert.NoError(t, err) + assert.Equal(t, 28, result) +} + +func TestUneitherize8(t *testing.T) { + f := func(a, b, c, d, e, f, g, h int) Either[error, int] { + return Right[error](a + b + c + d + e + f + g + h) + } + + uneitherized := Uneitherize8(f) + result, err := uneitherized(1, 2, 3, 4, 5, 6, 7, 8) + assert.NoError(t, err) + assert.Equal(t, 36, result) +} + +func TestUneitherize9(t *testing.T) { + f := func(a, b, c, d, e, f, g, h, i int) Either[error, int] { + return Right[error](a + b + c + d + e + f + g + h + i) + } + + uneitherized := Uneitherize9(f) + result, err := uneitherized(1, 2, 3, 4, 5, 6, 7, 8, 9) + assert.NoError(t, err) + assert.Equal(t, 45, result) +} + +func TestUneitherize10(t *testing.T) { + f := func(a, b, c, d, e, f, g, h, i, j int) Either[error, int] { + return Right[error](a + b + c + d + e + f + g + h + i + j) + } + + uneitherized := Uneitherize10(f) + result, err := uneitherized(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + assert.NoError(t, err) + assert.Equal(t, 55, result) +} + +func TestUneitherize11(t *testing.T) { + f := func(a, b, c, d, e, f, g, h, i, j, k int) Either[error, int] { + return Right[error](a + b + c + d + e + f + g + h + i + j + k) + } + + uneitherized := Uneitherize11(f) + result, err := uneitherized(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11) + assert.NoError(t, err) + assert.Equal(t, 66, result) +} + +func TestUneitherize12(t *testing.T) { + f := func(a, b, c, d, e, f, g, h, i, j, k, l int) Either[error, int] { + return Right[error](a + b + c + d + e + f + g + h + i + j + k + l) + } + + uneitherized := Uneitherize12(f) + result, err := uneitherized(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12) + assert.NoError(t, err) + assert.Equal(t, 78, result) +} + +func TestUneitherize13(t *testing.T) { + f := func(a, b, c, d, e, f, g, h, i, j, k, l, m int) Either[error, int] { + return Right[error](a + b + c + d + e + f + g + h + i + j + k + l + m) + } + + uneitherized := Uneitherize13(f) + result, err := uneitherized(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13) + assert.NoError(t, err) + assert.Equal(t, 91, result) +} + +func TestUneitherize14(t *testing.T) { + f := func(a, b, c, d, e, f, g, h, i, j, k, l, m, n int) Either[error, int] { + return Right[error](a + b + c + d + e + f + g + h + i + j + k + l + m + n) + } + + uneitherized := Uneitherize14(f) + result, err := uneitherized(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14) + assert.NoError(t, err) + assert.Equal(t, 105, result) +} + +func TestUneitherize15(t *testing.T) { + f := func(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o int) Either[error, int] { + return Right[error](a + b + c + d + e + f + g + h + i + j + k + l + m + n + o) + } + + uneitherized := Uneitherize15(f) + result, err := uneitherized(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15) + assert.NoError(t, err) + assert.Equal(t, 120, result) +} diff --git a/v2/either/http/request.go b/v2/either/http/request.go new file mode 100644 index 0000000..2ec882b --- /dev/null +++ b/v2/either/http/request.go @@ -0,0 +1,72 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +// Package http provides utilities for creating HTTP requests with Either-based error handling. +package http + +import ( + "bytes" + "net/http" + + E "github.com/IBM/fp-go/v2/either" +) + +var ( + // PostRequest creates a POST HTTP request with a body. + // Usage: PostRequest(url)(body) returns Either[error, *http.Request] + // + // Example: + // + // request := http.PostRequest("https://api.example.com/data")([]byte(`{"key":"value"}`)) + PostRequest = bodyRequest("POST") + + // PutRequest creates a PUT HTTP request with a body. + // Usage: PutRequest(url)(body) returns Either[error, *http.Request] + PutRequest = bodyRequest("PUT") + + // GetRequest creates a GET HTTP request without a body. + // Usage: GetRequest(url) returns Either[error, *http.Request] + // + // Example: + // + // request := http.GetRequest("https://api.example.com/data") + GetRequest = noBodyRequest("GET") + + // DeleteRequest creates a DELETE HTTP request without a body. + // Usage: DeleteRequest(url) returns Either[error, *http.Request] + DeleteRequest = noBodyRequest("DELETE") + + // OptionsRequest creates an OPTIONS HTTP request without a body. + // Usage: OptionsRequest(url) returns Either[error, *http.Request] + OptionsRequest = noBodyRequest("OPTIONS") + + // HeadRequest creates a HEAD HTTP request without a body. + // Usage: HeadRequest(url) returns Either[error, *http.Request] + HeadRequest = noBodyRequest("HEAD") +) + +func bodyRequest(method string) func(string) func([]byte) E.Either[error, *http.Request] { + return func(url string) func([]byte) E.Either[error, *http.Request] { + return func(body []byte) E.Either[error, *http.Request] { + return E.TryCatchError(http.NewRequest(method, url, bytes.NewReader(body))) + } + } +} + +func noBodyRequest(method string) func(string) E.Either[error, *http.Request] { + return func(url string) E.Either[error, *http.Request] { + return E.TryCatchError(http.NewRequest(method, url, nil)) + } +} diff --git a/v2/either/logger.go b/v2/either/logger.go new file mode 100644 index 0000000..95eb0ab --- /dev/null +++ b/v2/either/logger.go @@ -0,0 +1,64 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package either + +import ( + "log" + + F "github.com/IBM/fp-go/v2/function" + L "github.com/IBM/fp-go/v2/logging" +) + +func _log[E, A any](left func(string, ...any), right func(string, ...any), prefix string) func(Either[E, A]) Either[E, A] { + return Fold( + func(e E) Either[E, A] { + left("%s: %v", prefix, e) + return Left[A, E](e) + }, + func(a A) Either[E, A] { + right("%s: %v", prefix, a) + return Right[E](a) + }) +} + +// Logger creates a logging function for Either values that logs both Left and Right cases. +// The function logs the value and then returns the original Either unchanged. +// +// Parameters: +// - loggers: Optional log.Logger instances. If none provided, uses default logger. +// +// Example: +// +// logger := either.Logger[error, int]() +// result := F.Pipe2( +// either.Right[error](42), +// logger("Processing"), +// either.Map(func(x int) int { return x * 2 }), +// ) +// // Logs: "Processing: 42" +// // result is Right(84) +func Logger[E, A any](loggers ...*log.Logger) func(string) func(Either[E, A]) Either[E, A] { + left, right := L.LoggingCallbacks(loggers...) + return func(prefix string) func(Either[E, A]) Either[E, A] { + delegate := _log[E, A](left, right, prefix) + return func(ma Either[E, A]) Either[E, A] { + return F.Pipe1( + delegate(ma), + ChainTo[A](ma), + ) + } + } +} diff --git a/v2/either/logger_test.go b/v2/either/logger_test.go new file mode 100644 index 0000000..0b5f445 --- /dev/null +++ b/v2/either/logger_test.go @@ -0,0 +1,37 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package either + +import ( + "testing" + + F "github.com/IBM/fp-go/v2/function" + "github.com/stretchr/testify/assert" +) + +func TestLogger(t *testing.T) { + + l := Logger[error, string]() + + r := Right[error]("test") + + res := F.Pipe1( + r, + l("out"), + ) + + assert.Equal(t, r, res) +} diff --git a/v2/either/monad.go b/v2/either/monad.go new file mode 100644 index 0000000..1a97e66 --- /dev/null +++ b/v2/either/monad.go @@ -0,0 +1,56 @@ +// Copyright (c) 2024 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package either + +import ( + "github.com/IBM/fp-go/v2/internal/monad" +) + +type eitherMonad[E, A, B any] struct{} + +func (o *eitherMonad[E, A, B]) Of(a A) Either[E, A] { + return Of[E, A](a) +} + +func (o *eitherMonad[E, A, B]) Map(f func(A) B) func(Either[E, A]) Either[E, B] { + return Map[E, A, B](f) +} + +func (o *eitherMonad[E, A, B]) Chain(f func(A) Either[E, B]) func(Either[E, A]) Either[E, B] { + return Chain[E, A, B](f) +} + +func (o *eitherMonad[E, A, B]) Ap(fa Either[E, A]) func(Either[E, func(A) B]) Either[E, B] { + return Ap[B, E, A](fa) +} + +// Monad implements the monadic operations for Either. +// A monad combines the capabilities of Functor (Map), Applicative (Ap), and Chain (flatMap/bind). +// This allows for sequential composition of computations that may fail. +// +// Example: +// +// m := either.Monad[error, int, string]() +// result := m.Chain(func(x int) either.Either[error, string] { +// if x > 0 { +// return either.Right[error](strconv.Itoa(x)) +// } +// return either.Left[string](errors.New("negative")) +// })(either.Right[error](42)) +// // result is Right("42") +func Monad[E, A, B any]() monad.Monad[A, B, Either[E, A], Either[E, B], Either[E, func(A) B]] { + return &eitherMonad[E, A, B]{} +} diff --git a/v2/either/monoid.go b/v2/either/monoid.go new file mode 100644 index 0000000..0de3e73 --- /dev/null +++ b/v2/either/monoid.go @@ -0,0 +1,59 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package either + +import ( + L "github.com/IBM/fp-go/v2/lazy" + M "github.com/IBM/fp-go/v2/monoid" +) + +// AlternativeMonoid creates a monoid for Either using applicative semantics. +// The empty value is Right with the monoid's empty value. +// Combines values using applicative operations. +// +// Example: +// +// import "github.com/IBM/fp-go/v2/monoid" +// intAdd := monoid.MakeMonoid(0, func(a, b int) int { return a + b }) +// m := either.AlternativeMonoid[error](intAdd) +// result := m.Concat(either.Right[error](1), either.Right[error](2)) +// // result is Right(3) +func AlternativeMonoid[E, A any](m M.Monoid[A]) M.Monoid[Either[E, A]] { + return M.AlternativeMonoid( + Of[E, A], + MonadMap[E, A, func(A) A], + MonadAp[A, E, A], + MonadAlt[E, A], + m, + ) +} + +// AltMonoid creates a monoid for Either using the Alt operation. +// The empty value is provided as a lazy computation. +// When combining, returns the first Right value, or the second if the first is Left. +// +// Example: +// +// zero := func() either.Either[error, int] { return either.Left[int](errors.New("empty")) } +// m := either.AltMonoid[error, int](zero) +// result := m.Concat(either.Left[int](errors.New("err1")), either.Right[error](42)) +// // result is Right(42) +func AltMonoid[E, A any](zero L.Lazy[Either[E, A]]) M.Monoid[Either[E, A]] { + return M.AltMonoid( + zero, + MonadAlt[E, A], + ) +} diff --git a/v2/either/pointed.go b/v2/either/pointed.go new file mode 100644 index 0000000..c6b9d89 --- /dev/null +++ b/v2/either/pointed.go @@ -0,0 +1,37 @@ +// Copyright (c) 2024 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package either + +import ( + "github.com/IBM/fp-go/v2/internal/pointed" +) + +type eitherPointed[E, A any] struct{} + +func (o *eitherPointed[E, A]) Of(a A) Either[E, A] { + return Of[E, A](a) +} + +// Pointed implements the pointed functor operations for Either. +// A pointed functor provides the Of operation to lift a value into the Either context. +// +// Example: +// +// p := either.Pointed[error, int]() +// result := p.Of(42) // Right(42) +func Pointed[E, A any]() pointed.Pointed[A, Either[E, A]] { + return &eitherPointed[E, A]{} +} diff --git a/v2/either/record.go b/v2/either/record.go new file mode 100644 index 0000000..b94c924 --- /dev/null +++ b/v2/either/record.go @@ -0,0 +1,163 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package either + +import ( + F "github.com/IBM/fp-go/v2/function" + RR "github.com/IBM/fp-go/v2/internal/record" +) + +// TraverseRecordG transforms a map by applying a function that returns an Either to each value. +// If any value produces a Left, the entire result is that Left (short-circuits). +// Otherwise, returns Right containing the map of all Right values. +// The G suffix indicates support for generic map types. +// +// Example: +// +// parse := func(s string) either.Either[error, int] { +// v, err := strconv.Atoi(s) +// return either.FromError(v, err) +// } +// result := either.TraverseRecordG[map[string]string, map[string]int](parse)(map[string]string{"a": "1", "b": "2"}) +// // result is Right(map[string]int{"a": 1, "b": 2}) +func TraverseRecordG[GA ~map[K]A, GB ~map[K]B, K comparable, E, A, B any](f func(A) Either[E, B]) func(GA) Either[E, GB] { + return RR.Traverse[GA]( + Of[E, GB], + Map[E, GB, func(B) GB], + Ap[GB, E, B], + f, + ) +} + +// TraverseRecord transforms a map by applying a function that returns an Either to each value. +// If any value produces a Left, the entire result is that Left (short-circuits). +// Otherwise, returns Right containing the map of all Right values. +// +// Example: +// +// parse := func(s string) either.Either[error, int] { +// v, err := strconv.Atoi(s) +// return either.FromError(v, err) +// } +// result := either.TraverseRecord[string](parse)(map[string]string{"a": "1", "b": "2"}) +// // result is Right(map[string]int{"a": 1, "b": 2}) +func TraverseRecord[K comparable, E, A, B any](f func(A) Either[E, B]) func(map[K]A) Either[E, map[K]B] { + return TraverseRecordG[map[K]A, map[K]B](f) +} + +// TraverseRecordWithIndexG transforms a map by applying an indexed function that returns an Either. +// The function receives both the key and the value. +// If any value produces a Left, the entire result is that Left (short-circuits). +// The G suffix indicates support for generic map types. +// +// Example: +// +// validate := func(k string, v string) either.Either[error, string] { +// if len(v) > 0 { +// return either.Right[error](k + ":" + v) +// } +// return either.Left[string](fmt.Errorf("empty value for key %s", k)) +// } +// result := either.TraverseRecordWithIndexG[map[string]string, map[string]string](validate)(map[string]string{"a": "1"}) +// // result is Right(map[string]string{"a": "a:1"}) +func TraverseRecordWithIndexG[GA ~map[K]A, GB ~map[K]B, K comparable, E, A, B any](f func(K, A) Either[E, B]) func(GA) Either[E, GB] { + return RR.TraverseWithIndex[GA]( + Of[E, GB], + Map[E, GB, func(B) GB], + Ap[GB, E, B], + f, + ) +} + +// TraverseRecordWithIndex transforms a map by applying an indexed function that returns an Either. +// The function receives both the key and the value. +// If any value produces a Left, the entire result is that Left (short-circuits). +// +// Example: +// +// validate := func(k string, v string) either.Either[error, string] { +// if len(v) > 0 { +// return either.Right[error](k + ":" + v) +// } +// return either.Left[string](fmt.Errorf("empty value for key %s", k)) +// } +// result := either.TraverseRecordWithIndex[string](validate)(map[string]string{"a": "1"}) +// // result is Right(map[string]string{"a": "a:1"}) +func TraverseRecordWithIndex[K comparable, E, A, B any](f func(K, A) Either[E, B]) func(map[K]A) Either[E, map[K]B] { + return TraverseRecordWithIndexG[map[K]A, map[K]B](f) +} + +func SequenceRecordG[GA ~map[K]A, GOA ~map[K]Either[E, A], K comparable, E, A any](ma GOA) Either[E, GA] { + return TraverseRecordG[GOA, GA](F.Identity[Either[E, A]])(ma) +} + +// SequenceRecord converts a map of Either values into an Either of a map. +// If any value is Left, returns that Left (short-circuits). +// Otherwise, returns Right containing a map of all the Right values. +// +// Example: +// +// eithers := map[string]either.Either[error, int]{ +// "a": either.Right[error](1), +// "b": either.Right[error](2), +// } +// result := either.SequenceRecord(eithers) +// // result is Right(map[string]int{"a": 1, "b": 2}) +func SequenceRecord[K comparable, E, A any](ma map[K]Either[E, A]) Either[E, map[K]A] { + return SequenceRecordG[map[K]A](ma) +} + +func upsertAtReadWrite[M ~map[K]V, K comparable, V any](r M, k K, v V) M { + r[k] = v + return r +} + +// CompactRecordG discards all Left values and keeps only the Right values. +// The G suffix indicates support for generic map types. +// +// Example: +// +// eithers := map[string]either.Either[error, int]{ +// "a": either.Right[error](1), +// "b": either.Left[int](errors.New("error")), +// "c": either.Right[error](3), +// } +// result := either.CompactRecordG[map[string]either.Either[error, int], map[string]int](eithers) +// // result is map[string]int{"a": 1, "c": 3} +func CompactRecordG[M1 ~map[K]Either[E, A], M2 ~map[K]A, K comparable, E, A any](m M1) M2 { + out := make(M2) + onLeft := F.Constant1[E](out) + return RR.ReduceWithIndex(m, func(key K, _ M2, value Either[E, A]) M2 { + return MonadFold(value, onLeft, func(v A) M2 { + return upsertAtReadWrite(out, key, v) + }) + }, out) +} + +// CompactRecord discards all Left values and keeps only the Right values. +// +// Example: +// +// eithers := map[string]either.Either[error, int]{ +// "a": either.Right[error](1), +// "b": either.Left[int](errors.New("error")), +// "c": either.Right[error](3), +// } +// result := either.CompactRecord(eithers) +// // result is map[string]int{"a": 1, "c": 3} +func CompactRecord[K comparable, E, A any](m map[K]Either[E, A]) map[K]A { + return CompactRecordG[map[K]Either[E, A], map[K]A](m) +} diff --git a/v2/either/record_test.go b/v2/either/record_test.go new file mode 100644 index 0000000..c7032cb --- /dev/null +++ b/v2/either/record_test.go @@ -0,0 +1,37 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package either + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCompactRecord(t *testing.T) { + // make the map + m := make(map[string]Either[string, int]) + m["foo"] = Left[int]("error") + m["bar"] = Right[string](1) + // compact it + m1 := CompactRecord(m) + // check expected + exp := map[string]int{ + "bar": 1, + } + + assert.Equal(t, exp, m1) +} diff --git a/v2/either/resource.go b/v2/either/resource.go new file mode 100644 index 0000000..327f8f6 --- /dev/null +++ b/v2/either/resource.go @@ -0,0 +1,67 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package either + +import ( + F "github.com/IBM/fp-go/v2/function" +) + +// WithResource constructs a function that creates a resource, operates on it, and then releases it. +// This ensures proper resource cleanup even if operations fail. +// The resource is released immediately after the operation completes. +// +// Parameters: +// - onCreate: Function to create/acquire the resource +// - onRelease: Function to release/cleanup the resource +// +// Returns a function that takes an operation to perform on the resource. +// +// Example: +// +// withFile := either.WithResource( +// func() either.Either[error, *os.File] { +// return either.TryCatchError(os.Open("file.txt")) +// }, +// func(f *os.File) either.Either[error, any] { +// return either.TryCatchError(f.Close()) +// }, +// ) +// result := withFile(func(f *os.File) either.Either[error, string] { +// // Use file here +// return either.Right[error]("data") +// }) +func WithResource[E, R, A any](onCreate func() Either[E, R], onRelease func(R) Either[E, any]) func(func(R) Either[E, A]) Either[E, A] { + + return func(f func(R) Either[E, A]) Either[E, A] { + return MonadChain( + onCreate(), func(r R) Either[E, A] { + // run the code and make sure to release as quickly as possible + res := f(r) + released := onRelease(r) + // handle the errors + return MonadFold( + res, + Left[A, E], + func(a A) Either[E, A] { + return F.Pipe1( + released, + MapTo[E, any](a), + ) + }) + }, + ) + } +} diff --git a/v2/either/resource_test.go b/v2/either/resource_test.go new file mode 100644 index 0000000..51700ed --- /dev/null +++ b/v2/either/resource_test.go @@ -0,0 +1,48 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package either + +import ( + "os" + "testing" + + F "github.com/IBM/fp-go/v2/function" + "github.com/stretchr/testify/assert" +) + +func TestWithResource(t *testing.T) { + onCreate := func() Either[error, *os.File] { + return TryCatchError(os.CreateTemp("", "*")) + } + onDelete := F.Flow2( + func(f *os.File) Either[error, string] { + return TryCatchError(f.Name(), f.Close()) + }, + Chain(func(name string) Either[error, any] { + return TryCatchError(any(name), os.Remove(name)) + }), + ) + + onHandler := func(f *os.File) Either[error, string] { + return Of[error](f.Name()) + } + + tempFile := WithResource[error, *os.File, string](onCreate, onDelete) + + resE := tempFile(onHandler) + + assert.True(t, IsRight(resE)) +} diff --git a/v2/either/semigroup.go b/v2/either/semigroup.go new file mode 100644 index 0000000..bd0c62b --- /dev/null +++ b/v2/either/semigroup.go @@ -0,0 +1,36 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package either + +import ( + S "github.com/IBM/fp-go/v2/semigroup" +) + +// AltSemigroup creates a semigroup for Either that uses the Alt operation for combining values. +// When combining two Either values, it returns the first Right value, or the second value if the first is Left. +// +// Example: +// +// sg := either.AltSemigroup[error, int]() +// result := sg.Concat(either.Left[int](errors.New("error")), either.Right[error](42)) +// // result is Right(42) +// result2 := sg.Concat(either.Right[error](1), either.Right[error](2)) +// // result2 is Right(1) - first Right wins +func AltSemigroup[E, A any]() S.Semigroup[Either[E, A]] { + return S.AltSemigroup( + MonadAlt[E, A], + ) +} diff --git a/v2/either/testing/laws.go b/v2/either/testing/laws.go new file mode 100644 index 0000000..d53602d --- /dev/null +++ b/v2/either/testing/laws.go @@ -0,0 +1,103 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +// Package testing provides utilities for testing Either monad laws. +// This is useful for verifying that custom Either implementations satisfy the monad laws. +package testing + +import ( + "testing" + + ET "github.com/IBM/fp-go/v2/either" + EQ "github.com/IBM/fp-go/v2/eq" + L "github.com/IBM/fp-go/v2/internal/monad/testing" +) + +// AssertLaws asserts that the Either monad satisfies the monad laws. +// This includes testing: +// - Identity laws (left and right identity) +// - Associativity law +// - Functor laws +// - Applicative laws +// +// Parameters: +// - t: Testing context +// - eqe, eqa, eqb, eqc: Equality predicates for the types +// - ab: Function from A to B for testing +// - bc: Function from B to C for testing +// +// Returns a function that takes a value of type A and returns true if all laws hold. +// +// Example: +// +// func TestEitherLaws(t *testing.T) { +// eqInt := eq.FromStrictEquals[int]() +// eqString := eq.FromStrictEquals[string]() +// eqError := eq.FromStrictEquals[error]() +// +// ab := func(x int) string { return strconv.Itoa(x) } +// bc := func(s string) bool { return len(s) > 0 } +// +// testing.AssertLaws(t, eqError, eqInt, eqString, eq.FromStrictEquals[bool](), ab, bc)(42) +// } +func AssertLaws[E, A, B, C any](t *testing.T, + eqe EQ.Eq[E], + eqa EQ.Eq[A], + eqb EQ.Eq[B], + eqc EQ.Eq[C], + + ab func(A) B, + bc func(B) C, +) func(a A) bool { + + return L.AssertLaws(t, + ET.Eq(eqe, eqa), + ET.Eq(eqe, eqb), + ET.Eq(eqe, eqc), + + ET.Of[E, A], + ET.Of[E, B], + ET.Of[E, C], + + ET.Of[E, func(A) A], + ET.Of[E, func(A) B], + ET.Of[E, func(B) C], + ET.Of[E, func(func(A) B) B], + + ET.MonadMap[E, A, A], + ET.MonadMap[E, A, B], + ET.MonadMap[E, A, C], + ET.MonadMap[E, B, C], + + ET.MonadMap[E, func(B) C, func(func(A) B) func(A) C], + + ET.MonadChain[E, A, A], + ET.MonadChain[E, A, B], + ET.MonadChain[E, A, C], + ET.MonadChain[E, B, C], + + ET.MonadAp[A, E, A], + ET.MonadAp[B, E, A], + ET.MonadAp[C, E, B], + ET.MonadAp[C, E, A], + + ET.MonadAp[B, E, func(A) B], + ET.MonadAp[func(A) C, E, func(A) B], + + ab, + bc, + ) + +} diff --git a/v2/either/testing/laws_test.go b/v2/either/testing/laws_test.go new file mode 100644 index 0000000..d820332 --- /dev/null +++ b/v2/either/testing/laws_test.go @@ -0,0 +1,48 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package testing + +import ( + "fmt" + "testing" + + EQ "github.com/IBM/fp-go/v2/eq" + "github.com/stretchr/testify/assert" +) + +func TestMonadLaws(t *testing.T) { + // some comparison + eqe := EQ.FromStrictEquals[string]() + eqa := EQ.FromStrictEquals[bool]() + eqb := EQ.FromStrictEquals[int]() + eqc := EQ.FromStrictEquals[string]() + + ab := func(a bool) int { + if a { + return 1 + } + return 0 + } + + bc := func(b int) string { + return fmt.Sprintf("value %d", b) + } + + laws := AssertLaws(t, eqe, eqa, eqb, eqc, ab, bc) + + assert.True(t, laws(true)) + assert.True(t, laws(false)) +} diff --git a/v2/either/traverse.go b/v2/either/traverse.go new file mode 100644 index 0000000..b547003 --- /dev/null +++ b/v2/either/traverse.go @@ -0,0 +1,68 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package either + +import ( + F "github.com/IBM/fp-go/v2/function" +) + +// Traverse converts an Either of some higher kinded type into the higher kinded type of an Either. +// This is a generic traversal operation that works with any applicative functor. +// +// Parameters: +// - mof: Lifts an Either into the target higher-kinded type +// - mmap: Maps over the target higher-kinded type +// +// Example (conceptual - requires understanding of higher-kinded types): +// +// // Traverse an Either[error, Option[int]] to Option[Either[error, int]] +// result := either.Traverse[int, error, int, option.Option[int], option.Option[either.Either[error, int]]]( +// option.Of[either.Either[error, int]], +// option.Map[int, either.Either[error, int]], +// )(f)(eitherOfOption) +func Traverse[A, E, B, HKTB, HKTRB any]( + mof func(Either[E, B]) HKTRB, + mmap func(func(B) Either[E, B]) func(HKTB) HKTRB, +) func(func(A) HKTB) func(Either[E, A]) HKTRB { + + left := F.Flow2(Left[B, E], mof) + right := mmap(Right[E, B]) + + return func(f func(A) HKTB) func(Either[E, A]) HKTRB { + return Fold(left, F.Flow2(f, right)) + } +} + +// Sequence converts an Either of some higher kinded type into the higher kinded type of an Either. +// This is the identity version of Traverse - it doesn't transform the values, just swaps the type constructors. +// +// Parameters: +// - mof: Lifts an Either into the target higher-kinded type +// - mmap: Maps over the target higher-kinded type +// +// Example (conceptual - requires understanding of higher-kinded types): +// +// // Sequence an Either[error, Option[int]] to Option[Either[error, int]] +// result := either.Sequence[error, int, option.Option[int], option.Option[either.Either[error, int]]]( +// option.Of[either.Either[error, int]], +// option.Map[int, either.Either[error, int]], +// )(eitherOfOption) +func Sequence[E, A, HKTA, HKTRA any]( + mof func(Either[E, A]) HKTRA, + mmap func(func(A) Either[E, A]) func(HKTA) HKTRA, +) func(Either[E, HKTA]) HKTRA { + return Fold(F.Flow2(Left[A, E], mof), mmap(Right[E, A])) +} diff --git a/v2/either/traverse_test.go b/v2/either/traverse_test.go new file mode 100644 index 0000000..55af8fc --- /dev/null +++ b/v2/either/traverse_test.go @@ -0,0 +1,53 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package either + +import ( + "testing" + + F "github.com/IBM/fp-go/v2/function" + O "github.com/IBM/fp-go/v2/option" + "github.com/stretchr/testify/assert" +) + +func TestTraverse(t *testing.T) { + f := func(n int) Option[int] { + if n >= 2 { + return O.Of(n) + } + return O.None[int]() + } + trav := Traverse[int]( + O.Of[Either[string, int]], + O.Map[int, Either[string, int]], + )(f) + + assert.Equal(t, O.Of(Left[int]("a")), F.Pipe1(Left[int]("a"), trav)) + assert.Equal(t, O.None[Either[string, int]](), F.Pipe1(Right[string](1), trav)) + assert.Equal(t, O.Of(Right[string](3)), F.Pipe1(Right[string](3), trav)) +} + +func TestSequence(t *testing.T) { + + seq := Sequence( + O.Of[Either[string, int]], + O.Map[int, Either[string, int]], + ) + + assert.Equal(t, O.Of(Right[string](1)), seq(Right[string](O.Of(1)))) + assert.Equal(t, O.Of(Left[int]("a")), seq(Left[Option[int]]("a"))) + assert.Equal(t, O.None[Either[string, int]](), seq(Right[string](O.None[int]()))) +} diff --git a/v2/either/types.go b/v2/either/types.go new file mode 100644 index 0000000..54f0385 --- /dev/null +++ b/v2/either/types.go @@ -0,0 +1,24 @@ +// Copyright (c) 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package either + +import "github.com/IBM/fp-go/v2/option" + +// Option is a type alias for option.Option, provided for convenience +// when working with Either and Option together. +type ( + Option[A any] = option.Option[A] +) diff --git a/v2/either/variadic.go b/v2/either/variadic.go new file mode 100644 index 0000000..d4d7967 --- /dev/null +++ b/v2/either/variadic.go @@ -0,0 +1,96 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package either + +// Variadic0 converts a function taking a slice and returning (R, error) into a variadic function returning Either. +// +// Example: +// +// sum := func(nums []int) (int, error) { +// total := 0 +// for _, n := range nums { total += n } +// return total, nil +// } +// variadicSum := either.Variadic0(sum) +// result := variadicSum(1, 2, 3) // Right(6) +func Variadic0[V, R any](f func([]V) (R, error)) func(...V) Either[error, R] { + return func(v ...V) Either[error, R] { + return TryCatchError(f(v)) + } +} + +// Variadic1 converts a function with 1 fixed parameter and a slice into a variadic function returning Either. +func Variadic1[T1, V, R any](f func(T1, []V) (R, error)) func(T1, ...V) Either[error, R] { + return func(t1 T1, v ...V) Either[error, R] { + return TryCatchError(f(t1, v)) + } +} + +// Variadic2 converts a function with 2 fixed parameters and a slice into a variadic function returning Either. +func Variadic2[T1, T2, V, R any](f func(T1, T2, []V) (R, error)) func(T1, T2, ...V) Either[error, R] { + return func(t1 T1, t2 T2, v ...V) Either[error, R] { + return TryCatchError(f(t1, t2, v)) + } +} + +// Variadic3 converts a function with 3 fixed parameters and a slice into a variadic function returning Either. +func Variadic3[T1, T2, T3, V, R any](f func(T1, T2, T3, []V) (R, error)) func(T1, T2, T3, ...V) Either[error, R] { + return func(t1 T1, t2 T2, t3 T3, v ...V) Either[error, R] { + return TryCatchError(f(t1, t2, t3, v)) + } +} + +// Variadic4 converts a function with 4 fixed parameters and a slice into a variadic function returning Either. +func Variadic4[T1, T2, T3, T4, V, R any](f func(T1, T2, T3, T4, []V) (R, error)) func(T1, T2, T3, T4, ...V) Either[error, R] { + return func(t1 T1, t2 T2, t3 T3, t4 T4, v ...V) Either[error, R] { + return TryCatchError(f(t1, t2, t3, t4, v)) + } +} + +// Unvariadic0 converts a variadic function returning (R, error) into a function taking a slice and returning Either. +func Unvariadic0[V, R any](f func(...V) (R, error)) func([]V) Either[error, R] { + return func(v []V) Either[error, R] { + return TryCatchError(f(v...)) + } +} + +// Unvariadic1 converts a variadic function with 1 fixed parameter into a function taking a slice and returning Either. +func Unvariadic1[T1, V, R any](f func(T1, ...V) (R, error)) func(T1, []V) Either[error, R] { + return func(t1 T1, v []V) Either[error, R] { + return TryCatchError(f(t1, v...)) + } +} + +// Unvariadic2 converts a variadic function with 2 fixed parameters into a function taking a slice and returning Either. +func Unvariadic2[T1, T2, V, R any](f func(T1, T2, ...V) (R, error)) func(T1, T2, []V) Either[error, R] { + return func(t1 T1, t2 T2, v []V) Either[error, R] { + return TryCatchError(f(t1, t2, v...)) + } +} + +// Unvariadic3 converts a variadic function with 3 fixed parameters into a function taking a slice and returning Either. +func Unvariadic3[T1, T2, T3, V, R any](f func(T1, T2, T3, ...V) (R, error)) func(T1, T2, T3, []V) Either[error, R] { + return func(t1 T1, t2 T2, t3 T3, v []V) Either[error, R] { + return TryCatchError(f(t1, t2, t3, v...)) + } +} + +// Unvariadic4 converts a variadic function with 4 fixed parameters into a function taking a slice and returning Either. +func Unvariadic4[T1, T2, T3, T4, V, R any](f func(T1, T2, T3, T4, ...V) (R, error)) func(T1, T2, T3, T4, []V) Either[error, R] { + return func(t1 T1, t2 T2, t3 T3, t4 T4, v []V) Either[error, R] { + return TryCatchError(f(t1, t2, t3, t4, v...)) + } +} diff --git a/v2/endomorphism/curry.go b/v2/endomorphism/curry.go new file mode 100644 index 0000000..7c5fa92 --- /dev/null +++ b/v2/endomorphism/curry.go @@ -0,0 +1,73 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package endomorphism + +import ( + "github.com/IBM/fp-go/v2/function" +) + +// Curry2 curries a binary function that returns an endomorphism. +// +// This function takes a binary function f(T0, T1) T1 and converts it into a +// curried form that returns an endomorphism. The result is a function that +// takes the first argument and returns an endomorphism (a function T1 -> T1). +// +// Deprecated: This function is no longer needed. Use function.Curry2 directly +// from the function package instead. +// +// Parameters: +// - f: A binary function where the return type matches the second parameter type +// +// Returns: +// - A curried function that takes T0 and returns an Endomorphism[T1] +// +// Example: +// +// // A binary function that adds two numbers +// add := func(x, y int) int { return x + y } +// curriedAdd := endomorphism.Curry2(add) +// addFive := curriedAdd(5) // Returns an endomorphism that adds 5 +// result := addFive(10) // Returns: 15 +func Curry2[FCT ~func(T0, T1) T1, T0, T1 any](f FCT) func(T0) Endomorphism[T1] { + return function.Curry2(f) +} + +// Curry3 curries a ternary function that returns an endomorphism. +// +// This function takes a ternary function f(T0, T1, T2) T2 and converts it into +// a curried form. The result is a function that takes the first argument, returns +// a function that takes the second argument, and finally returns an endomorphism +// (a function T2 -> T2). +// +// Deprecated: This function is no longer needed. Use function.Curry3 directly +// from the function package instead. +// +// Parameters: +// - f: A ternary function where the return type matches the third parameter type +// +// Returns: +// - A curried function that takes T0, then T1, and returns an Endomorphism[T2] +// +// Example: +// +// // A ternary function +// combine := func(x, y, z int) int { return x + y + z } +// curriedCombine := endomorphism.Curry3(combine) +// addTen := curriedCombine(5)(5) // Returns an endomorphism that adds 10 +// result := addTen(20) // Returns: 30 +func Curry3[FCT ~func(T0, T1, T2) T2, T0, T1, T2 any](f FCT) func(T0) func(T1) Endomorphism[T2] { + return function.Curry3(f) +} diff --git a/v2/endomorphism/doc.go b/v2/endomorphism/doc.go new file mode 100644 index 0000000..27249cc --- /dev/null +++ b/v2/endomorphism/doc.go @@ -0,0 +1,86 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +// Package endomorphism provides functional programming utilities for working with endomorphisms. +// +// An endomorphism is a function from a type to itself: func(A) A. This package provides +// various operations and algebraic structures for composing and manipulating endomorphisms. +// +// # Core Concepts +// +// An Endomorphism[A] is simply a function that takes a value of type A and returns a value +// of the same type A. This simple concept has powerful algebraic properties: +// +// - Identity: The identity function is an endomorphism +// - Composition: Endomorphisms can be composed to form new endomorphisms +// - Monoid: Endomorphisms form a monoid under composition with identity as the empty element +// +// # Basic Usage +// +// Creating and composing endomorphisms: +// +// import ( +// "github.com/IBM/fp-go/v2/endomorphism" +// ) +// +// // Define some endomorphisms +// double := func(x int) int { return x * 2 } +// increment := func(x int) int { return x + 1 } +// +// // Compose them +// doubleAndIncrement := endomorphism.Compose(double, increment) +// result := doubleAndIncrement(5) // (5 * 2) + 1 = 11 +// +// # Monoid Operations +// +// Endomorphisms form a monoid, which means you can combine multiple endomorphisms: +// +// import ( +// "github.com/IBM/fp-go/v2/endomorphism" +// M "github.com/IBM/fp-go/v2/monoid" +// ) +// +// // Get the monoid for int endomorphisms +// monoid := endomorphism.Monoid[int]() +// +// // Combine multiple endomorphisms +// combined := M.ConcatAll(monoid)( +// func(x int) int { return x * 2 }, +// func(x int) int { return x + 1 }, +// func(x int) int { return x * 3 }, +// ) +// result := combined(5) // ((5 * 2) + 1) * 3 = 33 +// +// # Monad Operations +// +// The package also provides monadic operations for endomorphisms: +// +// // Chain allows sequencing of endomorphisms +// f := func(x int) int { return x * 2 } +// g := func(x int) int { return x + 1 } +// chained := endomorphism.MonadChain(f, g) +// +// # Type Safety +// +// The package uses Go generics to ensure type safety. All operations preserve +// the type of the endomorphism, preventing type mismatches at compile time. +// +// # Related Packages +// +// - function: Provides general function composition utilities +// - identity: Provides identity functor operations +// - monoid: Provides monoid algebraic structure +// - semigroup: Provides semigroup algebraic structure +package endomorphism diff --git a/v2/endomorphism/endo.go b/v2/endomorphism/endo.go new file mode 100644 index 0000000..028fba1 --- /dev/null +++ b/v2/endomorphism/endo.go @@ -0,0 +1,131 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package endomorphism + +import ( + "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/identity" +) + +// MonadAp applies an endomorphism to a value in a monadic context. +// +// This function applies the endomorphism fab to the value fa, returning the result. +// It's the monadic application operation for endomorphisms. +// +// Parameters: +// - fab: An endomorphism to apply +// - fa: The value to apply the endomorphism to +// +// Returns: +// - The result of applying fab to fa +// +// Example: +// +// double := func(x int) int { return x * 2 } +// result := endomorphism.MonadAp(double, 5) // Returns: 10 +func MonadAp[A any](fab Endomorphism[A], fa A) A { + return identity.MonadAp(fab, fa) +} + +// Ap returns a function that applies a value to an endomorphism. +// +// This is the curried version of MonadAp. It takes a value and returns a function +// that applies that value to any endomorphism. +// +// Parameters: +// - fa: The value to be applied +// +// Returns: +// - A function that takes an endomorphism and applies fa to it +// +// Example: +// +// applyFive := endomorphism.Ap(5) +// double := func(x int) int { return x * 2 } +// result := applyFive(double) // Returns: 10 +func Ap[A any](fa A) func(Endomorphism[A]) A { + return identity.Ap[A](fa) +} + +// Compose composes two endomorphisms into a single endomorphism. +// +// Given two endomorphisms f1 and f2, Compose returns a new endomorphism that +// applies f1 first, then applies f2 to the result. This is function composition: +// Compose(f1, f2)(x) = f2(f1(x)) +// +// Composition is associative: Compose(Compose(f, g), h) = Compose(f, Compose(g, h)) +// +// Parameters: +// - f1: The first endomorphism to apply +// - f2: The second endomorphism to apply +// +// Returns: +// - A new endomorphism that is the composition of f1 and f2 +// +// Example: +// +// double := func(x int) int { return x * 2 } +// increment := func(x int) int { return x + 1 } +// doubleAndIncrement := endomorphism.Compose(double, increment) +// result := doubleAndIncrement(5) // (5 * 2) + 1 = 11 +func Compose[A any](f1, f2 Endomorphism[A]) Endomorphism[A] { + return function.Flow2(f1, f2) +} + +// MonadChain chains two endomorphisms together. +// +// This is the monadic bind operation for endomorphisms. It composes two endomorphisms +// ma and f, returning a new endomorphism that applies ma first, then f. +// MonadChain is equivalent to Compose. +// +// Parameters: +// - ma: The first endomorphism in the chain +// - f: The second endomorphism in the chain +// +// Returns: +// - A new endomorphism that chains ma and f +// +// Example: +// +// double := func(x int) int { return x * 2 } +// increment := func(x int) int { return x + 1 } +// chained := endomorphism.MonadChain(double, increment) +// result := chained(5) // (5 * 2) + 1 = 11 +func MonadChain[A any](ma Endomorphism[A], f Endomorphism[A]) Endomorphism[A] { + return Compose(ma, f) +} + +// Chain returns a function that chains an endomorphism with another. +// +// This is the curried version of MonadChain. It takes an endomorphism f and returns +// a function that chains any endomorphism with f. +// +// Parameters: +// - f: The endomorphism to chain with +// +// Returns: +// - A function that takes an endomorphism and chains it with f +// +// Example: +// +// increment := func(x int) int { return x + 1 } +// chainWithIncrement := endomorphism.Chain(increment) +// double := func(x int) int { return x * 2 } +// chained := chainWithIncrement(double) +// result := chained(5) // (5 * 2) + 1 = 11 +func Chain[A any](f Endomorphism[A]) Endomorphism[Endomorphism[A]] { + return function.Bind2nd(MonadChain, f) +} diff --git a/v2/endomorphism/endomorphism_test.go b/v2/endomorphism/endomorphism_test.go new file mode 100644 index 0000000..8c2d9e0 --- /dev/null +++ b/v2/endomorphism/endomorphism_test.go @@ -0,0 +1,399 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package endomorphism + +import ( + "testing" + + M "github.com/IBM/fp-go/v2/monoid" + S "github.com/IBM/fp-go/v2/semigroup" + "github.com/stretchr/testify/assert" +) + +// Test helper functions +func double(x int) int { + return x * 2 +} + +func increment(x int) int { + return x + 1 +} + +func square(x int) int { + return x * x +} + +func negate(x int) int { + return -x +} + +// TestCurry2 tests the Curry2 function +func TestCurry2(t *testing.T) { + add := func(x, y int) int { + return x + y + } + + curriedAdd := Curry2(add) + addFive := curriedAdd(5) + + result := addFive(10) + assert.Equal(t, 15, result, "Curry2 should curry binary function correctly") + + // Test with different values + addTen := curriedAdd(10) + assert.Equal(t, 25, addTen(15), "Curry2 should work with different values") +} + +// TestCurry3 tests the Curry3 function +func TestCurry3(t *testing.T) { + combine := func(x, y, z int) int { + return x + y + z + } + + curriedCombine := Curry3(combine) + addTen := curriedCombine(5)(5) + + result := addTen(20) + assert.Equal(t, 30, result, "Curry3 should curry ternary function correctly") + + // Test with different values + addFifteen := curriedCombine(5)(10) + assert.Equal(t, 35, addFifteen(20), "Curry3 should work with different values") +} + +// TestMonadAp tests the MonadAp function +func TestMonadAp(t *testing.T) { + result := MonadAp(double, 5) + assert.Equal(t, 10, result, "MonadAp should apply endomorphism to value") + + result2 := MonadAp(increment, 10) + assert.Equal(t, 11, result2, "MonadAp should work with different endomorphisms") + + result3 := MonadAp(square, 4) + assert.Equal(t, 16, result3, "MonadAp should work with square function") +} + +// TestAp tests the Ap function +func TestAp(t *testing.T) { + applyFive := Ap(5) + + result := applyFive(double) + assert.Equal(t, 10, result, "Ap should apply value to endomorphism") + + result2 := applyFive(increment) + assert.Equal(t, 6, result2, "Ap should work with different endomorphisms") + + applyTen := Ap(10) + result3 := applyTen(square) + assert.Equal(t, 100, result3, "Ap should work with different values") +} + +// TestCompose tests the Compose function +func TestCompose(t *testing.T) { + // Test basic composition: (5 * 2) + 1 = 11 + doubleAndIncrement := Compose(double, increment) + result := doubleAndIncrement(5) + assert.Equal(t, 11, result, "Compose should compose endomorphisms correctly") + + // Test composition order: (5 + 1) * 2 = 12 + incrementAndDouble := Compose(increment, double) + result2 := incrementAndDouble(5) + assert.Equal(t, 12, result2, "Compose should respect order of composition") + + // Test with three compositions: ((5 * 2) + 1) * ((5 * 2) + 1) = 121 + complex := Compose(Compose(double, increment), square) + result3 := complex(5) + assert.Equal(t, 121, result3, "Compose should work with nested compositions") +} + +// TestMonadChain tests the MonadChain function +func TestMonadChain(t *testing.T) { + // MonadChain should behave like Compose + chained := MonadChain(double, increment) + result := chained(5) + assert.Equal(t, 11, result, "MonadChain should chain endomorphisms correctly") + + chained2 := MonadChain(increment, double) + result2 := chained2(5) + assert.Equal(t, 12, result2, "MonadChain should respect order") + + // Test with negative values + chained3 := MonadChain(negate, increment) + result3 := chained3(5) + assert.Equal(t, -4, result3, "MonadChain should work with negative values") +} + +// TestChain tests the Chain function +func TestChain(t *testing.T) { + chainWithIncrement := Chain(increment) + + chained := chainWithIncrement(double) + result := chained(5) + assert.Equal(t, 11, result, "Chain should create chaining function correctly") + + chainWithDouble := Chain(double) + chained2 := chainWithDouble(increment) + result2 := chained2(5) + assert.Equal(t, 12, result2, "Chain should work with different endomorphisms") + + // Test chaining with square + chainWithSquare := Chain(square) + chained3 := chainWithSquare(double) + result3 := chained3(3) + assert.Equal(t, 36, result3, "Chain should work with square function") +} + +// TestOf tests the Of function +func TestOf(t *testing.T) { + endo := Of(double) + result := endo(5) + assert.Equal(t, 10, result, "Of should convert function to endomorphism") + + endo2 := Of(increment) + result2 := endo2(10) + assert.Equal(t, 11, result2, "Of should work with different functions") +} + +// TestWrap tests the Wrap function (deprecated) +func TestWrap(t *testing.T) { + endo := Wrap(double) + result := endo(5) + assert.Equal(t, 10, result, "Wrap should convert function to endomorphism") +} + +// TestUnwrap tests the Unwrap function (deprecated) +func TestUnwrap(t *testing.T) { + endo := Of(double) + unwrapped := Unwrap[func(int) int](endo) + result := unwrapped(5) + assert.Equal(t, 10, result, "Unwrap should convert endomorphism to function") +} + +// TestIdentity tests the Identity function +func TestIdentity(t *testing.T) { + id := Identity[int]() + + // Identity should return input unchanged + assert.Equal(t, 42, id(42), "Identity should return input unchanged") + assert.Equal(t, 0, id(0), "Identity should work with zero") + assert.Equal(t, -10, id(-10), "Identity should work with negative values") + + // Identity should be neutral for composition + composed1 := Compose(id, double) + assert.Equal(t, 10, composed1(5), "Identity should be right neutral for composition") + + composed2 := Compose(double, id) + assert.Equal(t, 10, composed2(5), "Identity should be left neutral for composition") + + // Test with strings + idStr := Identity[string]() + assert.Equal(t, "hello", idStr("hello"), "Identity should work with strings") +} + +// TestSemigroup tests the Semigroup function +func TestSemigroup(t *testing.T) { + sg := Semigroup[int]() + + // Test basic concat + combined := sg.Concat(double, increment) + result := combined(5) + assert.Equal(t, 11, result, "Semigroup concat should compose endomorphisms") + + // Test associativity: (f . g) . h = f . (g . h) + f := double + g := increment + h := square + + left := sg.Concat(sg.Concat(f, g), h) + right := sg.Concat(f, sg.Concat(g, h)) + + testValue := 3 + assert.Equal(t, left(testValue), right(testValue), "Semigroup should be associative") + + // Test with ConcatAll from semigroup package + combined2 := S.ConcatAll(sg)(double)([]Endomorphism[int]{increment, square}) + result2 := combined2(5) + assert.Equal(t, 121, result2, "Semigroup should work with ConcatAll") +} + +// TestMonoid tests the Monoid function +func TestMonoid(t *testing.T) { + monoid := Monoid[int]() + + // Test that empty is identity + empty := monoid.Empty() + assert.Equal(t, 42, empty(42), "Monoid empty should be identity") + + // Test right identity: x . empty = x + rightIdentity := monoid.Concat(double, empty) + assert.Equal(t, 10, rightIdentity(5), "Monoid should satisfy right identity") + + // Test left identity: empty . x = x + leftIdentity := monoid.Concat(empty, double) + assert.Equal(t, 10, leftIdentity(5), "Monoid should satisfy left identity") + + // Test ConcatAll with multiple endomorphisms + combined := M.ConcatAll(monoid)([]Endomorphism[int]{double, increment, square}) + result := combined(5) + // (5 * 2) = 10, (10 + 1) = 11, (11 * 11) = 121 + assert.Equal(t, 121, result, "Monoid should work with ConcatAll") + + // Test ConcatAll with empty list should return identity + emptyResult := M.ConcatAll(monoid)([]Endomorphism[int]{}) + assert.Equal(t, 42, emptyResult(42), "ConcatAll with no args should return identity") +} + +// TestMonoidLaws tests that the Monoid satisfies monoid laws +func TestMonoidLaws(t *testing.T) { + monoid := Monoid[int]() + empty := monoid.Empty() + + testCases := []struct { + name string + f Endomorphism[int] + g Endomorphism[int] + h Endomorphism[int] + }{ + {"basic", double, increment, square}, + {"with negate", negate, double, increment}, + {"with identity", Identity[int](), double, increment}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + testValue := 5 + + // Right identity: x . empty = x + rightId := monoid.Concat(tc.f, empty) + assert.Equal(t, tc.f(testValue), rightId(testValue), "Right identity law") + + // Left identity: empty . x = x + leftId := monoid.Concat(empty, tc.f) + assert.Equal(t, tc.f(testValue), leftId(testValue), "Left identity law") + + // Associativity: (f . g) . h = f . (g . h) + left := monoid.Concat(monoid.Concat(tc.f, tc.g), tc.h) + right := monoid.Concat(tc.f, monoid.Concat(tc.g, tc.h)) + assert.Equal(t, left(testValue), right(testValue), "Associativity law") + }) + } +} + +// TestEndomorphismWithDifferentTypes tests endomorphisms with different types +func TestEndomorphismWithDifferentTypes(t *testing.T) { + // Test with strings + toUpper := func(s string) string { + return s + "!" + } + addPrefix := func(s string) string { + return "Hello, " + s + } + + strComposed := Compose(toUpper, addPrefix) + result := strComposed("World") + assert.Equal(t, "Hello, World!", result, "Endomorphism should work with strings") + + // Test with float64 + doubleFloat := func(x float64) float64 { + return x * 2.0 + } + addOne := func(x float64) float64 { + return x + 1.0 + } + + floatComposed := Compose(doubleFloat, addOne) + resultFloat := floatComposed(5.5) + assert.Equal(t, 12.0, resultFloat, "Endomorphism should work with float64") +} + +// TestComplexCompositions tests more complex composition scenarios +func TestComplexCompositions(t *testing.T) { + // Create a pipeline of transformations + pipeline := Compose( + Compose( + Compose(double, increment), + square, + ), + negate, + ) + + // (5 * 2) = 10, (10 + 1) = 11, (11 * 11) = 121, -(121) = -121 + result := pipeline(5) + assert.Equal(t, -121, result, "Complex composition should work correctly") + + // Test using monoid to build the same pipeline + monoid := Monoid[int]() + pipelineMonoid := M.ConcatAll(monoid)([]Endomorphism[int]{double, increment, square, negate}) + resultMonoid := pipelineMonoid(5) + assert.Equal(t, -121, resultMonoid, "Monoid-based pipeline should match composition") +} + +// TestOperatorType tests the Operator type +func TestOperatorType(t *testing.T) { + // Create an operator that lifts an int endomorphism to work on the length of strings + lengthOperator := func(f Endomorphism[int]) Endomorphism[string] { + return func(s string) string { + newLen := f(len(s)) + if newLen > len(s) { + // Pad with spaces + for i := len(s); i < newLen; i++ { + s += " " + } + } else if newLen < len(s) { + // Truncate + s = s[:newLen] + } + return s + } + } + + // Use the operator + var op Operator[int, string] = lengthOperator + doubleLength := op(double) + + result := doubleLength("hello") // len("hello") = 5, 5 * 2 = 10 + assert.Equal(t, 10, len(result), "Operator should transform endomorphisms correctly") + assert.Equal(t, "hello ", result, "Operator should pad string correctly") +} + +// BenchmarkCompose benchmarks the Compose function +func BenchmarkCompose(b *testing.B) { + composed := Compose(double, increment) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = composed(5) + } +} + +// BenchmarkMonoidConcatAll benchmarks ConcatAll with monoid +func BenchmarkMonoidConcatAll(b *testing.B) { + monoid := Monoid[int]() + combined := M.ConcatAll(monoid)([]Endomorphism[int]{double, increment, square}) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = combined(5) + } +} + +// BenchmarkChain benchmarks the Chain function +func BenchmarkChain(b *testing.B) { + chainWithIncrement := Chain(increment) + chained := chainWithIncrement(double) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = chained(5) + } +} diff --git a/v2/endomorphism/monoid.go b/v2/endomorphism/monoid.go new file mode 100644 index 0000000..bf26cc8 --- /dev/null +++ b/v2/endomorphism/monoid.go @@ -0,0 +1,143 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package endomorphism + +import ( + "github.com/IBM/fp-go/v2/function" + M "github.com/IBM/fp-go/v2/monoid" + S "github.com/IBM/fp-go/v2/semigroup" +) + +// Of converts any function to an Endomorphism. +// +// This function provides a way to explicitly convert a function with the signature +// func(A) A into an Endomorphism[A] type. Due to Go's type system, this is often +// not necessary as the types are compatible, but it can be useful for clarity. +// +// Parameters: +// - f: A function from type A to type A +// +// Returns: +// - The same function as an Endomorphism[A] +// +// Example: +// +// myFunc := func(x int) int { return x * 2 } +// endo := endomorphism.Of(myFunc) +func Of[F ~func(A) A, A any](f F) Endomorphism[A] { + return f +} + +// Wrap converts any function to an Endomorphism. +// +// Deprecated: This function is no longer needed due to Go's type compatibility. +// You can directly use functions where Endomorphism is expected. +func Wrap[F ~func(A) A, A any](f F) Endomorphism[A] { + return f +} + +// Unwrap converts any Endomorphism to a function. +// +// Deprecated: This function is no longer needed due to Go's type compatibility. +// Endomorphisms can be used directly as functions. +func Unwrap[F ~func(A) A, A any](f Endomorphism[A]) F { + return f +} + +// Identity returns the identity endomorphism. +// +// The identity endomorphism is a function that returns its input unchanged. +// It serves as the identity element for endomorphism composition, meaning: +// - Compose(Identity(), f) = f +// - Compose(f, Identity()) = f +// +// This is the empty element of the endomorphism monoid. +// +// Returns: +// - An endomorphism that returns its input unchanged +// +// Example: +// +// id := endomorphism.Identity[int]() +// result := id(42) // Returns: 42 +// +// // Identity is neutral for composition +// double := func(x int) int { return x * 2 } +// composed := endomorphism.Compose(id, double) +// // composed behaves exactly like double +func Identity[A any]() Endomorphism[A] { + return function.Identity[A] +} + +// Semigroup returns a Semigroup for endomorphisms where the concat operation is function composition. +// +// A semigroup is an algebraic structure with an associative binary operation. +// For endomorphisms, this operation is composition (Compose). This means: +// - Concat(f, Concat(g, h)) = Concat(Concat(f, g), h) +// +// The returned semigroup can be used with semigroup operations to combine +// multiple endomorphisms. +// +// Returns: +// - A Semigroup[Endomorphism[A]] where concat is composition +// +// Example: +// +// import S "github.com/IBM/fp-go/v2/semigroup" +// +// sg := endomorphism.Semigroup[int]() +// double := func(x int) int { return x * 2 } +// increment := func(x int) int { return x + 1 } +// +// // Combine using the semigroup +// combined := sg.Concat(double, increment) +// result := combined(5) // (5 * 2) + 1 = 11 +func Semigroup[A any]() S.Semigroup[Endomorphism[A]] { + return S.MakeSemigroup(Compose[A]) +} + +// Monoid returns a Monoid for endomorphisms where concat is composition and empty is identity. +// +// A monoid is a semigroup with an identity element. For endomorphisms: +// - The binary operation is composition (Compose) +// - The identity element is the identity function (Identity) +// +// This satisfies the monoid laws: +// - Right identity: Concat(x, Empty) = x +// - Left identity: Concat(Empty, x) = x +// - Associativity: Concat(x, Concat(y, z)) = Concat(Concat(x, y), z) +// +// The returned monoid can be used with monoid operations like ConcatAll to +// combine multiple endomorphisms. +// +// Returns: +// - A Monoid[Endomorphism[A]] with composition and identity +// +// Example: +// +// import M "github.com/IBM/fp-go/v2/monoid" +// +// monoid := endomorphism.Monoid[int]() +// double := func(x int) int { return x * 2 } +// increment := func(x int) int { return x + 1 } +// square := func(x int) int { return x * x } +// +// // Combine multiple endomorphisms +// combined := M.ConcatAll(monoid)(double, increment, square) +// result := combined(5) // ((5 * 2) + 1) * ((5 * 2) + 1) = 121 +func Monoid[A any]() M.Monoid[Endomorphism[A]] { + return M.MakeMonoid(Compose[A], Identity[A]()) +} diff --git a/v2/endomorphism/types.go b/v2/endomorphism/types.go new file mode 100644 index 0000000..9737a45 --- /dev/null +++ b/v2/endomorphism/types.go @@ -0,0 +1,56 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package endomorphism + +type ( + // Endomorphism represents a function from a type to itself. + // + // An endomorphism is a unary function that takes a value of type A and returns + // a value of the same type A. Mathematically, it's a function A → A. + // + // Endomorphisms have several important properties: + // - They can be composed: if f and g are endomorphisms, then f ∘ g is also an endomorphism + // - The identity function is an endomorphism + // - They form a monoid under composition + // + // Example: + // + // // Simple endomorphisms on integers + // double := func(x int) int { return x * 2 } + // increment := func(x int) int { return x + 1 } + // + // // Both are endomorphisms of type Endomorphism[int] + // var f endomorphism.Endomorphism[int] = double + // var g endomorphism.Endomorphism[int] = increment + Endomorphism[A any] = func(A) A + + // Operator represents a transformation from one endomorphism to another. + // + // An Operator takes an endomorphism on type A and produces an endomorphism on type B. + // This is useful for lifting operations or transforming endomorphisms in a generic way. + // + // Example: + // + // // An operator that converts an int endomorphism to a string endomorphism + // intToString := func(f endomorphism.Endomorphism[int]) endomorphism.Endomorphism[string] { + // return func(s string) string { + // n, _ := strconv.Atoi(s) + // result := f(n) + // return strconv.Itoa(result) + // } + // } + Operator[A, B any] = func(Endomorphism[A]) Endomorphism[B] +) diff --git a/v2/eq/contramap.go b/v2/eq/contramap.go new file mode 100644 index 0000000..bb59c26 --- /dev/null +++ b/v2/eq/contramap.go @@ -0,0 +1,26 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package eq + +// Contramap implements an Equals predicate based on a mapping +func Contramap[A, B any](f func(b B) A) func(Eq[A]) Eq[B] { + return func(fa Eq[A]) Eq[B] { + equals := fa.Equals + return FromEquals(func(x, y B) bool { + return equals(f(x), f(y)) + }) + } +} diff --git a/v2/eq/doc.go b/v2/eq/doc.go new file mode 100644 index 0000000..a9ade95 --- /dev/null +++ b/v2/eq/doc.go @@ -0,0 +1,206 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +/* +Package eq provides type-safe equality comparisons for any type in Go. + +# Overview + +The eq package implements the Eq type class from functional programming, which +represents types that support equality comparison. Unlike Go's built-in == operator +which only works with comparable types, Eq allows defining custom equality semantics +for any type, including complex structures, functions, and non-comparable types. + +# Core Concepts + +The Eq[T] interface represents an equality predicate for type T: + + type Eq[T any] interface { + Equals(x, y T) bool + } + +This abstraction enables: + - Custom equality semantics for any type + - Composition of equality predicates + - Contravariant mapping to transform equality predicates + - Monoid structure for combining multiple equality checks + +# Basic Usage + +Creating equality predicates for comparable types: + + // For built-in comparable types + intEq := eq.FromStrictEquals[int]() + assert.True(t, intEq.Equals(42, 42)) + assert.False(t, intEq.Equals(42, 43)) + + stringEq := eq.FromStrictEquals[string]() + assert.True(t, stringEq.Equals("hello", "hello")) + +Creating custom equality predicates: + + // Case-insensitive string equality + caseInsensitiveEq := eq.FromEquals(func(a, b string) bool { + return strings.EqualFold(a, b) + }) + assert.True(t, caseInsensitiveEq.Equals("Hello", "HELLO")) + + // Approximate float equality + approxEq := eq.FromEquals(func(a, b float64) bool { + return math.Abs(a-b) < 0.0001 + }) + assert.True(t, approxEq.Equals(1.0, 1.00009)) + +# Contramap - Transforming Equality + +Contramap allows you to create an equality predicate for type B from an equality +predicate for type A, given a function from B to A. This is useful for comparing +complex types by extracting comparable fields: + + type Person struct { + ID int + Name string + Age int + } + + // Compare persons by ID only + personEqByID := eq.Contramap(func(p Person) int { + return p.ID + })(eq.FromStrictEquals[int]()) + + p1 := Person{ID: 1, Name: "Alice", Age: 30} + p2 := Person{ID: 1, Name: "Bob", Age: 25} + assert.True(t, personEqByID.Equals(p1, p2)) // Same ID + + // Compare persons by name + personEqByName := eq.Contramap(func(p Person) string { + return p.Name + })(eq.FromStrictEquals[string]()) + + assert.False(t, personEqByName.Equals(p1, p2)) // Different names + +# Semigroup and Monoid + +The eq package provides Semigroup and Monoid instances for Eq[A], allowing you to +combine multiple equality predicates using logical AND: + + type User struct { + Username string + Email string + } + + // Compare by username + usernameEq := eq.Contramap(func(u User) string { + return u.Username + })(eq.FromStrictEquals[string]()) + + // Compare by email + emailEq := eq.Contramap(func(u User) string { + return u.Email + })(eq.FromStrictEquals[string]()) + + // Combine: users are equal if BOTH username AND email match + userEq := eq.Semigroup[User]().Concat(usernameEq, emailEq) + + u1 := User{Username: "alice", Email: "alice@example.com"} + u2 := User{Username: "alice", Email: "alice@example.com"} + u3 := User{Username: "alice", Email: "different@example.com"} + + assert.True(t, userEq.Equals(u1, u2)) // Both match + assert.False(t, userEq.Equals(u1, u3)) // Email differs + +The Monoid provides an identity element (Empty) that always returns true: + + monoid := eq.Monoid[int]() + alwaysTrue := monoid.Empty() + assert.True(t, alwaysTrue.Equals(1, 2)) // Always true + +# Curried Equality + +The Equals function provides a curried version of equality checking, useful for +partial application and functional composition: + + intEq := eq.FromStrictEquals[int]() + equals42 := eq.Equals(intEq)(42) + + assert.True(t, equals42(42)) + assert.False(t, equals42(43)) + + // Use in higher-order functions + numbers := []int{40, 41, 42, 43, 44} + filtered := array.Filter(equals42)(numbers) + // filtered = [42] + +# Advanced Examples + +Comparing slices element-wise: + + sliceEq := eq.FromEquals(func(a, b []int) bool { + if len(a) != len(b) { + return false + } + intEq := eq.FromStrictEquals[int]() + for i := range a { + if !intEq.Equals(a[i], b[i]) { + return false + } + } + return true + }) + + assert.True(t, sliceEq.Equals([]int{1, 2, 3}, []int{1, 2, 3})) + assert.False(t, sliceEq.Equals([]int{1, 2, 3}, []int{1, 2, 4})) + +Comparing maps: + + mapEq := eq.FromEquals(func(a, b map[string]int) bool { + if len(a) != len(b) { + return false + } + for k, v := range a { + if bv, ok := b[k]; !ok || v != bv { + return false + } + } + return true + }) + +# Type Class Laws + +Eq instances should satisfy the following laws: + +1. Reflexivity: For all x, Equals(x, x) = true +2. Symmetry: For all x, y, Equals(x, y) = Equals(y, x) +3. Transitivity: If Equals(x, y) and Equals(y, z), then Equals(x, z) + +These laws ensure that Eq behaves as a proper equivalence relation. + +# Functions + + - FromStrictEquals[T comparable]() - Create Eq from Go's == operator + - FromEquals[T any](func(x, y T) bool) - Create Eq from custom comparison + - Empty[T any]() - Create Eq that always returns true + - Equals[T any](Eq[T]) - Curried equality checking + - Contramap[A, B any](func(B) A) - Transform Eq by mapping input type + - Semigroup[A any]() - Combine Eq instances with logical AND + - Monoid[A any]() - Semigroup with identity element + +# Related Packages + + - ord: Provides ordering comparisons (less than, greater than) + - semigroup: Provides the Semigroup abstraction used by Eq + - monoid: Provides the Monoid abstraction used by Eq +*/ +package eq diff --git a/v2/eq/eq.go b/v2/eq/eq.go new file mode 100644 index 0000000..bff15bf --- /dev/null +++ b/v2/eq/eq.go @@ -0,0 +1,58 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package eq + +import ( + F "github.com/IBM/fp-go/v2/function" +) + +type Eq[T any] interface { + Equals(x, y T) bool +} + +type eq[T any] struct { + c func(x, y T) bool +} + +func (e eq[T]) Equals(x, y T) bool { + return e.c(x, y) +} + +func strictEq[A comparable](a, b A) bool { + return a == b +} + +// FromStrictEquals constructs an [EQ.Eq] from the canonical comparison function +func FromStrictEquals[T comparable]() Eq[T] { + return FromEquals(strictEq[T]) +} + +// FromEquals constructs an [EQ.Eq] from the comparison function +func FromEquals[T any](c func(x, y T) bool) Eq[T] { + return eq[T]{c: c} +} + +// Empty returns the equals predicate that is always true +func Empty[T any]() Eq[T] { + return FromEquals(F.Constant2[T, T](true)) +} + +// Equals returns a predicate to test if one value equals the other under an equals predicate +func Equals[T any](eq Eq[T]) func(T) func(T) bool { + return func(other T) func(T) bool { + return F.Bind2nd(eq.Equals, other) + } +} diff --git a/v2/eq/eq_test.go b/v2/eq/eq_test.go new file mode 100644 index 0000000..4914d1b --- /dev/null +++ b/v2/eq/eq_test.go @@ -0,0 +1,443 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package eq + +import ( + "math" + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestFromStrictEquals(t *testing.T) { + t.Run("int equality", func(t *testing.T) { + intEq := FromStrictEquals[int]() + + assert.True(t, intEq.Equals(42, 42)) + assert.True(t, intEq.Equals(0, 0)) + assert.True(t, intEq.Equals(-1, -1)) + + assert.False(t, intEq.Equals(42, 43)) + assert.False(t, intEq.Equals(0, 1)) + assert.False(t, intEq.Equals(-1, 1)) + }) + + t.Run("string equality", func(t *testing.T) { + stringEq := FromStrictEquals[string]() + + assert.True(t, stringEq.Equals("hello", "hello")) + assert.True(t, stringEq.Equals("", "")) + assert.True(t, stringEq.Equals("test", "test")) + + assert.False(t, stringEq.Equals("hello", "Hello")) + assert.False(t, stringEq.Equals("hello", "world")) + assert.False(t, stringEq.Equals("", "test")) + }) + + t.Run("float equality", func(t *testing.T) { + floatEq := FromStrictEquals[float64]() + + assert.True(t, floatEq.Equals(1.0, 1.0)) + assert.True(t, floatEq.Equals(0.0, 0.0)) + assert.True(t, floatEq.Equals(-1.5, -1.5)) + + assert.False(t, floatEq.Equals(1.0, 1.1)) + assert.False(t, floatEq.Equals(0.0, 0.1)) + }) + + t.Run("bool equality", func(t *testing.T) { + boolEq := FromStrictEquals[bool]() + + assert.True(t, boolEq.Equals(true, true)) + assert.True(t, boolEq.Equals(false, false)) + + assert.False(t, boolEq.Equals(true, false)) + assert.False(t, boolEq.Equals(false, true)) + }) +} + +func TestFromEquals(t *testing.T) { + t.Run("case-insensitive string equality", func(t *testing.T) { + caseInsensitiveEq := FromEquals(func(a, b string) bool { + return strings.EqualFold(a, b) + }) + + assert.True(t, caseInsensitiveEq.Equals("hello", "HELLO")) + assert.True(t, caseInsensitiveEq.Equals("Hello", "hello")) + assert.True(t, caseInsensitiveEq.Equals("TeSt", "test")) + + assert.False(t, caseInsensitiveEq.Equals("hello", "world")) + }) + + t.Run("approximate float equality", func(t *testing.T) { + epsilon := 0.0001 + approxEq := FromEquals(func(a, b float64) bool { + return math.Abs(a-b) < epsilon + }) + + assert.True(t, approxEq.Equals(1.0, 1.0)) + assert.True(t, approxEq.Equals(1.0, 1.00009)) + assert.True(t, approxEq.Equals(1.00009, 1.0)) + + assert.False(t, approxEq.Equals(1.0, 1.001)) + assert.False(t, approxEq.Equals(1.0, 2.0)) + }) + + t.Run("custom struct equality", func(t *testing.T) { + type Point struct { + X, Y int + } + + pointEq := FromEquals(func(a, b Point) bool { + return a.X == b.X && a.Y == b.Y + }) + + assert.True(t, pointEq.Equals(Point{1, 2}, Point{1, 2})) + assert.True(t, pointEq.Equals(Point{0, 0}, Point{0, 0})) + + assert.False(t, pointEq.Equals(Point{1, 2}, Point{2, 1})) + assert.False(t, pointEq.Equals(Point{1, 2}, Point{1, 3})) + }) + + t.Run("slice equality", func(t *testing.T) { + sliceEq := FromEquals(func(a, b []int) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if a[i] != b[i] { + return false + } + } + return true + }) + + assert.True(t, sliceEq.Equals([]int{1, 2, 3}, []int{1, 2, 3})) + assert.True(t, sliceEq.Equals([]int{}, []int{})) + assert.True(t, sliceEq.Equals(nil, nil)) + + assert.False(t, sliceEq.Equals([]int{1, 2, 3}, []int{1, 2, 4})) + assert.False(t, sliceEq.Equals([]int{1, 2}, []int{1, 2, 3})) + assert.False(t, sliceEq.Equals([]int{1, 2, 3}, []int{})) + }) +} + +func TestEmpty(t *testing.T) { + t.Run("always returns true", func(t *testing.T) { + emptyInt := Empty[int]() + + assert.True(t, emptyInt.Equals(1, 2)) + assert.True(t, emptyInt.Equals(42, 43)) + assert.True(t, emptyInt.Equals(0, 100)) + assert.True(t, emptyInt.Equals(-1, 1)) + }) + + t.Run("works with any type", func(t *testing.T) { + emptyString := Empty[string]() + assert.True(t, emptyString.Equals("hello", "world")) + assert.True(t, emptyString.Equals("", "test")) + + emptyBool := Empty[bool]() + assert.True(t, emptyBool.Equals(true, false)) + + type Custom struct{ Value int } + emptyCustom := Empty[Custom]() + assert.True(t, emptyCustom.Equals(Custom{1}, Custom{2})) + }) +} + +func TestEquals(t *testing.T) { + t.Run("curried equality for int", func(t *testing.T) { + intEq := FromStrictEquals[int]() + equals42 := Equals(intEq)(42) + + assert.True(t, equals42(42)) + assert.False(t, equals42(43)) + assert.False(t, equals42(0)) + assert.False(t, equals42(-42)) + }) + + t.Run("curried equality for string", func(t *testing.T) { + stringEq := FromStrictEquals[string]() + equalsHello := Equals(stringEq)("hello") + + assert.True(t, equalsHello("hello")) + assert.False(t, equalsHello("Hello")) + assert.False(t, equalsHello("world")) + assert.False(t, equalsHello("")) + }) + + t.Run("partial application", func(t *testing.T) { + intEq := FromStrictEquals[int]() + equalsFunc := Equals(intEq) + + equals10 := equalsFunc(10) + equals20 := equalsFunc(20) + + assert.True(t, equals10(10)) + assert.False(t, equals10(20)) + + assert.True(t, equals20(20)) + assert.False(t, equals20(10)) + }) +} + +func TestContramap(t *testing.T) { + type Person struct { + ID int + Name string + Age int + } + + t.Run("compare by ID", func(t *testing.T) { + personEqByID := Contramap(func(p Person) int { + return p.ID + })(FromStrictEquals[int]()) + + p1 := Person{ID: 1, Name: "Alice", Age: 30} + p2 := Person{ID: 1, Name: "Bob", Age: 25} + p3 := Person{ID: 2, Name: "Alice", Age: 30} + + assert.True(t, personEqByID.Equals(p1, p2)) // Same ID + assert.False(t, personEqByID.Equals(p1, p3)) // Different ID + }) + + t.Run("compare by name", func(t *testing.T) { + personEqByName := Contramap(func(p Person) string { + return p.Name + })(FromStrictEquals[string]()) + + p1 := Person{ID: 1, Name: "Alice", Age: 30} + p2 := Person{ID: 2, Name: "Alice", Age: 25} + p3 := Person{ID: 1, Name: "Bob", Age: 30} + + assert.True(t, personEqByName.Equals(p1, p2)) // Same name + assert.False(t, personEqByName.Equals(p1, p3)) // Different name + }) + + t.Run("compare by age", func(t *testing.T) { + personEqByAge := Contramap(func(p Person) int { + return p.Age + })(FromStrictEquals[int]()) + + p1 := Person{ID: 1, Name: "Alice", Age: 30} + p2 := Person{ID: 2, Name: "Bob", Age: 30} + p3 := Person{ID: 1, Name: "Alice", Age: 25} + + assert.True(t, personEqByAge.Equals(p1, p2)) // Same age + assert.False(t, personEqByAge.Equals(p1, p3)) // Different age + }) + + t.Run("case-insensitive name comparison", func(t *testing.T) { + caseInsensitiveEq := FromEquals(func(a, b string) bool { + return strings.EqualFold(a, b) + }) + + personEqByNameCI := Contramap(func(p Person) string { + return p.Name + })(caseInsensitiveEq) + + p1 := Person{ID: 1, Name: "Alice", Age: 30} + p2 := Person{ID: 2, Name: "ALICE", Age: 25} + p3 := Person{ID: 1, Name: "Bob", Age: 30} + + assert.True(t, personEqByNameCI.Equals(p1, p2)) // Same name (case-insensitive) + assert.False(t, personEqByNameCI.Equals(p1, p3)) // Different name + }) + + t.Run("nested contramap", func(t *testing.T) { + type Address struct { + Street string + City string + } + + type User struct { + Name string + Address Address + } + + // Compare users by city + userEqByCity := Contramap(func(u User) string { + return u.Address.City + })(FromStrictEquals[string]()) + + u1 := User{Name: "Alice", Address: Address{Street: "Main St", City: "NYC"}} + u2 := User{Name: "Bob", Address: Address{Street: "Oak Ave", City: "NYC"}} + u3 := User{Name: "Charlie", Address: Address{Street: "Main St", City: "LA"}} + + assert.True(t, userEqByCity.Equals(u1, u2)) // Same city + assert.False(t, userEqByCity.Equals(u1, u3)) // Different city + }) +} + +func TestSemigroup(t *testing.T) { + type User struct { + Username string + Email string + } + + t.Run("combine two equality predicates", func(t *testing.T) { + usernameEq := Contramap(func(u User) string { + return u.Username + })(FromStrictEquals[string]()) + + emailEq := Contramap(func(u User) string { + return u.Email + })(FromStrictEquals[string]()) + + // Both username AND email must match + userEq := Semigroup[User]().Concat(usernameEq, emailEq) + + u1 := User{Username: "alice", Email: "alice@example.com"} + u2 := User{Username: "alice", Email: "alice@example.com"} + u3 := User{Username: "alice", Email: "different@example.com"} + u4 := User{Username: "bob", Email: "alice@example.com"} + + assert.True(t, userEq.Equals(u1, u2)) // Both match + assert.False(t, userEq.Equals(u1, u3)) // Email differs + assert.False(t, userEq.Equals(u1, u4)) // Username differs + }) + + t.Run("combine multiple equality predicates", func(t *testing.T) { + type Product struct { + ID int + Name string + Price float64 + } + + idEq := Contramap(func(p Product) int { + return p.ID + })(FromStrictEquals[int]()) + + nameEq := Contramap(func(p Product) string { + return p.Name + })(FromStrictEquals[string]()) + + priceEq := Contramap(func(p Product) float64 { + return p.Price + })(FromStrictEquals[float64]()) + + sg := Semigroup[Product]() + productEq := sg.Concat(sg.Concat(idEq, nameEq), priceEq) + + p1 := Product{ID: 1, Name: "Widget", Price: 9.99} + p2 := Product{ID: 1, Name: "Widget", Price: 9.99} + p3 := Product{ID: 1, Name: "Widget", Price: 10.99} + + assert.True(t, productEq.Equals(p1, p2)) // All match + assert.False(t, productEq.Equals(p1, p3)) // Price differs + }) + + t.Run("associativity", func(t *testing.T) { + eq1 := FromStrictEquals[int]() + eq2 := FromStrictEquals[int]() + eq3 := FromStrictEquals[int]() + + sg := Semigroup[int]() + + // (eq1 <> eq2) <> eq3 + left := sg.Concat(sg.Concat(eq1, eq2), eq3) + + // eq1 <> (eq2 <> eq3) + right := sg.Concat(eq1, sg.Concat(eq2, eq3)) + + // Both should behave the same + assert.True(t, left.Equals(42, 42)) + assert.True(t, right.Equals(42, 42)) + assert.False(t, left.Equals(42, 43)) + assert.False(t, right.Equals(42, 43)) + }) +} + +func TestMonoid(t *testing.T) { + t.Run("empty is identity", func(t *testing.T) { + intEq := FromStrictEquals[int]() + monoid := Monoid[int]() + empty := monoid.Empty() + + // empty <> eq = eq + leftIdentity := monoid.Concat(empty, intEq) + assert.True(t, leftIdentity.Equals(42, 42)) + assert.False(t, leftIdentity.Equals(42, 43)) + + // eq <> empty = eq + rightIdentity := monoid.Concat(intEq, empty) + assert.True(t, rightIdentity.Equals(42, 42)) + assert.False(t, rightIdentity.Equals(42, 43)) + }) + + t.Run("empty always returns true", func(t *testing.T) { + monoid := Monoid[string]() + empty := monoid.Empty() + + assert.True(t, empty.Equals("hello", "world")) + assert.True(t, empty.Equals("", "test")) + assert.True(t, empty.Equals("same", "same")) + }) + + t.Run("concat with empty", func(t *testing.T) { + stringEq := FromStrictEquals[string]() + monoid := Monoid[string]() + + // Combining with empty should preserve the original behavior + combined := monoid.Concat(stringEq, monoid.Empty()) + + assert.True(t, combined.Equals("hello", "hello")) + assert.False(t, combined.Equals("hello", "world")) + }) +} + +// Test type class laws +func TestEqLaws(t *testing.T) { + intEq := FromStrictEquals[int]() + + t.Run("reflexivity", func(t *testing.T) { + // For all x, Equals(x, x) = true + values := []int{0, 1, -1, 42, 100, -100} + for _, x := range values { + assert.True(t, intEq.Equals(x, x), "reflexivity failed for %d", x) + } + }) + + t.Run("symmetry", func(t *testing.T) { + // For all x, y, Equals(x, y) = Equals(y, x) + pairs := [][2]int{ + {1, 1}, {1, 2}, {42, 42}, {0, 1}, {-1, 1}, + } + for _, pair := range pairs { + x, y := pair[0], pair[1] + assert.Equal(t, intEq.Equals(x, y), intEq.Equals(y, x), + "symmetry failed for (%d, %d)", x, y) + } + }) + + t.Run("transitivity", func(t *testing.T) { + // If Equals(x, y) and Equals(y, z), then Equals(x, z) + triples := [][3]int{ + {1, 1, 1}, + {42, 42, 42}, + {0, 0, 0}, + } + for _, triple := range triples { + x, y, z := triple[0], triple[1], triple[2] + if intEq.Equals(x, y) && intEq.Equals(y, z) { + assert.True(t, intEq.Equals(x, z), + "transitivity failed for (%d, %d, %d)", x, y, z) + } + } + }) +} diff --git a/v2/eq/monoid.go b/v2/eq/monoid.go new file mode 100644 index 0000000..12b2892 --- /dev/null +++ b/v2/eq/monoid.go @@ -0,0 +1,33 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package eq + +import ( + M "github.com/IBM/fp-go/v2/monoid" + S "github.com/IBM/fp-go/v2/semigroup" +) + +func Semigroup[A any]() S.Semigroup[Eq[A]] { + return S.MakeSemigroup(func(x, y Eq[A]) Eq[A] { + return FromEquals(func(a, b A) bool { + return x.Equals(a, b) && y.Equals(a, b) + }) + }) +} + +func Monoid[A any]() M.Monoid[Eq[A]] { + return M.MakeMonoid(Semigroup[A]().Concat, Empty[A]()) +} diff --git a/v2/eq/testing/eq.go b/v2/eq/testing/eq.go new file mode 100644 index 0000000..56c2d10 --- /dev/null +++ b/v2/eq/testing/eq.go @@ -0,0 +1,28 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package testing + +import ( + EQ "github.com/IBM/fp-go/v2/eq" + "github.com/stretchr/testify/assert" +) + +// Eq implements the equal operation based on `ObjectsAreEqualValues` from the assertion library +func Eq[A any]() EQ.Eq[A] { + return EQ.FromEquals(func(l, r A) bool { + return assert.ObjectsAreEqualValues(l, r) + }) +} diff --git a/v2/erasure/coverage.out b/v2/erasure/coverage.out new file mode 100644 index 0000000..0b180db --- /dev/null +++ b/v2/erasure/coverage.out @@ -0,0 +1,8 @@ +mode: set +github.com/IBM/fp-go/v2/erasure/erasure.go:58.28,60.2 1 1 +github.com/IBM/fp-go/v2/erasure/erasure.go:72.30,74.2 1 1 +github.com/IBM/fp-go/v2/erasure/erasure.go:90.51,96.2 1 1 +github.com/IBM/fp-go/v2/erasure/erasure.go:106.45,108.2 1 1 +github.com/IBM/fp-go/v2/erasure/erasure.go:118.54,124.2 1 1 +github.com/IBM/fp-go/v2/erasure/erasure.go:134.67,135.30 1 1 +github.com/IBM/fp-go/v2/erasure/erasure.go:135.30,137.3 1 1 diff --git a/v2/erasure/erasure.go b/v2/erasure/erasure.go new file mode 100644 index 0000000..75596a4 --- /dev/null +++ b/v2/erasure/erasure.go @@ -0,0 +1,138 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +// Package erasure provides utilities for type erasure and type-safe conversion between +// generic types and the any type. This is useful when working with heterogeneous collections +// or when interfacing with APIs that require type erasure. +// +// The package provides functions to: +// - Erase typed values to any (via pointers) +// - Unerase any values back to their original types +// - Safely unerase with error handling +// - Convert type-safe functions to erased functions +// +// Example usage: +// +// // Basic erasure and unerasure +// erased := erasure.Erase(42) +// value := erasure.Unerase[int](erased) // value == 42 +// +// // Safe unerasure with error handling +// result := erasure.SafeUnerase[int](erased) +// // result is Either[error, int] +// +// // Function erasure +// typedFunc := func(x int) string { return fmt.Sprintf("%d", x) } +// erasedFunc := erasure.Erase1(typedFunc) +// result := erasedFunc(erasure.Erase(42)) // returns erased "42" +package erasure + +import ( + E "github.com/IBM/fp-go/v2/either" + "github.com/IBM/fp-go/v2/errors" + F "github.com/IBM/fp-go/v2/function" +) + +// Erase converts a variable of type T to an any by returning a pointer to that variable. +// This allows type-safe storage and retrieval of values in heterogeneous collections. +// +// The value is stored as a pointer, which means the type information is preserved +// and can be recovered using Unerase or SafeUnerase. +// +// Example: +// +// erased := Erase(42) +// // erased is any, but internally holds *int +func Erase[T any](t T) any { + return &t +} + +// Unerase converts an erased variable back to its original value. +// This function panics if the type assertion fails, so use SafeUnerase +// for error handling. +// +// Example: +// +// erased := Erase(42) +// value := Unerase[int](erased) // value == 42 +// +// Panics if the erased value is not of type *T. +func Unerase[T any](t any) T { + return *t.(*T) +} + +// SafeUnerase converts an erased variable back to its original value with error handling. +// Returns Either[error, T] where Left contains an error if the type assertion fails, +// and Right contains the unerased value if successful. +// +// This is the safe alternative to Unerase that doesn't panic on type mismatch. +// +// Example: +// +// erased := Erase(42) +// result := SafeUnerase[int](erased) +// // result is Right(42) +// +// wrongType := SafeUnerase[string](erased) +// // wrongType is Left(error) with message about type mismatch +func SafeUnerase[T any](t any) E.Either[error, T] { + return F.Pipe2( + t, + E.ToType[*T](errors.OnSome[any]("Value of type [%T] is not erased")), + E.Map[error](F.Deref[T]), + ) +} + +// Erase0 converts a type-safe nullary function into an erased function. +// The resulting function returns an erased value. +// +// Example: +// +// typedFunc := func() int { return 42 } +// erasedFunc := Erase0(typedFunc) +// result := erasedFunc() // returns erased 42 +func Erase0[T1 any](f func() T1) func() any { + return F.Nullary2(f, Erase[T1]) +} + +// Erase1 converts a type-safe unary function into an erased function. +// The resulting function takes an erased argument and returns an erased value. +// +// Example: +// +// typedFunc := func(x int) string { return fmt.Sprintf("%d", x) } +// erasedFunc := Erase1(typedFunc) +// result := erasedFunc(Erase(42)) // returns erased "42" +func Erase1[T1, T2 any](f func(T1) T2) func(any) any { + return F.Flow3( + Unerase[T1], + f, + Erase[T2], + ) +} + +// Erase2 converts a type-safe binary function into an erased function. +// The resulting function takes two erased arguments and returns an erased value. +// +// Example: +// +// typedFunc := func(x, y int) int { return x + y } +// erasedFunc := Erase2(typedFunc) +// result := erasedFunc(Erase(10), Erase(32)) // returns erased 42 +func Erase2[T1, T2, T3 any](f func(T1, T2) T3) func(any, any) any { + return func(t1, t2 any) any { + return Erase(f(Unerase[T1](t1), Unerase[T2](t2))) + } +} diff --git a/v2/erasure/erasure_test.go b/v2/erasure/erasure_test.go new file mode 100644 index 0000000..0625a23 --- /dev/null +++ b/v2/erasure/erasure_test.go @@ -0,0 +1,312 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package erasure + +import ( + "fmt" + "strings" + "testing" + + E "github.com/IBM/fp-go/v2/either" + F "github.com/IBM/fp-go/v2/function" + "github.com/stretchr/testify/assert" +) + +func TestErase(t *testing.T) { + t.Run("erases int value", func(t *testing.T) { + value := 42 + erased := Erase(value) + assert.NotNil(t, erased) + // Verify it's a pointer to int + ptr, ok := erased.(*int) + assert.True(t, ok) + assert.Equal(t, 42, *ptr) + }) + + t.Run("erases string value", func(t *testing.T) { + value := "hello" + erased := Erase(value) + assert.NotNil(t, erased) + ptr, ok := erased.(*string) + assert.True(t, ok) + assert.Equal(t, "hello", *ptr) + }) + + t.Run("erases struct value", func(t *testing.T) { + type Person struct { + Name string + Age int + } + value := Person{Name: "Alice", Age: 30} + erased := Erase(value) + assert.NotNil(t, erased) + ptr, ok := erased.(*Person) + assert.True(t, ok) + assert.Equal(t, "Alice", ptr.Name) + assert.Equal(t, 30, ptr.Age) + }) +} + +func TestUnerase(t *testing.T) { + t.Run("unerases int value", func(t *testing.T) { + erased := Erase(42) + value := Unerase[int](erased) + assert.Equal(t, 42, value) + }) + + t.Run("unerases string value", func(t *testing.T) { + erased := Erase("hello") + value := Unerase[string](erased) + assert.Equal(t, "hello", value) + }) + + t.Run("unerases complex type", func(t *testing.T) { + type Data struct { + Values []int + Label string + } + original := Data{Values: []int{1, 2, 3}, Label: "test"} + erased := Erase(original) + value := Unerase[Data](erased) + assert.Equal(t, original, value) + }) +} + +func TestSafeUnerase(t *testing.T) { + t.Run("successfully unerases correct type", func(t *testing.T) { + erased := Erase(42) + result := SafeUnerase[int](erased) + assert.True(t, E.IsRight(result)) + value := E.GetOrElse(func(error) int { return 0 })(result) + assert.Equal(t, 42, value) + }) + + t.Run("returns error for wrong type", func(t *testing.T) { + erased := Erase(42) + result := SafeUnerase[string](erased) + assert.True(t, E.IsLeft(result)) + }) + + t.Run("returns error for non-erased value", func(t *testing.T) { + notErased := "plain string" + result := SafeUnerase[string](notErased) + assert.True(t, E.IsLeft(result)) + }) + + t.Run("successfully unerases string", func(t *testing.T) { + erased := Erase("hello") + result := SafeUnerase[string](erased) + assert.True(t, E.IsRight(result)) + value := E.GetOrElse(func(error) string { return "" })(result) + assert.Equal(t, "hello", value) + }) + + t.Run("successfully unerases complex type", func(t *testing.T) { + type Config struct { + Host string + Port int + } + original := Config{Host: "localhost", Port: 8080} + erased := Erase(original) + result := SafeUnerase[Config](erased) + assert.True(t, E.IsRight(result)) + value := E.GetOrElse(func(error) Config { return Config{} })(result) + assert.Equal(t, original, value) + }) +} + +func TestErase0(t *testing.T) { + t.Run("erases nullary function returning int", func(t *testing.T) { + typedFunc := func() int { return 42 } + erasedFunc := Erase0(typedFunc) + result := erasedFunc() + assert.NotNil(t, result) + value := Unerase[int](result) + assert.Equal(t, 42, value) + }) + + t.Run("erases nullary function returning string", func(t *testing.T) { + typedFunc := func() string { return "hello" } + erasedFunc := Erase0(typedFunc) + result := erasedFunc() + assert.NotNil(t, result) + value := Unerase[string](result) + assert.Equal(t, "hello", value) + }) + + t.Run("erases nullary function returning struct", func(t *testing.T) { + type Result struct { + Success bool + Message string + } + typedFunc := func() Result { + return Result{Success: true, Message: "OK"} + } + erasedFunc := Erase0(typedFunc) + result := erasedFunc() + assert.NotNil(t, result) + value := Unerase[Result](result) + assert.True(t, value.Success) + assert.Equal(t, "OK", value.Message) + }) +} + +func TestErase1(t *testing.T) { + t.Run("erases unary function int to string", func(t *testing.T) { + typedFunc := func(x int) string { return fmt.Sprintf("%d", x) } + erasedFunc := Erase1(typedFunc) + result := erasedFunc(Erase(42)) + assert.NotNil(t, result) + value := Unerase[string](result) + assert.Equal(t, "42", value) + }) + + t.Run("erases unary function string to upper", func(t *testing.T) { + typedFunc := strings.ToUpper + erasedFunc := Erase1(typedFunc) + result := erasedFunc(Erase("hello")) + assert.NotNil(t, result) + value := Unerase[string](result) + assert.Equal(t, "HELLO", value) + }) + + t.Run("erases unary function with complex types", func(t *testing.T) { + type Input struct { + Value int + } + type Output struct { + Result int + } + typedFunc := func(in Input) Output { + return Output{Result: in.Value * 2} + } + erasedFunc := Erase1(typedFunc) + result := erasedFunc(Erase(Input{Value: 21})) + assert.NotNil(t, result) + value := Unerase[Output](result) + assert.Equal(t, 42, value.Result) + }) +} + +func TestErase2(t *testing.T) { + t.Run("erases binary function int addition", func(t *testing.T) { + typedFunc := func(x, y int) int { return x + y } + erasedFunc := Erase2(typedFunc) + result := erasedFunc(Erase(10), Erase(32)) + assert.NotNil(t, result) + value := Unerase[int](result) + assert.Equal(t, 42, value) + }) + + t.Run("erases binary function string concatenation", func(t *testing.T) { + typedFunc := func(x, y string) string { return x + y } + erasedFunc := Erase2(typedFunc) + result := erasedFunc(Erase("hello"), Erase(" world")) + assert.NotNil(t, result) + value := Unerase[string](result) + assert.Equal(t, "hello world", value) + }) + + t.Run("erases binary function with different types", func(t *testing.T) { + typedFunc := func(x int, y string) string { + return fmt.Sprintf("%s: %d", y, x) + } + erasedFunc := Erase2(typedFunc) + result := erasedFunc(Erase(42), Erase("answer")) + assert.NotNil(t, result) + value := Unerase[string](result) + assert.Equal(t, "answer: 42", value) + }) + + t.Run("erases binary function with complex types", func(t *testing.T) { + type Point struct { + X, Y int + } + typedFunc := func(p1, p2 Point) Point { + return Point{X: p1.X + p2.X, Y: p1.Y + p2.Y} + } + erasedFunc := Erase2(typedFunc) + result := erasedFunc(Erase(Point{X: 1, Y: 2}), Erase(Point{X: 3, Y: 4})) + assert.NotNil(t, result) + value := Unerase[Point](result) + assert.Equal(t, 4, value.X) + assert.Equal(t, 6, value.Y) + }) +} + +func TestEither(t *testing.T) { + t.Run("integration test with Either", func(t *testing.T) { + e1 := F.Pipe3( + E.Of[error](Erase("Carsten")), + E.Map[error](Erase1(strings.ToUpper)), + E.GetOrElse(func(e error) any { + return Erase("Error") + }), + Unerase[string], + ) + + assert.Equal(t, "CARSTEN", e1) + }) + + t.Run("integration test with Either and SafeUnerase", func(t *testing.T) { + erased := Erase(42) + result := F.Pipe1( + SafeUnerase[int](erased), + E.Map[error](func(x int) int { return x * 2 }), + ) + + assert.True(t, E.IsRight(result)) + value := E.GetOrElse(func(error) int { return 0 })(result) + assert.Equal(t, 84, value) + }) + + t.Run("integration test with Either error case", func(t *testing.T) { + erased := Erase(42) + result := SafeUnerase[string](erased) // Wrong type + + assert.True(t, E.IsLeft(result)) + }) +} + +func TestRoundTrip(t *testing.T) { + t.Run("round trip with int", func(t *testing.T) { + original := 42 + erased := Erase(original) + recovered := Unerase[int](erased) + assert.Equal(t, original, recovered) + }) + + t.Run("round trip with string", func(t *testing.T) { + original := "hello world" + erased := Erase(original) + recovered := Unerase[string](erased) + assert.Equal(t, original, recovered) + }) + + t.Run("round trip with slice", func(t *testing.T) { + original := []int{1, 2, 3, 4, 5} + erased := Erase(original) + recovered := Unerase[[]int](erased) + assert.Equal(t, original, recovered) + }) + + t.Run("round trip with map", func(t *testing.T) { + original := map[string]int{"a": 1, "b": 2} + erased := Erase(original) + recovered := Unerase[map[string]int](erased) + assert.Equal(t, original, recovered) + }) +} diff --git a/v2/errors/conv.go b/v2/errors/conv.go new file mode 100644 index 0000000..bf4cdee --- /dev/null +++ b/v2/errors/conv.go @@ -0,0 +1,54 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package errors + +import ( + "errors" + + O "github.com/IBM/fp-go/v2/option" +) + +// As tries to extract an error of the desired type from the given error. +// It returns an Option containing the extracted error if successful, or None if the +// error cannot be converted to the target type. +// +// This function wraps Go's standard errors.As in a functional style, making it +// composable with other functional operations. +// +// Example: +// +// type MyError struct{ msg string } +// func (e *MyError) Error() string { return e.msg } +// +// rootErr := &MyError{msg: "custom error"} +// wrappedErr := fmt.Errorf("wrapped: %w", rootErr) +// +// // Extract MyError from the wrapped error +// extractMyError := As[*MyError]() +// result := extractMyError(wrappedErr) +// // result is Some(*MyError) containing the original error +// +// // Try to extract a different error type +// extractOther := As[*os.PathError]() +// result2 := extractOther(wrappedErr) +// // result2 is None since wrappedErr doesn't contain *os.PathError +func As[A error]() func(error) O.Option[A] { + return O.FromValidation(func(err error) (A, bool) { + var a A + ok := errors.As(err, &a) + return a, ok + }) +} diff --git a/v2/errors/conv_test.go b/v2/errors/conv_test.go new file mode 100644 index 0000000..3a12cc4 --- /dev/null +++ b/v2/errors/conv_test.go @@ -0,0 +1,54 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package errors + +import ( + "fmt" + "testing" + + F "github.com/IBM/fp-go/v2/function" + O "github.com/IBM/fp-go/v2/option" + "github.com/stretchr/testify/assert" +) + +type MyError struct{} + +func (m *MyError) Error() string { + return "boom" +} + +func TestAs(t *testing.T) { + root := &MyError{} + err := fmt.Errorf("This is my custom error, %w", root) + + errO := F.Pipe1( + err, + As[*MyError](), + ) + + assert.Equal(t, O.Of(root), errO) +} + +func TestNotAs(t *testing.T) { + err := fmt.Errorf("This is my custom error") + + errO := F.Pipe1( + err, + As[*MyError](), + ) + + assert.Equal(t, O.None[*MyError](), errO) +} diff --git a/v2/errors/coverage.out b/v2/errors/coverage.out new file mode 100644 index 0000000..7bc6aeb --- /dev/null +++ b/v2/errors/coverage.out @@ -0,0 +1,13 @@ +mode: set +github.com/IBM/fp-go/v2/errors/conv.go:48.44,49.52 1 1 +github.com/IBM/fp-go/v2/errors/conv.go:49.52,53.3 3 1 +github.com/IBM/fp-go/v2/errors/messages.go:35.51,36.22 1 1 +github.com/IBM/fp-go/v2/errors/messages.go:36.22,38.3 1 1 +github.com/IBM/fp-go/v2/errors/messages.go:58.59,60.12 2 1 +github.com/IBM/fp-go/v2/errors/messages.go:60.12,61.30 1 1 +github.com/IBM/fp-go/v2/errors/messages.go:61.30,63.4 1 1 +github.com/IBM/fp-go/v2/errors/messages.go:65.2,65.29 1 1 +github.com/IBM/fp-go/v2/errors/messages.go:65.29,70.3 4 1 +github.com/IBM/fp-go/v2/errors/messages.go:87.57,88.31 1 1 +github.com/IBM/fp-go/v2/errors/messages.go:88.31,90.3 1 1 +github.com/IBM/fp-go/v2/errors/messages.go:100.33,102.2 1 1 diff --git a/v2/errors/identity.go b/v2/errors/identity.go new file mode 100644 index 0000000..3c578e0 --- /dev/null +++ b/v2/errors/identity.go @@ -0,0 +1,33 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +// Package errors provides functional utilities for working with Go errors. +// It includes functions for error creation, transformation, and type conversion +// that integrate well with functional programming patterns. +package errors + +import ( + F "github.com/IBM/fp-go/v2/function" +) + +// IdentityError is the identity function specialized for error types. +// It returns the error unchanged, useful in functional composition where +// an error needs to be passed through without modification. +// +// Example: +// +// err := errors.New("something went wrong") +// same := IdentityError(err) // returns the same error +var IdentityError = F.Identity[error] diff --git a/v2/errors/message_test.go b/v2/errors/message_test.go new file mode 100644 index 0000000..2988a6c --- /dev/null +++ b/v2/errors/message_test.go @@ -0,0 +1,131 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package errors + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestOnError(t *testing.T) { + onError := OnError("Failed to process [%s]", "filename") + + err := fmt.Errorf("Cause") + + err1 := onError(err) + + assert.Equal(t, "Failed to process [filename], Caused By: Cause", err1.Error()) +} + +func TestOnNone(t *testing.T) { + t.Run("creates error without args", func(t *testing.T) { + getError := OnNone("value not found") + err := getError() + + assert.NotNil(t, err) + assert.Equal(t, "value not found", err.Error()) + }) + + t.Run("creates error with format args", func(t *testing.T) { + getError := OnNone("failed to load %s from %s", "config", "file.json") + err := getError() + + assert.NotNil(t, err) + assert.Equal(t, "failed to load config from file.json", err.Error()) + }) + + t.Run("can be called multiple times", func(t *testing.T) { + getError := OnNone("repeated error") + err1 := getError() + err2 := getError() + + assert.Equal(t, err1.Error(), err2.Error()) + }) +} + +func TestOnSome(t *testing.T) { + t.Run("creates error with value only", func(t *testing.T) { + makeError := OnSome[int]("invalid value: %d") + err := makeError(42) + + assert.NotNil(t, err) + assert.Equal(t, "invalid value: 42", err.Error()) + }) + + t.Run("creates error with value and additional args", func(t *testing.T) { + makeError := OnSome[string]("failed to process %s in file %s", "data.txt") + err := makeError("record123") + + assert.NotNil(t, err) + assert.Equal(t, "failed to process record123 in file data.txt", err.Error()) + }) + + t.Run("works with different types", func(t *testing.T) { + makeIntError := OnSome[int]("number: %d") + makeStringError := OnSome[string]("text: %s") + makeBoolError := OnSome[bool]("flag: %t") + + assert.Equal(t, "number: 100", makeIntError(100).Error()) + assert.Equal(t, "text: hello", makeStringError("hello").Error()) + assert.Equal(t, "flag: true", makeBoolError(true).Error()) + }) + + t.Run("works with struct types", func(t *testing.T) { + type User struct { + Name string + Age int + } + makeError := OnSome[User]("invalid user: %+v") + user := User{Name: "Alice", Age: 30} + err := makeError(user) + + assert.Contains(t, err.Error(), "invalid user:") + assert.Contains(t, err.Error(), "Alice") + }) +} + +func TestToString(t *testing.T) { + t.Run("converts simple error to string", func(t *testing.T) { + err := fmt.Errorf("simple error") + str := ToString(err) + + assert.Equal(t, "simple error", str) + }) + + t.Run("converts wrapped error to string", func(t *testing.T) { + rootErr := fmt.Errorf("root cause") + wrappedErr := fmt.Errorf("wrapped: %w", rootErr) + str := ToString(wrappedErr) + + assert.Equal(t, "wrapped: root cause", str) + }) + + t.Run("converts custom error to string", func(t *testing.T) { + type CustomError struct { + Code int + Msg string + } + customErr := &CustomError{Code: 404, Msg: "not found"} + // Need to implement Error() method + err := fmt.Errorf("custom: code=%d, msg=%s", customErr.Code, customErr.Msg) + str := ToString(err) + + assert.Contains(t, str, "404") + assert.Contains(t, str, "not found") + }) +} diff --git a/v2/errors/messages.go b/v2/errors/messages.go new file mode 100644 index 0000000..1174a30 --- /dev/null +++ b/v2/errors/messages.go @@ -0,0 +1,103 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package errors + +import ( + "fmt" + + A "github.com/IBM/fp-go/v2/array" + "github.com/IBM/fp-go/v2/endomorphism" +) + +// OnNone generates a nullary function that produces a formatted error. +// This is useful when you need to create an error lazily, such as when +// handling the None case in an Option type. +// +// Example: +// +// getError := OnNone("value not found") +// err := getError() // returns error: "value not found" +// +// getErrorWithArgs := OnNone("failed to load %s", "config.json") +// err2 := getErrorWithArgs() // returns error: "failed to load config.json" +func OnNone(msg string, args ...any) func() error { + return func() error { + return fmt.Errorf(msg, args...) + } +} + +// OnSome generates a unary function that produces a formatted error. +// The function takes a value of type T and includes it in the error message. +// If no additional args are provided, the value is used as the only format argument. +// If additional args are provided, the value becomes the first format argument. +// +// This is useful when you need to create an error that includes information +// about a value, such as when handling the Some case in an Option type. +// +// Example: +// +// // Without additional args - value is the only format argument +// makeError := OnSome[int]("invalid value: %d") +// err := makeError(42) // returns error: "invalid value: 42" +// +// // With additional args - value is the first format argument +// makeError2 := OnSome[string]("failed to process %s in file %s", "data.txt") +// err2 := makeError2("record123") // returns error: "failed to process record123 in file data.txt" +func OnSome[T any](msg string, args ...any) func(T) error { + l := len(args) + if l == 0 { + return func(value T) error { + return fmt.Errorf(msg, value) + } + } + return func(value T) error { + data := make([]any, l+1) + data[0] = value + copy(data[1:], args) + return fmt.Errorf(msg, data...) + } +} + +// OnError generates a unary function that produces a formatted error with error wrapping. +// The argument to that function is the root cause of the error and the message will be +// augmented with a format string containing %w for proper error wrapping. +// +// This is useful for adding context to errors while preserving the error chain, +// allowing errors.Is and errors.As to work correctly. +// +// Example: +// +// wrapError := OnError("failed to load configuration from %s", "config.json") +// rootErr := errors.New("file not found") +// wrappedErr := wrapError(rootErr) +// // returns error: "failed to load configuration from config.json, Caused By: file not found" +// // errors.Is(wrappedErr, rootErr) returns true +func OnError(msg string, args ...any) endomorphism.Endomorphism[error] { + return func(err error) error { + return fmt.Errorf(msg+", Caused By: %w", A.ArrayConcatAll(args, A.Of[any](err))...) + } +} + +// ToString converts an error to its string representation by calling the Error() method. +// This is useful in functional pipelines where you need to transform an error into a string. +// +// Example: +// +// err := errors.New("something went wrong") +// msg := ToString(err) // returns "something went wrong" +func ToString(err error) string { + return err.Error() +} diff --git a/v2/exec/exec.go b/v2/exec/exec.go new file mode 100644 index 0000000..086e2e5 --- /dev/null +++ b/v2/exec/exec.go @@ -0,0 +1,33 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package exec + +import ( + P "github.com/IBM/fp-go/v2/pair" +) + +type ( + // CommandOutput represents the output of executing a command. The first field in the [Tuple2] is + // stdout, the second one is stderr. Use [StdOut] and [StdErr] to access these fields + CommandOutput = P.Pair[[]byte, []byte] +) + +var ( + // StdOut returns the field of a [CommandOutput] representing `stdout` + StdOut = P.Head[[]byte, []byte] + // StdErr returns the field of a [CommandOutput] representing `stderr` + StdErr = P.Tail[[]byte, []byte] +) diff --git a/v2/file/getters.go b/v2/file/getters.go new file mode 100644 index 0000000..e9cb9d4 --- /dev/null +++ b/v2/file/getters.go @@ -0,0 +1,43 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package file + +import ( + "io" + "path/filepath" +) + +// Join appends a filename to a root path +func Join(name string) func(root string) string { + return func(root string) string { + return filepath.Join(root, name) + } +} + +// ToReader converts a [io.Reader] +func ToReader[R io.Reader](r R) io.Reader { + return r +} + +// ToWriter converts a [io.Writer] +func ToWriter[W io.Writer](w W) io.Writer { + return w +} + +// ToCloser converts a [io.Closer] +func ToCloser[C io.Closer](c C) io.Closer { + return c +} diff --git a/v2/function/bind.go b/v2/function/bind.go new file mode 100644 index 0000000..3df8216 --- /dev/null +++ b/v2/function/bind.go @@ -0,0 +1,109 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package function + +// Bind1st performs partial application by fixing the first argument of a binary function. +// +// Given a function f(a, b) and a value for 'a', this returns a new function g(b) +// where g(b) = f(a, b). This is useful for creating specialized functions from +// more general ones. +// +// Type Parameters: +// - T1: The type of the first parameter (to be bound) +// - T2: The type of the second parameter (remains free) +// - R: The return type +// +// Parameters: +// - f: The binary function to partially apply +// - t1: The value to bind to the first parameter +// +// Returns: +// - A unary function with the first parameter fixed +// +// Example: +// +// multiply := func(a, b int) int { return a * b } +// double := Bind1st(multiply, 2) +// result := double(5) // 10 (2 * 5) +// +// divide := func(a, b float64) float64 { return a / b } +// divideBy10 := Bind1st(divide, 10.0) +// result := divideBy10(2.0) // 5.0 (10 / 2) +func Bind1st[T1, T2, R any](f func(T1, T2) R, t1 T1) func(T2) R { + return func(t2 T2) R { + return f(t1, t2) + } +} + +// Bind2nd performs partial application by fixing the second argument of a binary function. +// +// Given a function f(a, b) and a value for 'b', this returns a new function g(a) +// where g(a) = f(a, b). This is useful for creating specialized functions from +// more general ones. +// +// Type Parameters: +// - T1: The type of the first parameter (remains free) +// - T2: The type of the second parameter (to be bound) +// - R: The return type +// +// Parameters: +// - f: The binary function to partially apply +// - t2: The value to bind to the second parameter +// +// Returns: +// - A unary function with the second parameter fixed +// +// Example: +// +// multiply := func(a, b int) int { return a * b } +// triple := Bind2nd(multiply, 3) +// result := triple(5) // 15 (5 * 3) +// +// divide := func(a, b float64) float64 { return a / b } +// halve := Bind2nd(divide, 2.0) +// result := halve(10.0) // 5.0 (10 / 2) +func Bind2nd[T1, T2, R any](f func(T1, T2) R, t2 T2) func(T1) R { + return func(t1 T1) R { + return f(t1, t2) + } +} + +// SK is the SK combinator from SKI combinator calculus. +// +// This function takes two arguments and returns the second, ignoring the first. +// It's identical to the Second function and represents the K combinator applied +// to the second argument. +// +// In combinatory logic: SK = λx.λy.y +// +// Type Parameters: +// - T1: The type of the first parameter (ignored) +// - T2: The type of the second parameter (returned) +// +// Parameters: +// - _: The first value (ignored) +// - t2: The second value +// +// Returns: +// - The second value +// +// Example: +// +// result := SK(42, "hello") // "hello" +// result := SK(true, 100) // 100 +func SK[T1, T2 any](_ T1, t2 T2) T2 { + return t2 +} diff --git a/v2/function/binds.go b/v2/function/binds.go new file mode 100644 index 0000000..7a7f2d5 --- /dev/null +++ b/v2/function/binds.go @@ -0,0 +1,451 @@ +// Code generated by go generate; DO NOT EDIT. +// This file was generated by robots at +// 2025-03-09 23:52:54.5676157 +0100 CET m=+0.101547001 + +package function +// Combinations for a total of 1 arguments + +// Bind1of1 takes a function with 1 parameters and returns a new function with 1 parameters that will bind these parameters to the positions [1] of the original function. +// The return value of is a function with the remaining 0 parameters at positions [] of the original function. +func Bind1of1[F ~func(T1) R, T1, R any](f F) func(T1) func() R { + return func(t1 T1) func() R { + return func() R { + return f(t1) + } + } +} + +// Ignore1of1 takes a function with 0 parameters and returns a new function with 1 parameters that will ignore the values at positions [1] and pass the remaining 0 parameters to the original function +func Ignore1of1[T1 any, F ~func() R, R any](f F) func(T1) R { + return func(t1 T1) R { + return f() + } +} +// Combinations for a total of 2 arguments + +// Bind1of2 takes a function with 2 parameters and returns a new function with 1 parameters that will bind these parameters to the positions [1] of the original function. +// The return value of is a function with the remaining 1 parameters at positions [2] of the original function. +func Bind1of2[F ~func(T1, T2) R, T1, T2, R any](f F) func(T1) func(T2) R { + return func(t1 T1) func(T2) R { + return func(t2 T2) R { + return f(t1, t2) + } + } +} + +// Ignore1of2 takes a function with 1 parameters and returns a new function with 2 parameters that will ignore the values at positions [1] and pass the remaining 1 parameters to the original function +func Ignore1of2[T1 any, F ~func(T2) R, T2, R any](f F) func(T1, T2) R { + return func(t1 T1, t2 T2) R { + return f(t2) + } +} + +// Bind2of2 takes a function with 2 parameters and returns a new function with 1 parameters that will bind these parameters to the positions [2] of the original function. +// The return value of is a function with the remaining 1 parameters at positions [1] of the original function. +func Bind2of2[F ~func(T1, T2) R, T1, T2, R any](f F) func(T2) func(T1) R { + return func(t2 T2) func(T1) R { + return func(t1 T1) R { + return f(t1, t2) + } + } +} + +// Ignore2of2 takes a function with 1 parameters and returns a new function with 2 parameters that will ignore the values at positions [2] and pass the remaining 1 parameters to the original function +func Ignore2of2[T2 any, F ~func(T1) R, T1, R any](f F) func(T1, T2) R { + return func(t1 T1, t2 T2) R { + return f(t1) + } +} + +// Bind12of2 takes a function with 2 parameters and returns a new function with 2 parameters that will bind these parameters to the positions [1, 2] of the original function. +// The return value of is a function with the remaining 0 parameters at positions [] of the original function. +func Bind12of2[F ~func(T1, T2) R, T1, T2, R any](f F) func(T1, T2) func() R { + return func(t1 T1, t2 T2) func() R { + return func() R { + return f(t1, t2) + } + } +} + +// Ignore12of2 takes a function with 0 parameters and returns a new function with 2 parameters that will ignore the values at positions [1, 2] and pass the remaining 0 parameters to the original function +func Ignore12of2[T1, T2 any, F ~func() R, R any](f F) func(T1, T2) R { + return func(t1 T1, t2 T2) R { + return f() + } +} +// Combinations for a total of 3 arguments + +// Bind1of3 takes a function with 3 parameters and returns a new function with 1 parameters that will bind these parameters to the positions [1] of the original function. +// The return value of is a function with the remaining 2 parameters at positions [2, 3] of the original function. +func Bind1of3[F ~func(T1, T2, T3) R, T1, T2, T3, R any](f F) func(T1) func(T2, T3) R { + return func(t1 T1) func(T2, T3) R { + return func(t2 T2, t3 T3) R { + return f(t1, t2, t3) + } + } +} + +// Ignore1of3 takes a function with 2 parameters and returns a new function with 3 parameters that will ignore the values at positions [1] and pass the remaining 2 parameters to the original function +func Ignore1of3[T1 any, F ~func(T2, T3) R, T2, T3, R any](f F) func(T1, T2, T3) R { + return func(t1 T1, t2 T2, t3 T3) R { + return f(t2, t3) + } +} + +// Bind2of3 takes a function with 3 parameters and returns a new function with 1 parameters that will bind these parameters to the positions [2] of the original function. +// The return value of is a function with the remaining 2 parameters at positions [1, 3] of the original function. +func Bind2of3[F ~func(T1, T2, T3) R, T1, T2, T3, R any](f F) func(T2) func(T1, T3) R { + return func(t2 T2) func(T1, T3) R { + return func(t1 T1, t3 T3) R { + return f(t1, t2, t3) + } + } +} + +// Ignore2of3 takes a function with 2 parameters and returns a new function with 3 parameters that will ignore the values at positions [2] and pass the remaining 2 parameters to the original function +func Ignore2of3[T2 any, F ~func(T1, T3) R, T1, T3, R any](f F) func(T1, T2, T3) R { + return func(t1 T1, t2 T2, t3 T3) R { + return f(t1, t3) + } +} + +// Bind3of3 takes a function with 3 parameters and returns a new function with 1 parameters that will bind these parameters to the positions [3] of the original function. +// The return value of is a function with the remaining 2 parameters at positions [1, 2] of the original function. +func Bind3of3[F ~func(T1, T2, T3) R, T1, T2, T3, R any](f F) func(T3) func(T1, T2) R { + return func(t3 T3) func(T1, T2) R { + return func(t1 T1, t2 T2) R { + return f(t1, t2, t3) + } + } +} + +// Ignore3of3 takes a function with 2 parameters and returns a new function with 3 parameters that will ignore the values at positions [3] and pass the remaining 2 parameters to the original function +func Ignore3of3[T3 any, F ~func(T1, T2) R, T1, T2, R any](f F) func(T1, T2, T3) R { + return func(t1 T1, t2 T2, t3 T3) R { + return f(t1, t2) + } +} + +// Bind12of3 takes a function with 3 parameters and returns a new function with 2 parameters that will bind these parameters to the positions [1, 2] of the original function. +// The return value of is a function with the remaining 1 parameters at positions [3] of the original function. +func Bind12of3[F ~func(T1, T2, T3) R, T1, T2, T3, R any](f F) func(T1, T2) func(T3) R { + return func(t1 T1, t2 T2) func(T3) R { + return func(t3 T3) R { + return f(t1, t2, t3) + } + } +} + +// Ignore12of3 takes a function with 1 parameters and returns a new function with 3 parameters that will ignore the values at positions [1, 2] and pass the remaining 1 parameters to the original function +func Ignore12of3[T1, T2 any, F ~func(T3) R, T3, R any](f F) func(T1, T2, T3) R { + return func(t1 T1, t2 T2, t3 T3) R { + return f(t3) + } +} + +// Bind13of3 takes a function with 3 parameters and returns a new function with 2 parameters that will bind these parameters to the positions [1, 3] of the original function. +// The return value of is a function with the remaining 1 parameters at positions [2] of the original function. +func Bind13of3[F ~func(T1, T2, T3) R, T1, T2, T3, R any](f F) func(T1, T3) func(T2) R { + return func(t1 T1, t3 T3) func(T2) R { + return func(t2 T2) R { + return f(t1, t2, t3) + } + } +} + +// Ignore13of3 takes a function with 1 parameters and returns a new function with 3 parameters that will ignore the values at positions [1, 3] and pass the remaining 1 parameters to the original function +func Ignore13of3[T1, T3 any, F ~func(T2) R, T2, R any](f F) func(T1, T2, T3) R { + return func(t1 T1, t2 T2, t3 T3) R { + return f(t2) + } +} + +// Bind23of3 takes a function with 3 parameters and returns a new function with 2 parameters that will bind these parameters to the positions [2, 3] of the original function. +// The return value of is a function with the remaining 1 parameters at positions [1] of the original function. +func Bind23of3[F ~func(T1, T2, T3) R, T1, T2, T3, R any](f F) func(T2, T3) func(T1) R { + return func(t2 T2, t3 T3) func(T1) R { + return func(t1 T1) R { + return f(t1, t2, t3) + } + } +} + +// Ignore23of3 takes a function with 1 parameters and returns a new function with 3 parameters that will ignore the values at positions [2, 3] and pass the remaining 1 parameters to the original function +func Ignore23of3[T2, T3 any, F ~func(T1) R, T1, R any](f F) func(T1, T2, T3) R { + return func(t1 T1, t2 T2, t3 T3) R { + return f(t1) + } +} + +// Bind123of3 takes a function with 3 parameters and returns a new function with 3 parameters that will bind these parameters to the positions [1, 2, 3] of the original function. +// The return value of is a function with the remaining 0 parameters at positions [] of the original function. +func Bind123of3[F ~func(T1, T2, T3) R, T1, T2, T3, R any](f F) func(T1, T2, T3) func() R { + return func(t1 T1, t2 T2, t3 T3) func() R { + return func() R { + return f(t1, t2, t3) + } + } +} + +// Ignore123of3 takes a function with 0 parameters and returns a new function with 3 parameters that will ignore the values at positions [1, 2, 3] and pass the remaining 0 parameters to the original function +func Ignore123of3[T1, T2, T3 any, F ~func() R, R any](f F) func(T1, T2, T3) R { + return func(t1 T1, t2 T2, t3 T3) R { + return f() + } +} +// Combinations for a total of 4 arguments + +// Bind1of4 takes a function with 4 parameters and returns a new function with 1 parameters that will bind these parameters to the positions [1] of the original function. +// The return value of is a function with the remaining 3 parameters at positions [2, 3, 4] of the original function. +func Bind1of4[F ~func(T1, T2, T3, T4) R, T1, T2, T3, T4, R any](f F) func(T1) func(T2, T3, T4) R { + return func(t1 T1) func(T2, T3, T4) R { + return func(t2 T2, t3 T3, t4 T4) R { + return f(t1, t2, t3, t4) + } + } +} + +// Ignore1of4 takes a function with 3 parameters and returns a new function with 4 parameters that will ignore the values at positions [1] and pass the remaining 3 parameters to the original function +func Ignore1of4[T1 any, F ~func(T2, T3, T4) R, T2, T3, T4, R any](f F) func(T1, T2, T3, T4) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4) R { + return f(t2, t3, t4) + } +} + +// Bind2of4 takes a function with 4 parameters and returns a new function with 1 parameters that will bind these parameters to the positions [2] of the original function. +// The return value of is a function with the remaining 3 parameters at positions [1, 3, 4] of the original function. +func Bind2of4[F ~func(T1, T2, T3, T4) R, T1, T2, T3, T4, R any](f F) func(T2) func(T1, T3, T4) R { + return func(t2 T2) func(T1, T3, T4) R { + return func(t1 T1, t3 T3, t4 T4) R { + return f(t1, t2, t3, t4) + } + } +} + +// Ignore2of4 takes a function with 3 parameters and returns a new function with 4 parameters that will ignore the values at positions [2] and pass the remaining 3 parameters to the original function +func Ignore2of4[T2 any, F ~func(T1, T3, T4) R, T1, T3, T4, R any](f F) func(T1, T2, T3, T4) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4) R { + return f(t1, t3, t4) + } +} + +// Bind3of4 takes a function with 4 parameters and returns a new function with 1 parameters that will bind these parameters to the positions [3] of the original function. +// The return value of is a function with the remaining 3 parameters at positions [1, 2, 4] of the original function. +func Bind3of4[F ~func(T1, T2, T3, T4) R, T1, T2, T3, T4, R any](f F) func(T3) func(T1, T2, T4) R { + return func(t3 T3) func(T1, T2, T4) R { + return func(t1 T1, t2 T2, t4 T4) R { + return f(t1, t2, t3, t4) + } + } +} + +// Ignore3of4 takes a function with 3 parameters and returns a new function with 4 parameters that will ignore the values at positions [3] and pass the remaining 3 parameters to the original function +func Ignore3of4[T3 any, F ~func(T1, T2, T4) R, T1, T2, T4, R any](f F) func(T1, T2, T3, T4) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4) R { + return f(t1, t2, t4) + } +} + +// Bind4of4 takes a function with 4 parameters and returns a new function with 1 parameters that will bind these parameters to the positions [4] of the original function. +// The return value of is a function with the remaining 3 parameters at positions [1, 2, 3] of the original function. +func Bind4of4[F ~func(T1, T2, T3, T4) R, T1, T2, T3, T4, R any](f F) func(T4) func(T1, T2, T3) R { + return func(t4 T4) func(T1, T2, T3) R { + return func(t1 T1, t2 T2, t3 T3) R { + return f(t1, t2, t3, t4) + } + } +} + +// Ignore4of4 takes a function with 3 parameters and returns a new function with 4 parameters that will ignore the values at positions [4] and pass the remaining 3 parameters to the original function +func Ignore4of4[T4 any, F ~func(T1, T2, T3) R, T1, T2, T3, R any](f F) func(T1, T2, T3, T4) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4) R { + return f(t1, t2, t3) + } +} + +// Bind12of4 takes a function with 4 parameters and returns a new function with 2 parameters that will bind these parameters to the positions [1, 2] of the original function. +// The return value of is a function with the remaining 2 parameters at positions [3, 4] of the original function. +func Bind12of4[F ~func(T1, T2, T3, T4) R, T1, T2, T3, T4, R any](f F) func(T1, T2) func(T3, T4) R { + return func(t1 T1, t2 T2) func(T3, T4) R { + return func(t3 T3, t4 T4) R { + return f(t1, t2, t3, t4) + } + } +} + +// Ignore12of4 takes a function with 2 parameters and returns a new function with 4 parameters that will ignore the values at positions [1, 2] and pass the remaining 2 parameters to the original function +func Ignore12of4[T1, T2 any, F ~func(T3, T4) R, T3, T4, R any](f F) func(T1, T2, T3, T4) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4) R { + return f(t3, t4) + } +} + +// Bind13of4 takes a function with 4 parameters and returns a new function with 2 parameters that will bind these parameters to the positions [1, 3] of the original function. +// The return value of is a function with the remaining 2 parameters at positions [2, 4] of the original function. +func Bind13of4[F ~func(T1, T2, T3, T4) R, T1, T2, T3, T4, R any](f F) func(T1, T3) func(T2, T4) R { + return func(t1 T1, t3 T3) func(T2, T4) R { + return func(t2 T2, t4 T4) R { + return f(t1, t2, t3, t4) + } + } +} + +// Ignore13of4 takes a function with 2 parameters and returns a new function with 4 parameters that will ignore the values at positions [1, 3] and pass the remaining 2 parameters to the original function +func Ignore13of4[T1, T3 any, F ~func(T2, T4) R, T2, T4, R any](f F) func(T1, T2, T3, T4) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4) R { + return f(t2, t4) + } +} + +// Bind14of4 takes a function with 4 parameters and returns a new function with 2 parameters that will bind these parameters to the positions [1, 4] of the original function. +// The return value of is a function with the remaining 2 parameters at positions [2, 3] of the original function. +func Bind14of4[F ~func(T1, T2, T3, T4) R, T1, T2, T3, T4, R any](f F) func(T1, T4) func(T2, T3) R { + return func(t1 T1, t4 T4) func(T2, T3) R { + return func(t2 T2, t3 T3) R { + return f(t1, t2, t3, t4) + } + } +} + +// Ignore14of4 takes a function with 2 parameters and returns a new function with 4 parameters that will ignore the values at positions [1, 4] and pass the remaining 2 parameters to the original function +func Ignore14of4[T1, T4 any, F ~func(T2, T3) R, T2, T3, R any](f F) func(T1, T2, T3, T4) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4) R { + return f(t2, t3) + } +} + +// Bind23of4 takes a function with 4 parameters and returns a new function with 2 parameters that will bind these parameters to the positions [2, 3] of the original function. +// The return value of is a function with the remaining 2 parameters at positions [1, 4] of the original function. +func Bind23of4[F ~func(T1, T2, T3, T4) R, T1, T2, T3, T4, R any](f F) func(T2, T3) func(T1, T4) R { + return func(t2 T2, t3 T3) func(T1, T4) R { + return func(t1 T1, t4 T4) R { + return f(t1, t2, t3, t4) + } + } +} + +// Ignore23of4 takes a function with 2 parameters and returns a new function with 4 parameters that will ignore the values at positions [2, 3] and pass the remaining 2 parameters to the original function +func Ignore23of4[T2, T3 any, F ~func(T1, T4) R, T1, T4, R any](f F) func(T1, T2, T3, T4) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4) R { + return f(t1, t4) + } +} + +// Bind24of4 takes a function with 4 parameters and returns a new function with 2 parameters that will bind these parameters to the positions [2, 4] of the original function. +// The return value of is a function with the remaining 2 parameters at positions [1, 3] of the original function. +func Bind24of4[F ~func(T1, T2, T3, T4) R, T1, T2, T3, T4, R any](f F) func(T2, T4) func(T1, T3) R { + return func(t2 T2, t4 T4) func(T1, T3) R { + return func(t1 T1, t3 T3) R { + return f(t1, t2, t3, t4) + } + } +} + +// Ignore24of4 takes a function with 2 parameters and returns a new function with 4 parameters that will ignore the values at positions [2, 4] and pass the remaining 2 parameters to the original function +func Ignore24of4[T2, T4 any, F ~func(T1, T3) R, T1, T3, R any](f F) func(T1, T2, T3, T4) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4) R { + return f(t1, t3) + } +} + +// Bind34of4 takes a function with 4 parameters and returns a new function with 2 parameters that will bind these parameters to the positions [3, 4] of the original function. +// The return value of is a function with the remaining 2 parameters at positions [1, 2] of the original function. +func Bind34of4[F ~func(T1, T2, T3, T4) R, T1, T2, T3, T4, R any](f F) func(T3, T4) func(T1, T2) R { + return func(t3 T3, t4 T4) func(T1, T2) R { + return func(t1 T1, t2 T2) R { + return f(t1, t2, t3, t4) + } + } +} + +// Ignore34of4 takes a function with 2 parameters and returns a new function with 4 parameters that will ignore the values at positions [3, 4] and pass the remaining 2 parameters to the original function +func Ignore34of4[T3, T4 any, F ~func(T1, T2) R, T1, T2, R any](f F) func(T1, T2, T3, T4) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4) R { + return f(t1, t2) + } +} + +// Bind123of4 takes a function with 4 parameters and returns a new function with 3 parameters that will bind these parameters to the positions [1, 2, 3] of the original function. +// The return value of is a function with the remaining 1 parameters at positions [4] of the original function. +func Bind123of4[F ~func(T1, T2, T3, T4) R, T1, T2, T3, T4, R any](f F) func(T1, T2, T3) func(T4) R { + return func(t1 T1, t2 T2, t3 T3) func(T4) R { + return func(t4 T4) R { + return f(t1, t2, t3, t4) + } + } +} + +// Ignore123of4 takes a function with 1 parameters and returns a new function with 4 parameters that will ignore the values at positions [1, 2, 3] and pass the remaining 1 parameters to the original function +func Ignore123of4[T1, T2, T3 any, F ~func(T4) R, T4, R any](f F) func(T1, T2, T3, T4) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4) R { + return f(t4) + } +} + +// Bind124of4 takes a function with 4 parameters and returns a new function with 3 parameters that will bind these parameters to the positions [1, 2, 4] of the original function. +// The return value of is a function with the remaining 1 parameters at positions [3] of the original function. +func Bind124of4[F ~func(T1, T2, T3, T4) R, T1, T2, T3, T4, R any](f F) func(T1, T2, T4) func(T3) R { + return func(t1 T1, t2 T2, t4 T4) func(T3) R { + return func(t3 T3) R { + return f(t1, t2, t3, t4) + } + } +} + +// Ignore124of4 takes a function with 1 parameters and returns a new function with 4 parameters that will ignore the values at positions [1, 2, 4] and pass the remaining 1 parameters to the original function +func Ignore124of4[T1, T2, T4 any, F ~func(T3) R, T3, R any](f F) func(T1, T2, T3, T4) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4) R { + return f(t3) + } +} + +// Bind134of4 takes a function with 4 parameters and returns a new function with 3 parameters that will bind these parameters to the positions [1, 3, 4] of the original function. +// The return value of is a function with the remaining 1 parameters at positions [2] of the original function. +func Bind134of4[F ~func(T1, T2, T3, T4) R, T1, T2, T3, T4, R any](f F) func(T1, T3, T4) func(T2) R { + return func(t1 T1, t3 T3, t4 T4) func(T2) R { + return func(t2 T2) R { + return f(t1, t2, t3, t4) + } + } +} + +// Ignore134of4 takes a function with 1 parameters and returns a new function with 4 parameters that will ignore the values at positions [1, 3, 4] and pass the remaining 1 parameters to the original function +func Ignore134of4[T1, T3, T4 any, F ~func(T2) R, T2, R any](f F) func(T1, T2, T3, T4) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4) R { + return f(t2) + } +} + +// Bind234of4 takes a function with 4 parameters and returns a new function with 3 parameters that will bind these parameters to the positions [2, 3, 4] of the original function. +// The return value of is a function with the remaining 1 parameters at positions [1] of the original function. +func Bind234of4[F ~func(T1, T2, T3, T4) R, T1, T2, T3, T4, R any](f F) func(T2, T3, T4) func(T1) R { + return func(t2 T2, t3 T3, t4 T4) func(T1) R { + return func(t1 T1) R { + return f(t1, t2, t3, t4) + } + } +} + +// Ignore234of4 takes a function with 1 parameters and returns a new function with 4 parameters that will ignore the values at positions [2, 3, 4] and pass the remaining 1 parameters to the original function +func Ignore234of4[T2, T3, T4 any, F ~func(T1) R, T1, R any](f F) func(T1, T2, T3, T4) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4) R { + return f(t1) + } +} + +// Bind1234of4 takes a function with 4 parameters and returns a new function with 4 parameters that will bind these parameters to the positions [1, 2, 3, 4] of the original function. +// The return value of is a function with the remaining 0 parameters at positions [] of the original function. +func Bind1234of4[F ~func(T1, T2, T3, T4) R, T1, T2, T3, T4, R any](f F) func(T1, T2, T3, T4) func() R { + return func(t1 T1, t2 T2, t3 T3, t4 T4) func() R { + return func() R { + return f(t1, t2, t3, t4) + } + } +} + +// Ignore1234of4 takes a function with 0 parameters and returns a new function with 4 parameters that will ignore the values at positions [1, 2, 3, 4] and pass the remaining 0 parameters to the original function +func Ignore1234of4[T1, T2, T3, T4 any, F ~func() R, R any](f F) func(T1, T2, T3, T4) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4) R { + return f() + } +} diff --git a/v2/function/cache.go b/v2/function/cache.go new file mode 100644 index 0000000..8080fc0 --- /dev/null +++ b/v2/function/cache.go @@ -0,0 +1,41 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package function + +import ( + G "github.com/IBM/fp-go/v2/function/generic" +) + +// Memoize converts a unary function into a unary function that caches the value depending on the parameter +func Memoize[K comparable, T any](f func(K) T) func(K) T { + return G.Memoize(f) +} + +// ContramapMemoize converts a unary function into a unary function that caches the value depending on the parameter +func ContramapMemoize[T, A any, K comparable](kf func(A) K) func(func(A) T) func(A) T { + return G.ContramapMemoize[func(A) T](kf) +} + +// CacheCallback converts a unary function into a unary function that caches the value depending on the parameter +func CacheCallback[ + T, A any, K comparable](kf func(A) K, getOrCreate func(K, func() func() T) func() T) func(func(A) T) func(A) T { + return G.CacheCallback[func(func(A) T) func(A) T](kf, getOrCreate) +} + +// SingleElementCache creates a cache function for use with the [CacheCallback] method that has a maximum capacity of one single item +func SingleElementCache[K comparable, T any]() func(K, func() func() T) func() T { + return G.SingleElementCache[func() func() T, K]() +} diff --git a/v2/function/cache_test.go b/v2/function/cache_test.go new file mode 100644 index 0000000..acb98a5 --- /dev/null +++ b/v2/function/cache_test.go @@ -0,0 +1,70 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package function + +import ( + "fmt" + "math/rand" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCache(t *testing.T) { + var count int + + withSideEffect := func(n int) int { + count++ + return n + } + + cached := Memoize(withSideEffect) + + assert.Equal(t, 0, count) + + assert.Equal(t, 10, cached(10)) + assert.Equal(t, 1, count) + + assert.Equal(t, 10, cached(10)) + assert.Equal(t, 1, count) + + assert.Equal(t, 20, cached(20)) + assert.Equal(t, 2, count) + + assert.Equal(t, 20, cached(20)) + assert.Equal(t, 2, count) + + assert.Equal(t, 10, cached(10)) + assert.Equal(t, 2, count) +} + +func TestSingleElementCache(t *testing.T) { + f := func(key string) string { + return fmt.Sprintf("%s: %d", key, rand.Int()) + } + cb := CacheCallback(func(s string) string { return s }, SingleElementCache[string, string]()) + cf := cb(f) + + v1 := cf("1") + v2 := cf("1") + v3 := cf("2") + v4 := cf("1") + + assert.Equal(t, v1, v2) + assert.NotEqual(t, v2, v3) + assert.NotEqual(t, v3, v4) + assert.NotEqual(t, v1, v4) +} diff --git a/v2/function/constants.go b/v2/function/constants.go new file mode 100644 index 0000000..4a70897 --- /dev/null +++ b/v2/function/constants.go @@ -0,0 +1,85 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package function + +// ConstTrue is a nullary function that always returns true. +// +// This is a pre-defined constant function created using Constant(true). +// It's useful as a default predicate or when you need a function that +// always evaluates to true. +// +// Example: +// +// result := ConstTrue() // true +// +// // Use as a default predicate +// filter := func(pred func() bool) string { +// if pred() { +// return "accepted" +// } +// return "rejected" +// } +// result := filter(ConstTrue) // "accepted" +var ConstTrue = Constant(true) + +// ConstFalse is a nullary function that always returns false. +// +// This is a pre-defined constant function created using Constant(false). +// It's useful as a default predicate or when you need a function that +// always evaluates to false. +// +// Example: +// +// result := ConstFalse() // false +// +// // Use as a default predicate +// filter := func(pred func() bool) string { +// if pred() { +// return "accepted" +// } +// return "rejected" +// } +// result := filter(ConstFalse) // "rejected" +var ConstFalse = Constant(false) + +// ConstNil returns a nil pointer of the specified type. +// +// This function creates a nil pointer for any type A. It's useful when you need +// to provide a nil value in a generic context or when initializing optional fields. +// +// Type Parameters: +// - A: The type for which to create a nil pointer +// +// Returns: +// - A nil pointer of type *A +// +// Example: +// +// nilInt := ConstNil[int]() // (*int)(nil) +// nilString := ConstNil[string]() // (*string)(nil) +// +// // Useful for optional fields +// type Config struct { +// Timeout *int +// MaxRetries *int +// } +// config := Config{ +// Timeout: ConstNil[int](), +// MaxRetries: Ref(3), +// } +func ConstNil[A any]() *A { + return (*A)(nil) +} diff --git a/v2/function/coverage.out b/v2/function/coverage.out new file mode 100644 index 0000000..61b256d --- /dev/null +++ b/v2/function/coverage.out @@ -0,0 +1,662 @@ +mode: set +github.com/IBM/fp-go/v2/function/bind.go:45.65,46.23 1 1 +github.com/IBM/fp-go/v2/function/bind.go:46.23,48.3 1 1 +github.com/IBM/fp-go/v2/function/bind.go:78.65,79.23 1 1 +github.com/IBM/fp-go/v2/function/bind.go:79.23,81.3 1 1 +github.com/IBM/fp-go/v2/function/bind.go:107.37,109.2 1 1 +github.com/IBM/fp-go/v2/function/binds.go:10.64,11.31 1 0 +github.com/IBM/fp-go/v2/function/binds.go:11.31,12.21 1 0 +github.com/IBM/fp-go/v2/function/binds.go:12.21,14.6 1 0 +github.com/IBM/fp-go/v2/function/binds.go:19.61,20.24 1 0 +github.com/IBM/fp-go/v2/function/binds.go:20.24,22.4 1 0 +github.com/IBM/fp-go/v2/function/binds.go:28.74,29.33 1 0 +github.com/IBM/fp-go/v2/function/binds.go:29.33,30.26 1 0 +github.com/IBM/fp-go/v2/function/binds.go:30.26,32.6 1 0 +github.com/IBM/fp-go/v2/function/binds.go:37.71,38.31 1 0 +github.com/IBM/fp-go/v2/function/binds.go:38.31,40.4 1 0 +github.com/IBM/fp-go/v2/function/binds.go:45.74,46.33 1 0 +github.com/IBM/fp-go/v2/function/binds.go:46.33,47.26 1 0 +github.com/IBM/fp-go/v2/function/binds.go:47.26,49.6 1 0 +github.com/IBM/fp-go/v2/function/binds.go:54.71,55.31 1 0 +github.com/IBM/fp-go/v2/function/binds.go:55.31,57.4 1 0 +github.com/IBM/fp-go/v2/function/binds.go:62.77,63.38 1 0 +github.com/IBM/fp-go/v2/function/binds.go:63.38,64.21 1 0 +github.com/IBM/fp-go/v2/function/binds.go:64.21,66.6 1 0 +github.com/IBM/fp-go/v2/function/binds.go:71.70,72.31 1 0 +github.com/IBM/fp-go/v2/function/binds.go:72.31,74.4 1 0 +github.com/IBM/fp-go/v2/function/binds.go:80.86,81.37 1 0 +github.com/IBM/fp-go/v2/function/binds.go:81.37,82.33 1 0 +github.com/IBM/fp-go/v2/function/binds.go:82.33,84.6 1 0 +github.com/IBM/fp-go/v2/function/binds.go:89.83,90.38 1 0 +github.com/IBM/fp-go/v2/function/binds.go:90.38,92.4 1 0 +github.com/IBM/fp-go/v2/function/binds.go:97.86,98.37 1 0 +github.com/IBM/fp-go/v2/function/binds.go:98.37,99.33 1 0 +github.com/IBM/fp-go/v2/function/binds.go:99.33,101.6 1 0 +github.com/IBM/fp-go/v2/function/binds.go:106.83,107.38 1 0 +github.com/IBM/fp-go/v2/function/binds.go:107.38,109.4 1 0 +github.com/IBM/fp-go/v2/function/binds.go:114.86,115.37 1 0 +github.com/IBM/fp-go/v2/function/binds.go:115.37,116.33 1 0 +github.com/IBM/fp-go/v2/function/binds.go:116.33,118.6 1 0 +github.com/IBM/fp-go/v2/function/binds.go:123.83,124.38 1 0 +github.com/IBM/fp-go/v2/function/binds.go:124.38,126.4 1 0 +github.com/IBM/fp-go/v2/function/binds.go:131.87,132.40 1 0 +github.com/IBM/fp-go/v2/function/binds.go:132.40,133.26 1 0 +github.com/IBM/fp-go/v2/function/binds.go:133.26,135.6 1 0 +github.com/IBM/fp-go/v2/function/binds.go:140.80,141.38 1 0 +github.com/IBM/fp-go/v2/function/binds.go:141.38,143.4 1 0 +github.com/IBM/fp-go/v2/function/binds.go:148.87,149.40 1 0 +github.com/IBM/fp-go/v2/function/binds.go:149.40,150.26 1 0 +github.com/IBM/fp-go/v2/function/binds.go:150.26,152.6 1 0 +github.com/IBM/fp-go/v2/function/binds.go:157.80,158.38 1 0 +github.com/IBM/fp-go/v2/function/binds.go:158.38,160.4 1 0 +github.com/IBM/fp-go/v2/function/binds.go:165.87,166.40 1 0 +github.com/IBM/fp-go/v2/function/binds.go:166.40,167.26 1 0 +github.com/IBM/fp-go/v2/function/binds.go:167.26,169.6 1 0 +github.com/IBM/fp-go/v2/function/binds.go:174.80,175.38 1 0 +github.com/IBM/fp-go/v2/function/binds.go:175.38,177.4 1 0 +github.com/IBM/fp-go/v2/function/binds.go:182.90,183.45 1 0 +github.com/IBM/fp-go/v2/function/binds.go:183.45,184.21 1 0 +github.com/IBM/fp-go/v2/function/binds.go:184.21,186.6 1 0 +github.com/IBM/fp-go/v2/function/binds.go:191.79,192.38 1 0 +github.com/IBM/fp-go/v2/function/binds.go:192.38,194.4 1 0 +github.com/IBM/fp-go/v2/function/binds.go:200.98,201.41 1 0 +github.com/IBM/fp-go/v2/function/binds.go:201.41,202.40 1 0 +github.com/IBM/fp-go/v2/function/binds.go:202.40,204.6 1 0 +github.com/IBM/fp-go/v2/function/binds.go:209.95,210.45 1 0 +github.com/IBM/fp-go/v2/function/binds.go:210.45,212.4 1 0 +github.com/IBM/fp-go/v2/function/binds.go:217.98,218.41 1 0 +github.com/IBM/fp-go/v2/function/binds.go:218.41,219.40 1 0 +github.com/IBM/fp-go/v2/function/binds.go:219.40,221.6 1 0 +github.com/IBM/fp-go/v2/function/binds.go:226.95,227.45 1 0 +github.com/IBM/fp-go/v2/function/binds.go:227.45,229.4 1 0 +github.com/IBM/fp-go/v2/function/binds.go:234.98,235.41 1 0 +github.com/IBM/fp-go/v2/function/binds.go:235.41,236.40 1 0 +github.com/IBM/fp-go/v2/function/binds.go:236.40,238.6 1 0 +github.com/IBM/fp-go/v2/function/binds.go:243.95,244.45 1 0 +github.com/IBM/fp-go/v2/function/binds.go:244.45,246.4 1 0 +github.com/IBM/fp-go/v2/function/binds.go:251.98,252.41 1 0 +github.com/IBM/fp-go/v2/function/binds.go:252.41,253.40 1 0 +github.com/IBM/fp-go/v2/function/binds.go:253.40,255.6 1 0 +github.com/IBM/fp-go/v2/function/binds.go:260.95,261.45 1 0 +github.com/IBM/fp-go/v2/function/binds.go:261.45,263.4 1 0 +github.com/IBM/fp-go/v2/function/binds.go:268.99,269.44 1 0 +github.com/IBM/fp-go/v2/function/binds.go:269.44,270.33 1 0 +github.com/IBM/fp-go/v2/function/binds.go:270.33,272.6 1 0 +github.com/IBM/fp-go/v2/function/binds.go:277.92,278.45 1 0 +github.com/IBM/fp-go/v2/function/binds.go:278.45,280.4 1 0 +github.com/IBM/fp-go/v2/function/binds.go:285.99,286.44 1 0 +github.com/IBM/fp-go/v2/function/binds.go:286.44,287.33 1 0 +github.com/IBM/fp-go/v2/function/binds.go:287.33,289.6 1 0 +github.com/IBM/fp-go/v2/function/binds.go:294.92,295.45 1 0 +github.com/IBM/fp-go/v2/function/binds.go:295.45,297.4 1 0 +github.com/IBM/fp-go/v2/function/binds.go:302.99,303.44 1 0 +github.com/IBM/fp-go/v2/function/binds.go:303.44,304.33 1 0 +github.com/IBM/fp-go/v2/function/binds.go:304.33,306.6 1 0 +github.com/IBM/fp-go/v2/function/binds.go:311.92,312.45 1 0 +github.com/IBM/fp-go/v2/function/binds.go:312.45,314.4 1 0 +github.com/IBM/fp-go/v2/function/binds.go:319.99,320.44 1 0 +github.com/IBM/fp-go/v2/function/binds.go:320.44,321.33 1 0 +github.com/IBM/fp-go/v2/function/binds.go:321.33,323.6 1 0 +github.com/IBM/fp-go/v2/function/binds.go:328.92,329.45 1 0 +github.com/IBM/fp-go/v2/function/binds.go:329.45,331.4 1 0 +github.com/IBM/fp-go/v2/function/binds.go:336.99,337.44 1 0 +github.com/IBM/fp-go/v2/function/binds.go:337.44,338.33 1 0 +github.com/IBM/fp-go/v2/function/binds.go:338.33,340.6 1 0 +github.com/IBM/fp-go/v2/function/binds.go:345.92,346.45 1 0 +github.com/IBM/fp-go/v2/function/binds.go:346.45,348.4 1 0 +github.com/IBM/fp-go/v2/function/binds.go:353.99,354.44 1 0 +github.com/IBM/fp-go/v2/function/binds.go:354.44,355.33 1 0 +github.com/IBM/fp-go/v2/function/binds.go:355.33,357.6 1 0 +github.com/IBM/fp-go/v2/function/binds.go:362.92,363.45 1 0 +github.com/IBM/fp-go/v2/function/binds.go:363.45,365.4 1 0 +github.com/IBM/fp-go/v2/function/binds.go:370.100,371.47 1 0 +github.com/IBM/fp-go/v2/function/binds.go:371.47,372.26 1 0 +github.com/IBM/fp-go/v2/function/binds.go:372.26,374.6 1 0 +github.com/IBM/fp-go/v2/function/binds.go:379.89,380.45 1 0 +github.com/IBM/fp-go/v2/function/binds.go:380.45,382.4 1 0 +github.com/IBM/fp-go/v2/function/binds.go:387.100,388.47 1 0 +github.com/IBM/fp-go/v2/function/binds.go:388.47,389.26 1 0 +github.com/IBM/fp-go/v2/function/binds.go:389.26,391.6 1 0 +github.com/IBM/fp-go/v2/function/binds.go:396.89,397.45 1 0 +github.com/IBM/fp-go/v2/function/binds.go:397.45,399.4 1 0 +github.com/IBM/fp-go/v2/function/binds.go:404.100,405.47 1 0 +github.com/IBM/fp-go/v2/function/binds.go:405.47,406.26 1 0 +github.com/IBM/fp-go/v2/function/binds.go:406.26,408.6 1 0 +github.com/IBM/fp-go/v2/function/binds.go:413.89,414.45 1 0 +github.com/IBM/fp-go/v2/function/binds.go:414.45,416.4 1 0 +github.com/IBM/fp-go/v2/function/binds.go:421.100,422.47 1 0 +github.com/IBM/fp-go/v2/function/binds.go:422.47,423.26 1 0 +github.com/IBM/fp-go/v2/function/binds.go:423.26,425.6 1 0 +github.com/IBM/fp-go/v2/function/binds.go:430.89,431.45 1 0 +github.com/IBM/fp-go/v2/function/binds.go:431.45,433.4 1 0 +github.com/IBM/fp-go/v2/function/binds.go:438.103,439.52 1 0 +github.com/IBM/fp-go/v2/function/binds.go:439.52,440.21 1 0 +github.com/IBM/fp-go/v2/function/binds.go:440.21,442.6 1 0 +github.com/IBM/fp-go/v2/function/binds.go:447.88,448.45 1 0 +github.com/IBM/fp-go/v2/function/binds.go:448.45,450.4 1 0 +github.com/IBM/fp-go/v2/function/cache.go:23.58,25.2 1 1 +github.com/IBM/fp-go/v2/function/cache.go:28.87,30.2 1 0 +github.com/IBM/fp-go/v2/function/cache.go:34.113,36.2 1 1 +github.com/IBM/fp-go/v2/function/cache.go:39.82,41.2 1 1 +github.com/IBM/fp-go/v2/function/constants.go:83.27,85.2 1 1 +github.com/IBM/fp-go/v2/function/flip.go:19.69,20.32 1 1 +github.com/IBM/fp-go/v2/function/flip.go:20.32,21.24 1 1 +github.com/IBM/fp-go/v2/function/flip.go:21.24,23.4 1 1 +github.com/IBM/fp-go/v2/function/function.go:34.29,36.2 1 1 +github.com/IBM/fp-go/v2/function/function.go:56.36,57.18 1 1 +github.com/IBM/fp-go/v2/function/function.go:57.18,59.3 1 1 +github.com/IBM/fp-go/v2/function/function.go:84.41,85.21 1 1 +github.com/IBM/fp-go/v2/function/function.go:85.21,87.3 1 1 +github.com/IBM/fp-go/v2/function/function.go:110.47,111.26 1 1 +github.com/IBM/fp-go/v2/function/function.go:111.26,113.3 1 1 +github.com/IBM/fp-go/v2/function/function.go:131.30,133.2 1 1 +github.com/IBM/fp-go/v2/function/function.go:152.33,154.2 1 1 +github.com/IBM/fp-go/v2/function/function.go:184.59,185.30 1 1 +github.com/IBM/fp-go/v2/function/function.go:185.30,187.3 1 1 +github.com/IBM/fp-go/v2/function/function.go:210.40,212.2 1 1 +github.com/IBM/fp-go/v2/function/function.go:234.41,236.2 1 1 +github.com/IBM/fp-go/v2/function/gen.go:9.30,11.2 1 0 +github.com/IBM/fp-go/v2/function/gen.go:14.54,15.25 1 0 +github.com/IBM/fp-go/v2/function/gen.go:15.25,17.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:21.56,22.24 1 1 +github.com/IBM/fp-go/v2/function/gen.go:22.24,24.4 1 1 +github.com/IBM/fp-go/v2/function/gen.go:28.56,29.19 1 0 +github.com/IBM/fp-go/v2/function/gen.go:29.19,31.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:36.58,38.2 1 1 +github.com/IBM/fp-go/v2/function/gen.go:42.60,43.25 1 0 +github.com/IBM/fp-go/v2/function/gen.go:43.25,45.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:49.55,50.20 1 0 +github.com/IBM/fp-go/v2/function/gen.go:50.20,52.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:57.62,58.25 1 0 +github.com/IBM/fp-go/v2/function/gen.go:58.25,60.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:65.64,66.25 1 0 +github.com/IBM/fp-go/v2/function/gen.go:66.25,68.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:72.66,73.32 1 0 +github.com/IBM/fp-go/v2/function/gen.go:73.32,75.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:79.68,80.31 1 0 +github.com/IBM/fp-go/v2/function/gen.go:80.31,82.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:86.57,87.23 1 0 +github.com/IBM/fp-go/v2/function/gen.go:87.23,89.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:94.86,96.2 1 0 +github.com/IBM/fp-go/v2/function/gen.go:100.88,101.25 1 0 +github.com/IBM/fp-go/v2/function/gen.go:101.25,103.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:107.83,108.20 1 0 +github.com/IBM/fp-go/v2/function/gen.go:108.20,110.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:115.79,116.37 1 1 +github.com/IBM/fp-go/v2/function/gen.go:116.37,117.27 1 1 +github.com/IBM/fp-go/v2/function/gen.go:117.27,119.6 1 1 +github.com/IBM/fp-go/v2/function/gen.go:125.81,126.32 1 0 +github.com/IBM/fp-go/v2/function/gen.go:126.32,128.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:132.78,133.39 1 0 +github.com/IBM/fp-go/v2/function/gen.go:133.39,135.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:139.80,140.38 1 0 +github.com/IBM/fp-go/v2/function/gen.go:140.38,142.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:146.60,147.27 1 1 +github.com/IBM/fp-go/v2/function/gen.go:147.27,149.4 1 1 +github.com/IBM/fp-go/v2/function/gen.go:154.114,156.2 1 1 +github.com/IBM/fp-go/v2/function/gen.go:160.116,161.25 1 1 +github.com/IBM/fp-go/v2/function/gen.go:161.25,163.4 1 1 +github.com/IBM/fp-go/v2/function/gen.go:167.111,168.20 1 0 +github.com/IBM/fp-go/v2/function/gen.go:168.20,170.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:175.96,176.49 1 0 +github.com/IBM/fp-go/v2/function/gen.go:176.49,177.39 1 0 +github.com/IBM/fp-go/v2/function/gen.go:177.39,178.29 1 0 +github.com/IBM/fp-go/v2/function/gen.go:178.29,180.8 1 0 +github.com/IBM/fp-go/v2/function/gen.go:187.98,188.39 1 0 +github.com/IBM/fp-go/v2/function/gen.go:188.39,190.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:194.90,195.46 1 0 +github.com/IBM/fp-go/v2/function/gen.go:195.46,197.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:201.92,202.45 1 0 +github.com/IBM/fp-go/v2/function/gen.go:202.45,204.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:208.63,209.31 1 0 +github.com/IBM/fp-go/v2/function/gen.go:209.31,211.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:216.142,218.2 1 0 +github.com/IBM/fp-go/v2/function/gen.go:222.144,223.25 1 0 +github.com/IBM/fp-go/v2/function/gen.go:223.25,225.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:229.139,230.20 1 0 +github.com/IBM/fp-go/v2/function/gen.go:230.20,232.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:237.113,238.61 1 0 +github.com/IBM/fp-go/v2/function/gen.go:238.61,239.51 1 0 +github.com/IBM/fp-go/v2/function/gen.go:239.51,240.41 1 0 +github.com/IBM/fp-go/v2/function/gen.go:240.41,241.31 1 0 +github.com/IBM/fp-go/v2/function/gen.go:241.31,243.10 1 0 +github.com/IBM/fp-go/v2/function/gen.go:251.115,252.46 1 0 +github.com/IBM/fp-go/v2/function/gen.go:252.46,254.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:258.102,259.53 1 0 +github.com/IBM/fp-go/v2/function/gen.go:259.53,261.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:265.104,266.52 1 0 +github.com/IBM/fp-go/v2/function/gen.go:266.52,268.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:272.66,273.35 1 0 +github.com/IBM/fp-go/v2/function/gen.go:273.35,275.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:280.170,282.2 1 0 +github.com/IBM/fp-go/v2/function/gen.go:286.172,287.25 1 0 +github.com/IBM/fp-go/v2/function/gen.go:287.25,289.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:293.167,294.20 1 0 +github.com/IBM/fp-go/v2/function/gen.go:294.20,296.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:301.130,302.73 1 0 +github.com/IBM/fp-go/v2/function/gen.go:302.73,303.63 1 0 +github.com/IBM/fp-go/v2/function/gen.go:303.63,304.53 1 0 +github.com/IBM/fp-go/v2/function/gen.go:304.53,305.43 1 0 +github.com/IBM/fp-go/v2/function/gen.go:305.43,306.33 1 0 +github.com/IBM/fp-go/v2/function/gen.go:306.33,308.12 1 0 +github.com/IBM/fp-go/v2/function/gen.go:317.132,318.53 1 0 +github.com/IBM/fp-go/v2/function/gen.go:318.53,320.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:324.114,325.60 1 0 +github.com/IBM/fp-go/v2/function/gen.go:325.60,327.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:331.116,332.59 1 0 +github.com/IBM/fp-go/v2/function/gen.go:332.59,334.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:338.69,339.39 1 0 +github.com/IBM/fp-go/v2/function/gen.go:339.39,341.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:346.198,348.2 1 0 +github.com/IBM/fp-go/v2/function/gen.go:352.200,353.25 1 0 +github.com/IBM/fp-go/v2/function/gen.go:353.25,355.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:359.195,360.20 1 0 +github.com/IBM/fp-go/v2/function/gen.go:360.20,362.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:367.147,368.85 1 0 +github.com/IBM/fp-go/v2/function/gen.go:368.85,369.75 1 0 +github.com/IBM/fp-go/v2/function/gen.go:369.75,370.65 1 0 +github.com/IBM/fp-go/v2/function/gen.go:370.65,371.55 1 0 +github.com/IBM/fp-go/v2/function/gen.go:371.55,372.45 1 0 +github.com/IBM/fp-go/v2/function/gen.go:372.45,373.35 1 0 +github.com/IBM/fp-go/v2/function/gen.go:373.35,375.14 1 0 +github.com/IBM/fp-go/v2/function/gen.go:385.149,386.60 1 0 +github.com/IBM/fp-go/v2/function/gen.go:386.60,388.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:392.126,393.67 1 0 +github.com/IBM/fp-go/v2/function/gen.go:393.67,395.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:399.128,400.66 1 0 +github.com/IBM/fp-go/v2/function/gen.go:400.66,402.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:406.72,407.43 1 0 +github.com/IBM/fp-go/v2/function/gen.go:407.43,409.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:414.226,416.2 1 0 +github.com/IBM/fp-go/v2/function/gen.go:420.228,421.25 1 0 +github.com/IBM/fp-go/v2/function/gen.go:421.25,423.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:427.223,428.20 1 0 +github.com/IBM/fp-go/v2/function/gen.go:428.20,430.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:435.164,436.97 1 0 +github.com/IBM/fp-go/v2/function/gen.go:436.97,437.87 1 0 +github.com/IBM/fp-go/v2/function/gen.go:437.87,438.77 1 0 +github.com/IBM/fp-go/v2/function/gen.go:438.77,439.67 1 0 +github.com/IBM/fp-go/v2/function/gen.go:439.67,440.57 1 0 +github.com/IBM/fp-go/v2/function/gen.go:440.57,441.47 1 0 +github.com/IBM/fp-go/v2/function/gen.go:441.47,442.37 1 0 +github.com/IBM/fp-go/v2/function/gen.go:442.37,444.16 1 0 +github.com/IBM/fp-go/v2/function/gen.go:455.166,456.67 1 0 +github.com/IBM/fp-go/v2/function/gen.go:456.67,458.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:462.138,463.74 1 0 +github.com/IBM/fp-go/v2/function/gen.go:463.74,465.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:469.140,470.73 1 0 +github.com/IBM/fp-go/v2/function/gen.go:470.73,472.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:476.75,477.47 1 0 +github.com/IBM/fp-go/v2/function/gen.go:477.47,479.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:484.254,486.2 1 0 +github.com/IBM/fp-go/v2/function/gen.go:490.256,491.25 1 0 +github.com/IBM/fp-go/v2/function/gen.go:491.25,493.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:497.251,498.20 1 0 +github.com/IBM/fp-go/v2/function/gen.go:498.20,500.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:505.181,506.109 1 0 +github.com/IBM/fp-go/v2/function/gen.go:506.109,507.99 1 0 +github.com/IBM/fp-go/v2/function/gen.go:507.99,508.89 1 0 +github.com/IBM/fp-go/v2/function/gen.go:508.89,509.79 1 0 +github.com/IBM/fp-go/v2/function/gen.go:509.79,510.69 1 0 +github.com/IBM/fp-go/v2/function/gen.go:510.69,511.59 1 0 +github.com/IBM/fp-go/v2/function/gen.go:511.59,512.49 1 0 +github.com/IBM/fp-go/v2/function/gen.go:512.49,513.39 1 0 +github.com/IBM/fp-go/v2/function/gen.go:513.39,515.18 1 0 +github.com/IBM/fp-go/v2/function/gen.go:527.183,528.74 1 0 +github.com/IBM/fp-go/v2/function/gen.go:528.74,530.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:534.150,535.81 1 0 +github.com/IBM/fp-go/v2/function/gen.go:535.81,537.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:541.152,542.80 1 0 +github.com/IBM/fp-go/v2/function/gen.go:542.80,544.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:548.78,549.51 1 0 +github.com/IBM/fp-go/v2/function/gen.go:549.51,551.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:556.282,558.2 1 0 +github.com/IBM/fp-go/v2/function/gen.go:562.284,563.25 1 0 +github.com/IBM/fp-go/v2/function/gen.go:563.25,565.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:569.279,570.20 1 0 +github.com/IBM/fp-go/v2/function/gen.go:570.20,572.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:577.198,578.121 1 0 +github.com/IBM/fp-go/v2/function/gen.go:578.121,579.111 1 0 +github.com/IBM/fp-go/v2/function/gen.go:579.111,580.101 1 0 +github.com/IBM/fp-go/v2/function/gen.go:580.101,581.91 1 0 +github.com/IBM/fp-go/v2/function/gen.go:581.91,582.81 1 0 +github.com/IBM/fp-go/v2/function/gen.go:582.81,583.71 1 0 +github.com/IBM/fp-go/v2/function/gen.go:583.71,584.61 1 0 +github.com/IBM/fp-go/v2/function/gen.go:584.61,585.51 1 0 +github.com/IBM/fp-go/v2/function/gen.go:585.51,586.41 1 0 +github.com/IBM/fp-go/v2/function/gen.go:586.41,588.20 1 0 +github.com/IBM/fp-go/v2/function/gen.go:601.200,602.81 1 0 +github.com/IBM/fp-go/v2/function/gen.go:602.81,604.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:608.162,609.88 1 0 +github.com/IBM/fp-go/v2/function/gen.go:609.88,611.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:615.164,616.87 1 0 +github.com/IBM/fp-go/v2/function/gen.go:616.87,618.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:622.81,623.55 1 0 +github.com/IBM/fp-go/v2/function/gen.go:623.55,625.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:630.317,632.2 1 0 +github.com/IBM/fp-go/v2/function/gen.go:636.319,637.26 1 0 +github.com/IBM/fp-go/v2/function/gen.go:637.26,639.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:643.314,644.21 1 0 +github.com/IBM/fp-go/v2/function/gen.go:644.21,646.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:651.219,652.134 1 0 +github.com/IBM/fp-go/v2/function/gen.go:652.134,653.124 1 0 +github.com/IBM/fp-go/v2/function/gen.go:653.124,654.114 1 0 +github.com/IBM/fp-go/v2/function/gen.go:654.114,655.104 1 0 +github.com/IBM/fp-go/v2/function/gen.go:655.104,656.94 1 0 +github.com/IBM/fp-go/v2/function/gen.go:656.94,657.84 1 0 +github.com/IBM/fp-go/v2/function/gen.go:657.84,658.74 1 0 +github.com/IBM/fp-go/v2/function/gen.go:658.74,659.64 1 0 +github.com/IBM/fp-go/v2/function/gen.go:659.64,660.54 1 0 +github.com/IBM/fp-go/v2/function/gen.go:660.54,661.44 1 0 +github.com/IBM/fp-go/v2/function/gen.go:661.44,663.22 1 0 +github.com/IBM/fp-go/v2/function/gen.go:677.221,678.89 1 0 +github.com/IBM/fp-go/v2/function/gen.go:678.89,680.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:684.178,685.97 1 0 +github.com/IBM/fp-go/v2/function/gen.go:685.97,687.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:691.180,692.96 1 0 +github.com/IBM/fp-go/v2/function/gen.go:692.96,694.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:698.85,699.60 1 0 +github.com/IBM/fp-go/v2/function/gen.go:699.60,701.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:706.351,708.2 1 0 +github.com/IBM/fp-go/v2/function/gen.go:712.353,713.26 1 0 +github.com/IBM/fp-go/v2/function/gen.go:713.26,715.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:719.348,720.21 1 0 +github.com/IBM/fp-go/v2/function/gen.go:720.21,722.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:727.239,728.148 1 0 +github.com/IBM/fp-go/v2/function/gen.go:728.148,729.138 1 0 +github.com/IBM/fp-go/v2/function/gen.go:729.138,730.128 1 0 +github.com/IBM/fp-go/v2/function/gen.go:730.128,731.118 1 0 +github.com/IBM/fp-go/v2/function/gen.go:731.118,732.108 1 0 +github.com/IBM/fp-go/v2/function/gen.go:732.108,733.98 1 0 +github.com/IBM/fp-go/v2/function/gen.go:733.98,734.88 1 0 +github.com/IBM/fp-go/v2/function/gen.go:734.88,735.78 1 0 +github.com/IBM/fp-go/v2/function/gen.go:735.78,736.68 1 0 +github.com/IBM/fp-go/v2/function/gen.go:736.68,737.58 1 0 +github.com/IBM/fp-go/v2/function/gen.go:737.58,738.48 1 0 +github.com/IBM/fp-go/v2/function/gen.go:738.48,740.24 1 0 +github.com/IBM/fp-go/v2/function/gen.go:755.241,756.98 1 0 +github.com/IBM/fp-go/v2/function/gen.go:756.98,758.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:762.193,763.106 1 0 +github.com/IBM/fp-go/v2/function/gen.go:763.106,765.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:769.195,770.105 1 0 +github.com/IBM/fp-go/v2/function/gen.go:770.105,772.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:776.88,777.65 1 0 +github.com/IBM/fp-go/v2/function/gen.go:777.65,779.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:784.385,786.2 1 0 +github.com/IBM/fp-go/v2/function/gen.go:790.387,791.26 1 0 +github.com/IBM/fp-go/v2/function/gen.go:791.26,793.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:797.382,798.21 1 0 +github.com/IBM/fp-go/v2/function/gen.go:798.21,800.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:805.259,806.162 1 0 +github.com/IBM/fp-go/v2/function/gen.go:806.162,807.152 1 0 +github.com/IBM/fp-go/v2/function/gen.go:807.152,808.142 1 0 +github.com/IBM/fp-go/v2/function/gen.go:808.142,809.132 1 0 +github.com/IBM/fp-go/v2/function/gen.go:809.132,810.122 1 0 +github.com/IBM/fp-go/v2/function/gen.go:810.122,811.112 1 0 +github.com/IBM/fp-go/v2/function/gen.go:811.112,812.102 1 0 +github.com/IBM/fp-go/v2/function/gen.go:812.102,813.92 1 0 +github.com/IBM/fp-go/v2/function/gen.go:813.92,814.82 1 0 +github.com/IBM/fp-go/v2/function/gen.go:814.82,815.72 1 0 +github.com/IBM/fp-go/v2/function/gen.go:815.72,816.62 1 0 +github.com/IBM/fp-go/v2/function/gen.go:816.62,817.50 1 0 +github.com/IBM/fp-go/v2/function/gen.go:817.50,819.26 1 0 +github.com/IBM/fp-go/v2/function/gen.go:835.261,836.107 1 0 +github.com/IBM/fp-go/v2/function/gen.go:836.107,838.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:842.208,843.115 1 0 +github.com/IBM/fp-go/v2/function/gen.go:843.115,845.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:849.210,850.114 1 0 +github.com/IBM/fp-go/v2/function/gen.go:850.114,852.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:856.91,857.70 1 0 +github.com/IBM/fp-go/v2/function/gen.go:857.70,859.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:864.419,866.2 1 0 +github.com/IBM/fp-go/v2/function/gen.go:870.421,871.26 1 0 +github.com/IBM/fp-go/v2/function/gen.go:871.26,873.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:877.416,878.21 1 0 +github.com/IBM/fp-go/v2/function/gen.go:878.21,880.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:885.279,886.176 1 0 +github.com/IBM/fp-go/v2/function/gen.go:886.176,887.166 1 0 +github.com/IBM/fp-go/v2/function/gen.go:887.166,888.156 1 0 +github.com/IBM/fp-go/v2/function/gen.go:888.156,889.146 1 0 +github.com/IBM/fp-go/v2/function/gen.go:889.146,890.136 1 0 +github.com/IBM/fp-go/v2/function/gen.go:890.136,891.126 1 0 +github.com/IBM/fp-go/v2/function/gen.go:891.126,892.116 1 0 +github.com/IBM/fp-go/v2/function/gen.go:892.116,893.106 1 0 +github.com/IBM/fp-go/v2/function/gen.go:893.106,894.96 1 0 +github.com/IBM/fp-go/v2/function/gen.go:894.96,895.86 1 0 +github.com/IBM/fp-go/v2/function/gen.go:895.86,896.76 1 0 +github.com/IBM/fp-go/v2/function/gen.go:896.76,897.64 1 0 +github.com/IBM/fp-go/v2/function/gen.go:897.64,898.52 1 0 +github.com/IBM/fp-go/v2/function/gen.go:898.52,900.28 1 0 +github.com/IBM/fp-go/v2/function/gen.go:917.281,918.116 1 0 +github.com/IBM/fp-go/v2/function/gen.go:918.116,920.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:924.223,925.124 1 0 +github.com/IBM/fp-go/v2/function/gen.go:925.124,927.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:931.225,932.123 1 0 +github.com/IBM/fp-go/v2/function/gen.go:932.123,934.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:938.94,939.75 1 0 +github.com/IBM/fp-go/v2/function/gen.go:939.75,941.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:946.453,948.2 1 0 +github.com/IBM/fp-go/v2/function/gen.go:952.455,953.26 1 0 +github.com/IBM/fp-go/v2/function/gen.go:953.26,955.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:959.450,960.21 1 0 +github.com/IBM/fp-go/v2/function/gen.go:960.21,962.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:967.299,968.190 1 0 +github.com/IBM/fp-go/v2/function/gen.go:968.190,969.180 1 0 +github.com/IBM/fp-go/v2/function/gen.go:969.180,970.170 1 0 +github.com/IBM/fp-go/v2/function/gen.go:970.170,971.160 1 0 +github.com/IBM/fp-go/v2/function/gen.go:971.160,972.150 1 0 +github.com/IBM/fp-go/v2/function/gen.go:972.150,973.140 1 0 +github.com/IBM/fp-go/v2/function/gen.go:973.140,974.130 1 0 +github.com/IBM/fp-go/v2/function/gen.go:974.130,975.120 1 0 +github.com/IBM/fp-go/v2/function/gen.go:975.120,976.110 1 0 +github.com/IBM/fp-go/v2/function/gen.go:976.110,977.100 1 0 +github.com/IBM/fp-go/v2/function/gen.go:977.100,978.90 1 0 +github.com/IBM/fp-go/v2/function/gen.go:978.90,979.78 1 0 +github.com/IBM/fp-go/v2/function/gen.go:979.78,980.66 1 0 +github.com/IBM/fp-go/v2/function/gen.go:980.66,981.54 1 0 +github.com/IBM/fp-go/v2/function/gen.go:981.54,983.30 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1001.301,1002.125 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1002.125,1004.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1008.238,1009.133 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1009.133,1011.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1015.240,1016.132 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1016.132,1018.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1022.97,1023.80 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1023.80,1025.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1030.487,1032.2 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1036.489,1037.26 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1037.26,1039.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1043.484,1044.21 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1044.21,1046.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1051.319,1052.204 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1052.204,1053.194 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1053.194,1054.184 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1054.184,1055.174 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1055.174,1056.164 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1056.164,1057.154 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1057.154,1058.144 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1058.144,1059.134 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1059.134,1060.124 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1060.124,1061.114 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1061.114,1062.104 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1062.104,1063.92 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1063.92,1064.80 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1064.80,1065.68 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1065.68,1066.56 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1066.56,1068.32 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1087.321,1088.134 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1088.134,1090.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1094.253,1095.142 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1095.142,1097.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1101.255,1102.141 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1102.141,1104.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1108.100,1109.85 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1109.85,1111.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1116.521,1118.2 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1122.523,1123.26 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1123.26,1125.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1129.518,1130.21 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1130.21,1132.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1137.339,1138.218 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1138.218,1139.208 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1139.208,1140.198 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1140.198,1141.188 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1141.188,1142.178 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1142.178,1143.168 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1143.168,1144.158 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1144.158,1145.148 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1145.148,1146.138 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1146.138,1147.128 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1147.128,1148.118 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1148.118,1149.106 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1149.106,1150.94 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1150.94,1151.82 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1151.82,1152.70 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1152.70,1153.58 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1153.58,1155.34 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1175.341,1176.143 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1176.143,1178.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1182.268,1183.151 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1183.151,1185.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1189.270,1190.150 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1190.150,1192.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1196.103,1197.90 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1197.90,1199.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1204.555,1206.2 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1210.557,1211.26 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1211.26,1213.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1217.552,1218.21 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1218.21,1220.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1225.359,1226.232 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1226.232,1227.222 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1227.222,1228.212 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1228.212,1229.202 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1229.202,1230.192 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1230.192,1231.182 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1231.182,1232.172 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1232.172,1233.162 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1233.162,1234.152 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1234.152,1235.142 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1235.142,1236.132 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1236.132,1237.120 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1237.120,1238.108 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1238.108,1239.96 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1239.96,1240.84 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1240.84,1241.72 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1241.72,1242.60 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1242.60,1244.36 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1265.361,1266.152 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1266.152,1268.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1272.283,1273.160 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1273.160,1275.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1279.285,1280.159 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1280.159,1282.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1286.106,1287.95 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1287.95,1289.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1294.589,1296.2 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1300.591,1301.26 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1301.26,1303.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1307.586,1308.21 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1308.21,1310.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1315.379,1316.246 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1316.246,1317.236 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1317.236,1318.226 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1318.226,1319.216 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1319.216,1320.206 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1320.206,1321.196 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1321.196,1322.186 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1322.186,1323.176 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1323.176,1324.166 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1324.166,1325.156 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1325.156,1326.146 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1326.146,1327.134 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1327.134,1328.122 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1328.122,1329.110 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1329.110,1330.98 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1330.98,1331.86 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1331.86,1332.74 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1332.74,1333.62 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1333.62,1335.38 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1357.381,1358.161 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1358.161,1360.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1364.298,1365.169 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1365.169,1367.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1371.300,1372.168 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1372.168,1374.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1378.109,1379.100 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1379.100,1381.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1386.623,1388.2 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1392.625,1393.26 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1393.26,1395.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1399.620,1400.21 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1400.21,1402.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1407.399,1408.260 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1408.260,1409.250 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1409.250,1410.240 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1410.240,1411.230 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1411.230,1412.220 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1412.220,1413.210 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1413.210,1414.200 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1414.200,1415.190 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1415.190,1416.180 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1416.180,1417.170 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1417.170,1418.160 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1418.160,1419.148 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1419.148,1420.136 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1420.136,1421.124 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1421.124,1422.112 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1422.112,1423.100 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1423.100,1424.88 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1424.88,1425.76 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1425.76,1426.64 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1426.64,1428.40 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1451.401,1452.170 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1452.170,1454.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1458.313,1459.178 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1459.178,1461.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1465.315,1466.177 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1466.177,1468.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1472.112,1473.105 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1473.105,1475.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1480.657,1482.2 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1486.659,1487.26 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1487.26,1489.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1493.654,1494.21 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1494.21,1496.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1501.419,1502.274 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1502.274,1503.264 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1503.264,1504.254 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1504.254,1505.244 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1505.244,1506.234 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1506.234,1507.224 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1507.224,1508.214 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1508.214,1509.204 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1509.204,1510.194 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1510.194,1511.184 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1511.184,1512.174 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1512.174,1513.162 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1513.162,1514.150 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1514.150,1515.138 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1515.138,1516.126 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1516.126,1517.114 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1517.114,1518.102 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1518.102,1519.90 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1519.90,1520.78 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1520.78,1521.66 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1521.66,1523.42 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1547.421,1548.179 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1548.179,1550.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1554.328,1555.187 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1555.187,1557.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1561.330,1562.186 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1562.186,1564.4 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1568.115,1569.110 1 0 +github.com/IBM/fp-go/v2/function/gen.go:1569.110,1571.4 1 0 +github.com/IBM/fp-go/v2/function/ref.go:48.25,50.2 1 1 +github.com/IBM/fp-go/v2/function/ref.go:83.27,85.2 1 1 +github.com/IBM/fp-go/v2/function/switch.go:94.93,96.2 1 1 +github.com/IBM/fp-go/v2/function/ternary.go:54.90,55.21 1 1 +github.com/IBM/fp-go/v2/function/ternary.go:55.21,56.14 1 1 +github.com/IBM/fp-go/v2/function/ternary.go:56.14,58.4 1 1 +github.com/IBM/fp-go/v2/function/ternary.go:59.3,59.20 1 1 +github.com/IBM/fp-go/v2/function/type.go:47.28,49.2 1 1 diff --git a/v2/function/coverage_detail.txt b/v2/function/coverage_detail.txt new file mode 100644 index 0000000..644fd78 --- /dev/null +++ b/v2/function/coverage_detail.txt @@ -0,0 +1,240 @@ +github.com/IBM/fp-go/v2/function/bind.go:45: Bind1st 100.0% +github.com/IBM/fp-go/v2/function/bind.go:78: Bind2nd 100.0% +github.com/IBM/fp-go/v2/function/bind.go:107: SK 100.0% +github.com/IBM/fp-go/v2/function/binds.go:10: Bind1of1 0.0% +github.com/IBM/fp-go/v2/function/binds.go:19: Ignore1of1 0.0% +github.com/IBM/fp-go/v2/function/binds.go:28: Bind1of2 0.0% +github.com/IBM/fp-go/v2/function/binds.go:37: Ignore1of2 0.0% +github.com/IBM/fp-go/v2/function/binds.go:45: Bind2of2 0.0% +github.com/IBM/fp-go/v2/function/binds.go:54: Ignore2of2 0.0% +github.com/IBM/fp-go/v2/function/binds.go:62: Bind12of2 0.0% +github.com/IBM/fp-go/v2/function/binds.go:71: Ignore12of2 0.0% +github.com/IBM/fp-go/v2/function/binds.go:80: Bind1of3 0.0% +github.com/IBM/fp-go/v2/function/binds.go:89: Ignore1of3 0.0% +github.com/IBM/fp-go/v2/function/binds.go:97: Bind2of3 0.0% +github.com/IBM/fp-go/v2/function/binds.go:106: Ignore2of3 0.0% +github.com/IBM/fp-go/v2/function/binds.go:114: Bind3of3 0.0% +github.com/IBM/fp-go/v2/function/binds.go:123: Ignore3of3 0.0% +github.com/IBM/fp-go/v2/function/binds.go:131: Bind12of3 0.0% +github.com/IBM/fp-go/v2/function/binds.go:140: Ignore12of3 0.0% +github.com/IBM/fp-go/v2/function/binds.go:148: Bind13of3 0.0% +github.com/IBM/fp-go/v2/function/binds.go:157: Ignore13of3 0.0% +github.com/IBM/fp-go/v2/function/binds.go:165: Bind23of3 0.0% +github.com/IBM/fp-go/v2/function/binds.go:174: Ignore23of3 0.0% +github.com/IBM/fp-go/v2/function/binds.go:182: Bind123of3 0.0% +github.com/IBM/fp-go/v2/function/binds.go:191: Ignore123of3 0.0% +github.com/IBM/fp-go/v2/function/binds.go:200: Bind1of4 0.0% +github.com/IBM/fp-go/v2/function/binds.go:209: Ignore1of4 0.0% +github.com/IBM/fp-go/v2/function/binds.go:217: Bind2of4 0.0% +github.com/IBM/fp-go/v2/function/binds.go:226: Ignore2of4 0.0% +github.com/IBM/fp-go/v2/function/binds.go:234: Bind3of4 0.0% +github.com/IBM/fp-go/v2/function/binds.go:243: Ignore3of4 0.0% +github.com/IBM/fp-go/v2/function/binds.go:251: Bind4of4 0.0% +github.com/IBM/fp-go/v2/function/binds.go:260: Ignore4of4 0.0% +github.com/IBM/fp-go/v2/function/binds.go:268: Bind12of4 0.0% +github.com/IBM/fp-go/v2/function/binds.go:277: Ignore12of4 0.0% +github.com/IBM/fp-go/v2/function/binds.go:285: Bind13of4 0.0% +github.com/IBM/fp-go/v2/function/binds.go:294: Ignore13of4 0.0% +github.com/IBM/fp-go/v2/function/binds.go:302: Bind14of4 0.0% +github.com/IBM/fp-go/v2/function/binds.go:311: Ignore14of4 0.0% +github.com/IBM/fp-go/v2/function/binds.go:319: Bind23of4 0.0% +github.com/IBM/fp-go/v2/function/binds.go:328: Ignore23of4 0.0% +github.com/IBM/fp-go/v2/function/binds.go:336: Bind24of4 0.0% +github.com/IBM/fp-go/v2/function/binds.go:345: Ignore24of4 0.0% +github.com/IBM/fp-go/v2/function/binds.go:353: Bind34of4 0.0% +github.com/IBM/fp-go/v2/function/binds.go:362: Ignore34of4 0.0% +github.com/IBM/fp-go/v2/function/binds.go:370: Bind123of4 0.0% +github.com/IBM/fp-go/v2/function/binds.go:379: Ignore123of4 0.0% +github.com/IBM/fp-go/v2/function/binds.go:387: Bind124of4 0.0% +github.com/IBM/fp-go/v2/function/binds.go:396: Ignore124of4 0.0% +github.com/IBM/fp-go/v2/function/binds.go:404: Bind134of4 0.0% +github.com/IBM/fp-go/v2/function/binds.go:413: Ignore134of4 0.0% +github.com/IBM/fp-go/v2/function/binds.go:421: Bind234of4 0.0% +github.com/IBM/fp-go/v2/function/binds.go:430: Ignore234of4 0.0% +github.com/IBM/fp-go/v2/function/binds.go:438: Bind1234of4 0.0% +github.com/IBM/fp-go/v2/function/binds.go:447: Ignore1234of4 0.0% +github.com/IBM/fp-go/v2/function/cache.go:23: Memoize 100.0% +github.com/IBM/fp-go/v2/function/cache.go:28: ContramapMemoize 0.0% +github.com/IBM/fp-go/v2/function/cache.go:33: CacheCallback 100.0% +github.com/IBM/fp-go/v2/function/cache.go:39: SingleElementCache 100.0% +github.com/IBM/fp-go/v2/function/constants.go:83: ConstNil 100.0% +github.com/IBM/fp-go/v2/function/flip.go:19: Flip 100.0% +github.com/IBM/fp-go/v2/function/function.go:34: Identity 100.0% +github.com/IBM/fp-go/v2/function/function.go:56: Constant 100.0% +github.com/IBM/fp-go/v2/function/function.go:84: Constant1 100.0% +github.com/IBM/fp-go/v2/function/function.go:110: Constant2 100.0% +github.com/IBM/fp-go/v2/function/function.go:131: IsNil 100.0% +github.com/IBM/fp-go/v2/function/function.go:152: IsNonNil 100.0% +github.com/IBM/fp-go/v2/function/function.go:184: Swap 100.0% +github.com/IBM/fp-go/v2/function/function.go:210: First 100.0% +github.com/IBM/fp-go/v2/function/function.go:234: Second 100.0% +github.com/IBM/fp-go/v2/function/gen.go:9: Pipe0 0.0% +github.com/IBM/fp-go/v2/function/gen.go:14: Variadic0 0.0% +github.com/IBM/fp-go/v2/function/gen.go:21: Unvariadic0 100.0% +github.com/IBM/fp-go/v2/function/gen.go:28: Unsliced0 0.0% +github.com/IBM/fp-go/v2/function/gen.go:36: Pipe1 100.0% +github.com/IBM/fp-go/v2/function/gen.go:42: Flow1 0.0% +github.com/IBM/fp-go/v2/function/gen.go:49: Nullary1 0.0% +github.com/IBM/fp-go/v2/function/gen.go:57: Curry1 0.0% +github.com/IBM/fp-go/v2/function/gen.go:65: Uncurry1 0.0% +github.com/IBM/fp-go/v2/function/gen.go:72: Variadic1 0.0% +github.com/IBM/fp-go/v2/function/gen.go:79: Unvariadic1 0.0% +github.com/IBM/fp-go/v2/function/gen.go:86: Unsliced1 0.0% +github.com/IBM/fp-go/v2/function/gen.go:94: Pipe2 0.0% +github.com/IBM/fp-go/v2/function/gen.go:100: Flow2 0.0% +github.com/IBM/fp-go/v2/function/gen.go:107: Nullary2 0.0% +github.com/IBM/fp-go/v2/function/gen.go:115: Curry2 100.0% +github.com/IBM/fp-go/v2/function/gen.go:125: Uncurry2 0.0% +github.com/IBM/fp-go/v2/function/gen.go:132: Variadic2 0.0% +github.com/IBM/fp-go/v2/function/gen.go:139: Unvariadic2 0.0% +github.com/IBM/fp-go/v2/function/gen.go:146: Unsliced2 100.0% +github.com/IBM/fp-go/v2/function/gen.go:154: Pipe3 100.0% +github.com/IBM/fp-go/v2/function/gen.go:160: Flow3 100.0% +github.com/IBM/fp-go/v2/function/gen.go:167: Nullary3 0.0% +github.com/IBM/fp-go/v2/function/gen.go:175: Curry3 0.0% +github.com/IBM/fp-go/v2/function/gen.go:187: Uncurry3 0.0% +github.com/IBM/fp-go/v2/function/gen.go:194: Variadic3 0.0% +github.com/IBM/fp-go/v2/function/gen.go:201: Unvariadic3 0.0% +github.com/IBM/fp-go/v2/function/gen.go:208: Unsliced3 0.0% +github.com/IBM/fp-go/v2/function/gen.go:216: Pipe4 0.0% +github.com/IBM/fp-go/v2/function/gen.go:222: Flow4 0.0% +github.com/IBM/fp-go/v2/function/gen.go:229: Nullary4 0.0% +github.com/IBM/fp-go/v2/function/gen.go:237: Curry4 0.0% +github.com/IBM/fp-go/v2/function/gen.go:251: Uncurry4 0.0% +github.com/IBM/fp-go/v2/function/gen.go:258: Variadic4 0.0% +github.com/IBM/fp-go/v2/function/gen.go:265: Unvariadic4 0.0% +github.com/IBM/fp-go/v2/function/gen.go:272: Unsliced4 0.0% +github.com/IBM/fp-go/v2/function/gen.go:280: Pipe5 0.0% +github.com/IBM/fp-go/v2/function/gen.go:286: Flow5 0.0% +github.com/IBM/fp-go/v2/function/gen.go:293: Nullary5 0.0% +github.com/IBM/fp-go/v2/function/gen.go:301: Curry5 0.0% +github.com/IBM/fp-go/v2/function/gen.go:317: Uncurry5 0.0% +github.com/IBM/fp-go/v2/function/gen.go:324: Variadic5 0.0% +github.com/IBM/fp-go/v2/function/gen.go:331: Unvariadic5 0.0% +github.com/IBM/fp-go/v2/function/gen.go:338: Unsliced5 0.0% +github.com/IBM/fp-go/v2/function/gen.go:346: Pipe6 0.0% +github.com/IBM/fp-go/v2/function/gen.go:352: Flow6 0.0% +github.com/IBM/fp-go/v2/function/gen.go:359: Nullary6 0.0% +github.com/IBM/fp-go/v2/function/gen.go:367: Curry6 0.0% +github.com/IBM/fp-go/v2/function/gen.go:385: Uncurry6 0.0% +github.com/IBM/fp-go/v2/function/gen.go:392: Variadic6 0.0% +github.com/IBM/fp-go/v2/function/gen.go:399: Unvariadic6 0.0% +github.com/IBM/fp-go/v2/function/gen.go:406: Unsliced6 0.0% +github.com/IBM/fp-go/v2/function/gen.go:414: Pipe7 0.0% +github.com/IBM/fp-go/v2/function/gen.go:420: Flow7 0.0% +github.com/IBM/fp-go/v2/function/gen.go:427: Nullary7 0.0% +github.com/IBM/fp-go/v2/function/gen.go:435: Curry7 0.0% +github.com/IBM/fp-go/v2/function/gen.go:455: Uncurry7 0.0% +github.com/IBM/fp-go/v2/function/gen.go:462: Variadic7 0.0% +github.com/IBM/fp-go/v2/function/gen.go:469: Unvariadic7 0.0% +github.com/IBM/fp-go/v2/function/gen.go:476: Unsliced7 0.0% +github.com/IBM/fp-go/v2/function/gen.go:484: Pipe8 0.0% +github.com/IBM/fp-go/v2/function/gen.go:490: Flow8 0.0% +github.com/IBM/fp-go/v2/function/gen.go:497: Nullary8 0.0% +github.com/IBM/fp-go/v2/function/gen.go:505: Curry8 0.0% +github.com/IBM/fp-go/v2/function/gen.go:527: Uncurry8 0.0% +github.com/IBM/fp-go/v2/function/gen.go:534: Variadic8 0.0% +github.com/IBM/fp-go/v2/function/gen.go:541: Unvariadic8 0.0% +github.com/IBM/fp-go/v2/function/gen.go:548: Unsliced8 0.0% +github.com/IBM/fp-go/v2/function/gen.go:556: Pipe9 0.0% +github.com/IBM/fp-go/v2/function/gen.go:562: Flow9 0.0% +github.com/IBM/fp-go/v2/function/gen.go:569: Nullary9 0.0% +github.com/IBM/fp-go/v2/function/gen.go:577: Curry9 0.0% +github.com/IBM/fp-go/v2/function/gen.go:601: Uncurry9 0.0% +github.com/IBM/fp-go/v2/function/gen.go:608: Variadic9 0.0% +github.com/IBM/fp-go/v2/function/gen.go:615: Unvariadic9 0.0% +github.com/IBM/fp-go/v2/function/gen.go:622: Unsliced9 0.0% +github.com/IBM/fp-go/v2/function/gen.go:630: Pipe10 0.0% +github.com/IBM/fp-go/v2/function/gen.go:636: Flow10 0.0% +github.com/IBM/fp-go/v2/function/gen.go:643: Nullary10 0.0% +github.com/IBM/fp-go/v2/function/gen.go:651: Curry10 0.0% +github.com/IBM/fp-go/v2/function/gen.go:677: Uncurry10 0.0% +github.com/IBM/fp-go/v2/function/gen.go:684: Variadic10 0.0% +github.com/IBM/fp-go/v2/function/gen.go:691: Unvariadic10 0.0% +github.com/IBM/fp-go/v2/function/gen.go:698: Unsliced10 0.0% +github.com/IBM/fp-go/v2/function/gen.go:706: Pipe11 0.0% +github.com/IBM/fp-go/v2/function/gen.go:712: Flow11 0.0% +github.com/IBM/fp-go/v2/function/gen.go:719: Nullary11 0.0% +github.com/IBM/fp-go/v2/function/gen.go:727: Curry11 0.0% +github.com/IBM/fp-go/v2/function/gen.go:755: Uncurry11 0.0% +github.com/IBM/fp-go/v2/function/gen.go:762: Variadic11 0.0% +github.com/IBM/fp-go/v2/function/gen.go:769: Unvariadic11 0.0% +github.com/IBM/fp-go/v2/function/gen.go:776: Unsliced11 0.0% +github.com/IBM/fp-go/v2/function/gen.go:784: Pipe12 0.0% +github.com/IBM/fp-go/v2/function/gen.go:790: Flow12 0.0% +github.com/IBM/fp-go/v2/function/gen.go:797: Nullary12 0.0% +github.com/IBM/fp-go/v2/function/gen.go:805: Curry12 0.0% +github.com/IBM/fp-go/v2/function/gen.go:835: Uncurry12 0.0% +github.com/IBM/fp-go/v2/function/gen.go:842: Variadic12 0.0% +github.com/IBM/fp-go/v2/function/gen.go:849: Unvariadic12 0.0% +github.com/IBM/fp-go/v2/function/gen.go:856: Unsliced12 0.0% +github.com/IBM/fp-go/v2/function/gen.go:864: Pipe13 0.0% +github.com/IBM/fp-go/v2/function/gen.go:870: Flow13 0.0% +github.com/IBM/fp-go/v2/function/gen.go:877: Nullary13 0.0% +github.com/IBM/fp-go/v2/function/gen.go:885: Curry13 0.0% +github.com/IBM/fp-go/v2/function/gen.go:917: Uncurry13 0.0% +github.com/IBM/fp-go/v2/function/gen.go:924: Variadic13 0.0% +github.com/IBM/fp-go/v2/function/gen.go:931: Unvariadic13 0.0% +github.com/IBM/fp-go/v2/function/gen.go:938: Unsliced13 0.0% +github.com/IBM/fp-go/v2/function/gen.go:946: Pipe14 0.0% +github.com/IBM/fp-go/v2/function/gen.go:952: Flow14 0.0% +github.com/IBM/fp-go/v2/function/gen.go:959: Nullary14 0.0% +github.com/IBM/fp-go/v2/function/gen.go:967: Curry14 0.0% +github.com/IBM/fp-go/v2/function/gen.go:1001: Uncurry14 0.0% +github.com/IBM/fp-go/v2/function/gen.go:1008: Variadic14 0.0% +github.com/IBM/fp-go/v2/function/gen.go:1015: Unvariadic14 0.0% +github.com/IBM/fp-go/v2/function/gen.go:1022: Unsliced14 0.0% +github.com/IBM/fp-go/v2/function/gen.go:1030: Pipe15 0.0% +github.com/IBM/fp-go/v2/function/gen.go:1036: Flow15 0.0% +github.com/IBM/fp-go/v2/function/gen.go:1043: Nullary15 0.0% +github.com/IBM/fp-go/v2/function/gen.go:1051: Curry15 0.0% +github.com/IBM/fp-go/v2/function/gen.go:1087: Uncurry15 0.0% +github.com/IBM/fp-go/v2/function/gen.go:1094: Variadic15 0.0% +github.com/IBM/fp-go/v2/function/gen.go:1101: Unvariadic15 0.0% +github.com/IBM/fp-go/v2/function/gen.go:1108: Unsliced15 0.0% +github.com/IBM/fp-go/v2/function/gen.go:1116: Pipe16 0.0% +github.com/IBM/fp-go/v2/function/gen.go:1122: Flow16 0.0% +github.com/IBM/fp-go/v2/function/gen.go:1129: Nullary16 0.0% +github.com/IBM/fp-go/v2/function/gen.go:1137: Curry16 0.0% +github.com/IBM/fp-go/v2/function/gen.go:1175: Uncurry16 0.0% +github.com/IBM/fp-go/v2/function/gen.go:1182: Variadic16 0.0% +github.com/IBM/fp-go/v2/function/gen.go:1189: Unvariadic16 0.0% +github.com/IBM/fp-go/v2/function/gen.go:1196: Unsliced16 0.0% +github.com/IBM/fp-go/v2/function/gen.go:1204: Pipe17 0.0% +github.com/IBM/fp-go/v2/function/gen.go:1210: Flow17 0.0% +github.com/IBM/fp-go/v2/function/gen.go:1217: Nullary17 0.0% +github.com/IBM/fp-go/v2/function/gen.go:1225: Curry17 0.0% +github.com/IBM/fp-go/v2/function/gen.go:1265: Uncurry17 0.0% +github.com/IBM/fp-go/v2/function/gen.go:1272: Variadic17 0.0% +github.com/IBM/fp-go/v2/function/gen.go:1279: Unvariadic17 0.0% +github.com/IBM/fp-go/v2/function/gen.go:1286: Unsliced17 0.0% +github.com/IBM/fp-go/v2/function/gen.go:1294: Pipe18 0.0% +github.com/IBM/fp-go/v2/function/gen.go:1300: Flow18 0.0% +github.com/IBM/fp-go/v2/function/gen.go:1307: Nullary18 0.0% +github.com/IBM/fp-go/v2/function/gen.go:1315: Curry18 0.0% +github.com/IBM/fp-go/v2/function/gen.go:1357: Uncurry18 0.0% +github.com/IBM/fp-go/v2/function/gen.go:1364: Variadic18 0.0% +github.com/IBM/fp-go/v2/function/gen.go:1371: Unvariadic18 0.0% +github.com/IBM/fp-go/v2/function/gen.go:1378: Unsliced18 0.0% +github.com/IBM/fp-go/v2/function/gen.go:1386: Pipe19 0.0% +github.com/IBM/fp-go/v2/function/gen.go:1392: Flow19 0.0% +github.com/IBM/fp-go/v2/function/gen.go:1399: Nullary19 0.0% +github.com/IBM/fp-go/v2/function/gen.go:1407: Curry19 0.0% +github.com/IBM/fp-go/v2/function/gen.go:1451: Uncurry19 0.0% +github.com/IBM/fp-go/v2/function/gen.go:1458: Variadic19 0.0% +github.com/IBM/fp-go/v2/function/gen.go:1465: Unvariadic19 0.0% +github.com/IBM/fp-go/v2/function/gen.go:1472: Unsliced19 0.0% +github.com/IBM/fp-go/v2/function/gen.go:1480: Pipe20 0.0% +github.com/IBM/fp-go/v2/function/gen.go:1486: Flow20 0.0% +github.com/IBM/fp-go/v2/function/gen.go:1493: Nullary20 0.0% +github.com/IBM/fp-go/v2/function/gen.go:1501: Curry20 0.0% +github.com/IBM/fp-go/v2/function/gen.go:1547: Uncurry20 0.0% +github.com/IBM/fp-go/v2/function/gen.go:1554: Variadic20 0.0% +github.com/IBM/fp-go/v2/function/gen.go:1561: Unvariadic20 0.0% +github.com/IBM/fp-go/v2/function/gen.go:1568: Unsliced20 0.0% +github.com/IBM/fp-go/v2/function/ref.go:48: Ref 100.0% +github.com/IBM/fp-go/v2/function/ref.go:83: Deref 100.0% +github.com/IBM/fp-go/v2/function/switch.go:94: Switch 100.0% +github.com/IBM/fp-go/v2/function/ternary.go:54: Ternary 100.0% +github.com/IBM/fp-go/v2/function/type.go:47: ToAny 100.0% +total: (statements) 6.7% diff --git a/v2/function/doc.go b/v2/function/doc.go new file mode 100644 index 0000000..6581973 --- /dev/null +++ b/v2/function/doc.go @@ -0,0 +1,156 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +// Package function provides functional programming utilities for function composition, +// transformation, and manipulation. +// +// This package offers a comprehensive set of tools for working with functions in a +// functional programming style, including: +// +// - Function composition (Pipe, Flow) +// - Currying and uncurrying +// - Partial application (Bind) +// - Function transformation (Flip, Swap) +// - Utility functions (Identity, Constant, etc.) +// +// # Core Concepts +// +// Function Composition: +// +// Pipe and Flow are the primary composition primitives. They differ in the order +// of function application: +// +// - Pipe: Applies functions left-to-right (data flows through the pipeline) +// - Flow: Applies functions right-to-left (mathematical function composition) +// +// Example: +// +// // Pipe: f(g(h(x))) +// result := Pipe3(x, h, g, f) +// +// // Flow: f(g(h(x))) +// composed := Flow3(f, g, h) +// result := composed(x) +// +// Currying: +// +// Currying transforms a function with multiple parameters into a sequence of +// functions each taking a single parameter. +// +// Example: +// +// add := func(a, b int) int { return a + b } +// curriedAdd := Curry2(add) +// add5 := curriedAdd(5) +// result := add5(3) // 8 +// +// Partial Application: +// +// Bind functions allow you to fix some arguments of a function, creating a new +// function with fewer parameters. +// +// Example: +// +// multiply := func(a, b int) int { return a * b } +// double := Bind1st(multiply, 2) +// result := double(5) // 10 +// +// # Common Functions +// +// Identity and Constants: +// +// Identity[A any](a A) A // Returns its argument unchanged +// Constant[A any](a A) func() A // Creates a nullary constant function +// Constant1[B, A any](a A) func(B) A // Creates a unary constant function +// Constant2[B, C, A any](a A) func(B, C) A // Creates a binary constant function +// +// Function Transformation: +// +// Flip[T1, T2, R any](func(T1) func(T2) R) func(T2) func(T1) R // Reverses curried function parameters +// Swap[T1, T2, R any](func(T1, T2) R) func(T2, T1) R // Swaps binary function parameters +// +// Pointer Utilities: +// +// Ref[A any](a A) *A // Creates a pointer to a value +// Deref[A any](a *A) A // Dereferences a pointer +// IsNil[A any](a *A) bool // Checks if pointer is nil +// +// Selection: +// +// First[T1, T2 any](t1 T1, t2 T2) T1 // Returns first argument +// Second[T1, T2 any](t1 T1, t2 T2) T2 // Returns second argument +// +// Conditional: +// +// Ternary[A, B any](pred func(A) bool, onTrue func(A) B, onFalse func(A) B) func(A) B +// Switch[K comparable, T, R any](kf func(T) K, cases map[K]func(T) R, default func(T) R) func(T) R +// +// # Generated Functions +// +// This package includes generated functions for various arities (0-20 parameters): +// +// - PipeN: Left-to-right composition +// - FlowN: Right-to-left composition +// - CurryN: Currying for N-ary functions +// - UncurryN: Uncurrying for N-ary functions +// - BindXofN: Partial application binding specific parameters +// - IgnoreXofN: Partial application ignoring specific parameters +// +// # Usage Examples +// +// Basic composition: +// +// // Transform a string: trim, lowercase, add prefix +// process := Flow3( +// func(s string) string { return "processed: " + s }, +// strings.ToLower, +// strings.TrimSpace, +// ) +// result := process(" HELLO ") // "processed: hello" +// +// Currying and partial application: +// +// // Create specialized functions from general ones +// divide := func(a, b float64) float64 { return a / b } +// divideBy2 := Bind2nd(divide, 2.0) +// half := divideBy2(10.0) // 5.0 +// +// Working with predicates: +// +// isPositive := func(n int) bool { return n > 0 } +// isEven := func(n int) bool { return n%2 == 0 } +// +// classify := Ternary( +// isPositive, +// Constant1[int]("positive"), +// Constant1[int]("non-positive"), +// ) +// result := classify(5) // "positive" +// result2 := classify(-3) // "non-positive" +// +// Memoization: +// +// expensive := func(n int) int { +// time.Sleep(time.Second) +// return n * n +// } +// memoized := Memoize(expensive) +// result1 := memoized(5) // Takes 1 second +// result2 := memoized(5) // Instant (cached) +package function + +//go:generate go run .. pipe --count 20 --filename gen.go + +//go:generate go run .. bind --count 5 --filename binds.go diff --git a/v2/function/flip.go b/v2/function/flip.go new file mode 100644 index 0000000..0e58531 --- /dev/null +++ b/v2/function/flip.go @@ -0,0 +1,25 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package function + +// Flip reverses the order of parameters of a curried function +func Flip[T1, T2, R any](f func(T1) func(T2) R) func(T2) func(T1) R { + return func(t2 T2) func(T1) R { + return func(t1 T1) R { + return f(t1)(t2) + } + } +} diff --git a/v2/function/flip_test.go b/v2/function/flip_test.go new file mode 100644 index 0000000..c9712af --- /dev/null +++ b/v2/function/flip_test.go @@ -0,0 +1,36 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package function + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestFlip(t *testing.T) { + + x := Curry2(func(a, b string) string { + return fmt.Sprintf("%s:%s", a, b) + }) + + assert.Equal(t, "a:b", x("a")("b")) + + y := Flip(x) + + assert.Equal(t, "b:a", y("a")("b")) +} diff --git a/v2/function/function.go b/v2/function/function.go new file mode 100644 index 0000000..6eb32c8 --- /dev/null +++ b/v2/function/function.go @@ -0,0 +1,236 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package function + +// Identity returns its argument unchanged. +// +// This is the identity function from category theory, which satisfies: +// - Identity(x) = x for all x +// +// It's useful as a default transformation or when you need a function that +// does nothing but is required by an API. +// +// Example: +// +// result := Identity(42) // 42 +// result := Identity("hello") // "hello" +// +// // Useful in higher-order functions +// values := []int{1, 2, 3} +// mapped := Map(Identity[int])(values) // [1, 2, 3] +func Identity[A any](a A) A { + return a +} + +// Constant creates a nullary function that always returns the same value. +// +// This creates a function with no parameters that returns the constant value 'a'. +// Useful for lazy evaluation or when you need a function that produces a fixed value. +// +// Parameters: +// - a: The constant value to return +// +// Returns: +// - A function that takes no arguments and returns 'a' +// +// Example: +// +// getFortyTwo := Constant(42) +// result := getFortyTwo() // 42 +// +// getMessage := Constant("Hello") +// msg := getMessage() // "Hello" +func Constant[A any](a A) func() A { + return func() A { + return a + } +} + +// Constant1 creates a unary function that always returns the same value, ignoring its input. +// +// This creates a function that takes one parameter but ignores it and always returns +// the constant value 'a'. Useful for providing default values or placeholder functions. +// +// Type Parameters: +// - B: The type of the ignored input parameter +// - A: The type of the constant return value +// +// Parameters: +// - a: The constant value to return +// +// Returns: +// - A function that takes a B and returns 'a' +// +// Example: +// +// alwaysZero := Constant1[string, int](0) +// result := alwaysZero("anything") // 0 +// +// defaultName := Constant1[int, string]("Unknown") +// name := defaultName(42) // "Unknown" +func Constant1[B, A any](a A) func(B) A { + return func(_ B) A { + return a + } +} + +// Constant2 creates a binary function that always returns the same value, ignoring its inputs. +// +// This creates a function that takes two parameters but ignores both and always returns +// the constant value 'a'. +// +// Type Parameters: +// - B: The type of the first ignored input parameter +// - C: The type of the second ignored input parameter +// - A: The type of the constant return value +// +// Parameters: +// - a: The constant value to return +// +// Returns: +// - A function that takes a B and C and returns 'a' +// +// Example: +// +// alwaysTrue := Constant2[int, string, bool](true) +// result := alwaysTrue(42, "test") // true +func Constant2[B, C, A any](a A) func(B, C) A { + return func(_ B, _ C) A { + return a + } +} + +// IsNil checks if a pointer is nil. +// +// Parameters: +// - a: A pointer to check +// +// Returns: +// - true if the pointer is nil, false otherwise +// +// Example: +// +// var ptr *int +// IsNil(ptr) // true +// +// value := 42 +// IsNil(&value) // false +func IsNil[A any](a *A) bool { + return a == nil +} + +// IsNonNil checks if a pointer is not nil. +// +// This is the logical negation of IsNil. +// +// Parameters: +// - a: A pointer to check +// +// Returns: +// - true if the pointer is not nil, false otherwise +// +// Example: +// +// var ptr *int +// IsNonNil(ptr) // false +// +// value := 42 +// IsNonNil(&value) // true +func IsNonNil[A any](a *A) bool { + return a != nil +} + +// Swap returns a new binary function with the parameter order reversed. +// +// Given a function f(a, b), Swap returns a function g(b, a) where g(b, a) = f(a, b). +// This is useful when you have a function but need to call it with arguments in +// a different order. +// +// Type Parameters: +// - T1: The type of the first parameter (becomes second) +// - T2: The type of the second parameter (becomes first) +// - R: The return type +// +// Parameters: +// - f: The function to swap +// +// Returns: +// - A new function with swapped parameters +// +// Example: +// +// divide := func(a, b float64) float64 { return a / b } +// divideSwapped := Swap(divide) +// +// result1 := divide(10, 2) // 5.0 (10 / 2) +// result2 := divideSwapped(10, 2) // 0.2 (2 / 10) +// +// subtract := func(a, b int) int { return a - b } +// subtractSwapped := Swap(subtract) +// result := subtractSwapped(5, 10) // 5 (10 - 5) +func Swap[T1, T2, R any](f func(T1, T2) R) func(T2, T1) R { + return func(t2 T2, t1 T1) R { + return f(t1, t2) + } +} + +// First returns the first of two input values, ignoring the second. +// +// This is a projection function that selects the first element of a pair. +// Also known as the K combinator in combinatory logic. +// +// Type Parameters: +// - T1: The type of the first value (returned) +// - T2: The type of the second value (ignored) +// +// Parameters: +// - t1: The first value +// - t2: The second value (ignored) +// +// Returns: +// - The first value +// +// Example: +// +// result := First(42, "hello") // 42 +// result := First(true, 100) // true +func First[T1, T2 any](t1 T1, _ T2) T1 { + return t1 +} + +// Second returns the second of two input values, ignoring the first. +// +// This is a projection function that selects the second element of a pair. +// Identical to SK combinator in combinatory logic. +// +// Type Parameters: +// - T1: The type of the first value (ignored) +// - T2: The type of the second value (returned) +// +// Parameters: +// - t1: The first value (ignored) +// - t2: The second value +// +// Returns: +// - The second value +// +// Example: +// +// result := Second(42, "hello") // "hello" +// result := Second(true, 100) // 100 +func Second[T1, T2 any](_ T1, t2 T2) T2 { + return t2 +} diff --git a/v2/function/function_test.go b/v2/function/function_test.go new file mode 100644 index 0000000..74fdf38 --- /dev/null +++ b/v2/function/function_test.go @@ -0,0 +1,498 @@ +// Copyright (c) 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package function + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +// TestIdentity tests the Identity function +func TestIdentity(t *testing.T) { + t.Run("returns int unchanged", func(t *testing.T) { + assert.Equal(t, 42, Identity(42)) + assert.Equal(t, 0, Identity(0)) + assert.Equal(t, -10, Identity(-10)) + }) + + t.Run("returns string unchanged", func(t *testing.T) { + assert.Equal(t, "hello", Identity("hello")) + assert.Equal(t, "", Identity("")) + }) + + t.Run("returns bool unchanged", func(t *testing.T) { + assert.True(t, Identity(true)) + assert.False(t, Identity(false)) + }) + + t.Run("returns struct unchanged", func(t *testing.T) { + type Person struct { + Name string + Age int + } + p := Person{Name: "Alice", Age: 30} + assert.Equal(t, p, Identity(p)) + }) +} + +// TestConstant tests the Constant function +func TestConstant(t *testing.T) { + t.Run("returns constant int", func(t *testing.T) { + getFortyTwo := Constant(42) + assert.Equal(t, 42, getFortyTwo()) + assert.Equal(t, 42, getFortyTwo()) + }) + + t.Run("returns constant string", func(t *testing.T) { + getMessage := Constant("Hello") + assert.Equal(t, "Hello", getMessage()) + }) + + t.Run("returns constant bool", func(t *testing.T) { + getTrue := Constant(true) + assert.True(t, getTrue()) + }) +} + +// TestConstant1 tests the Constant1 function +func TestConstant1(t *testing.T) { + t.Run("ignores input and returns constant", func(t *testing.T) { + alwaysZero := Constant1[string, int](0) + assert.Equal(t, 0, alwaysZero("anything")) + assert.Equal(t, 0, alwaysZero("something else")) + assert.Equal(t, 0, alwaysZero("")) + }) + + t.Run("works with different types", func(t *testing.T) { + defaultName := Constant1[int, string]("Unknown") + assert.Equal(t, "Unknown", defaultName(42)) + assert.Equal(t, "Unknown", defaultName(0)) + }) +} + +// TestConstant2 tests the Constant2 function +func TestConstant2(t *testing.T) { + t.Run("ignores both inputs and returns constant", func(t *testing.T) { + alwaysTrue := Constant2[int, string, bool](true) + assert.True(t, alwaysTrue(42, "test")) + assert.True(t, alwaysTrue(0, "")) + }) + + t.Run("works with different types", func(t *testing.T) { + alwaysPi := Constant2[string, bool, float64](3.14) + assert.Equal(t, 3.14, alwaysPi("test", true)) + }) +} + +// TestIsNil tests the IsNil function +func TestIsNil(t *testing.T) { + t.Run("returns true for nil pointer", func(t *testing.T) { + var ptr *int + assert.True(t, IsNil(ptr)) + + var strPtr *string + assert.True(t, IsNil(strPtr)) + }) + + t.Run("returns false for non-nil pointer", func(t *testing.T) { + value := 42 + assert.False(t, IsNil(&value)) + + str := "hello" + assert.False(t, IsNil(&str)) + }) +} + +// TestIsNonNil tests the IsNonNil function +func TestIsNonNil(t *testing.T) { + t.Run("returns false for nil pointer", func(t *testing.T) { + var ptr *int + assert.False(t, IsNonNil(ptr)) + }) + + t.Run("returns true for non-nil pointer", func(t *testing.T) { + value := 42 + assert.True(t, IsNonNil(&value)) + + str := "hello" + assert.True(t, IsNonNil(&str)) + }) +} + +// TestSwap tests the Swap function +func TestSwap(t *testing.T) { + t.Run("swaps parameters of subtraction", func(t *testing.T) { + subtract := func(a, b int) int { return a - b } + swapped := Swap(subtract) + + assert.Equal(t, 7, subtract(10, 3)) // 10 - 3 + assert.Equal(t, -7, swapped(10, 3)) // 3 - 10 + }) + + t.Run("swaps parameters of division", func(t *testing.T) { + divide := func(a, b float64) float64 { return a / b } + swapped := Swap(divide) + + assert.Equal(t, 5.0, divide(10, 2)) // 10 / 2 + assert.Equal(t, 0.2, swapped(10, 2)) // 2 / 10 + }) + + t.Run("swaps parameters of string concatenation", func(t *testing.T) { + concat := func(a, b string) string { return a + b } + swapped := Swap(concat) + + assert.Equal(t, "HelloWorld", concat("Hello", "World")) + assert.Equal(t, "WorldHello", swapped("Hello", "World")) + }) +} + +// TestFirst tests the First function +func TestFirst(t *testing.T) { + t.Run("returns first of two ints", func(t *testing.T) { + assert.Equal(t, 42, First(42, 100)) + assert.Equal(t, 0, First(0, 1)) + }) + + t.Run("returns first of two strings", func(t *testing.T) { + assert.Equal(t, "hello", First("hello", "world")) + }) + + t.Run("returns first of mixed types", func(t *testing.T) { + assert.Equal(t, 42, First(42, "hello")) + assert.True(t, First(true, 100)) + }) +} + +// TestSecond tests the Second function +func TestSecond(t *testing.T) { + t.Run("returns second of two ints", func(t *testing.T) { + assert.Equal(t, 100, Second(42, 100)) + assert.Equal(t, 1, Second(0, 1)) + }) + + t.Run("returns second of two strings", func(t *testing.T) { + assert.Equal(t, "world", Second("hello", "world")) + }) + + t.Run("returns second of mixed types", func(t *testing.T) { + assert.Equal(t, "hello", Second(42, "hello")) + assert.Equal(t, 100, Second(true, 100)) + }) +} + +// TestBind1st tests the Bind1st function +func TestBind1st(t *testing.T) { + t.Run("binds first parameter of multiplication", func(t *testing.T) { + multiply := func(a, b int) int { return a * b } + double := Bind1st(multiply, 2) + triple := Bind1st(multiply, 3) + + assert.Equal(t, 10, double(5)) + assert.Equal(t, 20, double(10)) + assert.Equal(t, 15, triple(5)) + }) + + t.Run("binds first parameter of division", func(t *testing.T) { + divide := func(a, b float64) float64 { return a / b } + divideBy10 := Bind1st(divide, 10.0) + + assert.Equal(t, 5.0, divideBy10(2.0)) + assert.Equal(t, 2.0, divideBy10(5.0)) + }) + + t.Run("binds first parameter of string concatenation", func(t *testing.T) { + concat := func(a, b string) string { return a + b } + addHello := Bind1st(concat, "Hello ") + + assert.Equal(t, "Hello World", addHello("World")) + assert.Equal(t, "Hello Go", addHello("Go")) + }) +} + +// TestBind2nd tests the Bind2nd function +func TestBind2nd(t *testing.T) { + t.Run("binds second parameter of multiplication", func(t *testing.T) { + multiply := func(a, b int) int { return a * b } + double := Bind2nd(multiply, 2) + triple := Bind2nd(multiply, 3) + + assert.Equal(t, 10, double(5)) + assert.Equal(t, 20, double(10)) + assert.Equal(t, 15, triple(5)) + }) + + t.Run("binds second parameter of division", func(t *testing.T) { + divide := func(a, b float64) float64 { return a / b } + halve := Bind2nd(divide, 2.0) + + assert.Equal(t, 5.0, halve(10.0)) + assert.Equal(t, 2.5, halve(5.0)) + }) + + t.Run("binds second parameter of subtraction", func(t *testing.T) { + subtract := func(a, b int) int { return a - b } + decrementBy5 := Bind2nd(subtract, 5) + + assert.Equal(t, 5, decrementBy5(10)) + assert.Equal(t, 0, decrementBy5(5)) + }) +} + +// TestSK tests the SK function +func TestSK(t *testing.T) { + t.Run("returns second argument ignoring first", func(t *testing.T) { + assert.Equal(t, "hello", SK(42, "hello")) + assert.Equal(t, 100, SK(true, 100)) + assert.Equal(t, 3.14, SK("test", 3.14)) + }) + + t.Run("behaves like Second", func(t *testing.T) { + // SK should be identical to Second + assert.Equal(t, Second(42, "hello"), SK(42, "hello")) + assert.Equal(t, Second(true, 100), SK(true, 100)) + }) +} + +// TestTernary tests the Ternary function +func TestTernary(t *testing.T) { + t.Run("applies onTrue when predicate is true", func(t *testing.T) { + isPositive := func(n int) bool { return n > 0 } + double := func(n int) int { return n * 2 } + negate := func(n int) int { return -n } + + transform := Ternary(isPositive, double, negate) + + assert.Equal(t, 10, transform(5)) + assert.Equal(t, 20, transform(10)) + }) + + t.Run("applies onFalse when predicate is false", func(t *testing.T) { + isPositive := func(n int) bool { return n > 0 } + double := func(n int) int { return n * 2 } + negate := func(n int) int { return -n } + + transform := Ternary(isPositive, double, negate) + + assert.Equal(t, 3, transform(-3)) + assert.Equal(t, 5, transform(-5)) + assert.Equal(t, 0, transform(0)) + }) + + t.Run("works with string classification", func(t *testing.T) { + isPositive := func(n int) bool { return n > 0 } + classify := Ternary( + isPositive, + Constant1[int, string]("positive"), + Constant1[int, string]("non-positive"), + ) + + assert.Equal(t, "positive", classify(5)) + assert.Equal(t, "non-positive", classify(-3)) + assert.Equal(t, "non-positive", classify(0)) + }) +} + +// TestRef tests the Ref function +func TestRef(t *testing.T) { + t.Run("creates pointer to int", func(t *testing.T) { + value := 42 + ptr := Ref(value) + assert.NotNil(t, ptr) + assert.Equal(t, 42, *ptr) + }) + + t.Run("creates pointer to string", func(t *testing.T) { + str := "hello" + ptr := Ref(str) + assert.NotNil(t, ptr) + assert.Equal(t, "hello", *ptr) + }) + + t.Run("creates pointer to struct", func(t *testing.T) { + type Person struct { + Name string + Age int + } + p := Person{Name: "Alice", Age: 30} + ptr := Ref(p) + assert.NotNil(t, ptr) + assert.Equal(t, "Alice", ptr.Name) + assert.Equal(t, 30, ptr.Age) + }) +} + +// TestDeref tests the Deref function +func TestDeref(t *testing.T) { + t.Run("dereferences int pointer", func(t *testing.T) { + value := 42 + ptr := &value + assert.Equal(t, 42, Deref(ptr)) + }) + + t.Run("dereferences string pointer", func(t *testing.T) { + str := "hello" + ptr := &str + assert.Equal(t, "hello", Deref(ptr)) + }) + + t.Run("round trip with Ref", func(t *testing.T) { + original := "test" + copy := Deref(Ref(original)) + assert.Equal(t, original, copy) + }) +} + +// TestToAny tests the ToAny function +func TestToAny(t *testing.T) { + t.Run("converts int to any", func(t *testing.T) { + value := 42 + anyValue := ToAny(value) + assert.Equal(t, any(42), anyValue) + }) + + t.Run("converts string to any", func(t *testing.T) { + str := "hello" + anyStr := ToAny(str) + assert.Equal(t, any("hello"), anyStr) + }) + + t.Run("converts bool to any", func(t *testing.T) { + b := true + anyBool := ToAny(b) + assert.Equal(t, any(true), anyBool) + }) +} + +// TestConstNil tests the ConstNil function +func TestConstNil(t *testing.T) { + t.Run("returns nil int pointer", func(t *testing.T) { + nilInt := ConstNil[int]() + assert.Nil(t, nilInt) + assert.True(t, IsNil(nilInt)) + }) + + t.Run("returns nil string pointer", func(t *testing.T) { + nilString := ConstNil[string]() + assert.Nil(t, nilString) + assert.True(t, IsNil(nilString)) + }) + + t.Run("returns nil struct pointer", func(t *testing.T) { + type Person struct { + Name string + } + nilPerson := ConstNil[Person]() + assert.Nil(t, nilPerson) + }) +} + +// TestConstTrue tests the ConstTrue constant +func TestConstTrue(t *testing.T) { + t.Run("always returns true", func(t *testing.T) { + assert.True(t, ConstTrue()) + assert.True(t, ConstTrue()) + }) +} + +// TestConstFalse tests the ConstFalse constant +func TestConstFalse(t *testing.T) { + t.Run("always returns false", func(t *testing.T) { + assert.False(t, ConstFalse()) + assert.False(t, ConstFalse()) + }) +} + +// TestSwitch tests the Switch function +func TestSwitch(t *testing.T) { + type Animal struct { + Type string + Name string + } + + getType := func(a Animal) string { return a.Type } + + handlers := map[string]func(Animal) string{ + "dog": func(a Animal) string { return a.Name + " barks" }, + "cat": func(a Animal) string { return a.Name + " meows" }, + } + + defaultHandler := func(a Animal) string { + return a.Name + " makes a sound" + } + + makeSound := Switch(getType, handlers, defaultHandler) + + t.Run("applies handler for dog", func(t *testing.T) { + dog := Animal{Type: "dog", Name: "Rex"} + assert.Equal(t, "Rex barks", makeSound(dog)) + }) + + t.Run("applies handler for cat", func(t *testing.T) { + cat := Animal{Type: "cat", Name: "Whiskers"} + assert.Equal(t, "Whiskers meows", makeSound(cat)) + }) + + t.Run("applies default handler for unknown type", func(t *testing.T) { + bird := Animal{Type: "bird", Name: "Tweety"} + assert.Equal(t, "Tweety makes a sound", makeSound(bird)) + }) +} + +// TestPipeAndFlow tests basic Pipe and Flow functions +func TestPipeAndFlow(t *testing.T) { + t.Run("Pipe1 applies function", func(t *testing.T) { + double := func(n int) int { return n * 2 } + result := Pipe1(5, double) + assert.Equal(t, 10, result) + }) + + t.Run("Pipe3 composes functions left-to-right", func(t *testing.T) { + add1 := func(n int) int { return n + 1 } + double := func(n int) int { return n * 2 } + square := func(n int) int { return n * n } + + // (5 + 1) * 2 = 12, then 12 * 12 = 144 + result := Pipe3(5, add1, double, square) + assert.Equal(t, 144, result) + }) + + t.Run("Flow3 creates composed function", func(t *testing.T) { + add1 := func(n int) int { return n + 1 } + double := func(n int) int { return n * 2 } + square := func(n int) int { return n * n } + + // Flow3 composes left-to-right like Pipe3 + // Flow3(f1, f2, f3)(x) = f3(f2(f1(x))) + // So Flow3(add1, double, square)(5) = square(double(add1(5))) + // = square(double(6)) = square(12) = 144 + composed := Flow3(add1, double, square) + result := composed(5) + assert.Equal(t, 144, result) + }) +} + +// TestCurry tests currying functions +func TestCurry(t *testing.T) { + t.Run("Curry2 curries binary function", func(t *testing.T) { + add := func(a, b int) int { return a + b } + curriedAdd := Curry2(add) + + add5 := curriedAdd(5) + assert.Equal(t, 8, add5(3)) + assert.Equal(t, 10, add5(5)) + }) +} diff --git a/v2/function/gen.go b/v2/function/gen.go new file mode 100644 index 0000000..4dae290 --- /dev/null +++ b/v2/function/gen.go @@ -0,0 +1,1572 @@ +// Code generated by go generate; DO NOT EDIT. +// This file was generated by robots at +// 2025-03-09 23:52:50.7205396 +0100 CET m=+0.001580301 + +package function + +// Pipe0 takes an initial value t0 and successively applies 0 functions where the input of a function is the return value of the previous function +// The final return value is the result of the last function application +func Pipe0[T0 any](t0 T0) T0 { + return t0 +} + +// Variadic0 converts a function taking 0 parameters and a final slice into a function with 0 parameters but a final variadic argument +func Variadic0[V, R any](f func([]V) R) func(...V) R { + return func(v ...V) R { + return f(v) + } +} + +// Unvariadic0 converts a function taking 0 parameters and a final variadic argument into a function with 0 parameters but a final slice argument +func Unvariadic0[V, R any](f func(...V) R) func([]V) R { + return func(v []V) R { + return f(v...) + } +} + +// Unsliced0 converts a function taking a slice parameter into a function with 0 parameters +func Unsliced0[F ~func([]T) R, T, R any](f F) func() R { + return func() R { + return f([]T{}) + } +} + +// Pipe1 takes an initial value t0 and successively applies 1 functions where the input of a function is the return value of the previous function +// The final return value is the result of the last function application +func Pipe1[F1 ~func(T0) T1, T0, T1 any](t0 T0, f1 F1) T1 { + return f1(t0) +} + +// Flow1 creates a function that takes an initial value t0 and successively applies 1 functions where the input of a function is the return value of the previous function +// The final return value is the result of the last function application +func Flow1[F1 ~func(T0) T1, T0, T1 any](f1 F1) func(T0) T1 { + return func(t0 T0) T1 { + return Pipe1(t0, f1) + } +} + +// Nullary1 creates a parameter less function from a parameter less function and 0 functions. When executed the first parameter less function gets executed and then the result is piped through the remaining functions +func Nullary1[F1 ~func() T1, T1 any](f1 F1) func() T1 { + return func() T1 { + return Pipe0(f1()) + } +} + +// Curry1 takes a function with 1 parameters and returns a cascade of functions each taking only one parameter. +// The inverse function is [Uncurry1] +func Curry1[FCT ~func(T0) T1, T0, T1 any](f FCT) func(T0) T1 { + return func(t0 T0) T1 { + return f(t0) + } +} + +// Uncurry1 takes a cascade of 1 functions each taking only one parameter and returns a function with 1 parameters . +// The inverse function is [Curry1] +func Uncurry1[FCT ~func(T0) T1, T0, T1 any](f FCT) func(T0) T1 { + return func(t0 T0) T1 { + return f(t0) + } +} + +// Variadic1 converts a function taking 1 parameters and a final slice into a function with 1 parameters but a final variadic argument +func Variadic1[T1, V, R any](f func(T1, []V) R) func(T1, ...V) R { + return func(t1 T1, v ...V) R { + return f(t1, v) + } +} + +// Unvariadic1 converts a function taking 1 parameters and a final variadic argument into a function with 1 parameters but a final slice argument +func Unvariadic1[T1, V, R any](f func(T1, ...V) R) func(T1, []V) R { + return func(t1 T1, v []V) R { + return f(t1, v...) + } +} + +// Unsliced1 converts a function taking a slice parameter into a function with 1 parameters +func Unsliced1[F ~func([]T) R, T, R any](f F) func(T) R { + return func(t1 T) R { + return f([]T{t1}) + } +} + +// Pipe2 takes an initial value t0 and successively applies 2 functions where the input of a function is the return value of the previous function +// The final return value is the result of the last function application +func Pipe2[F1 ~func(T0) T1, F2 ~func(T1) T2, T0, T1, T2 any](t0 T0, f1 F1, f2 F2) T2 { + return f2(f1(t0)) +} + +// Flow2 creates a function that takes an initial value t0 and successively applies 2 functions where the input of a function is the return value of the previous function +// The final return value is the result of the last function application +func Flow2[F1 ~func(T0) T1, F2 ~func(T1) T2, T0, T1, T2 any](f1 F1, f2 F2) func(T0) T2 { + return func(t0 T0) T2 { + return Pipe2(t0, f1, f2) + } +} + +// Nullary2 creates a parameter less function from a parameter less function and 1 functions. When executed the first parameter less function gets executed and then the result is piped through the remaining functions +func Nullary2[F1 ~func() T1, F2 ~func(T1) T2, T1, T2 any](f1 F1, f2 F2) func() T2 { + return func() T2 { + return Pipe1(f1(), f2) + } +} + +// Curry2 takes a function with 2 parameters and returns a cascade of functions each taking only one parameter. +// The inverse function is [Uncurry2] +func Curry2[FCT ~func(T0, T1) T2, T0, T1, T2 any](f FCT) func(T0) func(T1) T2 { + return func(t0 T0) func(t1 T1) T2 { + return func(t1 T1) T2 { + return f(t0, t1) + } + } +} + +// Uncurry2 takes a cascade of 2 functions each taking only one parameter and returns a function with 2 parameters . +// The inverse function is [Curry2] +func Uncurry2[FCT ~func(T0) func(T1) T2, T0, T1, T2 any](f FCT) func(T0, T1) T2 { + return func(t0 T0, t1 T1) T2 { + return f(t0)(t1) + } +} + +// Variadic2 converts a function taking 2 parameters and a final slice into a function with 2 parameters but a final variadic argument +func Variadic2[T1, T2, V, R any](f func(T1, T2, []V) R) func(T1, T2, ...V) R { + return func(t1 T1, t2 T2, v ...V) R { + return f(t1, t2, v) + } +} + +// Unvariadic2 converts a function taking 2 parameters and a final variadic argument into a function with 2 parameters but a final slice argument +func Unvariadic2[T1, T2, V, R any](f func(T1, T2, ...V) R) func(T1, T2, []V) R { + return func(t1 T1, t2 T2, v []V) R { + return f(t1, t2, v...) + } +} + +// Unsliced2 converts a function taking a slice parameter into a function with 2 parameters +func Unsliced2[F ~func([]T) R, T, R any](f F) func(T, T) R { + return func(t1, t2 T) R { + return f([]T{t1, t2}) + } +} + +// Pipe3 takes an initial value t0 and successively applies 3 functions where the input of a function is the return value of the previous function +// The final return value is the result of the last function application +func Pipe3[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, T0, T1, T2, T3 any](t0 T0, f1 F1, f2 F2, f3 F3) T3 { + return f3(f2(f1(t0))) +} + +// Flow3 creates a function that takes an initial value t0 and successively applies 3 functions where the input of a function is the return value of the previous function +// The final return value is the result of the last function application +func Flow3[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, T0, T1, T2, T3 any](f1 F1, f2 F2, f3 F3) func(T0) T3 { + return func(t0 T0) T3 { + return Pipe3(t0, f1, f2, f3) + } +} + +// Nullary3 creates a parameter less function from a parameter less function and 2 functions. When executed the first parameter less function gets executed and then the result is piped through the remaining functions +func Nullary3[F1 ~func() T1, F2 ~func(T1) T2, F3 ~func(T2) T3, T1, T2, T3 any](f1 F1, f2 F2, f3 F3) func() T3 { + return func() T3 { + return Pipe2(f1(), f2, f3) + } +} + +// Curry3 takes a function with 3 parameters and returns a cascade of functions each taking only one parameter. +// The inverse function is [Uncurry3] +func Curry3[FCT ~func(T0, T1, T2) T3, T0, T1, T2, T3 any](f FCT) func(T0) func(T1) func(T2) T3 { + return func(t0 T0) func(t1 T1) func(t2 T2) T3 { + return func(t1 T1) func(t2 T2) T3 { + return func(t2 T2) T3 { + return f(t0, t1, t2) + } + } + } +} + +// Uncurry3 takes a cascade of 3 functions each taking only one parameter and returns a function with 3 parameters . +// The inverse function is [Curry3] +func Uncurry3[FCT ~func(T0) func(T1) func(T2) T3, T0, T1, T2, T3 any](f FCT) func(T0, T1, T2) T3 { + return func(t0 T0, t1 T1, t2 T2) T3 { + return f(t0)(t1)(t2) + } +} + +// Variadic3 converts a function taking 3 parameters and a final slice into a function with 3 parameters but a final variadic argument +func Variadic3[T1, T2, T3, V, R any](f func(T1, T2, T3, []V) R) func(T1, T2, T3, ...V) R { + return func(t1 T1, t2 T2, t3 T3, v ...V) R { + return f(t1, t2, t3, v) + } +} + +// Unvariadic3 converts a function taking 3 parameters and a final variadic argument into a function with 3 parameters but a final slice argument +func Unvariadic3[T1, T2, T3, V, R any](f func(T1, T2, T3, ...V) R) func(T1, T2, T3, []V) R { + return func(t1 T1, t2 T2, t3 T3, v []V) R { + return f(t1, t2, t3, v...) + } +} + +// Unsliced3 converts a function taking a slice parameter into a function with 3 parameters +func Unsliced3[F ~func([]T) R, T, R any](f F) func(T, T, T) R { + return func(t1, t2, t3 T) R { + return f([]T{t1, t2, t3}) + } +} + +// Pipe4 takes an initial value t0 and successively applies 4 functions where the input of a function is the return value of the previous function +// The final return value is the result of the last function application +func Pipe4[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, T0, T1, T2, T3, T4 any](t0 T0, f1 F1, f2 F2, f3 F3, f4 F4) T4 { + return f4(f3(f2(f1(t0)))) +} + +// Flow4 creates a function that takes an initial value t0 and successively applies 4 functions where the input of a function is the return value of the previous function +// The final return value is the result of the last function application +func Flow4[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, T0, T1, T2, T3, T4 any](f1 F1, f2 F2, f3 F3, f4 F4) func(T0) T4 { + return func(t0 T0) T4 { + return Pipe4(t0, f1, f2, f3, f4) + } +} + +// Nullary4 creates a parameter less function from a parameter less function and 3 functions. When executed the first parameter less function gets executed and then the result is piped through the remaining functions +func Nullary4[F1 ~func() T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, T1, T2, T3, T4 any](f1 F1, f2 F2, f3 F3, f4 F4) func() T4 { + return func() T4 { + return Pipe3(f1(), f2, f3, f4) + } +} + +// Curry4 takes a function with 4 parameters and returns a cascade of functions each taking only one parameter. +// The inverse function is [Uncurry4] +func Curry4[FCT ~func(T0, T1, T2, T3) T4, T0, T1, T2, T3, T4 any](f FCT) func(T0) func(T1) func(T2) func(T3) T4 { + return func(t0 T0) func(t1 T1) func(t2 T2) func(t3 T3) T4 { + return func(t1 T1) func(t2 T2) func(t3 T3) T4 { + return func(t2 T2) func(t3 T3) T4 { + return func(t3 T3) T4 { + return f(t0, t1, t2, t3) + } + } + } + } +} + +// Uncurry4 takes a cascade of 4 functions each taking only one parameter and returns a function with 4 parameters . +// The inverse function is [Curry4] +func Uncurry4[FCT ~func(T0) func(T1) func(T2) func(T3) T4, T0, T1, T2, T3, T4 any](f FCT) func(T0, T1, T2, T3) T4 { + return func(t0 T0, t1 T1, t2 T2, t3 T3) T4 { + return f(t0)(t1)(t2)(t3) + } +} + +// Variadic4 converts a function taking 4 parameters and a final slice into a function with 4 parameters but a final variadic argument +func Variadic4[T1, T2, T3, T4, V, R any](f func(T1, T2, T3, T4, []V) R) func(T1, T2, T3, T4, ...V) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4, v ...V) R { + return f(t1, t2, t3, t4, v) + } +} + +// Unvariadic4 converts a function taking 4 parameters and a final variadic argument into a function with 4 parameters but a final slice argument +func Unvariadic4[T1, T2, T3, T4, V, R any](f func(T1, T2, T3, T4, ...V) R) func(T1, T2, T3, T4, []V) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4, v []V) R { + return f(t1, t2, t3, t4, v...) + } +} + +// Unsliced4 converts a function taking a slice parameter into a function with 4 parameters +func Unsliced4[F ~func([]T) R, T, R any](f F) func(T, T, T, T) R { + return func(t1, t2, t3, t4 T) R { + return f([]T{t1, t2, t3, t4}) + } +} + +// Pipe5 takes an initial value t0 and successively applies 5 functions where the input of a function is the return value of the previous function +// The final return value is the result of the last function application +func Pipe5[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, T0, T1, T2, T3, T4, T5 any](t0 T0, f1 F1, f2 F2, f3 F3, f4 F4, f5 F5) T5 { + return f5(f4(f3(f2(f1(t0))))) +} + +// Flow5 creates a function that takes an initial value t0 and successively applies 5 functions where the input of a function is the return value of the previous function +// The final return value is the result of the last function application +func Flow5[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, T0, T1, T2, T3, T4, T5 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5) func(T0) T5 { + return func(t0 T0) T5 { + return Pipe5(t0, f1, f2, f3, f4, f5) + } +} + +// Nullary5 creates a parameter less function from a parameter less function and 4 functions. When executed the first parameter less function gets executed and then the result is piped through the remaining functions +func Nullary5[F1 ~func() T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, T1, T2, T3, T4, T5 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5) func() T5 { + return func() T5 { + return Pipe4(f1(), f2, f3, f4, f5) + } +} + +// Curry5 takes a function with 5 parameters and returns a cascade of functions each taking only one parameter. +// The inverse function is [Uncurry5] +func Curry5[FCT ~func(T0, T1, T2, T3, T4) T5, T0, T1, T2, T3, T4, T5 any](f FCT) func(T0) func(T1) func(T2) func(T3) func(T4) T5 { + return func(t0 T0) func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) T5 { + return func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) T5 { + return func(t2 T2) func(t3 T3) func(t4 T4) T5 { + return func(t3 T3) func(t4 T4) T5 { + return func(t4 T4) T5 { + return f(t0, t1, t2, t3, t4) + } + } + } + } + } +} + +// Uncurry5 takes a cascade of 5 functions each taking only one parameter and returns a function with 5 parameters . +// The inverse function is [Curry5] +func Uncurry5[FCT ~func(T0) func(T1) func(T2) func(T3) func(T4) T5, T0, T1, T2, T3, T4, T5 any](f FCT) func(T0, T1, T2, T3, T4) T5 { + return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4) T5 { + return f(t0)(t1)(t2)(t3)(t4) + } +} + +// Variadic5 converts a function taking 5 parameters and a final slice into a function with 5 parameters but a final variadic argument +func Variadic5[T1, T2, T3, T4, T5, V, R any](f func(T1, T2, T3, T4, T5, []V) R) func(T1, T2, T3, T4, T5, ...V) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, v ...V) R { + return f(t1, t2, t3, t4, t5, v) + } +} + +// Unvariadic5 converts a function taking 5 parameters and a final variadic argument into a function with 5 parameters but a final slice argument +func Unvariadic5[T1, T2, T3, T4, T5, V, R any](f func(T1, T2, T3, T4, T5, ...V) R) func(T1, T2, T3, T4, T5, []V) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, v []V) R { + return f(t1, t2, t3, t4, t5, v...) + } +} + +// Unsliced5 converts a function taking a slice parameter into a function with 5 parameters +func Unsliced5[F ~func([]T) R, T, R any](f F) func(T, T, T, T, T) R { + return func(t1, t2, t3, t4, t5 T) R { + return f([]T{t1, t2, t3, t4, t5}) + } +} + +// Pipe6 takes an initial value t0 and successively applies 6 functions where the input of a function is the return value of the previous function +// The final return value is the result of the last function application +func Pipe6[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, T0, T1, T2, T3, T4, T5, T6 any](t0 T0, f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6) T6 { + return f6(f5(f4(f3(f2(f1(t0)))))) +} + +// Flow6 creates a function that takes an initial value t0 and successively applies 6 functions where the input of a function is the return value of the previous function +// The final return value is the result of the last function application +func Flow6[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, T0, T1, T2, T3, T4, T5, T6 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6) func(T0) T6 { + return func(t0 T0) T6 { + return Pipe6(t0, f1, f2, f3, f4, f5, f6) + } +} + +// Nullary6 creates a parameter less function from a parameter less function and 5 functions. When executed the first parameter less function gets executed and then the result is piped through the remaining functions +func Nullary6[F1 ~func() T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, T1, T2, T3, T4, T5, T6 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6) func() T6 { + return func() T6 { + return Pipe5(f1(), f2, f3, f4, f5, f6) + } +} + +// Curry6 takes a function with 6 parameters and returns a cascade of functions each taking only one parameter. +// The inverse function is [Uncurry6] +func Curry6[FCT ~func(T0, T1, T2, T3, T4, T5) T6, T0, T1, T2, T3, T4, T5, T6 any](f FCT) func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) T6 { + return func(t0 T0) func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) T6 { + return func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) T6 { + return func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) T6 { + return func(t3 T3) func(t4 T4) func(t5 T5) T6 { + return func(t4 T4) func(t5 T5) T6 { + return func(t5 T5) T6 { + return f(t0, t1, t2, t3, t4, t5) + } + } + } + } + } + } +} + +// Uncurry6 takes a cascade of 6 functions each taking only one parameter and returns a function with 6 parameters . +// The inverse function is [Curry6] +func Uncurry6[FCT ~func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) T6, T0, T1, T2, T3, T4, T5, T6 any](f FCT) func(T0, T1, T2, T3, T4, T5) T6 { + return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5) T6 { + return f(t0)(t1)(t2)(t3)(t4)(t5) + } +} + +// Variadic6 converts a function taking 6 parameters and a final slice into a function with 6 parameters but a final variadic argument +func Variadic6[T1, T2, T3, T4, T5, T6, V, R any](f func(T1, T2, T3, T4, T5, T6, []V) R) func(T1, T2, T3, T4, T5, T6, ...V) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, v ...V) R { + return f(t1, t2, t3, t4, t5, t6, v) + } +} + +// Unvariadic6 converts a function taking 6 parameters and a final variadic argument into a function with 6 parameters but a final slice argument +func Unvariadic6[T1, T2, T3, T4, T5, T6, V, R any](f func(T1, T2, T3, T4, T5, T6, ...V) R) func(T1, T2, T3, T4, T5, T6, []V) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, v []V) R { + return f(t1, t2, t3, t4, t5, t6, v...) + } +} + +// Unsliced6 converts a function taking a slice parameter into a function with 6 parameters +func Unsliced6[F ~func([]T) R, T, R any](f F) func(T, T, T, T, T, T) R { + return func(t1, t2, t3, t4, t5, t6 T) R { + return f([]T{t1, t2, t3, t4, t5, t6}) + } +} + +// Pipe7 takes an initial value t0 and successively applies 7 functions where the input of a function is the return value of the previous function +// The final return value is the result of the last function application +func Pipe7[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, T0, T1, T2, T3, T4, T5, T6, T7 any](t0 T0, f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7) T7 { + return f7(f6(f5(f4(f3(f2(f1(t0))))))) +} + +// Flow7 creates a function that takes an initial value t0 and successively applies 7 functions where the input of a function is the return value of the previous function +// The final return value is the result of the last function application +func Flow7[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, T0, T1, T2, T3, T4, T5, T6, T7 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7) func(T0) T7 { + return func(t0 T0) T7 { + return Pipe7(t0, f1, f2, f3, f4, f5, f6, f7) + } +} + +// Nullary7 creates a parameter less function from a parameter less function and 6 functions. When executed the first parameter less function gets executed and then the result is piped through the remaining functions +func Nullary7[F1 ~func() T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, T1, T2, T3, T4, T5, T6, T7 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7) func() T7 { + return func() T7 { + return Pipe6(f1(), f2, f3, f4, f5, f6, f7) + } +} + +// Curry7 takes a function with 7 parameters and returns a cascade of functions each taking only one parameter. +// The inverse function is [Uncurry7] +func Curry7[FCT ~func(T0, T1, T2, T3, T4, T5, T6) T7, T0, T1, T2, T3, T4, T5, T6, T7 any](f FCT) func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) T7 { + return func(t0 T0) func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) T7 { + return func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) T7 { + return func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) T7 { + return func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) T7 { + return func(t4 T4) func(t5 T5) func(t6 T6) T7 { + return func(t5 T5) func(t6 T6) T7 { + return func(t6 T6) T7 { + return f(t0, t1, t2, t3, t4, t5, t6) + } + } + } + } + } + } + } +} + +// Uncurry7 takes a cascade of 7 functions each taking only one parameter and returns a function with 7 parameters . +// The inverse function is [Curry7] +func Uncurry7[FCT ~func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) T7, T0, T1, T2, T3, T4, T5, T6, T7 any](f FCT) func(T0, T1, T2, T3, T4, T5, T6) T7 { + return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6) T7 { + return f(t0)(t1)(t2)(t3)(t4)(t5)(t6) + } +} + +// Variadic7 converts a function taking 7 parameters and a final slice into a function with 7 parameters but a final variadic argument +func Variadic7[T1, T2, T3, T4, T5, T6, T7, V, R any](f func(T1, T2, T3, T4, T5, T6, T7, []V) R) func(T1, T2, T3, T4, T5, T6, T7, ...V) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, v ...V) R { + return f(t1, t2, t3, t4, t5, t6, t7, v) + } +} + +// Unvariadic7 converts a function taking 7 parameters and a final variadic argument into a function with 7 parameters but a final slice argument +func Unvariadic7[T1, T2, T3, T4, T5, T6, T7, V, R any](f func(T1, T2, T3, T4, T5, T6, T7, ...V) R) func(T1, T2, T3, T4, T5, T6, T7, []V) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, v []V) R { + return f(t1, t2, t3, t4, t5, t6, t7, v...) + } +} + +// Unsliced7 converts a function taking a slice parameter into a function with 7 parameters +func Unsliced7[F ~func([]T) R, T, R any](f F) func(T, T, T, T, T, T, T) R { + return func(t1, t2, t3, t4, t5, t6, t7 T) R { + return f([]T{t1, t2, t3, t4, t5, t6, t7}) + } +} + +// Pipe8 takes an initial value t0 and successively applies 8 functions where the input of a function is the return value of the previous function +// The final return value is the result of the last function application +func Pipe8[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, F8 ~func(T7) T8, T0, T1, T2, T3, T4, T5, T6, T7, T8 any](t0 T0, f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8) T8 { + return f8(f7(f6(f5(f4(f3(f2(f1(t0)))))))) +} + +// Flow8 creates a function that takes an initial value t0 and successively applies 8 functions where the input of a function is the return value of the previous function +// The final return value is the result of the last function application +func Flow8[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, F8 ~func(T7) T8, T0, T1, T2, T3, T4, T5, T6, T7, T8 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8) func(T0) T8 { + return func(t0 T0) T8 { + return Pipe8(t0, f1, f2, f3, f4, f5, f6, f7, f8) + } +} + +// Nullary8 creates a parameter less function from a parameter less function and 7 functions. When executed the first parameter less function gets executed and then the result is piped through the remaining functions +func Nullary8[F1 ~func() T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, F8 ~func(T7) T8, T1, T2, T3, T4, T5, T6, T7, T8 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8) func() T8 { + return func() T8 { + return Pipe7(f1(), f2, f3, f4, f5, f6, f7, f8) + } +} + +// Curry8 takes a function with 8 parameters and returns a cascade of functions each taking only one parameter. +// The inverse function is [Uncurry8] +func Curry8[FCT ~func(T0, T1, T2, T3, T4, T5, T6, T7) T8, T0, T1, T2, T3, T4, T5, T6, T7, T8 any](f FCT) func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) T8 { + return func(t0 T0) func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) T8 { + return func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) T8 { + return func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) T8 { + return func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) T8 { + return func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) T8 { + return func(t5 T5) func(t6 T6) func(t7 T7) T8 { + return func(t6 T6) func(t7 T7) T8 { + return func(t7 T7) T8 { + return f(t0, t1, t2, t3, t4, t5, t6, t7) + } + } + } + } + } + } + } + } +} + +// Uncurry8 takes a cascade of 8 functions each taking only one parameter and returns a function with 8 parameters . +// The inverse function is [Curry8] +func Uncurry8[FCT ~func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) T8, T0, T1, T2, T3, T4, T5, T6, T7, T8 any](f FCT) func(T0, T1, T2, T3, T4, T5, T6, T7) T8 { + return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7) T8 { + return f(t0)(t1)(t2)(t3)(t4)(t5)(t6)(t7) + } +} + +// Variadic8 converts a function taking 8 parameters and a final slice into a function with 8 parameters but a final variadic argument +func Variadic8[T1, T2, T3, T4, T5, T6, T7, T8, V, R any](f func(T1, T2, T3, T4, T5, T6, T7, T8, []V) R) func(T1, T2, T3, T4, T5, T6, T7, T8, ...V) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, v ...V) R { + return f(t1, t2, t3, t4, t5, t6, t7, t8, v) + } +} + +// Unvariadic8 converts a function taking 8 parameters and a final variadic argument into a function with 8 parameters but a final slice argument +func Unvariadic8[T1, T2, T3, T4, T5, T6, T7, T8, V, R any](f func(T1, T2, T3, T4, T5, T6, T7, T8, ...V) R) func(T1, T2, T3, T4, T5, T6, T7, T8, []V) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, v []V) R { + return f(t1, t2, t3, t4, t5, t6, t7, t8, v...) + } +} + +// Unsliced8 converts a function taking a slice parameter into a function with 8 parameters +func Unsliced8[F ~func([]T) R, T, R any](f F) func(T, T, T, T, T, T, T, T) R { + return func(t1, t2, t3, t4, t5, t6, t7, t8 T) R { + return f([]T{t1, t2, t3, t4, t5, t6, t7, t8}) + } +} + +// Pipe9 takes an initial value t0 and successively applies 9 functions where the input of a function is the return value of the previous function +// The final return value is the result of the last function application +func Pipe9[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, F8 ~func(T7) T8, F9 ~func(T8) T9, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9 any](t0 T0, f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9) T9 { + return f9(f8(f7(f6(f5(f4(f3(f2(f1(t0))))))))) +} + +// Flow9 creates a function that takes an initial value t0 and successively applies 9 functions where the input of a function is the return value of the previous function +// The final return value is the result of the last function application +func Flow9[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, F8 ~func(T7) T8, F9 ~func(T8) T9, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9) func(T0) T9 { + return func(t0 T0) T9 { + return Pipe9(t0, f1, f2, f3, f4, f5, f6, f7, f8, f9) + } +} + +// Nullary9 creates a parameter less function from a parameter less function and 8 functions. When executed the first parameter less function gets executed and then the result is piped through the remaining functions +func Nullary9[F1 ~func() T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, F8 ~func(T7) T8, F9 ~func(T8) T9, T1, T2, T3, T4, T5, T6, T7, T8, T9 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9) func() T9 { + return func() T9 { + return Pipe8(f1(), f2, f3, f4, f5, f6, f7, f8, f9) + } +} + +// Curry9 takes a function with 9 parameters and returns a cascade of functions each taking only one parameter. +// The inverse function is [Uncurry9] +func Curry9[FCT ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8) T9, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9 any](f FCT) func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) T9 { + return func(t0 T0) func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) T9 { + return func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) T9 { + return func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) T9 { + return func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) T9 { + return func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) T9 { + return func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) T9 { + return func(t6 T6) func(t7 T7) func(t8 T8) T9 { + return func(t7 T7) func(t8 T8) T9 { + return func(t8 T8) T9 { + return f(t0, t1, t2, t3, t4, t5, t6, t7, t8) + } + } + } + } + } + } + } + } + } +} + +// Uncurry9 takes a cascade of 9 functions each taking only one parameter and returns a function with 9 parameters . +// The inverse function is [Curry9] +func Uncurry9[FCT ~func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) T9, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9 any](f FCT) func(T0, T1, T2, T3, T4, T5, T6, T7, T8) T9 { + return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8) T9 { + return f(t0)(t1)(t2)(t3)(t4)(t5)(t6)(t7)(t8) + } +} + +// Variadic9 converts a function taking 9 parameters and a final slice into a function with 9 parameters but a final variadic argument +func Variadic9[T1, T2, T3, T4, T5, T6, T7, T8, T9, V, R any](f func(T1, T2, T3, T4, T5, T6, T7, T8, T9, []V) R) func(T1, T2, T3, T4, T5, T6, T7, T8, T9, ...V) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, v ...V) R { + return f(t1, t2, t3, t4, t5, t6, t7, t8, t9, v) + } +} + +// Unvariadic9 converts a function taking 9 parameters and a final variadic argument into a function with 9 parameters but a final slice argument +func Unvariadic9[T1, T2, T3, T4, T5, T6, T7, T8, T9, V, R any](f func(T1, T2, T3, T4, T5, T6, T7, T8, T9, ...V) R) func(T1, T2, T3, T4, T5, T6, T7, T8, T9, []V) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, v []V) R { + return f(t1, t2, t3, t4, t5, t6, t7, t8, t9, v...) + } +} + +// Unsliced9 converts a function taking a slice parameter into a function with 9 parameters +func Unsliced9[F ~func([]T) R, T, R any](f F) func(T, T, T, T, T, T, T, T, T) R { + return func(t1, t2, t3, t4, t5, t6, t7, t8, t9 T) R { + return f([]T{t1, t2, t3, t4, t5, t6, t7, t8, t9}) + } +} + +// Pipe10 takes an initial value t0 and successively applies 10 functions where the input of a function is the return value of the previous function +// The final return value is the result of the last function application +func Pipe10[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, F8 ~func(T7) T8, F9 ~func(T8) T9, F10 ~func(T9) T10, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any](t0 T0, f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10) T10 { + return f10(f9(f8(f7(f6(f5(f4(f3(f2(f1(t0)))))))))) +} + +// Flow10 creates a function that takes an initial value t0 and successively applies 10 functions where the input of a function is the return value of the previous function +// The final return value is the result of the last function application +func Flow10[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, F8 ~func(T7) T8, F9 ~func(T8) T9, F10 ~func(T9) T10, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10) func(T0) T10 { + return func(t0 T0) T10 { + return Pipe10(t0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10) + } +} + +// Nullary10 creates a parameter less function from a parameter less function and 9 functions. When executed the first parameter less function gets executed and then the result is piped through the remaining functions +func Nullary10[F1 ~func() T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, F8 ~func(T7) T8, F9 ~func(T8) T9, F10 ~func(T9) T10, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10) func() T10 { + return func() T10 { + return Pipe9(f1(), f2, f3, f4, f5, f6, f7, f8, f9, f10) + } +} + +// Curry10 takes a function with 10 parameters and returns a cascade of functions each taking only one parameter. +// The inverse function is [Uncurry10] +func Curry10[FCT ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9) T10, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any](f FCT) func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) T10 { + return func(t0 T0) func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) T10 { + return func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) T10 { + return func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) T10 { + return func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) T10 { + return func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) T10 { + return func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) T10 { + return func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) T10 { + return func(t7 T7) func(t8 T8) func(t9 T9) T10 { + return func(t8 T8) func(t9 T9) T10 { + return func(t9 T9) T10 { + return f(t0, t1, t2, t3, t4, t5, t6, t7, t8, t9) + } + } + } + } + } + } + } + } + } + } +} + +// Uncurry10 takes a cascade of 10 functions each taking only one parameter and returns a function with 10 parameters . +// The inverse function is [Curry10] +func Uncurry10[FCT ~func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) T10, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any](f FCT) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9) T10 { + return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9) T10 { + return f(t0)(t1)(t2)(t3)(t4)(t5)(t6)(t7)(t8)(t9) + } +} + +// Variadic10 converts a function taking 10 parameters and a final slice into a function with 10 parameters but a final variadic argument +func Variadic10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, V, R any](f func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, []V) R) func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, ...V) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, v ...V) R { + return f(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, v) + } +} + +// Unvariadic10 converts a function taking 10 parameters and a final variadic argument into a function with 10 parameters but a final slice argument +func Unvariadic10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, V, R any](f func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, ...V) R) func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, []V) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, v []V) R { + return f(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, v...) + } +} + +// Unsliced10 converts a function taking a slice parameter into a function with 10 parameters +func Unsliced10[F ~func([]T) R, T, R any](f F) func(T, T, T, T, T, T, T, T, T, T) R { + return func(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10 T) R { + return f([]T{t1, t2, t3, t4, t5, t6, t7, t8, t9, t10}) + } +} + +// Pipe11 takes an initial value t0 and successively applies 11 functions where the input of a function is the return value of the previous function +// The final return value is the result of the last function application +func Pipe11[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, F8 ~func(T7) T8, F9 ~func(T8) T9, F10 ~func(T9) T10, F11 ~func(T10) T11, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11 any](t0 T0, f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10, f11 F11) T11 { + return f11(f10(f9(f8(f7(f6(f5(f4(f3(f2(f1(t0))))))))))) +} + +// Flow11 creates a function that takes an initial value t0 and successively applies 11 functions where the input of a function is the return value of the previous function +// The final return value is the result of the last function application +func Flow11[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, F8 ~func(T7) T8, F9 ~func(T8) T9, F10 ~func(T9) T10, F11 ~func(T10) T11, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10, f11 F11) func(T0) T11 { + return func(t0 T0) T11 { + return Pipe11(t0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11) + } +} + +// Nullary11 creates a parameter less function from a parameter less function and 10 functions. When executed the first parameter less function gets executed and then the result is piped through the remaining functions +func Nullary11[F1 ~func() T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, F8 ~func(T7) T8, F9 ~func(T8) T9, F10 ~func(T9) T10, F11 ~func(T10) T11, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10, f11 F11) func() T11 { + return func() T11 { + return Pipe10(f1(), f2, f3, f4, f5, f6, f7, f8, f9, f10, f11) + } +} + +// Curry11 takes a function with 11 parameters and returns a cascade of functions each taking only one parameter. +// The inverse function is [Uncurry11] +func Curry11[FCT ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) T11, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11 any](f FCT) func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T11 { + return func(t0 T0) func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) T11 { + return func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) T11 { + return func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) T11 { + return func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) T11 { + return func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) T11 { + return func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) T11 { + return func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) T11 { + return func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) T11 { + return func(t8 T8) func(t9 T9) func(t10 T10) T11 { + return func(t9 T9) func(t10 T10) T11 { + return func(t10 T10) T11 { + return f(t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10) + } + } + } + } + } + } + } + } + } + } + } +} + +// Uncurry11 takes a cascade of 11 functions each taking only one parameter and returns a function with 11 parameters . +// The inverse function is [Curry11] +func Uncurry11[FCT ~func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T11, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11 any](f FCT) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) T11 { + return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10) T11 { + return f(t0)(t1)(t2)(t3)(t4)(t5)(t6)(t7)(t8)(t9)(t10) + } +} + +// Variadic11 converts a function taking 11 parameters and a final slice into a function with 11 parameters but a final variadic argument +func Variadic11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, V, R any](f func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, []V) R) func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, ...V) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11, v ...V) R { + return f(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, v) + } +} + +// Unvariadic11 converts a function taking 11 parameters and a final variadic argument into a function with 11 parameters but a final slice argument +func Unvariadic11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, V, R any](f func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, ...V) R) func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, []V) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11, v []V) R { + return f(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, v...) + } +} + +// Unsliced11 converts a function taking a slice parameter into a function with 11 parameters +func Unsliced11[F ~func([]T) R, T, R any](f F) func(T, T, T, T, T, T, T, T, T, T, T) R { + return func(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11 T) R { + return f([]T{t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11}) + } +} + +// Pipe12 takes an initial value t0 and successively applies 12 functions where the input of a function is the return value of the previous function +// The final return value is the result of the last function application +func Pipe12[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, F8 ~func(T7) T8, F9 ~func(T8) T9, F10 ~func(T9) T10, F11 ~func(T10) T11, F12 ~func(T11) T12, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12 any](t0 T0, f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10, f11 F11, f12 F12) T12 { + return f12(f11(f10(f9(f8(f7(f6(f5(f4(f3(f2(f1(t0)))))))))))) +} + +// Flow12 creates a function that takes an initial value t0 and successively applies 12 functions where the input of a function is the return value of the previous function +// The final return value is the result of the last function application +func Flow12[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, F8 ~func(T7) T8, F9 ~func(T8) T9, F10 ~func(T9) T10, F11 ~func(T10) T11, F12 ~func(T11) T12, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10, f11 F11, f12 F12) func(T0) T12 { + return func(t0 T0) T12 { + return Pipe12(t0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12) + } +} + +// Nullary12 creates a parameter less function from a parameter less function and 11 functions. When executed the first parameter less function gets executed and then the result is piped through the remaining functions +func Nullary12[F1 ~func() T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, F8 ~func(T7) T8, F9 ~func(T8) T9, F10 ~func(T9) T10, F11 ~func(T10) T11, F12 ~func(T11) T12, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10, f11 F11, f12 F12) func() T12 { + return func() T12 { + return Pipe11(f1(), f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12) + } +} + +// Curry12 takes a function with 12 parameters and returns a cascade of functions each taking only one parameter. +// The inverse function is [Uncurry12] +func Curry12[FCT ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11) T12, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12 any](f FCT) func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) T12 { + return func(t0 T0) func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) T12 { + return func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) T12 { + return func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) T12 { + return func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) T12 { + return func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) T12 { + return func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) T12 { + return func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) T12 { + return func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) T12 { + return func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) T12 { + return func(t9 T9) func(t10 T10) func(t11 T11) T12 { + return func(t10 T10) func(t11 T11) T12 { + return func(t11 T11) T12 { + return f(t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11) + } + } + } + } + } + } + } + } + } + } + } + } +} + +// Uncurry12 takes a cascade of 12 functions each taking only one parameter and returns a function with 12 parameters . +// The inverse function is [Curry12] +func Uncurry12[FCT ~func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) T12, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12 any](f FCT) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11) T12 { + return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11) T12 { + return f(t0)(t1)(t2)(t3)(t4)(t5)(t6)(t7)(t8)(t9)(t10)(t11) + } +} + +// Variadic12 converts a function taking 12 parameters and a final slice into a function with 12 parameters but a final variadic argument +func Variadic12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, V, R any](f func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, []V) R) func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, ...V) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11, t12 T12, v ...V) R { + return f(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, v) + } +} + +// Unvariadic12 converts a function taking 12 parameters and a final variadic argument into a function with 12 parameters but a final slice argument +func Unvariadic12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, V, R any](f func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, ...V) R) func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, []V) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11, t12 T12, v []V) R { + return f(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, v...) + } +} + +// Unsliced12 converts a function taking a slice parameter into a function with 12 parameters +func Unsliced12[F ~func([]T) R, T, R any](f F) func(T, T, T, T, T, T, T, T, T, T, T, T) R { + return func(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12 T) R { + return f([]T{t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12}) + } +} + +// Pipe13 takes an initial value t0 and successively applies 13 functions where the input of a function is the return value of the previous function +// The final return value is the result of the last function application +func Pipe13[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, F8 ~func(T7) T8, F9 ~func(T8) T9, F10 ~func(T9) T10, F11 ~func(T10) T11, F12 ~func(T11) T12, F13 ~func(T12) T13, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13 any](t0 T0, f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10, f11 F11, f12 F12, f13 F13) T13 { + return f13(f12(f11(f10(f9(f8(f7(f6(f5(f4(f3(f2(f1(t0))))))))))))) +} + +// Flow13 creates a function that takes an initial value t0 and successively applies 13 functions where the input of a function is the return value of the previous function +// The final return value is the result of the last function application +func Flow13[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, F8 ~func(T7) T8, F9 ~func(T8) T9, F10 ~func(T9) T10, F11 ~func(T10) T11, F12 ~func(T11) T12, F13 ~func(T12) T13, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10, f11 F11, f12 F12, f13 F13) func(T0) T13 { + return func(t0 T0) T13 { + return Pipe13(t0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13) + } +} + +// Nullary13 creates a parameter less function from a parameter less function and 12 functions. When executed the first parameter less function gets executed and then the result is piped through the remaining functions +func Nullary13[F1 ~func() T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, F8 ~func(T7) T8, F9 ~func(T8) T9, F10 ~func(T9) T10, F11 ~func(T10) T11, F12 ~func(T11) T12, F13 ~func(T12) T13, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10, f11 F11, f12 F12, f13 F13) func() T13 { + return func() T13 { + return Pipe12(f1(), f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13) + } +} + +// Curry13 takes a function with 13 parameters and returns a cascade of functions each taking only one parameter. +// The inverse function is [Uncurry13] +func Curry13[FCT ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12) T13, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13 any](f FCT) func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) T13 { + return func(t0 T0) func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) T13 { + return func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) T13 { + return func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) T13 { + return func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) T13 { + return func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) T13 { + return func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) T13 { + return func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) T13 { + return func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) T13 { + return func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) T13 { + return func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) T13 { + return func(t10 T10) func(t11 T11) func(t12 T12) T13 { + return func(t11 T11) func(t12 T12) T13 { + return func(t12 T12) T13 { + return f(t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12) + } + } + } + } + } + } + } + } + } + } + } + } + } +} + +// Uncurry13 takes a cascade of 13 functions each taking only one parameter and returns a function with 13 parameters . +// The inverse function is [Curry13] +func Uncurry13[FCT ~func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) T13, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13 any](f FCT) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12) T13 { + return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11, t12 T12) T13 { + return f(t0)(t1)(t2)(t3)(t4)(t5)(t6)(t7)(t8)(t9)(t10)(t11)(t12) + } +} + +// Variadic13 converts a function taking 13 parameters and a final slice into a function with 13 parameters but a final variadic argument +func Variadic13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, V, R any](f func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, []V) R) func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, ...V) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11, t12 T12, t13 T13, v ...V) R { + return f(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, v) + } +} + +// Unvariadic13 converts a function taking 13 parameters and a final variadic argument into a function with 13 parameters but a final slice argument +func Unvariadic13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, V, R any](f func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, ...V) R) func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, []V) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11, t12 T12, t13 T13, v []V) R { + return f(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, v...) + } +} + +// Unsliced13 converts a function taking a slice parameter into a function with 13 parameters +func Unsliced13[F ~func([]T) R, T, R any](f F) func(T, T, T, T, T, T, T, T, T, T, T, T, T) R { + return func(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13 T) R { + return f([]T{t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13}) + } +} + +// Pipe14 takes an initial value t0 and successively applies 14 functions where the input of a function is the return value of the previous function +// The final return value is the result of the last function application +func Pipe14[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, F8 ~func(T7) T8, F9 ~func(T8) T9, F10 ~func(T9) T10, F11 ~func(T10) T11, F12 ~func(T11) T12, F13 ~func(T12) T13, F14 ~func(T13) T14, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14 any](t0 T0, f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10, f11 F11, f12 F12, f13 F13, f14 F14) T14 { + return f14(f13(f12(f11(f10(f9(f8(f7(f6(f5(f4(f3(f2(f1(t0)))))))))))))) +} + +// Flow14 creates a function that takes an initial value t0 and successively applies 14 functions where the input of a function is the return value of the previous function +// The final return value is the result of the last function application +func Flow14[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, F8 ~func(T7) T8, F9 ~func(T8) T9, F10 ~func(T9) T10, F11 ~func(T10) T11, F12 ~func(T11) T12, F13 ~func(T12) T13, F14 ~func(T13) T14, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10, f11 F11, f12 F12, f13 F13, f14 F14) func(T0) T14 { + return func(t0 T0) T14 { + return Pipe14(t0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14) + } +} + +// Nullary14 creates a parameter less function from a parameter less function and 13 functions. When executed the first parameter less function gets executed and then the result is piped through the remaining functions +func Nullary14[F1 ~func() T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, F8 ~func(T7) T8, F9 ~func(T8) T9, F10 ~func(T9) T10, F11 ~func(T10) T11, F12 ~func(T11) T12, F13 ~func(T12) T13, F14 ~func(T13) T14, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10, f11 F11, f12 F12, f13 F13, f14 F14) func() T14 { + return func() T14 { + return Pipe13(f1(), f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14) + } +} + +// Curry14 takes a function with 14 parameters and returns a cascade of functions each taking only one parameter. +// The inverse function is [Uncurry14] +func Curry14[FCT ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13) T14, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14 any](f FCT) func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) T14 { + return func(t0 T0) func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) T14 { + return func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) T14 { + return func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) T14 { + return func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) T14 { + return func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) T14 { + return func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) T14 { + return func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) T14 { + return func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) T14 { + return func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) T14 { + return func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) T14 { + return func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) T14 { + return func(t11 T11) func(t12 T12) func(t13 T13) T14 { + return func(t12 T12) func(t13 T13) T14 { + return func(t13 T13) T14 { + return f(t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13) + } + } + } + } + } + } + } + } + } + } + } + } + } + } +} + +// Uncurry14 takes a cascade of 14 functions each taking only one parameter and returns a function with 14 parameters . +// The inverse function is [Curry14] +func Uncurry14[FCT ~func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) T14, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14 any](f FCT) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13) T14 { + return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11, t12 T12, t13 T13) T14 { + return f(t0)(t1)(t2)(t3)(t4)(t5)(t6)(t7)(t8)(t9)(t10)(t11)(t12)(t13) + } +} + +// Variadic14 converts a function taking 14 parameters and a final slice into a function with 14 parameters but a final variadic argument +func Variadic14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, V, R any](f func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, []V) R) func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, ...V) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11, t12 T12, t13 T13, t14 T14, v ...V) R { + return f(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, v) + } +} + +// Unvariadic14 converts a function taking 14 parameters and a final variadic argument into a function with 14 parameters but a final slice argument +func Unvariadic14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, V, R any](f func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, ...V) R) func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, []V) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11, t12 T12, t13 T13, t14 T14, v []V) R { + return f(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, v...) + } +} + +// Unsliced14 converts a function taking a slice parameter into a function with 14 parameters +func Unsliced14[F ~func([]T) R, T, R any](f F) func(T, T, T, T, T, T, T, T, T, T, T, T, T, T) R { + return func(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14 T) R { + return f([]T{t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14}) + } +} + +// Pipe15 takes an initial value t0 and successively applies 15 functions where the input of a function is the return value of the previous function +// The final return value is the result of the last function application +func Pipe15[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, F8 ~func(T7) T8, F9 ~func(T8) T9, F10 ~func(T9) T10, F11 ~func(T10) T11, F12 ~func(T11) T12, F13 ~func(T12) T13, F14 ~func(T13) T14, F15 ~func(T14) T15, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15 any](t0 T0, f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10, f11 F11, f12 F12, f13 F13, f14 F14, f15 F15) T15 { + return f15(f14(f13(f12(f11(f10(f9(f8(f7(f6(f5(f4(f3(f2(f1(t0))))))))))))))) +} + +// Flow15 creates a function that takes an initial value t0 and successively applies 15 functions where the input of a function is the return value of the previous function +// The final return value is the result of the last function application +func Flow15[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, F8 ~func(T7) T8, F9 ~func(T8) T9, F10 ~func(T9) T10, F11 ~func(T10) T11, F12 ~func(T11) T12, F13 ~func(T12) T13, F14 ~func(T13) T14, F15 ~func(T14) T15, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10, f11 F11, f12 F12, f13 F13, f14 F14, f15 F15) func(T0) T15 { + return func(t0 T0) T15 { + return Pipe15(t0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15) + } +} + +// Nullary15 creates a parameter less function from a parameter less function and 14 functions. When executed the first parameter less function gets executed and then the result is piped through the remaining functions +func Nullary15[F1 ~func() T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, F8 ~func(T7) T8, F9 ~func(T8) T9, F10 ~func(T9) T10, F11 ~func(T10) T11, F12 ~func(T11) T12, F13 ~func(T12) T13, F14 ~func(T13) T14, F15 ~func(T14) T15, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10, f11 F11, f12 F12, f13 F13, f14 F14, f15 F15) func() T15 { + return func() T15 { + return Pipe14(f1(), f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15) + } +} + +// Curry15 takes a function with 15 parameters and returns a cascade of functions each taking only one parameter. +// The inverse function is [Uncurry15] +func Curry15[FCT ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14) T15, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15 any](f FCT) func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T15 { + return func(t0 T0) func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) T15 { + return func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) T15 { + return func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) T15 { + return func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) T15 { + return func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) T15 { + return func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) T15 { + return func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) T15 { + return func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) T15 { + return func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) T15 { + return func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) T15 { + return func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) T15 { + return func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) T15 { + return func(t12 T12) func(t13 T13) func(t14 T14) T15 { + return func(t13 T13) func(t14 T14) T15 { + return func(t14 T14) T15 { + return f(t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14) + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } +} + +// Uncurry15 takes a cascade of 15 functions each taking only one parameter and returns a function with 15 parameters . +// The inverse function is [Curry15] +func Uncurry15[FCT ~func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T15, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15 any](f FCT) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14) T15 { + return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11, t12 T12, t13 T13, t14 T14) T15 { + return f(t0)(t1)(t2)(t3)(t4)(t5)(t6)(t7)(t8)(t9)(t10)(t11)(t12)(t13)(t14) + } +} + +// Variadic15 converts a function taking 15 parameters and a final slice into a function with 15 parameters but a final variadic argument +func Variadic15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, V, R any](f func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, []V) R) func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, ...V) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11, t12 T12, t13 T13, t14 T14, t15 T15, v ...V) R { + return f(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15, v) + } +} + +// Unvariadic15 converts a function taking 15 parameters and a final variadic argument into a function with 15 parameters but a final slice argument +func Unvariadic15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, V, R any](f func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, ...V) R) func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, []V) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11, t12 T12, t13 T13, t14 T14, t15 T15, v []V) R { + return f(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15, v...) + } +} + +// Unsliced15 converts a function taking a slice parameter into a function with 15 parameters +func Unsliced15[F ~func([]T) R, T, R any](f F) func(T, T, T, T, T, T, T, T, T, T, T, T, T, T, T) R { + return func(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15 T) R { + return f([]T{t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15}) + } +} + +// Pipe16 takes an initial value t0 and successively applies 16 functions where the input of a function is the return value of the previous function +// The final return value is the result of the last function application +func Pipe16[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, F8 ~func(T7) T8, F9 ~func(T8) T9, F10 ~func(T9) T10, F11 ~func(T10) T11, F12 ~func(T11) T12, F13 ~func(T12) T13, F14 ~func(T13) T14, F15 ~func(T14) T15, F16 ~func(T15) T16, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16 any](t0 T0, f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10, f11 F11, f12 F12, f13 F13, f14 F14, f15 F15, f16 F16) T16 { + return f16(f15(f14(f13(f12(f11(f10(f9(f8(f7(f6(f5(f4(f3(f2(f1(t0)))))))))))))))) +} + +// Flow16 creates a function that takes an initial value t0 and successively applies 16 functions where the input of a function is the return value of the previous function +// The final return value is the result of the last function application +func Flow16[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, F8 ~func(T7) T8, F9 ~func(T8) T9, F10 ~func(T9) T10, F11 ~func(T10) T11, F12 ~func(T11) T12, F13 ~func(T12) T13, F14 ~func(T13) T14, F15 ~func(T14) T15, F16 ~func(T15) T16, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10, f11 F11, f12 F12, f13 F13, f14 F14, f15 F15, f16 F16) func(T0) T16 { + return func(t0 T0) T16 { + return Pipe16(t0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, f16) + } +} + +// Nullary16 creates a parameter less function from a parameter less function and 15 functions. When executed the first parameter less function gets executed and then the result is piped through the remaining functions +func Nullary16[F1 ~func() T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, F8 ~func(T7) T8, F9 ~func(T8) T9, F10 ~func(T9) T10, F11 ~func(T10) T11, F12 ~func(T11) T12, F13 ~func(T12) T13, F14 ~func(T13) T14, F15 ~func(T14) T15, F16 ~func(T15) T16, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10, f11 F11, f12 F12, f13 F13, f14 F14, f15 F15, f16 F16) func() T16 { + return func() T16 { + return Pipe15(f1(), f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, f16) + } +} + +// Curry16 takes a function with 16 parameters and returns a cascade of functions each taking only one parameter. +// The inverse function is [Uncurry16] +func Curry16[FCT ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15) T16, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16 any](f FCT) func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T16 { + return func(t0 T0) func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) T16 { + return func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) T16 { + return func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) T16 { + return func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) T16 { + return func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) T16 { + return func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) T16 { + return func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) T16 { + return func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) T16 { + return func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) T16 { + return func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) T16 { + return func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) T16 { + return func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) T16 { + return func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) T16 { + return func(t13 T13) func(t14 T14) func(t15 T15) T16 { + return func(t14 T14) func(t15 T15) T16 { + return func(t15 T15) T16 { + return f(t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15) + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } +} + +// Uncurry16 takes a cascade of 16 functions each taking only one parameter and returns a function with 16 parameters . +// The inverse function is [Curry16] +func Uncurry16[FCT ~func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T16, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16 any](f FCT) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15) T16 { + return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11, t12 T12, t13 T13, t14 T14, t15 T15) T16 { + return f(t0)(t1)(t2)(t3)(t4)(t5)(t6)(t7)(t8)(t9)(t10)(t11)(t12)(t13)(t14)(t15) + } +} + +// Variadic16 converts a function taking 16 parameters and a final slice into a function with 16 parameters but a final variadic argument +func Variadic16[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, V, R any](f func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, []V) R) func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, ...V) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11, t12 T12, t13 T13, t14 T14, t15 T15, t16 T16, v ...V) R { + return f(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15, t16, v) + } +} + +// Unvariadic16 converts a function taking 16 parameters and a final variadic argument into a function with 16 parameters but a final slice argument +func Unvariadic16[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, V, R any](f func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, ...V) R) func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, []V) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11, t12 T12, t13 T13, t14 T14, t15 T15, t16 T16, v []V) R { + return f(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15, t16, v...) + } +} + +// Unsliced16 converts a function taking a slice parameter into a function with 16 parameters +func Unsliced16[F ~func([]T) R, T, R any](f F) func(T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T) R { + return func(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15, t16 T) R { + return f([]T{t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15, t16}) + } +} + +// Pipe17 takes an initial value t0 and successively applies 17 functions where the input of a function is the return value of the previous function +// The final return value is the result of the last function application +func Pipe17[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, F8 ~func(T7) T8, F9 ~func(T8) T9, F10 ~func(T9) T10, F11 ~func(T10) T11, F12 ~func(T11) T12, F13 ~func(T12) T13, F14 ~func(T13) T14, F15 ~func(T14) T15, F16 ~func(T15) T16, F17 ~func(T16) T17, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17 any](t0 T0, f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10, f11 F11, f12 F12, f13 F13, f14 F14, f15 F15, f16 F16, f17 F17) T17 { + return f17(f16(f15(f14(f13(f12(f11(f10(f9(f8(f7(f6(f5(f4(f3(f2(f1(t0))))))))))))))))) +} + +// Flow17 creates a function that takes an initial value t0 and successively applies 17 functions where the input of a function is the return value of the previous function +// The final return value is the result of the last function application +func Flow17[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, F8 ~func(T7) T8, F9 ~func(T8) T9, F10 ~func(T9) T10, F11 ~func(T10) T11, F12 ~func(T11) T12, F13 ~func(T12) T13, F14 ~func(T13) T14, F15 ~func(T14) T15, F16 ~func(T15) T16, F17 ~func(T16) T17, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10, f11 F11, f12 F12, f13 F13, f14 F14, f15 F15, f16 F16, f17 F17) func(T0) T17 { + return func(t0 T0) T17 { + return Pipe17(t0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, f16, f17) + } +} + +// Nullary17 creates a parameter less function from a parameter less function and 16 functions. When executed the first parameter less function gets executed and then the result is piped through the remaining functions +func Nullary17[F1 ~func() T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, F8 ~func(T7) T8, F9 ~func(T8) T9, F10 ~func(T9) T10, F11 ~func(T10) T11, F12 ~func(T11) T12, F13 ~func(T12) T13, F14 ~func(T13) T14, F15 ~func(T14) T15, F16 ~func(T15) T16, F17 ~func(T16) T17, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10, f11 F11, f12 F12, f13 F13, f14 F14, f15 F15, f16 F16, f17 F17) func() T17 { + return func() T17 { + return Pipe16(f1(), f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, f16, f17) + } +} + +// Curry17 takes a function with 17 parameters and returns a cascade of functions each taking only one parameter. +// The inverse function is [Uncurry17] +func Curry17[FCT ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16) T17, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17 any](f FCT) func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) func(T16) T17 { + return func(t0 T0) func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) T17 { + return func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) T17 { + return func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) T17 { + return func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) T17 { + return func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) T17 { + return func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) T17 { + return func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) T17 { + return func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) T17 { + return func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) T17 { + return func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) T17 { + return func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) T17 { + return func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) T17 { + return func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) T17 { + return func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) T17 { + return func(t14 T14) func(t15 T15) func(t16 T16) T17 { + return func(t15 T15) func(t16 T16) T17 { + return func(t16 T16) T17 { + return f(t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15, t16) + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } +} + +// Uncurry17 takes a cascade of 17 functions each taking only one parameter and returns a function with 17 parameters . +// The inverse function is [Curry17] +func Uncurry17[FCT ~func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) func(T16) T17, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17 any](f FCT) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16) T17 { + return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11, t12 T12, t13 T13, t14 T14, t15 T15, t16 T16) T17 { + return f(t0)(t1)(t2)(t3)(t4)(t5)(t6)(t7)(t8)(t9)(t10)(t11)(t12)(t13)(t14)(t15)(t16) + } +} + +// Variadic17 converts a function taking 17 parameters and a final slice into a function with 17 parameters but a final variadic argument +func Variadic17[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, V, R any](f func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, []V) R) func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, ...V) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11, t12 T12, t13 T13, t14 T14, t15 T15, t16 T16, t17 T17, v ...V) R { + return f(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15, t16, t17, v) + } +} + +// Unvariadic17 converts a function taking 17 parameters and a final variadic argument into a function with 17 parameters but a final slice argument +func Unvariadic17[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, V, R any](f func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, ...V) R) func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, []V) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11, t12 T12, t13 T13, t14 T14, t15 T15, t16 T16, t17 T17, v []V) R { + return f(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15, t16, t17, v...) + } +} + +// Unsliced17 converts a function taking a slice parameter into a function with 17 parameters +func Unsliced17[F ~func([]T) R, T, R any](f F) func(T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T) R { + return func(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15, t16, t17 T) R { + return f([]T{t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15, t16, t17}) + } +} + +// Pipe18 takes an initial value t0 and successively applies 18 functions where the input of a function is the return value of the previous function +// The final return value is the result of the last function application +func Pipe18[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, F8 ~func(T7) T8, F9 ~func(T8) T9, F10 ~func(T9) T10, F11 ~func(T10) T11, F12 ~func(T11) T12, F13 ~func(T12) T13, F14 ~func(T13) T14, F15 ~func(T14) T15, F16 ~func(T15) T16, F17 ~func(T16) T17, F18 ~func(T17) T18, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18 any](t0 T0, f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10, f11 F11, f12 F12, f13 F13, f14 F14, f15 F15, f16 F16, f17 F17, f18 F18) T18 { + return f18(f17(f16(f15(f14(f13(f12(f11(f10(f9(f8(f7(f6(f5(f4(f3(f2(f1(t0)))))))))))))))))) +} + +// Flow18 creates a function that takes an initial value t0 and successively applies 18 functions where the input of a function is the return value of the previous function +// The final return value is the result of the last function application +func Flow18[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, F8 ~func(T7) T8, F9 ~func(T8) T9, F10 ~func(T9) T10, F11 ~func(T10) T11, F12 ~func(T11) T12, F13 ~func(T12) T13, F14 ~func(T13) T14, F15 ~func(T14) T15, F16 ~func(T15) T16, F17 ~func(T16) T17, F18 ~func(T17) T18, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10, f11 F11, f12 F12, f13 F13, f14 F14, f15 F15, f16 F16, f17 F17, f18 F18) func(T0) T18 { + return func(t0 T0) T18 { + return Pipe18(t0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, f16, f17, f18) + } +} + +// Nullary18 creates a parameter less function from a parameter less function and 17 functions. When executed the first parameter less function gets executed and then the result is piped through the remaining functions +func Nullary18[F1 ~func() T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, F8 ~func(T7) T8, F9 ~func(T8) T9, F10 ~func(T9) T10, F11 ~func(T10) T11, F12 ~func(T11) T12, F13 ~func(T12) T13, F14 ~func(T13) T14, F15 ~func(T14) T15, F16 ~func(T15) T16, F17 ~func(T16) T17, F18 ~func(T17) T18, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10, f11 F11, f12 F12, f13 F13, f14 F14, f15 F15, f16 F16, f17 F17, f18 F18) func() T18 { + return func() T18 { + return Pipe17(f1(), f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, f16, f17, f18) + } +} + +// Curry18 takes a function with 18 parameters and returns a cascade of functions each taking only one parameter. +// The inverse function is [Uncurry18] +func Curry18[FCT ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17) T18, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18 any](f FCT) func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) func(T16) func(T17) T18 { + return func(t0 T0) func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) func(t17 T17) T18 { + return func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) func(t17 T17) T18 { + return func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) func(t17 T17) T18 { + return func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) func(t17 T17) T18 { + return func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) func(t17 T17) T18 { + return func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) func(t17 T17) T18 { + return func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) func(t17 T17) T18 { + return func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) func(t17 T17) T18 { + return func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) func(t17 T17) T18 { + return func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) func(t17 T17) T18 { + return func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) func(t17 T17) T18 { + return func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) func(t17 T17) T18 { + return func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) func(t17 T17) T18 { + return func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) func(t17 T17) T18 { + return func(t14 T14) func(t15 T15) func(t16 T16) func(t17 T17) T18 { + return func(t15 T15) func(t16 T16) func(t17 T17) T18 { + return func(t16 T16) func(t17 T17) T18 { + return func(t17 T17) T18 { + return f(t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15, t16, t17) + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } +} + +// Uncurry18 takes a cascade of 18 functions each taking only one parameter and returns a function with 18 parameters . +// The inverse function is [Curry18] +func Uncurry18[FCT ~func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) func(T16) func(T17) T18, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18 any](f FCT) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17) T18 { + return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11, t12 T12, t13 T13, t14 T14, t15 T15, t16 T16, t17 T17) T18 { + return f(t0)(t1)(t2)(t3)(t4)(t5)(t6)(t7)(t8)(t9)(t10)(t11)(t12)(t13)(t14)(t15)(t16)(t17) + } +} + +// Variadic18 converts a function taking 18 parameters and a final slice into a function with 18 parameters but a final variadic argument +func Variadic18[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, V, R any](f func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, []V) R) func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, ...V) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11, t12 T12, t13 T13, t14 T14, t15 T15, t16 T16, t17 T17, t18 T18, v ...V) R { + return f(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15, t16, t17, t18, v) + } +} + +// Unvariadic18 converts a function taking 18 parameters and a final variadic argument into a function with 18 parameters but a final slice argument +func Unvariadic18[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, V, R any](f func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, ...V) R) func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, []V) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11, t12 T12, t13 T13, t14 T14, t15 T15, t16 T16, t17 T17, t18 T18, v []V) R { + return f(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15, t16, t17, t18, v...) + } +} + +// Unsliced18 converts a function taking a slice parameter into a function with 18 parameters +func Unsliced18[F ~func([]T) R, T, R any](f F) func(T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T) R { + return func(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15, t16, t17, t18 T) R { + return f([]T{t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15, t16, t17, t18}) + } +} + +// Pipe19 takes an initial value t0 and successively applies 19 functions where the input of a function is the return value of the previous function +// The final return value is the result of the last function application +func Pipe19[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, F8 ~func(T7) T8, F9 ~func(T8) T9, F10 ~func(T9) T10, F11 ~func(T10) T11, F12 ~func(T11) T12, F13 ~func(T12) T13, F14 ~func(T13) T14, F15 ~func(T14) T15, F16 ~func(T15) T16, F17 ~func(T16) T17, F18 ~func(T17) T18, F19 ~func(T18) T19, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19 any](t0 T0, f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10, f11 F11, f12 F12, f13 F13, f14 F14, f15 F15, f16 F16, f17 F17, f18 F18, f19 F19) T19 { + return f19(f18(f17(f16(f15(f14(f13(f12(f11(f10(f9(f8(f7(f6(f5(f4(f3(f2(f1(t0))))))))))))))))))) +} + +// Flow19 creates a function that takes an initial value t0 and successively applies 19 functions where the input of a function is the return value of the previous function +// The final return value is the result of the last function application +func Flow19[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, F8 ~func(T7) T8, F9 ~func(T8) T9, F10 ~func(T9) T10, F11 ~func(T10) T11, F12 ~func(T11) T12, F13 ~func(T12) T13, F14 ~func(T13) T14, F15 ~func(T14) T15, F16 ~func(T15) T16, F17 ~func(T16) T17, F18 ~func(T17) T18, F19 ~func(T18) T19, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10, f11 F11, f12 F12, f13 F13, f14 F14, f15 F15, f16 F16, f17 F17, f18 F18, f19 F19) func(T0) T19 { + return func(t0 T0) T19 { + return Pipe19(t0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, f16, f17, f18, f19) + } +} + +// Nullary19 creates a parameter less function from a parameter less function and 18 functions. When executed the first parameter less function gets executed and then the result is piped through the remaining functions +func Nullary19[F1 ~func() T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, F8 ~func(T7) T8, F9 ~func(T8) T9, F10 ~func(T9) T10, F11 ~func(T10) T11, F12 ~func(T11) T12, F13 ~func(T12) T13, F14 ~func(T13) T14, F15 ~func(T14) T15, F16 ~func(T15) T16, F17 ~func(T16) T17, F18 ~func(T17) T18, F19 ~func(T18) T19, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10, f11 F11, f12 F12, f13 F13, f14 F14, f15 F15, f16 F16, f17 F17, f18 F18, f19 F19) func() T19 { + return func() T19 { + return Pipe18(f1(), f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, f16, f17, f18, f19) + } +} + +// Curry19 takes a function with 19 parameters and returns a cascade of functions each taking only one parameter. +// The inverse function is [Uncurry19] +func Curry19[FCT ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18) T19, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19 any](f FCT) func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) func(T16) func(T17) func(T18) T19 { + return func(t0 T0) func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) func(t17 T17) func(t18 T18) T19 { + return func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) func(t17 T17) func(t18 T18) T19 { + return func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) func(t17 T17) func(t18 T18) T19 { + return func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) func(t17 T17) func(t18 T18) T19 { + return func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) func(t17 T17) func(t18 T18) T19 { + return func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) func(t17 T17) func(t18 T18) T19 { + return func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) func(t17 T17) func(t18 T18) T19 { + return func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) func(t17 T17) func(t18 T18) T19 { + return func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) func(t17 T17) func(t18 T18) T19 { + return func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) func(t17 T17) func(t18 T18) T19 { + return func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) func(t17 T17) func(t18 T18) T19 { + return func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) func(t17 T17) func(t18 T18) T19 { + return func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) func(t17 T17) func(t18 T18) T19 { + return func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) func(t17 T17) func(t18 T18) T19 { + return func(t14 T14) func(t15 T15) func(t16 T16) func(t17 T17) func(t18 T18) T19 { + return func(t15 T15) func(t16 T16) func(t17 T17) func(t18 T18) T19 { + return func(t16 T16) func(t17 T17) func(t18 T18) T19 { + return func(t17 T17) func(t18 T18) T19 { + return func(t18 T18) T19 { + return f(t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15, t16, t17, t18) + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } +} + +// Uncurry19 takes a cascade of 19 functions each taking only one parameter and returns a function with 19 parameters . +// The inverse function is [Curry19] +func Uncurry19[FCT ~func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) func(T16) func(T17) func(T18) T19, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19 any](f FCT) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18) T19 { + return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11, t12 T12, t13 T13, t14 T14, t15 T15, t16 T16, t17 T17, t18 T18) T19 { + return f(t0)(t1)(t2)(t3)(t4)(t5)(t6)(t7)(t8)(t9)(t10)(t11)(t12)(t13)(t14)(t15)(t16)(t17)(t18) + } +} + +// Variadic19 converts a function taking 19 parameters and a final slice into a function with 19 parameters but a final variadic argument +func Variadic19[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, V, R any](f func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, []V) R) func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, ...V) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11, t12 T12, t13 T13, t14 T14, t15 T15, t16 T16, t17 T17, t18 T18, t19 T19, v ...V) R { + return f(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15, t16, t17, t18, t19, v) + } +} + +// Unvariadic19 converts a function taking 19 parameters and a final variadic argument into a function with 19 parameters but a final slice argument +func Unvariadic19[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, V, R any](f func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, ...V) R) func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, []V) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11, t12 T12, t13 T13, t14 T14, t15 T15, t16 T16, t17 T17, t18 T18, t19 T19, v []V) R { + return f(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15, t16, t17, t18, t19, v...) + } +} + +// Unsliced19 converts a function taking a slice parameter into a function with 19 parameters +func Unsliced19[F ~func([]T) R, T, R any](f F) func(T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T) R { + return func(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15, t16, t17, t18, t19 T) R { + return f([]T{t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15, t16, t17, t18, t19}) + } +} + +// Pipe20 takes an initial value t0 and successively applies 20 functions where the input of a function is the return value of the previous function +// The final return value is the result of the last function application +func Pipe20[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, F8 ~func(T7) T8, F9 ~func(T8) T9, F10 ~func(T9) T10, F11 ~func(T10) T11, F12 ~func(T11) T12, F13 ~func(T12) T13, F14 ~func(T13) T14, F15 ~func(T14) T15, F16 ~func(T15) T16, F17 ~func(T16) T17, F18 ~func(T17) T18, F19 ~func(T18) T19, F20 ~func(T19) T20, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20 any](t0 T0, f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10, f11 F11, f12 F12, f13 F13, f14 F14, f15 F15, f16 F16, f17 F17, f18 F18, f19 F19, f20 F20) T20 { + return f20(f19(f18(f17(f16(f15(f14(f13(f12(f11(f10(f9(f8(f7(f6(f5(f4(f3(f2(f1(t0)))))))))))))))))))) +} + +// Flow20 creates a function that takes an initial value t0 and successively applies 20 functions where the input of a function is the return value of the previous function +// The final return value is the result of the last function application +func Flow20[F1 ~func(T0) T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, F8 ~func(T7) T8, F9 ~func(T8) T9, F10 ~func(T9) T10, F11 ~func(T10) T11, F12 ~func(T11) T12, F13 ~func(T12) T13, F14 ~func(T13) T14, F15 ~func(T14) T15, F16 ~func(T15) T16, F17 ~func(T16) T17, F18 ~func(T17) T18, F19 ~func(T18) T19, F20 ~func(T19) T20, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10, f11 F11, f12 F12, f13 F13, f14 F14, f15 F15, f16 F16, f17 F17, f18 F18, f19 F19, f20 F20) func(T0) T20 { + return func(t0 T0) T20 { + return Pipe20(t0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, f16, f17, f18, f19, f20) + } +} + +// Nullary20 creates a parameter less function from a parameter less function and 19 functions. When executed the first parameter less function gets executed and then the result is piped through the remaining functions +func Nullary20[F1 ~func() T1, F2 ~func(T1) T2, F3 ~func(T2) T3, F4 ~func(T3) T4, F5 ~func(T4) T5, F6 ~func(T5) T6, F7 ~func(T6) T7, F8 ~func(T7) T8, F9 ~func(T8) T9, F10 ~func(T9) T10, F11 ~func(T10) T11, F12 ~func(T11) T12, F13 ~func(T12) T13, F14 ~func(T13) T14, F15 ~func(T14) T15, F16 ~func(T15) T16, F17 ~func(T16) T17, F18 ~func(T17) T18, F19 ~func(T18) T19, F20 ~func(T19) T20, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10, f11 F11, f12 F12, f13 F13, f14 F14, f15 F15, f16 F16, f17 F17, f18 F18, f19 F19, f20 F20) func() T20 { + return func() T20 { + return Pipe19(f1(), f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, f12, f13, f14, f15, f16, f17, f18, f19, f20) + } +} + +// Curry20 takes a function with 20 parameters and returns a cascade of functions each taking only one parameter. +// The inverse function is [Uncurry20] +func Curry20[FCT ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19) T20, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20 any](f FCT) func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) func(T16) func(T17) func(T18) func(T19) T20 { + return func(t0 T0) func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) func(t17 T17) func(t18 T18) func(t19 T19) T20 { + return func(t1 T1) func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) func(t17 T17) func(t18 T18) func(t19 T19) T20 { + return func(t2 T2) func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) func(t17 T17) func(t18 T18) func(t19 T19) T20 { + return func(t3 T3) func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) func(t17 T17) func(t18 T18) func(t19 T19) T20 { + return func(t4 T4) func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) func(t17 T17) func(t18 T18) func(t19 T19) T20 { + return func(t5 T5) func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) func(t17 T17) func(t18 T18) func(t19 T19) T20 { + return func(t6 T6) func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) func(t17 T17) func(t18 T18) func(t19 T19) T20 { + return func(t7 T7) func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) func(t17 T17) func(t18 T18) func(t19 T19) T20 { + return func(t8 T8) func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) func(t17 T17) func(t18 T18) func(t19 T19) T20 { + return func(t9 T9) func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) func(t17 T17) func(t18 T18) func(t19 T19) T20 { + return func(t10 T10) func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) func(t17 T17) func(t18 T18) func(t19 T19) T20 { + return func(t11 T11) func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) func(t17 T17) func(t18 T18) func(t19 T19) T20 { + return func(t12 T12) func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) func(t17 T17) func(t18 T18) func(t19 T19) T20 { + return func(t13 T13) func(t14 T14) func(t15 T15) func(t16 T16) func(t17 T17) func(t18 T18) func(t19 T19) T20 { + return func(t14 T14) func(t15 T15) func(t16 T16) func(t17 T17) func(t18 T18) func(t19 T19) T20 { + return func(t15 T15) func(t16 T16) func(t17 T17) func(t18 T18) func(t19 T19) T20 { + return func(t16 T16) func(t17 T17) func(t18 T18) func(t19 T19) T20 { + return func(t17 T17) func(t18 T18) func(t19 T19) T20 { + return func(t18 T18) func(t19 T19) T20 { + return func(t19 T19) T20 { + return f(t0, t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15, t16, t17, t18, t19) + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } +} + +// Uncurry20 takes a cascade of 20 functions each taking only one parameter and returns a function with 20 parameters . +// The inverse function is [Curry20] +func Uncurry20[FCT ~func(T0) func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) func(T16) func(T17) func(T18) func(T19) T20, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20 any](f FCT) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19) T20 { + return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11, t12 T12, t13 T13, t14 T14, t15 T15, t16 T16, t17 T17, t18 T18, t19 T19) T20 { + return f(t0)(t1)(t2)(t3)(t4)(t5)(t6)(t7)(t8)(t9)(t10)(t11)(t12)(t13)(t14)(t15)(t16)(t17)(t18)(t19) + } +} + +// Variadic20 converts a function taking 20 parameters and a final slice into a function with 20 parameters but a final variadic argument +func Variadic20[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, V, R any](f func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, []V) R) func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, ...V) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11, t12 T12, t13 T13, t14 T14, t15 T15, t16 T16, t17 T17, t18 T18, t19 T19, t20 T20, v ...V) R { + return f(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15, t16, t17, t18, t19, t20, v) + } +} + +// Unvariadic20 converts a function taking 20 parameters and a final variadic argument into a function with 20 parameters but a final slice argument +func Unvariadic20[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, V, R any](f func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, ...V) R) func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16, T17, T18, T19, T20, []V) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11, t12 T12, t13 T13, t14 T14, t15 T15, t16 T16, t17 T17, t18 T18, t19 T19, t20 T20, v []V) R { + return f(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15, t16, t17, t18, t19, t20, v...) + } +} + +// Unsliced20 converts a function taking a slice parameter into a function with 20 parameters +func Unsliced20[F ~func([]T) R, T, R any](f F) func(T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T, T) R { + return func(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15, t16, t17, t18, t19, t20 T) R { + return f([]T{t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15, t16, t17, t18, t19, t20}) + } +} diff --git a/v2/function/generic/cache.go b/v2/function/generic/cache.go new file mode 100644 index 0000000..5c12041 --- /dev/null +++ b/v2/function/generic/cache.go @@ -0,0 +1,103 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + "sync" + + L "github.com/IBM/fp-go/v2/internal/lazy" +) + +// Memoize converts a unary function into a unary function that caches the value depending on the parameter +func Memoize[F ~func(K) T, K comparable, T any](f F) F { + return ContramapMemoize[F](func(k K) K { return k })(f) +} + +// ContramapMemoize converts a unary function into a unary function that caches the value depending on the parameter +func ContramapMemoize[F ~func(A) T, KF func(A) K, A any, K comparable, T any](kf KF) func(F) F { + return CacheCallback[func(F) F, func() func() T](kf, getOrCreate[K, T]()) +} + +// getOrCreate is a naive implementation of a cache, without bounds +func getOrCreate[K comparable, T any]() func(K, func() func() T) func() T { + cache := make(map[K]func() T) + var l sync.Mutex + + return func(k K, cb func() func() T) func() T { + // only lock to access a lazy accessor to the value + l.Lock() + existing, ok := cache[k] + if !ok { + existing = cb() + cache[k] = existing + } + l.Unlock() + // compute the value outside of the lock + return existing + } +} + +// SingleElementCache is a cache with a capacity of a single element +func SingleElementCache[ + LLT ~func() LT, // generator of the generator + K comparable, // key into the cache + LT ~func() T, // generator of a value + T any, // the cached data type +]() func(K, LLT) LT { + var l sync.Mutex + + var key K + var value LT + hasKey := false + + return func(k K, gen LLT) LT { + l.Lock() + + existing := value + if !hasKey || key != k { + existing = gen() + // update state + key = k + value = existing + hasKey = true + } + + l.Unlock() + + return existing + } +} + +// CacheCallback converts a unary function into a unary function that caches the value depending on the parameter +func CacheCallback[ + EM ~func(F) F, // endomorphism of the function + LLT ~func() LT, // generator of the generator + LT ~func() T, // generator of a value + F ~func(A) T, // function to actually cache + KF func(A) K, // extracts the cache key from the input + C ~func(K, LLT) LT, // the cache callback function + A any, K comparable, T any](kf KF, getOrCreate C) EM { + return func(f F) F { + return func(a A) T { + // cache entry + return getOrCreate(kf(a), func() LT { + return L.Memoize[LT](func() T { + return f(a) + }) + })() + } + } +} diff --git a/v2/function/generic/switch.go b/v2/function/generic/switch.go new file mode 100644 index 0000000..7f2a042 --- /dev/null +++ b/v2/function/generic/switch.go @@ -0,0 +1,28 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +// Switch applies a handler to different cases. The handers are stored in a map. A key function +// extracts the case from a value. +func Switch[HF ~func(T) R, N ~map[K]HF, KF ~func(T) K, K comparable, T, R any](kf KF, n N, d HF) HF { + return func(t T) R { + f, ok := n[kf(t)] + if ok { + return f(t) + } + return d(t) + } +} diff --git a/v2/function/pipe_test.go b/v2/function/pipe_test.go new file mode 100644 index 0000000..57ea005 --- /dev/null +++ b/v2/function/pipe_test.go @@ -0,0 +1,62 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package function + +import ( + "fmt" +) + +func addSthg(value int) int { + return value + 1 +} + +func doSthgElse(value int) int { + return value * 2 +} + +func doFinalSthg(value int) string { + return fmt.Sprintf("final value: %d", value) +} + +func Example() { + // start point + value := 1 + // imperative style + value1 := addSthg(value) // 2 + value2 := doSthgElse(value1) // 4 + finalValueImperative := doFinalSthg(value2) // "final value: 4" + + // the same but inline + finalValueInline := doFinalSthg(doSthgElse(addSthg(value))) + + // with pipe + finalValuePipe := Pipe3(value, addSthg, doSthgElse, doFinalSthg) + + // with flow + transform := Flow3(addSthg, doSthgElse, doFinalSthg) + finalValueFlow := transform(value) + + fmt.Println(finalValueImperative) + fmt.Println(finalValueInline) + fmt.Println(finalValuePipe) + fmt.Println(finalValueFlow) + + // Output: + // final value: 4 + // final value: 4 + // final value: 4 + // final value: 4 +} diff --git a/v2/function/ref.go b/v2/function/ref.go new file mode 100644 index 0000000..e4d0dff --- /dev/null +++ b/v2/function/ref.go @@ -0,0 +1,85 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package function + +// Ref creates a pointer to a value. +// +// This function takes a value and returns a pointer to it. It's useful when you need +// to convert a value to a pointer, particularly when working with APIs that require +// pointers or when you need to create optional values. +// +// Type Parameters: +// - A: The type of the value +// +// Parameters: +// - a: The value to create a pointer to +// +// Returns: +// - A pointer to the value +// +// Example: +// +// value := 42 +// ptr := Ref(value) +// fmt.Println(*ptr) // 42 +// +// // Useful for creating pointers to literals +// strPtr := Ref("hello") +// fmt.Println(*strPtr) // "hello" +// +// // Creating optional values +// type Config struct { +// Timeout *int +// } +// config := Config{Timeout: Ref(30)} +func Ref[A any](a A) *A { + return &a +} + +// Deref dereferences a pointer to get its value. +// +// This function takes a pointer and returns the value it points to. It will panic +// if the pointer is nil, so it should only be used when you're certain the pointer +// is not nil. For safe dereferencing, check with IsNonNil first. +// +// Type Parameters: +// - A: The type of the value +// +// Parameters: +// - a: The pointer to dereference +// +// Returns: +// - The value pointed to by the pointer +// +// Example: +// +// value := 42 +// ptr := &value +// result := Deref(ptr) // 42 +// +// // Safe usage with nil check +// var ptr *int +// if IsNonNil(ptr) { +// result := Deref(ptr) +// fmt.Println(result) +// } +// +// // Chaining with Ref +// original := "hello" +// copy := Deref(Ref(original)) // "hello" +func Deref[A any](a *A) A { + return *a +} diff --git a/v2/function/switch.go b/v2/function/switch.go new file mode 100644 index 0000000..5d0239d --- /dev/null +++ b/v2/function/switch.go @@ -0,0 +1,96 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package function + +import ( + G "github.com/IBM/fp-go/v2/function/generic" +) + +// Switch creates a function that applies different handlers based on a key extracted from the input. +// +// This implements a switch/case-like pattern in a functional style. Given a key extraction function, +// a map of handlers for different cases, and a default handler, it returns a function that: +// 1. Extracts a key from the input using the key function +// 2. Looks up the handler for that key in the map +// 3. Applies the handler if found, or the default handler if not +// +// This is useful for implementing polymorphic behavior, routing, or state machines in a +// functional way. +// +// Type Parameters: +// - K: The type of the key (must be comparable for map lookup) +// - T: The input type +// - R: The return type +// +// Parameters: +// - kf: A function that extracts a key from the input +// - n: A map from keys to handler functions +// - d: The default handler to use when the key is not found in the map +// +// Returns: +// - A function that applies the appropriate handler based on the extracted key +// +// Example: +// +// type Animal struct { +// Type string +// Name string +// } +// +// getType := func(a Animal) string { return a.Type } +// +// handlers := map[string]func(Animal) string{ +// "dog": func(a Animal) string { return a.Name + " barks" }, +// "cat": func(a Animal) string { return a.Name + " meows" }, +// } +// +// defaultHandler := func(a Animal) string { +// return a.Name + " makes a sound" +// } +// +// makeSound := Switch(getType, handlers, defaultHandler) +// +// dog := Animal{Type: "dog", Name: "Rex"} +// cat := Animal{Type: "cat", Name: "Whiskers"} +// bird := Animal{Type: "bird", Name: "Tweety"} +// +// result1 := makeSound(dog) // "Rex barks" +// result2 := makeSound(cat) // "Whiskers meows" +// result3 := makeSound(bird) // "Tweety makes a sound" +// +// HTTP routing example: +// +// type Request struct { +// Method string +// Path string +// } +// +// getMethod := func(r Request) string { return r.Method } +// +// routes := map[string]func(Request) string{ +// "GET": func(r Request) string { return "Handling GET " + r.Path }, +// "POST": func(r Request) string { return "Handling POST " + r.Path }, +// "DELETE": func(r Request) string { return "Handling DELETE " + r.Path }, +// } +// +// notFound := func(r Request) string { +// return "Method not allowed: " + r.Method +// } +// +// router := Switch(getMethod, routes, notFound) +func Switch[K comparable, T, R any](kf func(T) K, n map[K]func(T) R, d func(T) R) func(T) R { + return G.Switch(kf, n, d) +} diff --git a/v2/function/ternary.go b/v2/function/ternary.go new file mode 100644 index 0000000..ed99a8c --- /dev/null +++ b/v2/function/ternary.go @@ -0,0 +1,61 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package function + +// Ternary creates a conditional function that applies different transformations based on a predicate. +// +// This function implements a ternary operator (condition ? trueCase : falseCase) in a functional style. +// It takes a predicate and two transformation functions, returning a new function that applies +// the appropriate transformation based on whether the predicate is satisfied. +// +// Type Parameters: +// - A: The input type +// - B: The output type +// +// Parameters: +// - pred: A predicate function that determines which branch to take +// - onTrue: The transformation to apply when the predicate returns true +// - onFalse: The transformation to apply when the predicate returns false +// +// Returns: +// - A function that conditionally applies onTrue or onFalse based on pred +// +// Example: +// +// isPositive := func(n int) bool { return n > 0 } +// double := func(n int) int { return n * 2 } +// negate := func(n int) int { return -n } +// +// transform := Ternary(isPositive, double, negate) +// result1 := transform(5) // 10 (positive, so doubled) +// result2 := transform(-3) // 3 (negative, so negated) +// +// // Classify numbers +// classify := Ternary( +// func(n int) bool { return n > 0 }, +// Constant1[int, string]("positive"), +// Constant1[int, string]("non-positive"), +// ) +// result := classify(5) // "positive" +// result2 := classify(-3) // "non-positive" +func Ternary[A, B any](pred func(A) bool, onTrue func(A) B, onFalse func(A) B) func(A) B { + return func(a A) B { + if pred(a) { + return onTrue(a) + } + return onFalse(a) + } +} diff --git a/v2/function/type.go b/v2/function/type.go new file mode 100644 index 0000000..1a6113a --- /dev/null +++ b/v2/function/type.go @@ -0,0 +1,49 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package function + +// ToAny converts a value of any type to the any (interface{}) type. +// +// This function performs an explicit type conversion to the any type, which can be +// useful when you need to store values of different types in a homogeneous collection +// or when interfacing with APIs that require any/interface{}. +// +// Type Parameters: +// - A: The type of the input value +// +// Parameters: +// - a: The value to convert +// +// Returns: +// - The value as type any +// +// Example: +// +// value := 42 +// anyValue := ToAny(value) // any(42) +// +// str := "hello" +// anyStr := ToAny(str) // any("hello") +// +// // Useful for creating heterogeneous collections +// values := []any{ +// ToAny(42), +// ToAny("hello"), +// ToAny(true), +// } +func ToAny[A any](a A) any { + return any(a) +} diff --git a/v2/function/unvariadic_test.go b/v2/function/unvariadic_test.go new file mode 100644 index 0000000..5110dc0 --- /dev/null +++ b/v2/function/unvariadic_test.go @@ -0,0 +1,45 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package function + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func fromLibrary(data ...string) string { + return strings.Join(data, "-") +} + +func TestUnvariadic(t *testing.T) { + + res := Pipe1( + []string{"A", "B"}, + Unvariadic0(fromLibrary), + ) + + assert.Equal(t, "A-B", res) +} + +func TestVariadicArity(t *testing.T) { + + f := Unsliced2(Unvariadic0(fromLibrary)) + + res := f("A", "B") + assert.Equal(t, "A-B", res) +} diff --git a/v2/go.mod b/v2/go.mod new file mode 100644 index 0000000..755161b --- /dev/null +++ b/v2/go.mod @@ -0,0 +1,17 @@ +module github.com/IBM/fp-go/v2 + +go 1.24 + +require ( + github.com/stretchr/testify v1.10.0 + github.com/urfave/cli/v2 v2.27.5 +) + +require ( + github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/v2/go.sum b/v2/go.sum new file mode 100644 index 0000000..9767476 --- /dev/null +++ b/v2/go.sum @@ -0,0 +1,18 @@ +github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= +github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= +github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= +github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/v2/http/builder/builder.go b/v2/http/builder/builder.go new file mode 100644 index 0000000..6fc7ba6 --- /dev/null +++ b/v2/http/builder/builder.go @@ -0,0 +1,387 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package builder + +import ( + "bytes" + "crypto/sha256" + "fmt" + "net/http" + "net/url" + + A "github.com/IBM/fp-go/v2/array" + B "github.com/IBM/fp-go/v2/bytes" + E "github.com/IBM/fp-go/v2/either" + ENDO "github.com/IBM/fp-go/v2/endomorphism" + F "github.com/IBM/fp-go/v2/function" + C "github.com/IBM/fp-go/v2/http/content" + FM "github.com/IBM/fp-go/v2/http/form" + H "github.com/IBM/fp-go/v2/http/headers" + J "github.com/IBM/fp-go/v2/json" + LZ "github.com/IBM/fp-go/v2/lazy" + L "github.com/IBM/fp-go/v2/optics/lens" + O "github.com/IBM/fp-go/v2/option" + R "github.com/IBM/fp-go/v2/record" + S "github.com/IBM/fp-go/v2/string" + T "github.com/IBM/fp-go/v2/tuple" +) + +type ( + Builder struct { + method O.Option[string] + url string + headers http.Header + body O.Option[E.Either[error, []byte]] + query url.Values + } + + // Endomorphism returns an [ENDO.Endomorphism] that transforms a builder + Endomorphism = ENDO.Endomorphism[*Builder] +) + +var ( + // Default is the default builder + Default = &Builder{method: O.Some(defaultMethod()), headers: make(http.Header), body: noBody} + + defaultMethod = F.Constant(http.MethodGet) + + // Monoid is the [M.Monoid] for the [Endomorphism] + Monoid = ENDO.Monoid[*Builder]() + + // Url is a [L.Lens] for the URL + // + // Deprecated: use [URL] instead + Url = L.MakeLensRef((*Builder).GetURL, (*Builder).SetURL) + // URL is a [L.Lens] for the URL + URL = L.MakeLensRef((*Builder).GetURL, (*Builder).SetURL) + // Method is a [L.Lens] for the HTTP method + Method = L.MakeLensRef((*Builder).GetMethod, (*Builder).SetMethod) + // Body is a [L.Lens] for the request body + Body = L.MakeLensRef((*Builder).GetBody, (*Builder).SetBody) + // Headers is a [L.Lens] for the complete set of request headers + Headers = L.MakeLensRef((*Builder).GetHeaders, (*Builder).SetHeaders) + // Query is a [L.Lens] for the set of query parameters + Query = L.MakeLensRef((*Builder).GetQuery, (*Builder).SetQuery) + + rawQuery = L.MakeLensRef(getRawQuery, setRawQuery) + + getHeader = F.Bind2of2((*Builder).GetHeader) + delHeader = F.Bind2of2((*Builder).DelHeader) + setHeader = F.Bind2of3((*Builder).SetHeader) + + noHeader = O.None[string]() + noBody = O.None[E.Either[error, []byte]]() + noQueryArg = O.None[string]() + + parseURL = E.Eitherize1(url.Parse) + parseQuery = E.Eitherize1(url.ParseQuery) + + // WithQuery creates a [Endomorphism] for a complete set of query parameters + WithQuery = Query.Set + // WithMethod creates a [Endomorphism] for a certain method + WithMethod = Method.Set + // WithUrl creates a [Endomorphism] for the URL + // + // Deprecated: use [WithURL] instead + WithUrl = URL.Set + // WithURL creates a [Endomorphism] for the URL + WithURL = URL.Set + // WithHeaders creates a [Endomorphism] for a set of headers + WithHeaders = Headers.Set + // WithBody creates a [Endomorphism] for a request body + WithBody = F.Flow2( + O.Of[E.Either[error, []byte]], + Body.Set, + ) + // WithBytes creates a [Endomorphism] for a request body using bytes + WithBytes = F.Flow2( + E.Of[error, []byte], + WithBody, + ) + // WithContentType adds the [H.ContentType] header + WithContentType = WithHeader(H.ContentType) + // WithAuthorization adds the [H.Authorization] header + WithAuthorization = WithHeader(H.Authorization) + + // WithGet adds the [http.MethodGet] method + WithGet = WithMethod(http.MethodGet) + // WithPost adds the [http.MethodPost] method + WithPost = WithMethod(http.MethodPost) + // WithPut adds the [http.MethodPut] method + WithPut = WithMethod(http.MethodPut) + // WithDelete adds the [http.MethodDelete] method + WithDelete = WithMethod(http.MethodDelete) + + // WithBearer creates a [Endomorphism] to add a Bearer [H.Authorization] header + WithBearer = F.Flow2( + S.Format[string]("Bearer %s"), + WithAuthorization, + ) + + // WithoutBody creates a [Endomorphism] to remove the body + WithoutBody = F.Pipe1( + noBody, + Body.Set, + ) + + // WithFormData creates a [Endomorphism] to send form data payload + WithFormData = F.Flow4( + url.Values.Encode, + S.ToBytes, + WithBytes, + ENDO.Chain(WithContentType(C.FormEncoded)), + ) + + // bodyAsBytes returns a []byte with a fallback to the empty array + bodyAsBytes = O.Fold(B.Empty, E.Fold(F.Ignore1of1[error](B.Empty), F.Identity[[]byte])) +) + +func setRawQuery(u *url.URL, raw string) *url.URL { + u.RawQuery = raw + return u +} + +func getRawQuery(u *url.URL) string { + return u.RawQuery +} + +func (builder *Builder) clone() *Builder { + cpy := *builder + cpy.headers = cpy.headers.Clone() + return &cpy +} + +// GetTargetUrl constructs a full URL with query parameters on top of the provided URL string +// +// Deprecated: use [GetTargetURL] instead +func (builder *Builder) GetTargetUrl() E.Either[error, string] { + return builder.GetTargetURL() +} + +// GetTargetURL constructs a full URL with query parameters on top of the provided URL string +func (builder *Builder) GetTargetURL() E.Either[error, string] { + // construct the final URL + return F.Pipe3( + builder, + Url.Get, + parseURL, + E.Chain(F.Flow4( + T.Replicate2[*url.URL], + T.Map2( + F.Flow2( + F.Curry2(setRawQuery), + E.Of[error, func(string) *url.URL], + ), + F.Flow3( + rawQuery.Get, + parseQuery, + E.Map[error](F.Flow2( + F.Curry2(FM.ValuesMonoid.Concat)(builder.GetQuery()), + (url.Values).Encode, + )), + ), + ), + T.Tupled2(E.MonadAp[*url.URL, error, string]), + E.Map[error]((*url.URL).String), + )), + ) +} + +// Deprecated: use [GetURL] instead +func (builder *Builder) GetUrl() string { + return builder.url +} + +func (builder *Builder) GetURL() string { + return builder.url +} + +func (builder *Builder) GetMethod() string { + return F.Pipe1( + builder.method, + O.GetOrElse(defaultMethod), + ) +} + +func (builder *Builder) GetHeaders() http.Header { + return builder.headers +} + +func (builder *Builder) GetQuery() url.Values { + return builder.query +} + +func (builder *Builder) SetQuery(query url.Values) *Builder { + builder.query = query + return builder +} + +func (builder *Builder) GetBody() O.Option[E.Either[error, []byte]] { + return builder.body +} + +func (builder *Builder) SetMethod(method string) *Builder { + builder.method = O.Some(method) + return builder +} + +// Deprecated: use [SetURL] instead +func (builder *Builder) SetUrl(url string) *Builder { + builder.url = url + return builder +} + +func (builder *Builder) SetURL(url string) *Builder { + builder.url = url + return builder +} + +func (builder *Builder) SetHeaders(headers http.Header) *Builder { + builder.headers = headers + return builder +} + +func (builder *Builder) SetBody(body O.Option[E.Either[error, []byte]]) *Builder { + builder.body = body + return builder +} + +func (builder *Builder) SetHeader(name, value string) *Builder { + builder.headers.Set(name, value) + return builder +} + +func (builder *Builder) DelHeader(name string) *Builder { + builder.headers.Del(name) + return builder +} + +func (builder *Builder) GetHeader(name string) O.Option[string] { + return F.Pipe2( + name, + builder.headers.Get, + O.FromPredicate(S.IsNonEmpty), + ) +} + +func (builder *Builder) GetHeaderValues(name string) []string { + return builder.headers.Values(name) +} + +// GetHash returns a hash value for the builder that can be used as a cache key +func (builder *Builder) GetHash() string { + return MakeHash(builder) +} + +// Header returns a [L.Lens] for a single header +func Header(name string) L.Lens[*Builder, O.Option[string]] { + get := getHeader(name) + set := F.Bind1of2(setHeader(name)) + del := F.Flow2( + LZ.Of[*Builder], + LZ.Map(delHeader(name)), + ) + + return L.MakeLens(get, func(b *Builder, value O.Option[string]) *Builder { + cpy := b.clone() + return F.Pipe1( + value, + O.Fold(del(cpy), set(cpy)), + ) + }) +} + +// WithHeader creates a [Endomorphism] for a certain header +func WithHeader(name string) func(value string) Endomorphism { + return F.Flow2( + O.Of[string], + Header(name).Set, + ) +} + +// WithoutHeader creates a [Endomorphism] to remove a certain header +func WithoutHeader(name string) Endomorphism { + return Header(name).Set(noHeader) +} + +// WithJson creates a [Endomorphism] to send JSON payload +// +// Deprecated: use [WithJSON] instead +func WithJson[T any](data T) Endomorphism { + return WithJSON[T](data) +} + +// WithJSON creates a [Endomorphism] to send JSON payload +func WithJSON[T any](data T) Endomorphism { + return Monoid.Concat( + F.Pipe2( + data, + J.Marshal[T], + WithBody, + ), + WithContentType(C.JSON), + ) +} + +// QueryArg is a [L.Lens] for the first value of a query argument +func QueryArg(name string) L.Lens[*Builder, O.Option[string]] { + return F.Pipe1( + Query, + L.Compose[*Builder](FM.AtValue(name)), + ) +} + +// WithQueryArg creates a [Endomorphism] for a certain query argument +func WithQueryArg(name string) func(value string) Endomorphism { + return F.Flow2( + O.Of[string], + QueryArg(name).Set, + ) +} + +// WithoutQueryArg creates a [Endomorphism] that removes a query argument +func WithoutQueryArg(name string) Endomorphism { + return QueryArg(name).Set(noQueryArg) +} + +func hashWriteValue(buf *bytes.Buffer, value string) *bytes.Buffer { + buf.WriteString(value) + return buf +} + +func hashWriteQuery(name string, buf *bytes.Buffer, values []string) *bytes.Buffer { + buf.WriteString(name) + return A.Reduce(hashWriteValue, buf)(values) +} + +func makeBytes(b *Builder) []byte { + var buf bytes.Buffer + + buf.WriteString(b.GetMethod()) + buf.WriteString(b.GetURL()) + b.GetHeaders().Write(&buf) // #nosec: G104 + + R.ReduceOrdWithIndex[[]string, *bytes.Buffer](S.Ord)(hashWriteQuery, &buf)(b.GetQuery()) + + buf.Write(bodyAsBytes(b.GetBody())) + + return buf.Bytes() +} + +// MakeHash converts a [Builder] into a hash string, convenient to use as a cache key +func MakeHash(b *Builder) string { + return fmt.Sprintf("%x", sha256.Sum256(makeBytes(b))) +} diff --git a/v2/http/builder/builder_test.go b/v2/http/builder/builder_test.go new file mode 100644 index 0000000..c04639b --- /dev/null +++ b/v2/http/builder/builder_test.go @@ -0,0 +1,93 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package builder + +import ( + "fmt" + "testing" + + F "github.com/IBM/fp-go/v2/function" + C "github.com/IBM/fp-go/v2/http/content" + FD "github.com/IBM/fp-go/v2/http/form" + H "github.com/IBM/fp-go/v2/http/headers" + O "github.com/IBM/fp-go/v2/option" + "github.com/stretchr/testify/assert" +) + +func TestBuilder(t *testing.T) { + + name := H.ContentType + withContentType := WithHeader(name) + withoutContentType := WithoutHeader(name) + + b1 := F.Pipe1( + Default, + withContentType(C.JSON), + ) + + b2 := F.Pipe1( + b1, + withContentType(C.TextPlain), + ) + + b3 := F.Pipe1( + b2, + withoutContentType, + ) + + assert.Equal(t, O.None[string](), Default.GetHeader(name)) + assert.Equal(t, O.Of(C.JSON), b1.GetHeader(name)) + assert.Equal(t, O.Of(C.TextPlain), b2.GetHeader(name)) + assert.Equal(t, O.None[string](), b3.GetHeader(name)) +} + +func TestWithFormData(t *testing.T) { + data := F.Pipe1( + FD.Default, + FD.WithValue("a")("b"), + ) + + res := F.Pipe1( + Default, + WithFormData(data), + ) + + assert.Equal(t, C.FormEncoded, Headers.Get(res).Get(H.ContentType)) +} + +func TestHash(t *testing.T) { + + b1 := F.Pipe4( + Default, + WithContentType(C.JSON), + WithHeader(H.Accept)(C.JSON), + WithURL("http://www.example.com"), + WithJSON(map[string]string{"a": "b"}), + ) + + b2 := F.Pipe4( + Default, + WithURL("http://www.example.com"), + WithHeader(H.Accept)(C.JSON), + WithContentType(C.JSON), + WithJSON(map[string]string{"a": "b"}), + ) + + assert.Equal(t, MakeHash(b1), MakeHash(b2)) + assert.NotEqual(t, MakeHash(Default), MakeHash(b2)) + + fmt.Println(MakeHash(b1)) +} diff --git a/v2/http/content/content.go b/v2/http/content/content.go new file mode 100644 index 0000000..217c9e3 --- /dev/null +++ b/v2/http/content/content.go @@ -0,0 +1,23 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package content + +const ( + TextPlain = "text/plain" + JSON = "application/json" + Json = JSON // Deprecated: use [JSON] instead + FormEncoded = "application/x-www-form-urlencoded" +) diff --git a/v2/http/form/form.go b/v2/http/form/form.go new file mode 100644 index 0000000..f7f159b --- /dev/null +++ b/v2/http/form/form.go @@ -0,0 +1,74 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package form + +import ( + "net/url" + + A "github.com/IBM/fp-go/v2/array" + ENDO "github.com/IBM/fp-go/v2/endomorphism" + F "github.com/IBM/fp-go/v2/function" + L "github.com/IBM/fp-go/v2/optics/lens" + LA "github.com/IBM/fp-go/v2/optics/lens/array" + LRG "github.com/IBM/fp-go/v2/optics/lens/record/generic" + O "github.com/IBM/fp-go/v2/option" + RG "github.com/IBM/fp-go/v2/record/generic" +) + +type ( + // Endomorphism returns an [ENDO.Endomorphism] that transforms a form + Endomorphism = ENDO.Endomorphism[url.Values] +) + +var ( + // Default is the default form field + Default = make(url.Values) + + noField = O.None[string]() + + // Monoid is the [M.Monoid] for the [Endomorphism] + Monoid = ENDO.Monoid[url.Values]() + + // ValuesMonoid is a [M.Monoid] to concatenate [url.Values] maps + ValuesMonoid = RG.UnionMonoid[url.Values](A.Semigroup[string]()) + + // AtValues is a [L.Lens] that focusses on the values of a form field + AtValues = LRG.AtRecord[url.Values, []string] + + composeHead = F.Pipe1( + LA.AtHead[string](), + L.ComposeOptions[url.Values, string](A.Empty[string]()), + ) + + // AtValue is a [L.Lens] that focusses on first value in form fields + AtValue = F.Flow2( + AtValues, + composeHead, + ) +) + +// WithValue creates a [FormBuilder] for a certain field +func WithValue(name string) func(value string) Endomorphism { + return F.Flow2( + O.Of[string], + AtValue(name).Set, + ) +} + +// WithoutValue creates a [FormBuilder] that removes a field +func WithoutValue(name string) Endomorphism { + return AtValue(name).Set(noField) +} diff --git a/v2/http/form/form_test.go b/v2/http/form/form_test.go new file mode 100644 index 0000000..8d3d5ce --- /dev/null +++ b/v2/http/form/form_test.go @@ -0,0 +1,93 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package form + +import ( + "net/url" + "testing" + + A "github.com/IBM/fp-go/v2/array" + "github.com/IBM/fp-go/v2/eq" + F "github.com/IBM/fp-go/v2/function" + LT "github.com/IBM/fp-go/v2/optics/lens/testing" + O "github.com/IBM/fp-go/v2/option" + RG "github.com/IBM/fp-go/v2/record/generic" + S "github.com/IBM/fp-go/v2/string" + "github.com/stretchr/testify/assert" +) + +var ( + sEq = eq.FromEquals(S.Eq) + valuesEq = RG.Eq[url.Values](A.Eq(sEq)) +) + +func TestLaws(t *testing.T) { + name := "Content-Type" + fieldLaws := LT.AssertLaws[url.Values, O.Option[string]](t, O.Eq(sEq), valuesEq)(AtValue(name)) + + n := O.None[string]() + s1 := O.Some("s1") + + v1 := F.Pipe1( + Default, + WithValue(name)("v1"), + ) + + v2 := F.Pipe1( + Default, + WithValue("Other-Header")("v2"), + ) + + assert.True(t, fieldLaws(Default, n)) + assert.True(t, fieldLaws(v1, n)) + assert.True(t, fieldLaws(v2, n)) + + assert.True(t, fieldLaws(Default, s1)) + assert.True(t, fieldLaws(v1, s1)) + assert.True(t, fieldLaws(v2, s1)) +} + +func TestFormField(t *testing.T) { + + v1 := F.Pipe1( + Default, + WithValue("h1")("v1"), + ) + + v2 := F.Pipe1( + v1, + WithValue("h2")("v2"), + ) + + // make sure the code does not change structures + assert.False(t, valuesEq.Equals(Default, v1)) + assert.False(t, valuesEq.Equals(Default, v2)) + assert.False(t, valuesEq.Equals(v1, v2)) + + // check for existence of values + assert.Equal(t, "v1", v1.Get("h1")) + assert.Equal(t, "v1", v2.Get("h1")) + assert.Equal(t, "v2", v2.Get("h2")) + + // check getter on lens + + l1 := AtValue("h1") + l2 := AtValue("h2") + + assert.Equal(t, O.Of("v1"), l1.Get(v1)) + assert.Equal(t, O.Of("v1"), l1.Get(v2)) + assert.Equal(t, O.Of("v2"), l2.Get(v2)) +} diff --git a/v2/http/headers/headers.go b/v2/http/headers/headers.go new file mode 100644 index 0000000..5fc8d74 --- /dev/null +++ b/v2/http/headers/headers.go @@ -0,0 +1,58 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package headers + +import ( + "net/http" + "net/textproto" + + A "github.com/IBM/fp-go/v2/array" + F "github.com/IBM/fp-go/v2/function" + L "github.com/IBM/fp-go/v2/optics/lens" + LA "github.com/IBM/fp-go/v2/optics/lens/array" + LRG "github.com/IBM/fp-go/v2/optics/lens/record/generic" + RG "github.com/IBM/fp-go/v2/record/generic" +) + +// HTTP headers +const ( + Accept = "Accept" + Authorization = "Authorization" + ContentType = "Content-Type" + ContentLength = "Content-Length" +) + +var ( + // Monoid is a [M.Monoid] to concatenate [http.Header] maps + Monoid = RG.UnionMonoid[http.Header](A.Semigroup[string]()) + + // AtValues is a [L.Lens] that focusses on the values of a header + AtValues = F.Flow2( + textproto.CanonicalMIMEHeaderKey, + LRG.AtRecord[http.Header, []string], + ) + + composeHead = F.Pipe1( + LA.AtHead[string](), + L.ComposeOptions[http.Header, string](A.Empty[string]()), + ) + + // AtValue is a [L.Lens] that focusses on first value of a header + AtValue = F.Flow2( + AtValues, + composeHead, + ) +) diff --git a/v2/http/headers/headers_test.go b/v2/http/headers/headers_test.go new file mode 100644 index 0000000..1d4f0f0 --- /dev/null +++ b/v2/http/headers/headers_test.go @@ -0,0 +1,58 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package headers + +import ( + "net/http" + "testing" + + A "github.com/IBM/fp-go/v2/array" + "github.com/IBM/fp-go/v2/eq" + LT "github.com/IBM/fp-go/v2/optics/lens/testing" + O "github.com/IBM/fp-go/v2/option" + RG "github.com/IBM/fp-go/v2/record/generic" + S "github.com/IBM/fp-go/v2/string" + "github.com/stretchr/testify/assert" +) + +var ( + sEq = eq.FromEquals(S.Eq) + valuesEq = RG.Eq[http.Header](A.Eq(sEq)) +) + +func TestLaws(t *testing.T) { + name := "Content-Type" + fieldLaws := LT.AssertLaws[http.Header, O.Option[string]](t, O.Eq(sEq), valuesEq)(AtValue(name)) + + n := O.None[string]() + s1 := O.Some("s1") + + def := make(http.Header) + + v1 := make(http.Header) + v1.Set(name, "v1") + + v2 := make(http.Header) + v2.Set("Other-Header", "v2") + + assert.True(t, fieldLaws(def, n)) + assert.True(t, fieldLaws(v1, n)) + assert.True(t, fieldLaws(v2, n)) + + assert.True(t, fieldLaws(def, s1)) + assert.True(t, fieldLaws(v1, s1)) + assert.True(t, fieldLaws(v2, s1)) +} diff --git a/v2/http/types.go b/v2/http/types.go new file mode 100644 index 0000000..5d0d458 --- /dev/null +++ b/v2/http/types.go @@ -0,0 +1,32 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package http + +import ( + H "net/http" + + P "github.com/IBM/fp-go/v2/pair" +) + +type ( + // FullResponse represents a full http response, including headers and body + FullResponse = P.Pair[*H.Response, []byte] +) + +var ( + Response = P.Head[*H.Response, []byte] + Body = P.Tail[*H.Response, []byte] +) diff --git a/v2/http/utils.go b/v2/http/utils.go new file mode 100644 index 0000000..c2b24c1 --- /dev/null +++ b/v2/http/utils.go @@ -0,0 +1,131 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package http + +import ( + "fmt" + "io" + "mime" + H "net/http" + "net/url" + "regexp" + + A "github.com/IBM/fp-go/v2/array" + E "github.com/IBM/fp-go/v2/either" + "github.com/IBM/fp-go/v2/errors" + F "github.com/IBM/fp-go/v2/function" + O "github.com/IBM/fp-go/v2/option" + P "github.com/IBM/fp-go/v2/pair" + R "github.com/IBM/fp-go/v2/record/generic" +) + +type ( + ParsedMediaType = P.Pair[string, map[string]string] + + HttpError struct { + statusCode int + headers H.Header + body []byte + url *url.URL + } +) + +var ( + // mime type to check if a media type matches + isJSONMimeType = regexp.MustCompile(`application/(?:\w+\+)?json`).MatchString + // ValidateResponse validates an HTTP response and returns an [E.Either] if the response is not a success + ValidateResponse = E.FromPredicate(isValidStatus, StatusCodeError) + // alidateJsonContentTypeString parses a content type a validates that it is valid JSON + validateJSONContentTypeString = F.Flow2( + ParseMediaType, + E.ChainFirst(F.Flow2( + P.Head[string, map[string]string], + E.FromPredicate(isJSONMimeType, errors.OnSome[string]("mimetype [%s] is not a valid JSON content type")), + )), + ) + // ValidateJSONResponse checks if an HTTP response is a valid JSON response + ValidateJSONResponse = F.Flow2( + E.Of[error, *H.Response], + E.ChainFirst(F.Flow5( + GetHeader, + R.Lookup[H.Header](HeaderContentType), + O.Chain(A.First[string]), + E.FromOption[string](errors.OnNone("unable to access the [%s] header", HeaderContentType)), + E.ChainFirst(validateJSONContentTypeString), + ))) + // ValidateJsonResponse checks if an HTTP response is a valid JSON response + // + // Deprecated: use [ValidateJSONResponse] instead + ValidateJsonResponse = ValidateJSONResponse +) + +const ( + HeaderContentType = "Content-Type" +) + +// ParseMediaType parses a media type into a tuple +func ParseMediaType(mediaType string) E.Either[error, ParsedMediaType] { + m, p, err := mime.ParseMediaType(mediaType) + return E.TryCatchError(P.MakePair(m, p), err) +} + +// Error fulfills the error interface +func (r *HttpError) Error() string { + return fmt.Sprintf("invalid status code [%d] when accessing URL [%s]", r.statusCode, r.url) +} + +func (r *HttpError) String() string { + return r.Error() +} + +func (r *HttpError) StatusCode() int { + return r.statusCode +} + +func (r *HttpError) Headers() H.Header { + return r.headers +} + +func (r *HttpError) URL() *url.URL { + return r.url +} + +func (r *HttpError) Body() []byte { + return r.body +} + +func GetHeader(resp *H.Response) H.Header { + return resp.Header +} + +func GetBody(resp *H.Response) io.ReadCloser { + return resp.Body +} + +func isValidStatus(resp *H.Response) bool { + return resp.StatusCode >= H.StatusOK && resp.StatusCode < H.StatusMultipleChoices +} + +// StatusCodeError creates an instance of [HttpError] filled with information from the response +func StatusCodeError(resp *H.Response) error { + // read the body + bodyRdr := GetBody(resp) + defer bodyRdr.Close() + // try to access body content + body, _ := io.ReadAll(bodyRdr) + // return an error with comprehensive information + return &HttpError{statusCode: resp.StatusCode, headers: GetHeader(resp).Clone(), body: body, url: resp.Request.URL} +} diff --git a/v2/http/utils_test.go b/v2/http/utils_test.go new file mode 100644 index 0000000..89f27c5 --- /dev/null +++ b/v2/http/utils_test.go @@ -0,0 +1,57 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package http + +import ( + "testing" + + E "github.com/IBM/fp-go/v2/either" + F "github.com/IBM/fp-go/v2/function" + C "github.com/IBM/fp-go/v2/http/content" + "github.com/stretchr/testify/assert" +) + +func NoError[A any](t *testing.T) func(E.Either[error, A]) bool { + return E.Fold(func(err error) bool { + return assert.NoError(t, err) + }, F.Constant1[A](true)) +} + +func Error[A any](t *testing.T) func(E.Either[error, A]) bool { + return E.Fold(F.Constant1[error](true), func(A) bool { + return assert.Error(t, nil) + }) +} + +func TestValidateJsonContentTypeString(t *testing.T) { + + res := F.Pipe1( + validateJSONContentTypeString(C.JSON), + NoError[ParsedMediaType](t), + ) + + assert.True(t, res) +} + +func TestValidateInvalidJsonContentTypeString(t *testing.T) { + + res := F.Pipe1( + validateJSONContentTypeString("application/xml"), + Error[ParsedMediaType](t), + ) + + assert.True(t, res) +} diff --git a/v2/identity/bind.go b/v2/identity/bind.go new file mode 100644 index 0000000..b473a28 --- /dev/null +++ b/v2/identity/bind.go @@ -0,0 +1,89 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package identity + +import ( + A "github.com/IBM/fp-go/v2/internal/apply" + C "github.com/IBM/fp-go/v2/internal/chain" + F "github.com/IBM/fp-go/v2/internal/functor" +) + +// Bind creates an empty context of type [S] to be used with the [Bind] operation +func Do[S any]( + empty S, +) S { + return empty +} + +// Bind attaches the result of a computation to a context [S1] to produce a context [S2] +func Bind[S1, S2, T any]( + setter func(T) func(S1) S2, + f func(S1) T, +) func(S1) S2 { + return C.Bind( + Chain[S1, S2], + Map[T, S2], + setter, + f, + ) +} + +// Let attaches the result of a computation to a context [S1] to produce a context [S2] +func Let[S1, S2, T any]( + key func(T) func(S1) S2, + f func(S1) T, +) func(S1) S2 { + return F.Let( + Map[S1, S2], + key, + f, + ) +} + +// LetTo attaches the a value to a context [S1] to produce a context [S2] +func LetTo[S1, S2, B any]( + key func(B) func(S1) S2, + b B, +) func(S1) S2 { + return F.LetTo( + Map[S1, S2], + key, + b, + ) +} + +// BindTo initializes a new state [S1] from a value [T] +func BindTo[S1, T any]( + setter func(T) S1, +) func(T) S1 { + return C.BindTo( + Map[T, S1], + setter, + ) +} + +// ApS attaches a value to a context [S1] to produce a context [S2] by considering the context and the value concurrently +func ApS[S1, S2, T any]( + setter func(T) func(S1) S2, + fa T, +) func(S1) S2 { + return A.ApS( + Ap[S2, T], + Map[S1, func(T) S2], + setter, + fa, + ) +} diff --git a/v2/identity/doc.go b/v2/identity/doc.go new file mode 100644 index 0000000..060cd32 --- /dev/null +++ b/v2/identity/doc.go @@ -0,0 +1,271 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +/* +Package identity implements the Identity monad, the simplest possible monad. + +# Overview + +The Identity monad is a trivial monad that simply wraps a value without adding +any computational context. It's the identity element in the category of monads, +meaning it doesn't add any effects or behavior - it just passes values through. + +While seemingly useless, the Identity monad serves several important purposes: + - As a baseline for understanding more complex monads + - For testing monad transformers + - As a default when no specific monad is needed + - For generic code that works with any monad + +In this implementation, Identity[A] is simply represented as type A itself, +making it a zero-cost abstraction. + +# Core Concepts + +The Identity monad implements the standard monadic operations: + + - Of: Wraps a value (identity function) + - Map: Transforms the wrapped value + - Chain (FlatMap): Chains computations + - Ap: Applies a wrapped function to a wrapped value + +Since Identity adds no context, all these operations reduce to simple function +application. + +# Basic Usage + +Creating and transforming Identity values: + + // Of wraps a value (but it's just the identity) + x := identity.Of(42) + // x is just 42 + + // Map transforms the value + doubled := identity.Map(func(n int) int { + return n * 2 + })(x) + // doubled is 84 + + // Chain for monadic composition + result := identity.Chain(func(n int) int { + return n + 10 + })(doubled) + // result is 94 + +# Functor Operations + +Map transforms values: + + import F "github.com/IBM/fp-go/v2/function" + + // Simple mapping + result := F.Pipe1( + 5, + identity.Map(func(n int) int { return n * n }), + ) + // result is 25 + + // MapTo replaces with a constant + result := F.Pipe1( + "ignored", + identity.MapTo[string, int](100), + ) + // result is 100 + +# Applicative Operations + +Ap applies wrapped functions: + + add := func(a int) func(int) int { + return func(b int) int { + return a + b + } + } + + // Apply a curried function + result := F.Pipe1( + add(10), + identity.Ap[int, int](5), + ) + // result is 15 + +# Monad Operations + +Chain for sequential composition: + + // Chain multiple operations + result := F.Pipe2( + 10, + identity.Chain(func(n int) int { return n * 2 }), + identity.Chain(func(n int) int { return n + 5 }), + ) + // result is 25 + + // ChainFirst executes for side effects but keeps original value + result := F.Pipe1( + 42, + identity.ChainFirst(func(n int) string { + return fmt.Sprintf("Value: %d", n) + }), + ) + // result is still 42 + +# Do Notation + +The package provides "do notation" for imperative-style composition: + + type Result struct { + X int + Y int + Sum int + } + + result := F.Pipe3( + identity.Do(Result{}), + identity.Bind( + func(r Result) func(int) Result { + return func(x int) Result { + r.X = x + return r + } + }, + func(Result) int { return 10 }, + ), + identity.Bind( + func(r Result) func(int) Result { + return func(y int) Result { + r.Y = y + return r + } + }, + func(Result) int { return 20 }, + ), + identity.Let( + func(r Result) func(int) Result { + return func(sum int) Result { + r.Sum = sum + return r + } + }, + func(r Result) int { return r.X + r.Y }, + ), + ) + // result is Result{X: 10, Y: 20, Sum: 30} + +# Sequence and Traverse + +Convert tuples of Identity values: + + import T "github.com/IBM/fp-go/v2/tuple" + + // Sequence a tuple + tuple := T.MakeTuple2(1, 2) + result := identity.SequenceTuple2(tuple) + // result is T.Tuple2[int, int]{1, 2} + + // Traverse with transformation + tuple := T.MakeTuple2(1, 2) + result := identity.TraverseTuple2( + func(n int) int { return n * 2 }, + func(n int) int { return n * 3 }, + )(tuple) + // result is T.Tuple2[int, int]{2, 6} + +# Monad Interface + +Get a monad instance for generic code: + + m := identity.Monad[int, string]() + + // Use monad operations + value := m.Of(42) + mapped := m.Map(func(n int) string { + return fmt.Sprintf("Number: %d", n) + })(value) + +# Why Identity? + +The Identity monad might seem pointless, but it's useful for: + +1. Testing: Test monad transformers with a simple base monad +2. Defaults: Provide a default when no specific monad is needed +3. Learning: Understand monad laws without additional complexity +4. Abstraction: Write generic code that works with any monad + +Example of generic code: + + func ProcessWithMonad[M any]( + monad monad.Monad[int, string, M, M, func(int) M], + value int, + ) M { + return F.Pipe2( + monad.Of(value), + monad.Map(func(n int) int { return n * 2 }), + monad.Map(func(n int) string { return fmt.Sprintf("%d", n) }), + ) + } + + // Works with Identity + result := ProcessWithMonad(identity.Monad[int, string](), 21) + // result is "42" + +# Type Alias + +The package defines: + + type Operator[A, B any] = func(A) B + +This represents an Identity computation from A to B, which is just a function. + +# Functions + +Core operations: + - Of[A any](A) A - Wrap a value (identity) + - Map[A, B any](func(A) B) func(A) B - Transform value + - Chain[A, B any](func(A) B) func(A) B - Monadic bind + - Ap[B, A any](A) func(func(A) B) B - Apply function + +Monad variants: + - MonadMap, MonadChain, MonadAp - Uncurried versions + +Additional operations: + - MapTo[A, B any](B) func(A) B - Replace with constant + - ChainFirst[A, B any](func(A) B) func(A) A - Execute for effect + - Flap[B, A any](A) func(func(A) B) B - Flip application + +Do notation: + - Do[S any](S) S - Initialize context + - Bind[S1, S2, T any] - Bind computation result + - Let[S1, S2, T any] - Bind pure value + - LetTo[S1, S2, B any] - Bind constant + - BindTo[S1, T any] - Initialize from value + - ApS[S1, S2, T any] - Apply in context + +Sequence/Traverse: + - SequenceT1-10 - Sequence tuples of size 1-10 + - SequenceTuple1-10 - Sequence tuple types + - TraverseTuple1-10 - Traverse with transformations + +Monad instance: + - Monad[A, B any]() - Get monad interface + +# Related Packages + + - function: Function composition utilities + - monad: Monad interface definition + - tuple: Tuple types for sequence operations +*/ +package identity + +//go:generate go run .. identity --count 10 --filename gen.go diff --git a/v2/identity/gen.go b/v2/identity/gen.go new file mode 100644 index 0000000..ef4af95 --- /dev/null +++ b/v2/identity/gen.go @@ -0,0 +1,506 @@ +// Code generated by go generate; DO NOT EDIT. +// This file was generated by robots at +// 2025-03-09 23:52:59.8415644 +0100 CET m=+0.002613001 + +package identity + + +import ( + A "github.com/IBM/fp-go/v2/internal/apply" + T "github.com/IBM/fp-go/v2/tuple" +) + +// SequenceT1 converts 1 parameters of [T] into a [Tuple1]. +func SequenceT1[T1 any](t1 T1) T.Tuple1[T1] { + return A.SequenceT1( + Map[T1, T.Tuple1[T1]], + t1, + ) +} + +// SequenceTuple1 converts a [Tuple1] of [T] into an [Tuple1]. +func SequenceTuple1[T1 any](t T.Tuple1[T1]) T.Tuple1[T1] { + return A.SequenceTuple1( + Map[T1, T.Tuple1[T1]], + t, + ) +} + +// TraverseTuple1 converts a [Tuple1] of [A] via transformation functions transforming [A] to [A] into a [Tuple1]. +func TraverseTuple1[F1 ~func(A1) T1, A1, T1 any](f1 F1) func (T.Tuple1[A1]) T.Tuple1[T1] { + return func(t T.Tuple1[A1]) T.Tuple1[T1] { + return A.TraverseTuple1( + Map[T1, T.Tuple1[T1]], + f1, + t, + ) + } +} + +// SequenceT2 converts 2 parameters of [T] into a [Tuple2]. +func SequenceT2[T1, T2 any](t1 T1, t2 T2) T.Tuple2[T1, T2] { + return A.SequenceT2( + Map[T1, func(T2) T.Tuple2[T1, T2]], + Ap[T.Tuple2[T1, T2], T2], + t1, + t2, + ) +} + +// SequenceTuple2 converts a [Tuple2] of [T] into an [Tuple2]. +func SequenceTuple2[T1, T2 any](t T.Tuple2[T1, T2]) T.Tuple2[T1, T2] { + return A.SequenceTuple2( + Map[T1, func(T2) T.Tuple2[T1, T2]], + Ap[T.Tuple2[T1, T2], T2], + t, + ) +} + +// TraverseTuple2 converts a [Tuple2] of [A] via transformation functions transforming [A] to [A] into a [Tuple2]. +func TraverseTuple2[F1 ~func(A1) T1, F2 ~func(A2) T2, A1, T1, A2, T2 any](f1 F1, f2 F2) func (T.Tuple2[A1, A2]) T.Tuple2[T1, T2] { + return func(t T.Tuple2[A1, A2]) T.Tuple2[T1, T2] { + return A.TraverseTuple2( + Map[T1, func(T2) T.Tuple2[T1, T2]], + Ap[T.Tuple2[T1, T2], T2], + f1, + f2, + t, + ) + } +} + +// SequenceT3 converts 3 parameters of [T] into a [Tuple3]. +func SequenceT3[T1, T2, T3 any](t1 T1, t2 T2, t3 T3) T.Tuple3[T1, T2, T3] { + return A.SequenceT3( + Map[T1, func(T2) func(T3) T.Tuple3[T1, T2, T3]], + Ap[func(T3) T.Tuple3[T1, T2, T3], T2], + Ap[T.Tuple3[T1, T2, T3], T3], + t1, + t2, + t3, + ) +} + +// SequenceTuple3 converts a [Tuple3] of [T] into an [Tuple3]. +func SequenceTuple3[T1, T2, T3 any](t T.Tuple3[T1, T2, T3]) T.Tuple3[T1, T2, T3] { + return A.SequenceTuple3( + Map[T1, func(T2) func(T3) T.Tuple3[T1, T2, T3]], + Ap[func(T3) T.Tuple3[T1, T2, T3], T2], + Ap[T.Tuple3[T1, T2, T3], T3], + t, + ) +} + +// TraverseTuple3 converts a [Tuple3] of [A] via transformation functions transforming [A] to [A] into a [Tuple3]. +func TraverseTuple3[F1 ~func(A1) T1, F2 ~func(A2) T2, F3 ~func(A3) T3, A1, T1, A2, T2, A3, T3 any](f1 F1, f2 F2, f3 F3) func (T.Tuple3[A1, A2, A3]) T.Tuple3[T1, T2, T3] { + return func(t T.Tuple3[A1, A2, A3]) T.Tuple3[T1, T2, T3] { + return A.TraverseTuple3( + Map[T1, func(T2) func(T3) T.Tuple3[T1, T2, T3]], + Ap[func(T3) T.Tuple3[T1, T2, T3], T2], + Ap[T.Tuple3[T1, T2, T3], T3], + f1, + f2, + f3, + t, + ) + } +} + +// SequenceT4 converts 4 parameters of [T] into a [Tuple4]. +func SequenceT4[T1, T2, T3, T4 any](t1 T1, t2 T2, t3 T3, t4 T4) T.Tuple4[T1, T2, T3, T4] { + return A.SequenceT4( + Map[T1, func(T2) func(T3) func(T4) T.Tuple4[T1, T2, T3, T4]], + Ap[func(T3) func(T4) T.Tuple4[T1, T2, T3, T4], T2], + Ap[func(T4) T.Tuple4[T1, T2, T3, T4], T3], + Ap[T.Tuple4[T1, T2, T3, T4], T4], + t1, + t2, + t3, + t4, + ) +} + +// SequenceTuple4 converts a [Tuple4] of [T] into an [Tuple4]. +func SequenceTuple4[T1, T2, T3, T4 any](t T.Tuple4[T1, T2, T3, T4]) T.Tuple4[T1, T2, T3, T4] { + return A.SequenceTuple4( + Map[T1, func(T2) func(T3) func(T4) T.Tuple4[T1, T2, T3, T4]], + Ap[func(T3) func(T4) T.Tuple4[T1, T2, T3, T4], T2], + Ap[func(T4) T.Tuple4[T1, T2, T3, T4], T3], + Ap[T.Tuple4[T1, T2, T3, T4], T4], + t, + ) +} + +// TraverseTuple4 converts a [Tuple4] of [A] via transformation functions transforming [A] to [A] into a [Tuple4]. +func TraverseTuple4[F1 ~func(A1) T1, F2 ~func(A2) T2, F3 ~func(A3) T3, F4 ~func(A4) T4, A1, T1, A2, T2, A3, T3, A4, T4 any](f1 F1, f2 F2, f3 F3, f4 F4) func (T.Tuple4[A1, A2, A3, A4]) T.Tuple4[T1, T2, T3, T4] { + return func(t T.Tuple4[A1, A2, A3, A4]) T.Tuple4[T1, T2, T3, T4] { + return A.TraverseTuple4( + Map[T1, func(T2) func(T3) func(T4) T.Tuple4[T1, T2, T3, T4]], + Ap[func(T3) func(T4) T.Tuple4[T1, T2, T3, T4], T2], + Ap[func(T4) T.Tuple4[T1, T2, T3, T4], T3], + Ap[T.Tuple4[T1, T2, T3, T4], T4], + f1, + f2, + f3, + f4, + t, + ) + } +} + +// SequenceT5 converts 5 parameters of [T] into a [Tuple5]. +func SequenceT5[T1, T2, T3, T4, T5 any](t1 T1, t2 T2, t3 T3, t4 T4, t5 T5) T.Tuple5[T1, T2, T3, T4, T5] { + return A.SequenceT5( + Map[T1, func(T2) func(T3) func(T4) func(T5) T.Tuple5[T1, T2, T3, T4, T5]], + Ap[func(T3) func(T4) func(T5) T.Tuple5[T1, T2, T3, T4, T5], T2], + Ap[func(T4) func(T5) T.Tuple5[T1, T2, T3, T4, T5], T3], + Ap[func(T5) T.Tuple5[T1, T2, T3, T4, T5], T4], + Ap[T.Tuple5[T1, T2, T3, T4, T5], T5], + t1, + t2, + t3, + t4, + t5, + ) +} + +// SequenceTuple5 converts a [Tuple5] of [T] into an [Tuple5]. +func SequenceTuple5[T1, T2, T3, T4, T5 any](t T.Tuple5[T1, T2, T3, T4, T5]) T.Tuple5[T1, T2, T3, T4, T5] { + return A.SequenceTuple5( + Map[T1, func(T2) func(T3) func(T4) func(T5) T.Tuple5[T1, T2, T3, T4, T5]], + Ap[func(T3) func(T4) func(T5) T.Tuple5[T1, T2, T3, T4, T5], T2], + Ap[func(T4) func(T5) T.Tuple5[T1, T2, T3, T4, T5], T3], + Ap[func(T5) T.Tuple5[T1, T2, T3, T4, T5], T4], + Ap[T.Tuple5[T1, T2, T3, T4, T5], T5], + t, + ) +} + +// TraverseTuple5 converts a [Tuple5] of [A] via transformation functions transforming [A] to [A] into a [Tuple5]. +func TraverseTuple5[F1 ~func(A1) T1, F2 ~func(A2) T2, F3 ~func(A3) T3, F4 ~func(A4) T4, F5 ~func(A5) T5, A1, T1, A2, T2, A3, T3, A4, T4, A5, T5 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5) func (T.Tuple5[A1, A2, A3, A4, A5]) T.Tuple5[T1, T2, T3, T4, T5] { + return func(t T.Tuple5[A1, A2, A3, A4, A5]) T.Tuple5[T1, T2, T3, T4, T5] { + return A.TraverseTuple5( + Map[T1, func(T2) func(T3) func(T4) func(T5) T.Tuple5[T1, T2, T3, T4, T5]], + Ap[func(T3) func(T4) func(T5) T.Tuple5[T1, T2, T3, T4, T5], T2], + Ap[func(T4) func(T5) T.Tuple5[T1, T2, T3, T4, T5], T3], + Ap[func(T5) T.Tuple5[T1, T2, T3, T4, T5], T4], + Ap[T.Tuple5[T1, T2, T3, T4, T5], T5], + f1, + f2, + f3, + f4, + f5, + t, + ) + } +} + +// SequenceT6 converts 6 parameters of [T] into a [Tuple6]. +func SequenceT6[T1, T2, T3, T4, T5, T6 any](t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6) T.Tuple6[T1, T2, T3, T4, T5, T6] { + return A.SequenceT6( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6]], + Ap[func(T3) func(T4) func(T5) func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6], T2], + Ap[func(T4) func(T5) func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6], T3], + Ap[func(T5) func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6], T4], + Ap[func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6], T5], + Ap[T.Tuple6[T1, T2, T3, T4, T5, T6], T6], + t1, + t2, + t3, + t4, + t5, + t6, + ) +} + +// SequenceTuple6 converts a [Tuple6] of [T] into an [Tuple6]. +func SequenceTuple6[T1, T2, T3, T4, T5, T6 any](t T.Tuple6[T1, T2, T3, T4, T5, T6]) T.Tuple6[T1, T2, T3, T4, T5, T6] { + return A.SequenceTuple6( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6]], + Ap[func(T3) func(T4) func(T5) func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6], T2], + Ap[func(T4) func(T5) func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6], T3], + Ap[func(T5) func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6], T4], + Ap[func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6], T5], + Ap[T.Tuple6[T1, T2, T3, T4, T5, T6], T6], + t, + ) +} + +// TraverseTuple6 converts a [Tuple6] of [A] via transformation functions transforming [A] to [A] into a [Tuple6]. +func TraverseTuple6[F1 ~func(A1) T1, F2 ~func(A2) T2, F3 ~func(A3) T3, F4 ~func(A4) T4, F5 ~func(A5) T5, F6 ~func(A6) T6, A1, T1, A2, T2, A3, T3, A4, T4, A5, T5, A6, T6 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6) func (T.Tuple6[A1, A2, A3, A4, A5, A6]) T.Tuple6[T1, T2, T3, T4, T5, T6] { + return func(t T.Tuple6[A1, A2, A3, A4, A5, A6]) T.Tuple6[T1, T2, T3, T4, T5, T6] { + return A.TraverseTuple6( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6]], + Ap[func(T3) func(T4) func(T5) func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6], T2], + Ap[func(T4) func(T5) func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6], T3], + Ap[func(T5) func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6], T4], + Ap[func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6], T5], + Ap[T.Tuple6[T1, T2, T3, T4, T5, T6], T6], + f1, + f2, + f3, + f4, + f5, + f6, + t, + ) + } +} + +// SequenceT7 converts 7 parameters of [T] into a [Tuple7]. +func SequenceT7[T1, T2, T3, T4, T5, T6, T7 any](t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7] { + return A.SequenceT7( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7], T2], + Ap[func(T4) func(T5) func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7], T3], + Ap[func(T5) func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7], T4], + Ap[func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7], T5], + Ap[func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7], T6], + Ap[T.Tuple7[T1, T2, T3, T4, T5, T6, T7], T7], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + ) +} + +// SequenceTuple7 converts a [Tuple7] of [T] into an [Tuple7]. +func SequenceTuple7[T1, T2, T3, T4, T5, T6, T7 any](t T.Tuple7[T1, T2, T3, T4, T5, T6, T7]) T.Tuple7[T1, T2, T3, T4, T5, T6, T7] { + return A.SequenceTuple7( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7], T2], + Ap[func(T4) func(T5) func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7], T3], + Ap[func(T5) func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7], T4], + Ap[func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7], T5], + Ap[func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7], T6], + Ap[T.Tuple7[T1, T2, T3, T4, T5, T6, T7], T7], + t, + ) +} + +// TraverseTuple7 converts a [Tuple7] of [A] via transformation functions transforming [A] to [A] into a [Tuple7]. +func TraverseTuple7[F1 ~func(A1) T1, F2 ~func(A2) T2, F3 ~func(A3) T3, F4 ~func(A4) T4, F5 ~func(A5) T5, F6 ~func(A6) T6, F7 ~func(A7) T7, A1, T1, A2, T2, A3, T3, A4, T4, A5, T5, A6, T6, A7, T7 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7) func (T.Tuple7[A1, A2, A3, A4, A5, A6, A7]) T.Tuple7[T1, T2, T3, T4, T5, T6, T7] { + return func(t T.Tuple7[A1, A2, A3, A4, A5, A6, A7]) T.Tuple7[T1, T2, T3, T4, T5, T6, T7] { + return A.TraverseTuple7( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7], T2], + Ap[func(T4) func(T5) func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7], T3], + Ap[func(T5) func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7], T4], + Ap[func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7], T5], + Ap[func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7], T6], + Ap[T.Tuple7[T1, T2, T3, T4, T5, T6, T7], T7], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + t, + ) + } +} + +// SequenceT8 converts 8 parameters of [T] into a [Tuple8]. +func SequenceT8[T1, T2, T3, T4, T5, T6, T7, T8 any](t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8] { + return A.SequenceT8( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T3], + Ap[func(T5) func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T4], + Ap[func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T5], + Ap[func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T6], + Ap[func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T7], + Ap[T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T8], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + t8, + ) +} + +// SequenceTuple8 converts a [Tuple8] of [T] into an [Tuple8]. +func SequenceTuple8[T1, T2, T3, T4, T5, T6, T7, T8 any](t T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8] { + return A.SequenceTuple8( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T3], + Ap[func(T5) func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T4], + Ap[func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T5], + Ap[func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T6], + Ap[func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T7], + Ap[T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T8], + t, + ) +} + +// TraverseTuple8 converts a [Tuple8] of [A] via transformation functions transforming [A] to [A] into a [Tuple8]. +func TraverseTuple8[F1 ~func(A1) T1, F2 ~func(A2) T2, F3 ~func(A3) T3, F4 ~func(A4) T4, F5 ~func(A5) T5, F6 ~func(A6) T6, F7 ~func(A7) T7, F8 ~func(A8) T8, A1, T1, A2, T2, A3, T3, A4, T4, A5, T5, A6, T6, A7, T7, A8, T8 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8) func (T.Tuple8[A1, A2, A3, A4, A5, A6, A7, A8]) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8] { + return func(t T.Tuple8[A1, A2, A3, A4, A5, A6, A7, A8]) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8] { + return A.TraverseTuple8( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T3], + Ap[func(T5) func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T4], + Ap[func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T5], + Ap[func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T6], + Ap[func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T7], + Ap[T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T8], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + f8, + t, + ) + } +} + +// SequenceT9 converts 9 parameters of [T] into a [Tuple9]. +func SequenceT9[T1, T2, T3, T4, T5, T6, T7, T8, T9 any](t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9] { + return A.SequenceT9( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T3], + Ap[func(T5) func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T4], + Ap[func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T5], + Ap[func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T6], + Ap[func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T7], + Ap[func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T8], + Ap[T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T9], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + t8, + t9, + ) +} + +// SequenceTuple9 converts a [Tuple9] of [T] into an [Tuple9]. +func SequenceTuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9 any](t T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9] { + return A.SequenceTuple9( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T3], + Ap[func(T5) func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T4], + Ap[func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T5], + Ap[func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T6], + Ap[func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T7], + Ap[func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T8], + Ap[T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T9], + t, + ) +} + +// TraverseTuple9 converts a [Tuple9] of [A] via transformation functions transforming [A] to [A] into a [Tuple9]. +func TraverseTuple9[F1 ~func(A1) T1, F2 ~func(A2) T2, F3 ~func(A3) T3, F4 ~func(A4) T4, F5 ~func(A5) T5, F6 ~func(A6) T6, F7 ~func(A7) T7, F8 ~func(A8) T8, F9 ~func(A9) T9, A1, T1, A2, T2, A3, T3, A4, T4, A5, T5, A6, T6, A7, T7, A8, T8, A9, T9 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9) func (T.Tuple9[A1, A2, A3, A4, A5, A6, A7, A8, A9]) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9] { + return func(t T.Tuple9[A1, A2, A3, A4, A5, A6, A7, A8, A9]) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9] { + return A.TraverseTuple9( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T3], + Ap[func(T5) func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T4], + Ap[func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T5], + Ap[func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T6], + Ap[func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T7], + Ap[func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T8], + Ap[T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T9], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + f8, + f9, + t, + ) + } +} + +// SequenceT10 converts 10 parameters of [T] into a [Tuple10]. +func SequenceT10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any](t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10] { + return A.SequenceT10( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T3], + Ap[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T4], + Ap[func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T5], + Ap[func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T6], + Ap[func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T7], + Ap[func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T8], + Ap[func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T9], + Ap[T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T10], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + t8, + t9, + t10, + ) +} + +// SequenceTuple10 converts a [Tuple10] of [T] into an [Tuple10]. +func SequenceTuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any](t T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10] { + return A.SequenceTuple10( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T3], + Ap[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T4], + Ap[func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T5], + Ap[func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T6], + Ap[func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T7], + Ap[func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T8], + Ap[func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T9], + Ap[T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T10], + t, + ) +} + +// TraverseTuple10 converts a [Tuple10] of [A] via transformation functions transforming [A] to [A] into a [Tuple10]. +func TraverseTuple10[F1 ~func(A1) T1, F2 ~func(A2) T2, F3 ~func(A3) T3, F4 ~func(A4) T4, F5 ~func(A5) T5, F6 ~func(A6) T6, F7 ~func(A7) T7, F8 ~func(A8) T8, F9 ~func(A9) T9, F10 ~func(A10) T10, A1, T1, A2, T2, A3, T3, A4, T4, A5, T5, A6, T6, A7, T7, A8, T8, A9, T9, A10, T10 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10) func (T.Tuple10[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10]) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10] { + return func(t T.Tuple10[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10]) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10] { + return A.TraverseTuple10( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T3], + Ap[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T4], + Ap[func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T5], + Ap[func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T6], + Ap[func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T7], + Ap[func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T8], + Ap[func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T9], + Ap[T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T10], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + f8, + f9, + f10, + t, + ) + } +} diff --git a/v2/identity/identity.go b/v2/identity/identity.go new file mode 100644 index 0000000..64b749e --- /dev/null +++ b/v2/identity/identity.go @@ -0,0 +1,74 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package identity + +import ( + "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/internal/chain" + "github.com/IBM/fp-go/v2/internal/functor" +) + +func MonadAp[B, A any](fab func(A) B, fa A) B { + return fab(fa) +} + +func Ap[B, A any](fa A) Operator[func(A) B, B] { + return function.Bind2nd(MonadAp[B, A], fa) +} + +func MonadMap[A, B any](fa A, f func(A) B) B { + return f(fa) +} + +func Map[A, B any](f func(A) B) Operator[A, B] { + return f +} + +func MonadMapTo[A, B any](_ A, b B) B { + return b +} + +func MapTo[A, B any](b B) func(A) B { + return function.Constant1[A](b) +} + +func Of[A any](a A) A { + return a +} + +func MonadChain[A, B any](ma A, f func(A) B) B { + return f(ma) +} + +func Chain[A, B any](f func(A) B) Operator[A, B] { + return f +} + +func MonadChainFirst[A, B any](fa A, f func(A) B) A { + return chain.MonadChainFirst(MonadChain[A, A], MonadMap[B, A], fa, f) +} + +func ChainFirst[A, B any](f func(A) B) Operator[A, A] { + return chain.ChainFirst(Chain[A, A], Map[B, A], f) +} + +func MonadFlap[B, A any](fab func(A) B, a A) B { + return functor.MonadFlap(MonadMap[func(A) B, B], fab, a) +} + +func Flap[B, A any](a A) Operator[func(A) B, B] { + return functor.Flap(Map[func(A) B, B], a) +} diff --git a/v2/identity/identity_test.go b/v2/identity/identity_test.go new file mode 100644 index 0000000..857dedc --- /dev/null +++ b/v2/identity/identity_test.go @@ -0,0 +1,740 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package identity + +import ( + "fmt" + "testing" + + F "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/internal/utils" + T "github.com/IBM/fp-go/v2/tuple" + "github.com/stretchr/testify/assert" +) + +func TestOf(t *testing.T) { + t.Run("wraps int", func(t *testing.T) { + result := Of(42) + assert.Equal(t, 42, result) + }) + + t.Run("wraps string", func(t *testing.T) { + result := Of("hello") + assert.Equal(t, "hello", result) + }) + + t.Run("wraps struct", func(t *testing.T) { + type Person struct{ Name string } + p := Person{Name: "Alice"} + result := Of(p) + assert.Equal(t, p, result) + }) +} + +func TestMap(t *testing.T) { + t.Run("transforms int", func(t *testing.T) { + result := F.Pipe1(1, Map(utils.Double)) + assert.Equal(t, 2, result) + }) + + t.Run("transforms string", func(t *testing.T) { + result := F.Pipe1("hello", Map(func(s string) int { + return len(s) + })) + assert.Equal(t, 5, result) + }) + + t.Run("chains multiple maps", func(t *testing.T) { + result := F.Pipe2( + 5, + Map(func(n int) int { return n * 2 }), + Map(func(n int) int { return n + 3 }), + ) + assert.Equal(t, 13, result) + }) +} + +func TestMonadMap(t *testing.T) { + t.Run("transforms value", func(t *testing.T) { + result := MonadMap(10, func(n int) int { return n * 3 }) + assert.Equal(t, 30, result) + }) + + t.Run("changes type", func(t *testing.T) { + result := MonadMap(42, func(n int) string { + return fmt.Sprintf("Number: %d", n) + }) + assert.Equal(t, "Number: 42", result) + }) +} + +func TestMapTo(t *testing.T) { + t.Run("replaces with constant int", func(t *testing.T) { + result := F.Pipe1("ignored", MapTo[string, int](100)) + assert.Equal(t, 100, result) + }) + + t.Run("replaces with constant string", func(t *testing.T) { + result := F.Pipe1(42, MapTo[int, string]("constant")) + assert.Equal(t, "constant", result) + }) +} + +func TestMonadMapTo(t *testing.T) { + t.Run("replaces value", func(t *testing.T) { + result := MonadMapTo("anything", 999) + assert.Equal(t, 999, result) + }) +} + +func TestChain(t *testing.T) { + t.Run("chains computation", func(t *testing.T) { + result := F.Pipe1(1, Chain(utils.Double)) + assert.Equal(t, 2, result) + }) + + t.Run("chains multiple operations", func(t *testing.T) { + result := F.Pipe2( + 10, + Chain(func(n int) int { return n * 2 }), + Chain(func(n int) int { return n + 5 }), + ) + assert.Equal(t, 25, result) + }) + + t.Run("changes type", func(t *testing.T) { + result := F.Pipe1(5, Chain(func(n int) string { + return fmt.Sprintf("Value: %d", n) + })) + assert.Equal(t, "Value: 5", result) + }) +} + +func TestMonadChain(t *testing.T) { + t.Run("chains computation", func(t *testing.T) { + result := MonadChain(7, func(n int) int { return n * 7 }) + assert.Equal(t, 49, result) + }) +} + +func TestChainFirst(t *testing.T) { + t.Run("executes but keeps original", func(t *testing.T) { + sideEffect := "" + result := F.Pipe1( + 42, + ChainFirst(func(n int) string { + sideEffect = fmt.Sprintf("Processed: %d", n) + return sideEffect + }), + ) + assert.Equal(t, 42, result) + assert.Equal(t, "Processed: 42", sideEffect) + }) + + t.Run("chains with other operations", func(t *testing.T) { + result := F.Pipe2( + 10, + ChainFirst(func(n int) string { return "ignored" }), + Map(func(n int) int { return n * 2 }), + ) + assert.Equal(t, 20, result) + }) +} + +func TestMonadChainFirst(t *testing.T) { + t.Run("keeps original value", func(t *testing.T) { + result := MonadChainFirst(100, func(n int) string { + return fmt.Sprintf("%d", n) + }) + assert.Equal(t, 100, result) + }) +} + +func TestAp(t *testing.T) { + t.Run("applies function", func(t *testing.T) { + result := F.Pipe1(utils.Double, Ap[int, int](1)) + assert.Equal(t, 2, result) + }) + + t.Run("applies curried function", func(t *testing.T) { + add := func(a int) func(int) int { + return func(b int) int { return a + b } + } + result := F.Pipe1(add(10), Ap[int, int](5)) + assert.Equal(t, 15, result) + }) + + t.Run("changes type", func(t *testing.T) { + toString := func(n int) string { + return fmt.Sprintf("Number: %d", n) + } + result := F.Pipe1(toString, Ap[string, int](42)) + assert.Equal(t, "Number: 42", result) + }) +} + +func TestMonadAp(t *testing.T) { + t.Run("applies function to value", func(t *testing.T) { + result := MonadAp(func(n int) int { return n * 3 }, 7) + assert.Equal(t, 21, result) + }) +} + +func TestFlap(t *testing.T) { + t.Run("flips application", func(t *testing.T) { + double := func(n int) int { return n * 2 } + result := F.Pipe1(double, Flap[int, int](5)) + assert.Equal(t, 10, result) + }) + + t.Run("with multiple functions", func(t *testing.T) { + funcs := []func(int) int{ + func(n int) int { return n * 2 }, + func(n int) int { return n + 10 }, + func(n int) int { return n * n }, + } + + results := make([]int, len(funcs)) + for i, f := range funcs { + results[i] = Flap[int, int](5)(f) + } + + assert.Equal(t, []int{10, 15, 25}, results) + }) +} + +func TestMonadFlap(t *testing.T) { + t.Run("applies value to function", func(t *testing.T) { + result := MonadFlap(func(n int) string { + return fmt.Sprintf("Value: %d", n) + }, 42) + assert.Equal(t, "Value: 42", result) + }) +} + +func TestDo(t *testing.T) { + t.Run("initializes context", func(t *testing.T) { + type State struct{ Value int } + result := Do(State{Value: 10}) + assert.Equal(t, State{Value: 10}, result) + }) +} + +func TestBind(t *testing.T) { + t.Run("binds computation result", func(t *testing.T) { + type State struct { + X int + Y int + } + + result := F.Pipe2( + Do(State{}), + Bind( + func(x int) func(State) State { + return func(s State) State { + s.X = x + return s + } + }, + func(State) int { return 10 }, + ), + Bind( + func(y int) func(State) State { + return func(s State) State { + s.Y = y + return s + } + }, + func(State) int { return 20 }, + ), + ) + + assert.Equal(t, State{X: 10, Y: 20}, result) + }) +} + +func TestLet(t *testing.T) { + t.Run("attaches computed value", func(t *testing.T) { + type State struct { + X int + Sum int + } + + result := F.Pipe2( + Do(State{X: 5}), + Let( + func(sum int) func(State) State { + return func(s State) State { + s.Sum = sum + return s + } + }, + func(s State) int { return s.X * 2 }, + ), + Map(func(s State) State { + s.Sum += 10 + return s + }), + ) + + assert.Equal(t, State{X: 5, Sum: 20}, result) + }) +} + +func TestLetTo(t *testing.T) { + t.Run("attaches constant value", func(t *testing.T) { + type State struct { + Name string + } + + result := F.Pipe1( + Do(State{}), + LetTo( + func(name string) func(State) State { + return func(s State) State { + s.Name = name + return s + } + }, + "Alice", + ), + ) + + assert.Equal(t, State{Name: "Alice"}, result) + }) +} + +func TestBindTo(t *testing.T) { + t.Run("initializes state from value", func(t *testing.T) { + type State struct{ Value int } + + result := F.Pipe1( + 42, + BindTo(func(v int) State { + return State{Value: v} + }), + ) + + assert.Equal(t, State{Value: 42}, result) + }) +} + +func TestApS(t *testing.T) { + t.Run("applies value in context", func(t *testing.T) { + type State struct { + X int + Y int + } + + result := F.Pipe1( + Do(State{X: 10}), + ApS( + func(y int) func(State) State { + return func(s State) State { + s.Y = y + return s + } + }, + 20, + ), + ) + + assert.Equal(t, State{X: 10, Y: 20}, result) + }) +} + +func TestSequenceT(t *testing.T) { + t.Run("SequenceT2", func(t *testing.T) { + result := SequenceT2(1, 2) + assert.Equal(t, T.MakeTuple2(1, 2), result) + }) + + t.Run("SequenceT3", func(t *testing.T) { + result := SequenceT3("a", "b", "c") + assert.Equal(t, T.MakeTuple3("a", "b", "c"), result) + }) + + t.Run("SequenceT4", func(t *testing.T) { + result := SequenceT4(1, 2, 3, 4) + assert.Equal(t, T.MakeTuple4(1, 2, 3, 4), result) + }) +} + +func TestSequenceTuple(t *testing.T) { + t.Run("SequenceTuple2", func(t *testing.T) { + tuple := T.MakeTuple2(10, 20) + result := SequenceTuple2(tuple) + assert.Equal(t, tuple, result) + }) + + t.Run("SequenceTuple3", func(t *testing.T) { + tuple := T.MakeTuple3(1, 2, 3) + result := SequenceTuple3(tuple) + assert.Equal(t, tuple, result) + }) +} + +func TestTraverseTuple(t *testing.T) { + t.Run("TraverseTuple2", func(t *testing.T) { + tuple := T.MakeTuple2(1, 2) + result := TraverseTuple2( + func(n int) int { return n * 2 }, + func(n int) int { return n * 3 }, + )(tuple) + assert.Equal(t, T.MakeTuple2(2, 6), result) + }) + + t.Run("TraverseTuple3", func(t *testing.T) { + tuple := T.MakeTuple3(1, 2, 3) + result := TraverseTuple3( + func(n int) int { return n + 10 }, + func(n int) int { return n + 20 }, + func(n int) int { return n + 30 }, + )(tuple) + assert.Equal(t, T.MakeTuple3(11, 22, 33), result) + }) + + t.Run("TraverseTuple2 with type change", func(t *testing.T) { + tuple := T.MakeTuple2(5, 10) + result := TraverseTuple2( + func(n int) string { return fmt.Sprintf("A%d", n) }, + func(n int) string { return fmt.Sprintf("B%d", n) }, + )(tuple) + assert.Equal(t, T.MakeTuple2("A5", "B10"), result) + }) +} + +func TestMonad(t *testing.T) { + t.Run("monad interface", func(t *testing.T) { + m := Monad[int, string]() + + // Test Of + value := m.Of(42) + assert.Equal(t, 42, value) + + // Test Map + mapped := m.Map(func(n int) string { + return fmt.Sprintf("Number: %d", n) + })(value) + assert.Equal(t, "Number: 42", mapped) + + // Test Chain + chained := m.Chain(func(n int) string { + return fmt.Sprintf("Value: %d", n) + })(value) + assert.Equal(t, "Value: 42", chained) + + // Test Ap + applied := m.Ap(10)(func(n int) string { + return fmt.Sprintf("Result: %d", n) + }) + assert.Equal(t, "Result: 10", applied) + }) +} + +// Test monad laws +func TestMonadLaws(t *testing.T) { + t.Run("left identity", func(t *testing.T) { + // Of(a).Chain(f) === f(a) + a := 42 + f := func(n int) int { return n * 2 } + + left := F.Pipe1(Of(a), Chain(f)) + right := f(a) + + assert.Equal(t, right, left) + }) + + t.Run("right identity", func(t *testing.T) { + // m.Chain(Of) === m + m := 42 + + result := F.Pipe1(m, Chain(Of[int])) + + assert.Equal(t, m, result) + }) + + t.Run("associativity", func(t *testing.T) { + // m.Chain(f).Chain(g) === m.Chain(x => f(x).Chain(g)) + m := 5 + f := func(n int) int { return n * 2 } + g := func(n int) int { return n + 10 } + + left := F.Pipe2(m, Chain(f), Chain(g)) + right := F.Pipe1(m, Chain(func(x int) int { + return F.Pipe1(f(x), Chain(g)) + })) + + assert.Equal(t, right, left) + }) +} + +// Test functor laws +func TestFunctorLaws(t *testing.T) { + t.Run("identity", func(t *testing.T) { + // Map(id) === id + value := 42 + + result := F.Pipe1(value, Map(F.Identity[int])) + + assert.Equal(t, value, result) + }) + + t.Run("composition", func(t *testing.T) { + // Map(f).Map(g) === Map(g ∘ f) + value := 5 + f := func(n int) int { return n * 2 } + g := func(n int) int { return n + 10 } + + left := F.Pipe2(value, Map(f), Map(g)) + right := F.Pipe1(value, Map(F.Flow2(f, g))) + + assert.Equal(t, right, left) + }) +} + +func TestSequenceT1(t *testing.T) { + t.Run("sequences single value", func(t *testing.T) { + result := SequenceT1(42) + assert.Equal(t, T.MakeTuple1(42), result) + }) +} + +func TestSequenceTuple1(t *testing.T) { + t.Run("sequences tuple1", func(t *testing.T) { + tuple := T.MakeTuple1("hello") + result := SequenceTuple1(tuple) + assert.Equal(t, tuple, result) + }) +} + +func TestTraverseTuple1(t *testing.T) { + t.Run("traverses tuple1", func(t *testing.T) { + tuple := T.MakeTuple1(5) + result := TraverseTuple1(func(n int) int { return n * 10 })(tuple) + assert.Equal(t, T.MakeTuple1(50), result) + }) +} + +func TestSequenceTuple4(t *testing.T) { + t.Run("sequences tuple4", func(t *testing.T) { + tuple := T.MakeTuple4(1, 2, 3, 4) + result := SequenceTuple4(tuple) + assert.Equal(t, tuple, result) + }) +} + +func TestTraverseTuple4(t *testing.T) { + t.Run("traverses tuple4", func(t *testing.T) { + tuple := T.MakeTuple4(1, 2, 3, 4) + result := TraverseTuple4( + func(n int) int { return n + 10 }, + func(n int) int { return n + 20 }, + func(n int) int { return n + 30 }, + func(n int) int { return n + 40 }, + )(tuple) + assert.Equal(t, T.MakeTuple4(11, 22, 33, 44), result) + }) +} + +func TestSequenceT5(t *testing.T) { + t.Run("sequences 5 values", func(t *testing.T) { + result := SequenceT5(1, 2, 3, 4, 5) + assert.Equal(t, T.MakeTuple5(1, 2, 3, 4, 5), result) + }) +} + +func TestSequenceTuple5(t *testing.T) { + t.Run("sequences tuple5", func(t *testing.T) { + tuple := T.MakeTuple5(1, 2, 3, 4, 5) + result := SequenceTuple5(tuple) + assert.Equal(t, tuple, result) + }) +} + +func TestTraverseTuple5(t *testing.T) { + t.Run("traverses tuple5", func(t *testing.T) { + tuple := T.MakeTuple5(1, 2, 3, 4, 5) + result := TraverseTuple5( + func(n int) int { return n * 1 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 3 }, + func(n int) int { return n * 4 }, + func(n int) int { return n * 5 }, + )(tuple) + assert.Equal(t, T.MakeTuple5(1, 4, 9, 16, 25), result) + }) +} + +func TestSequenceT6(t *testing.T) { + t.Run("sequences 6 values", func(t *testing.T) { + result := SequenceT6(1, 2, 3, 4, 5, 6) + assert.Equal(t, T.MakeTuple6(1, 2, 3, 4, 5, 6), result) + }) +} + +func TestSequenceTuple6(t *testing.T) { + t.Run("sequences tuple6", func(t *testing.T) { + tuple := T.MakeTuple6(1, 2, 3, 4, 5, 6) + result := SequenceTuple6(tuple) + assert.Equal(t, tuple, result) + }) +} + +func TestTraverseTuple6(t *testing.T) { + t.Run("traverses tuple6", func(t *testing.T) { + tuple := T.MakeTuple6(1, 2, 3, 4, 5, 6) + result := TraverseTuple6( + func(n int) int { return n + 1 }, + func(n int) int { return n + 2 }, + func(n int) int { return n + 3 }, + func(n int) int { return n + 4 }, + func(n int) int { return n + 5 }, + func(n int) int { return n + 6 }, + )(tuple) + assert.Equal(t, T.MakeTuple6(2, 4, 6, 8, 10, 12), result) + }) +} + +func TestSequenceT7(t *testing.T) { + t.Run("sequences 7 values", func(t *testing.T) { + result := SequenceT7(1, 2, 3, 4, 5, 6, 7) + assert.Equal(t, T.MakeTuple7(1, 2, 3, 4, 5, 6, 7), result) + }) +} + +func TestSequenceTuple7(t *testing.T) { + t.Run("sequences tuple7", func(t *testing.T) { + tuple := T.MakeTuple7(1, 2, 3, 4, 5, 6, 7) + result := SequenceTuple7(tuple) + assert.Equal(t, tuple, result) + }) +} + +func TestTraverseTuple7(t *testing.T) { + t.Run("traverses tuple7", func(t *testing.T) { + tuple := T.MakeTuple7(1, 2, 3, 4, 5, 6, 7) + result := TraverseTuple7( + func(n int) int { return n * 10 }, + func(n int) int { return n * 10 }, + func(n int) int { return n * 10 }, + func(n int) int { return n * 10 }, + func(n int) int { return n * 10 }, + func(n int) int { return n * 10 }, + func(n int) int { return n * 10 }, + )(tuple) + assert.Equal(t, T.MakeTuple7(10, 20, 30, 40, 50, 60, 70), result) + }) +} + +func TestSequenceT8(t *testing.T) { + t.Run("sequences 8 values", func(t *testing.T) { + result := SequenceT8(1, 2, 3, 4, 5, 6, 7, 8) + assert.Equal(t, T.MakeTuple8(1, 2, 3, 4, 5, 6, 7, 8), result) + }) +} + +func TestSequenceTuple8(t *testing.T) { + t.Run("sequences tuple8", func(t *testing.T) { + tuple := T.MakeTuple8(1, 2, 3, 4, 5, 6, 7, 8) + result := SequenceTuple8(tuple) + assert.Equal(t, tuple, result) + }) +} + +func TestTraverseTuple8(t *testing.T) { + t.Run("traverses tuple8", func(t *testing.T) { + tuple := T.MakeTuple8(1, 2, 3, 4, 5, 6, 7, 8) + result := TraverseTuple8( + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + )(tuple) + assert.Equal(t, tuple, result) + }) +} + +func TestSequenceT9(t *testing.T) { + t.Run("sequences 9 values", func(t *testing.T) { + result := SequenceT9(1, 2, 3, 4, 5, 6, 7, 8, 9) + assert.Equal(t, T.MakeTuple9(1, 2, 3, 4, 5, 6, 7, 8, 9), result) + }) +} + +func TestSequenceTuple9(t *testing.T) { + t.Run("sequences tuple9", func(t *testing.T) { + tuple := T.MakeTuple9(1, 2, 3, 4, 5, 6, 7, 8, 9) + result := SequenceTuple9(tuple) + assert.Equal(t, tuple, result) + }) +} + +func TestTraverseTuple9(t *testing.T) { + t.Run("traverses tuple9", func(t *testing.T) { + tuple := T.MakeTuple9(1, 2, 3, 4, 5, 6, 7, 8, 9) + result := TraverseTuple9( + func(n int) int { return n + 1 }, + func(n int) int { return n + 1 }, + func(n int) int { return n + 1 }, + func(n int) int { return n + 1 }, + func(n int) int { return n + 1 }, + func(n int) int { return n + 1 }, + func(n int) int { return n + 1 }, + func(n int) int { return n + 1 }, + func(n int) int { return n + 1 }, + )(tuple) + assert.Equal(t, T.MakeTuple9(2, 3, 4, 5, 6, 7, 8, 9, 10), result) + }) +} + +func TestSequenceT10(t *testing.T) { + t.Run("sequences 10 values", func(t *testing.T) { + result := SequenceT10(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + assert.Equal(t, T.MakeTuple10(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), result) + }) +} + +func TestSequenceTuple10(t *testing.T) { + t.Run("sequences tuple10", func(t *testing.T) { + tuple := T.MakeTuple10(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + result := SequenceTuple10(tuple) + assert.Equal(t, tuple, result) + }) +} + +func TestTraverseTuple10(t *testing.T) { + t.Run("traverses tuple10", func(t *testing.T) { + tuple := T.MakeTuple10(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + result := TraverseTuple10( + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + )(tuple) + assert.Equal(t, T.MakeTuple10(2, 4, 6, 8, 10, 12, 14, 16, 18, 20), result) + }) +} diff --git a/v2/identity/monad.go b/v2/identity/monad.go new file mode 100644 index 0000000..6988921 --- /dev/null +++ b/v2/identity/monad.go @@ -0,0 +1,43 @@ +// Copyright (c) 2024 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package identity + +import ( + "github.com/IBM/fp-go/v2/internal/monad" +) + +type identityMonad[A, B any] struct{} + +func (o *identityMonad[A, B]) Of(a A) A { + return Of(a) +} + +func (o *identityMonad[A, B]) Map(f func(A) B) func(A) B { + return Map(f) +} + +func (o *identityMonad[A, B]) Chain(f func(A) B) func(A) B { + return Chain(f) +} + +func (o *identityMonad[A, B]) Ap(fa A) func(func(A) B) B { + return Ap[B](fa) +} + +// Monad implements the monadic operations for [Option] +func Monad[A, B any]() monad.Monad[A, B, A, B, func(A) B] { + return &identityMonad[A, B]{} +} diff --git a/v2/identity/types.go b/v2/identity/types.go new file mode 100644 index 0000000..93b6f40 --- /dev/null +++ b/v2/identity/types.go @@ -0,0 +1,20 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package identity + +type ( + Operator[A, B any] = func(A) B +) diff --git a/v2/internal/applicative/testing/law.go b/v2/internal/applicative/testing/law.go new file mode 100644 index 0000000..75a88de --- /dev/null +++ b/v2/internal/applicative/testing/law.go @@ -0,0 +1,283 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package testing + +import ( + "testing" + + E "github.com/IBM/fp-go/v2/eq" + F "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/internal/applicative" + "github.com/IBM/fp-go/v2/internal/apply" + L "github.com/IBM/fp-go/v2/internal/apply/testing" + "github.com/IBM/fp-go/v2/internal/functor" + "github.com/IBM/fp-go/v2/internal/pointed" + "github.com/stretchr/testify/assert" +) + +// Applicative identity law +// +// A.ap(A.of(a => a), fa) <-> fa +// +// Deprecated: use [ApplicativeAssertIdentity] +func AssertIdentity[HKTA, HKTAA, A any](t *testing.T, + eq E.Eq[HKTA], + + fof func(func(A) A) HKTAA, + + fap func(HKTAA, HKTA) HKTA, +) func(fa HKTA) bool { + // mark as test helper + t.Helper() + + return func(fa HKTA) bool { + + left := fap(fof(F.Identity[A]), fa) + right := fa + + return assert.True(t, eq.Equals(left, right), "Applicative identity") + } +} + +// Applicative identity law +// +// A.ap(A.of(a => a), fa) <-> fa +func ApplicativeAssertIdentity[HKTA, HKTFAA, A any](t *testing.T, + eq E.Eq[HKTA], + + ap applicative.Applicative[A, A, HKTA, HKTA, HKTFAA], + paa pointed.Pointed[func(A) A, HKTFAA], + +) func(fa HKTA) bool { + // mark as test helper + t.Helper() + + return func(fa HKTA) bool { + + left := ap.Ap(fa)(paa.Of(F.Identity[A])) + right := fa + + return assert.True(t, eq.Equals(left, right), "Applicative identity") + } +} + +// Applicative homomorphism law +// +// A.ap(A.of(ab), A.of(a)) <-> A.of(ab(a)) +// +// Deprecated: use [ApplicativeAssertHomomorphism] +func AssertHomomorphism[HKTA, HKTB, HKTAB, A, B any](t *testing.T, + eq E.Eq[HKTB], + + fofa func(A) HKTA, + fofb func(B) HKTB, + fofab func(func(A) B) HKTAB, + + fap func(HKTAB, HKTA) HKTB, + + ab func(A) B, +) func(a A) bool { + // mark as test helper + t.Helper() + + return func(a A) bool { + + left := fap(fofab(ab), fofa(a)) + right := fofb(ab(a)) + + return assert.True(t, eq.Equals(left, right), "Applicative homomorphism") + } +} + +// Applicative homomorphism law +// +// A.ap(A.of(ab), A.of(a)) <-> A.of(ab(a)) +func ApplicativeAssertHomomorphism[HKTA, HKTB, HKTFAB, A, B any](t *testing.T, + eq E.Eq[HKTB], + + apab applicative.Applicative[A, B, HKTA, HKTB, HKTFAB], + pb pointed.Pointed[B, HKTB], + pfab pointed.Pointed[func(A) B, HKTFAB], + + ab func(A) B, +) func(a A) bool { + // mark as test helper + t.Helper() + + return func(a A) bool { + + left := apab.Ap(apab.Of(a))(pfab.Of(ab)) + right := pb.Of(ab(a)) + + return assert.True(t, eq.Equals(left, right), "Applicative homomorphism") + } +} + +// Applicative interchange law +// +// A.ap(fab, A.of(a)) <-> A.ap(A.of(ab => ab(a)), fab) +// +// Deprecated: use [ApplicativeAssertInterchange] +func AssertInterchange[HKTA, HKTB, HKTAB, HKTABB, A, B any](t *testing.T, + eq E.Eq[HKTB], + + fofa func(A) HKTA, + fofab func(func(A) B) HKTAB, + fofabb func(func(func(A) B) B) HKTABB, + + fapab func(HKTAB, HKTA) HKTB, + fapabb func(HKTABB, HKTAB) HKTB, + + ab func(A) B, +) func(a A) bool { + // mark as test helper + t.Helper() + + return func(a A) bool { + + fab := fofab(ab) + + left := fapab(fab, fofa(a)) + right := fapabb(fofabb(func(ab func(A) B) B { + return ab(a) + }), fab) + + return assert.True(t, eq.Equals(left, right), "Applicative homomorphism") + } +} + +// Applicative interchange law +// +// A.ap(fab, A.of(a)) <-> A.ap(A.of(ab => ab(a)), fab) +func ApplicativeAssertInterchange[HKTA, HKTB, HKTFAB, HKTABB, A, B any](t *testing.T, + eq E.Eq[HKTB], + + apab applicative.Applicative[A, B, HKTA, HKTB, HKTFAB], + apabb applicative.Applicative[func(A) B, B, HKTFAB, HKTB, HKTABB], + pabb pointed.Pointed[func(func(A) B) B, HKTABB], + + ab func(A) B, +) func(a A) bool { + // mark as test helper + t.Helper() + + return func(a A) bool { + + fab := apabb.Of(ab) + + left := apab.Ap(apab.Of(a))(fab) + + right := apabb.Ap(fab)(pabb.Of(func(ab func(A) B) B { + return ab(a) + })) + + return assert.True(t, eq.Equals(left, right), "Applicative homomorphism") + } +} + +// AssertLaws asserts the apply laws `identity`, `composition`, `associative composition`, 'applicative identity', 'homomorphism', 'interchange' +// +// Deprecated: use [ApplicativeAssertLaws] instead +func AssertLaws[HKTA, HKTB, HKTC, HKTAA, HKTAB, HKTBC, HKTAC, HKTABB, HKTABAC, A, B, C any](t *testing.T, + eqa E.Eq[HKTA], + eqb E.Eq[HKTB], + eqc E.Eq[HKTC], + + fofa func(A) HKTA, + fofb func(B) HKTB, + + fofaa func(func(A) A) HKTAA, + fofab func(func(A) B) HKTAB, + fofbc func(func(B) C) HKTBC, + fofabb func(func(func(A) B) B) HKTABB, + + faa func(HKTA, func(A) A) HKTA, + fab func(HKTA, func(A) B) HKTB, + fac func(HKTA, func(A) C) HKTC, + fbc func(HKTB, func(B) C) HKTC, + + fmap func(HKTBC, func(func(B) C) func(func(A) B) func(A) C) HKTABAC, + + fapaa func(HKTAA, HKTA) HKTA, + fapab func(HKTAB, HKTA) HKTB, + fapbc func(HKTBC, HKTB) HKTC, + fapac func(HKTAC, HKTA) HKTC, + + fapabb func(HKTABB, HKTAB) HKTB, + fapabac func(HKTABAC, HKTAB) HKTAC, + + ab func(A) B, + bc func(B) C, +) func(a A) bool { + // mark as test helper + t.Helper() + + // apply laws + apply := L.AssertLaws(t, eqa, eqc, fofab, fofbc, faa, fab, fac, fbc, fmap, fapab, fapbc, fapac, fapabac, ab, bc) + // applicative laws + identity := AssertIdentity(t, eqa, fofaa, fapaa) + homomorphism := AssertHomomorphism(t, eqb, fofa, fofb, fofab, fapab, ab) + interchange := AssertInterchange(t, eqb, fofa, fofab, fofabb, fapab, fapabb, ab) + + return func(a A) bool { + fa := fofa(a) + return apply(fa) && identity(fa) && homomorphism(a) && interchange(a) + } +} + +// ApplicativeAssertLaws asserts the apply laws `identity`, `composition`, `associative composition`, 'applicative identity', 'homomorphism', 'interchange' +func ApplicativeAssertLaws[HKTA, HKTB, HKTC, HKTAA, HKTAB, HKTBC, HKTAC, HKTABB, HKTABAC, A, B, C any](t *testing.T, + eqa E.Eq[HKTA], + eqb E.Eq[HKTB], + eqc E.Eq[HKTC], + + fofb pointed.Pointed[B, HKTB], + + fofaa pointed.Pointed[func(A) A, HKTAA], + fofbc pointed.Pointed[func(B) C, HKTBC], + + fofabb pointed.Pointed[func(func(A) B) B, HKTABB], + + faa functor.Functor[A, A, HKTA, HKTA], + + fmap functor.Functor[func(B) C, func(func(A) B) func(A) C, HKTBC, HKTABAC], + + fapaa applicative.Applicative[A, A, HKTA, HKTA, HKTAA], + fapab applicative.Applicative[A, B, HKTA, HKTB, HKTAB], + fapbc apply.Apply[B, C, HKTB, HKTC, HKTBC], + fapac apply.Apply[A, C, HKTA, HKTC, HKTAC], + + fapabb applicative.Applicative[func(A) B, B, HKTAB, HKTB, HKTABB], + fapabac applicative.Applicative[func(A) B, func(A) C, HKTAB, HKTAC, HKTABAC], + + ab func(A) B, + bc func(B) C, +) func(a A) bool { + // mark as test helper + t.Helper() + + // apply laws + apply := L.ApplyAssertLaws(t, eqa, eqc, applicative.ToPointed(fapabac), fofbc, faa, fmap, applicative.ToApply(fapab), fapbc, fapac, applicative.ToApply(fapabac), ab, bc) + // applicative laws + identity := ApplicativeAssertIdentity(t, eqa, fapaa, fofaa) + homomorphism := ApplicativeAssertHomomorphism(t, eqb, fapab, fofb, applicative.ToPointed(fapabb), ab) + interchange := ApplicativeAssertInterchange(t, eqb, fapab, fapabb, fofabb, ab) + + return func(a A) bool { + fa := fapaa.Of(a) + return apply(fa) && identity(fa) && homomorphism(a) && interchange(a) + } +} diff --git a/v2/internal/applicative/types.go b/v2/internal/applicative/types.go new file mode 100644 index 0000000..da57eed --- /dev/null +++ b/v2/internal/applicative/types.go @@ -0,0 +1,42 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package applicative + +import ( + "github.com/IBM/fp-go/v2/internal/apply" + "github.com/IBM/fp-go/v2/internal/functor" + "github.com/IBM/fp-go/v2/internal/pointed" +) + +type Applicative[A, B, HKTA, HKTB, HKTFAB any] interface { + apply.Apply[A, B, HKTA, HKTB, HKTFAB] + pointed.Pointed[A, HKTA] +} + +// ToFunctor converts from [Applicative] to [functor.Functor] +func ToFunctor[A, B, HKTA, HKTB, HKTFAB any](ap Applicative[A, B, HKTA, HKTB, HKTFAB]) functor.Functor[A, B, HKTA, HKTB] { + return ap +} + +// ToApply converts from [Applicative] to [apply.Apply] +func ToApply[A, B, HKTA, HKTB, HKTFAB any](ap Applicative[A, B, HKTA, HKTB, HKTFAB]) apply.Apply[A, B, HKTA, HKTB, HKTFAB] { + return ap +} + +// ToPointed converts from [Applicative] to [pointed.Pointed] +func ToPointed[A, B, HKTA, HKTB, HKTFAB any](ap Applicative[A, B, HKTA, HKTB, HKTFAB]) pointed.Pointed[A, HKTA] { + return ap +} diff --git a/v2/internal/apply/ap.go b/v2/internal/apply/ap.go new file mode 100644 index 0000000..ed7800c --- /dev/null +++ b/v2/internal/apply/ap.go @@ -0,0 +1,147 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package apply + +import ( + F "github.com/IBM/fp-go/v2/function" +) + +func MonadAp[HKTGA, HKTGB, HKTGAB, HKTFGAB, HKTFGGAB, HKTFGA, HKTFGB any]( + fap func(HKTFGGAB, HKTFGA) HKTFGB, + fmap func(HKTFGAB, func(HKTGAB) func(HKTGA) HKTGB) HKTFGGAB, + gap func(HKTGAB, HKTGA) HKTGB, + + fab HKTFGAB, + fa HKTFGA) HKTFGB { + + return fap(fmap(fab, F.Bind1st(F.Bind1st[HKTGAB, HKTGA, HKTGB], gap)), fa) +} + +func Ap[HKTGA, HKTGB, HKTGAB, HKTFGAB, HKTFGGAB, HKTFGA, HKTFGB any]( + fap func(HKTFGA) func(HKTFGGAB) HKTFGB, + fmap func(func(HKTGAB) func(HKTGA) HKTGB) func(HKTFGAB) HKTFGGAB, + gap func(HKTGA) func(HKTGAB) HKTGB, + + fa HKTFGA) func(HKTFGAB) HKTFGB { + + return F.Flow2( + fmap(F.Flip(gap)), + fap(fa), + ) +} + +// func Ap[HKTGA, HKTGB, HKTGAB, HKTFGAB, HKTFGGAB, HKTFGA, HKTFGB any]( +// fap func(HKTFGA) func(HKTFGGAB) HKTFGB, +// fmap func(func(HKTGAB) func(HKTGA) HKTGB) func(HKTFGAB) HKTFGGAB, +// gap func(HKTGA) func(HKTGAB) HKTGB, + +// fa HKTFGA) func(HKTFGAB) HKTFGB { + +// return fap(fmap(F.Bind1st(F.Bind1st[HKTGAB, HKTGA, HKTGB], gap)), fa) +// } + +// export function ap( +// F: Apply, +// G: Apply +// ): (fa: HKT>) => (fab: HKT B>>) => HKT> { +// return (fa: HKT>) => (fab: HKT B>>): HKT> => +// F.ap( +// F.map(fab, (gab) => (ga: HKT) => G.ap(gab, ga)), +// fa +// ) +// } + +// function apFirst(A: Apply): (second: HKT) => (first: HKT) => HKT { +// return (second) => (first) => +// A.ap( +// A.map(first, (a) => () => a), +// second +// ) +// } + +// Functor.map: A>(fa: HKT, f: (a: A) => () => A) => HKT A> + +// Apply.ap: (fab: HKT A>, fa: HKT) => HKT + +func MonadApFirst[HKTGA, HKTGB, HKTGBA, A, B any]( + fap func(HKTGBA, HKTGB) HKTGA, + fmap func(HKTGA, func(A) func(B) A) HKTGBA, + + first HKTGA, + second HKTGB, +) HKTGA { + return fap( + fmap(first, F.Constant1[B, A]), + second, + ) +} + +func ApFirst[HKTGA, HKTGB, HKTGBA, A, B any]( + fap func(HKTGBA, HKTGB) HKTGA, + fmap func(HKTGA, func(A) func(B) A) HKTGBA, + + second HKTGB, +) func(HKTGA) HKTGA { + return func(first HKTGA) HKTGA { + return MonadApFirst(fap, fmap, first, second) + } +} + +func MonadApSecond[HKTGA, HKTGB, HKTGBB, A, B any]( + fap func(HKTGBB, HKTGB) HKTGB, + fmap func(HKTGA, func(A) func(B) B) HKTGBB, + + first HKTGA, + second HKTGB, +) HKTGB { + return fap( + fmap(first, F.Constant1[A](F.Identity[B])), + second, + ) +} + +func ApSecond[HKTGA, HKTGB, HKTGBB, A, B any]( + fap func(HKTGBB, HKTGB) HKTGB, + fmap func(HKTGA, func(A) func(B) B) HKTGBB, + + second HKTGB, +) func(HKTGA) HKTGB { + return func(first HKTGA) HKTGB { + return MonadApSecond(fap, fmap, first, second) + } +} + +func MonadApS[S1, S2, B, HKTBGBS2, HKTS1, HKTS2, HKTB any]( + fap func(HKTBGBS2, HKTB) HKTS2, + fmap func(HKTS1, func(S1) func(B) S2) HKTBGBS2, + fa HKTS1, + key func(B) func(S1) S2, + fb HKTB, +) HKTS2 { + return fap(fmap(fa, F.Flip(key)), fb) +} + +func ApS[S1, S2, B, HKTBGBS2, HKTS1, HKTS2, HKTB any]( + fap func(HKTB) func(HKTBGBS2) HKTS2, + fmap func(func(S1) func(B) S2) func(HKTS1) HKTBGBS2, + key func(B) func(S1) S2, + fb HKTB, +) func(HKTS1) HKTS2 { + return F.Flow2( + fmap(F.Flip(key)), + fap(fb), + ) +} diff --git a/v2/internal/apply/doc.go b/v2/internal/apply/doc.go new file mode 100644 index 0000000..a644931 --- /dev/null +++ b/v2/internal/apply/doc.go @@ -0,0 +1,18 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package apply + +//go:generate go run ../.. apply --count 15 --filename gen.go diff --git a/v2/internal/apply/gen.go b/v2/internal/apply/gen.go new file mode 100644 index 0000000..4620c23 --- /dev/null +++ b/v2/internal/apply/gen.go @@ -0,0 +1,3086 @@ +// Code generated by go generate; DO NOT EDIT. +// This file was generated by robots at +// 2025-03-09 23:53:02.3581922 +0100 CET m=+0.002164001 + +package apply + + +import ( + F "github.com/IBM/fp-go/v2/function" + T "github.com/IBM/fp-go/v2/tuple" +) + +// tupleConstructor1 returns a curried version of [T.MakeTuple1] +func tupleConstructor1[T1 any]() func(T1) T.Tuple1[T1] { + return F.Curry1(T.MakeTuple1[T1]) +} + +// SequenceT1 is a utility function used to implement the sequence operation for higher kinded types based only on map and ap. +// The function takes 1 higher higher kinded types and returns a higher kinded type of a [Tuple1] with the resolved values. +func SequenceT1[ + MAP ~func(func(T1) T.Tuple1[T1]) func(HKT_T1) HKT_TUPLE1, + T1, + HKT_T1, // HKT[T1] + HKT_TUPLE1 any, // HKT[Tuple1[T1]] +]( + fmap MAP, + t1 HKT_T1, +) HKT_TUPLE1 { + return F.Pipe1( + t1, + fmap(tupleConstructor1[T1]()), +) +} + +// SequenceTuple1 is a utility function used to implement the sequence operation for higher kinded types based only on map and ap. +// The function takes a [Tuple1] of higher higher kinded types and returns a higher kinded type of a [Tuple1] with the resolved values. +func SequenceTuple1[ + MAP ~func(func(T1) T.Tuple1[T1]) func(HKT_T1) HKT_TUPLE1, + T1, + HKT_T1, // HKT[T1] + HKT_TUPLE1 any, // HKT[Tuple1[T1]] +]( + fmap MAP, + t T.Tuple1[HKT_T1], +) HKT_TUPLE1 { + return F.Pipe1( + t.F1, + fmap(tupleConstructor1[T1]()), +) +} + +// TraverseTuple1 is a utility function used to implement the sequence operation for higher kinded types based only on map and ap. +// The function takes a [Tuple1] of base types and 1 functions that transform these based types into higher higher kinded types. It returns a higher kinded type of a [Tuple1] with the resolved values. +func TraverseTuple1[ + MAP ~func(func(T1) T.Tuple1[T1]) func(HKT_T1) HKT_TUPLE1, + F1 ~func(A1) HKT_T1, + A1, T1, + HKT_T1, // HKT[T1] + HKT_TUPLE1 any, // HKT[Tuple1[T1]] +]( + fmap MAP, + f1 F1, + t T.Tuple1[A1], +) HKT_TUPLE1 { + return F.Pipe1( + f1(t.F1), + fmap(tupleConstructor1[T1]()), +) +} + +// tupleConstructor2 returns a curried version of [T.MakeTuple2] +func tupleConstructor2[T1, T2 any]() func(T1) func(T2) T.Tuple2[T1, T2] { + return F.Curry2(T.MakeTuple2[T1, T2]) +} + +// SequenceT2 is a utility function used to implement the sequence operation for higher kinded types based only on map and ap. +// The function takes 2 higher higher kinded types and returns a higher kinded type of a [Tuple2] with the resolved values. +func SequenceT2[ + MAP ~func(func(T1) func(T2) T.Tuple2[T1, T2]) func(HKT_T1) HKT_F_T2, + AP1 ~func(HKT_T2) func(HKT_F_T2) HKT_TUPLE2, + T1, + T2, + HKT_T1, // HKT[T1] + HKT_T2, // HKT[T2] + HKT_F_T2, // HKT[func(T2) T.Tuple2[T1, T2]] + HKT_TUPLE2 any, // HKT[Tuple2[T1, T2]] +]( + fmap MAP, + fap1 AP1, + t1 HKT_T1, + t2 HKT_T2, +) HKT_TUPLE2 { + return F.Pipe2( + t1, + fmap(tupleConstructor2[T1, T2]()), + fap1(t2), +) +} + +// SequenceTuple2 is a utility function used to implement the sequence operation for higher kinded types based only on map and ap. +// The function takes a [Tuple2] of higher higher kinded types and returns a higher kinded type of a [Tuple2] with the resolved values. +func SequenceTuple2[ + MAP ~func(func(T1) func(T2) T.Tuple2[T1, T2]) func(HKT_T1) HKT_F_T2, + AP1 ~func(HKT_T2) func(HKT_F_T2) HKT_TUPLE2, + T1, + T2, + HKT_T1, // HKT[T1] + HKT_T2, // HKT[T2] + HKT_F_T2, // HKT[func(T2) T.Tuple2[T1, T2]] + HKT_TUPLE2 any, // HKT[Tuple2[T1, T2]] +]( + fmap MAP, + fap1 AP1, + t T.Tuple2[HKT_T1, HKT_T2], +) HKT_TUPLE2 { + return F.Pipe2( + t.F1, + fmap(tupleConstructor2[T1, T2]()), + fap1(t.F2), +) +} + +// TraverseTuple2 is a utility function used to implement the sequence operation for higher kinded types based only on map and ap. +// The function takes a [Tuple2] of base types and 2 functions that transform these based types into higher higher kinded types. It returns a higher kinded type of a [Tuple2] with the resolved values. +func TraverseTuple2[ + MAP ~func(func(T1) func(T2) T.Tuple2[T1, T2]) func(HKT_T1) HKT_F_T2, + AP1 ~func(HKT_T2) func(HKT_F_T2) HKT_TUPLE2, + F1 ~func(A1) HKT_T1, + F2 ~func(A2) HKT_T2, + A1, T1, + A2, T2, + HKT_T1, // HKT[T1] + HKT_T2, // HKT[T2] + HKT_F_T2, // HKT[func(T2) T.Tuple2[T1, T2]] + HKT_TUPLE2 any, // HKT[Tuple2[T1, T2]] +]( + fmap MAP, + fap1 AP1, + f1 F1, + f2 F2, + t T.Tuple2[A1, A2], +) HKT_TUPLE2 { + return F.Pipe2( + f1(t.F1), + fmap(tupleConstructor2[T1, T2]()), + fap1(f2(t.F2)), +) +} + +// tupleConstructor3 returns a curried version of [T.MakeTuple3] +func tupleConstructor3[T1, T2, T3 any]() func(T1) func(T2) func(T3) T.Tuple3[T1, T2, T3] { + return F.Curry3(T.MakeTuple3[T1, T2, T3]) +} + +// SequenceT3 is a utility function used to implement the sequence operation for higher kinded types based only on map and ap. +// The function takes 3 higher higher kinded types and returns a higher kinded type of a [Tuple3] with the resolved values. +func SequenceT3[ + MAP ~func(func(T1) func(T2) func(T3) T.Tuple3[T1, T2, T3]) func(HKT_T1) HKT_F_T2_T3, + AP1 ~func(HKT_T2) func(HKT_F_T2_T3) HKT_F_T3, + AP2 ~func(HKT_T3) func(HKT_F_T3) HKT_TUPLE3, + T1, + T2, + T3, + HKT_T1, // HKT[T1] + HKT_T2, // HKT[T2] + HKT_T3, // HKT[T3] + HKT_F_T2_T3, // HKT[func(T2) func(T3) T.Tuple3[T1, T2, T3]] + HKT_F_T3, // HKT[func(T3) T.Tuple3[T1, T2, T3]] + HKT_TUPLE3 any, // HKT[Tuple3[T1, T2, T3]] +]( + fmap MAP, + fap1 AP1, + fap2 AP2, + t1 HKT_T1, + t2 HKT_T2, + t3 HKT_T3, +) HKT_TUPLE3 { + return F.Pipe3( + t1, + fmap(tupleConstructor3[T1, T2, T3]()), + fap1(t2), + fap2(t3), +) +} + +// SequenceTuple3 is a utility function used to implement the sequence operation for higher kinded types based only on map and ap. +// The function takes a [Tuple3] of higher higher kinded types and returns a higher kinded type of a [Tuple3] with the resolved values. +func SequenceTuple3[ + MAP ~func(func(T1) func(T2) func(T3) T.Tuple3[T1, T2, T3]) func(HKT_T1) HKT_F_T2_T3, + AP1 ~func(HKT_T2) func(HKT_F_T2_T3) HKT_F_T3, + AP2 ~func(HKT_T3) func(HKT_F_T3) HKT_TUPLE3, + T1, + T2, + T3, + HKT_T1, // HKT[T1] + HKT_T2, // HKT[T2] + HKT_T3, // HKT[T3] + HKT_F_T2_T3, // HKT[func(T2) func(T3) T.Tuple3[T1, T2, T3]] + HKT_F_T3, // HKT[func(T3) T.Tuple3[T1, T2, T3]] + HKT_TUPLE3 any, // HKT[Tuple3[T1, T2, T3]] +]( + fmap MAP, + fap1 AP1, + fap2 AP2, + t T.Tuple3[HKT_T1, HKT_T2, HKT_T3], +) HKT_TUPLE3 { + return F.Pipe3( + t.F1, + fmap(tupleConstructor3[T1, T2, T3]()), + fap1(t.F2), + fap2(t.F3), +) +} + +// TraverseTuple3 is a utility function used to implement the sequence operation for higher kinded types based only on map and ap. +// The function takes a [Tuple3] of base types and 3 functions that transform these based types into higher higher kinded types. It returns a higher kinded type of a [Tuple3] with the resolved values. +func TraverseTuple3[ + MAP ~func(func(T1) func(T2) func(T3) T.Tuple3[T1, T2, T3]) func(HKT_T1) HKT_F_T2_T3, + AP1 ~func(HKT_T2) func(HKT_F_T2_T3) HKT_F_T3, + AP2 ~func(HKT_T3) func(HKT_F_T3) HKT_TUPLE3, + F1 ~func(A1) HKT_T1, + F2 ~func(A2) HKT_T2, + F3 ~func(A3) HKT_T3, + A1, T1, + A2, T2, + A3, T3, + HKT_T1, // HKT[T1] + HKT_T2, // HKT[T2] + HKT_T3, // HKT[T3] + HKT_F_T2_T3, // HKT[func(T2) func(T3) T.Tuple3[T1, T2, T3]] + HKT_F_T3, // HKT[func(T3) T.Tuple3[T1, T2, T3]] + HKT_TUPLE3 any, // HKT[Tuple3[T1, T2, T3]] +]( + fmap MAP, + fap1 AP1, + fap2 AP2, + f1 F1, + f2 F2, + f3 F3, + t T.Tuple3[A1, A2, A3], +) HKT_TUPLE3 { + return F.Pipe3( + f1(t.F1), + fmap(tupleConstructor3[T1, T2, T3]()), + fap1(f2(t.F2)), + fap2(f3(t.F3)), +) +} + +// tupleConstructor4 returns a curried version of [T.MakeTuple4] +func tupleConstructor4[T1, T2, T3, T4 any]() func(T1) func(T2) func(T3) func(T4) T.Tuple4[T1, T2, T3, T4] { + return F.Curry4(T.MakeTuple4[T1, T2, T3, T4]) +} + +// SequenceT4 is a utility function used to implement the sequence operation for higher kinded types based only on map and ap. +// The function takes 4 higher higher kinded types and returns a higher kinded type of a [Tuple4] with the resolved values. +func SequenceT4[ + MAP ~func(func(T1) func(T2) func(T3) func(T4) T.Tuple4[T1, T2, T3, T4]) func(HKT_T1) HKT_F_T2_T3_T4, + AP1 ~func(HKT_T2) func(HKT_F_T2_T3_T4) HKT_F_T3_T4, + AP2 ~func(HKT_T3) func(HKT_F_T3_T4) HKT_F_T4, + AP3 ~func(HKT_T4) func(HKT_F_T4) HKT_TUPLE4, + T1, + T2, + T3, + T4, + HKT_T1, // HKT[T1] + HKT_T2, // HKT[T2] + HKT_T3, // HKT[T3] + HKT_T4, // HKT[T4] + HKT_F_T2_T3_T4, // HKT[func(T2) func(T3) func(T4) T.Tuple4[T1, T2, T3, T4]] + HKT_F_T3_T4, // HKT[func(T3) func(T4) T.Tuple4[T1, T2, T3, T4]] + HKT_F_T4, // HKT[func(T4) T.Tuple4[T1, T2, T3, T4]] + HKT_TUPLE4 any, // HKT[Tuple4[T1, T2, T3, T4]] +]( + fmap MAP, + fap1 AP1, + fap2 AP2, + fap3 AP3, + t1 HKT_T1, + t2 HKT_T2, + t3 HKT_T3, + t4 HKT_T4, +) HKT_TUPLE4 { + return F.Pipe4( + t1, + fmap(tupleConstructor4[T1, T2, T3, T4]()), + fap1(t2), + fap2(t3), + fap3(t4), +) +} + +// SequenceTuple4 is a utility function used to implement the sequence operation for higher kinded types based only on map and ap. +// The function takes a [Tuple4] of higher higher kinded types and returns a higher kinded type of a [Tuple4] with the resolved values. +func SequenceTuple4[ + MAP ~func(func(T1) func(T2) func(T3) func(T4) T.Tuple4[T1, T2, T3, T4]) func(HKT_T1) HKT_F_T2_T3_T4, + AP1 ~func(HKT_T2) func(HKT_F_T2_T3_T4) HKT_F_T3_T4, + AP2 ~func(HKT_T3) func(HKT_F_T3_T4) HKT_F_T4, + AP3 ~func(HKT_T4) func(HKT_F_T4) HKT_TUPLE4, + T1, + T2, + T3, + T4, + HKT_T1, // HKT[T1] + HKT_T2, // HKT[T2] + HKT_T3, // HKT[T3] + HKT_T4, // HKT[T4] + HKT_F_T2_T3_T4, // HKT[func(T2) func(T3) func(T4) T.Tuple4[T1, T2, T3, T4]] + HKT_F_T3_T4, // HKT[func(T3) func(T4) T.Tuple4[T1, T2, T3, T4]] + HKT_F_T4, // HKT[func(T4) T.Tuple4[T1, T2, T3, T4]] + HKT_TUPLE4 any, // HKT[Tuple4[T1, T2, T3, T4]] +]( + fmap MAP, + fap1 AP1, + fap2 AP2, + fap3 AP3, + t T.Tuple4[HKT_T1, HKT_T2, HKT_T3, HKT_T4], +) HKT_TUPLE4 { + return F.Pipe4( + t.F1, + fmap(tupleConstructor4[T1, T2, T3, T4]()), + fap1(t.F2), + fap2(t.F3), + fap3(t.F4), +) +} + +// TraverseTuple4 is a utility function used to implement the sequence operation for higher kinded types based only on map and ap. +// The function takes a [Tuple4] of base types and 4 functions that transform these based types into higher higher kinded types. It returns a higher kinded type of a [Tuple4] with the resolved values. +func TraverseTuple4[ + MAP ~func(func(T1) func(T2) func(T3) func(T4) T.Tuple4[T1, T2, T3, T4]) func(HKT_T1) HKT_F_T2_T3_T4, + AP1 ~func(HKT_T2) func(HKT_F_T2_T3_T4) HKT_F_T3_T4, + AP2 ~func(HKT_T3) func(HKT_F_T3_T4) HKT_F_T4, + AP3 ~func(HKT_T4) func(HKT_F_T4) HKT_TUPLE4, + F1 ~func(A1) HKT_T1, + F2 ~func(A2) HKT_T2, + F3 ~func(A3) HKT_T3, + F4 ~func(A4) HKT_T4, + A1, T1, + A2, T2, + A3, T3, + A4, T4, + HKT_T1, // HKT[T1] + HKT_T2, // HKT[T2] + HKT_T3, // HKT[T3] + HKT_T4, // HKT[T4] + HKT_F_T2_T3_T4, // HKT[func(T2) func(T3) func(T4) T.Tuple4[T1, T2, T3, T4]] + HKT_F_T3_T4, // HKT[func(T3) func(T4) T.Tuple4[T1, T2, T3, T4]] + HKT_F_T4, // HKT[func(T4) T.Tuple4[T1, T2, T3, T4]] + HKT_TUPLE4 any, // HKT[Tuple4[T1, T2, T3, T4]] +]( + fmap MAP, + fap1 AP1, + fap2 AP2, + fap3 AP3, + f1 F1, + f2 F2, + f3 F3, + f4 F4, + t T.Tuple4[A1, A2, A3, A4], +) HKT_TUPLE4 { + return F.Pipe4( + f1(t.F1), + fmap(tupleConstructor4[T1, T2, T3, T4]()), + fap1(f2(t.F2)), + fap2(f3(t.F3)), + fap3(f4(t.F4)), +) +} + +// tupleConstructor5 returns a curried version of [T.MakeTuple5] +func tupleConstructor5[T1, T2, T3, T4, T5 any]() func(T1) func(T2) func(T3) func(T4) func(T5) T.Tuple5[T1, T2, T3, T4, T5] { + return F.Curry5(T.MakeTuple5[T1, T2, T3, T4, T5]) +} + +// SequenceT5 is a utility function used to implement the sequence operation for higher kinded types based only on map and ap. +// The function takes 5 higher higher kinded types and returns a higher kinded type of a [Tuple5] with the resolved values. +func SequenceT5[ + MAP ~func(func(T1) func(T2) func(T3) func(T4) func(T5) T.Tuple5[T1, T2, T3, T4, T5]) func(HKT_T1) HKT_F_T2_T3_T4_T5, + AP1 ~func(HKT_T2) func(HKT_F_T2_T3_T4_T5) HKT_F_T3_T4_T5, + AP2 ~func(HKT_T3) func(HKT_F_T3_T4_T5) HKT_F_T4_T5, + AP3 ~func(HKT_T4) func(HKT_F_T4_T5) HKT_F_T5, + AP4 ~func(HKT_T5) func(HKT_F_T5) HKT_TUPLE5, + T1, + T2, + T3, + T4, + T5, + HKT_T1, // HKT[T1] + HKT_T2, // HKT[T2] + HKT_T3, // HKT[T3] + HKT_T4, // HKT[T4] + HKT_T5, // HKT[T5] + HKT_F_T2_T3_T4_T5, // HKT[func(T2) func(T3) func(T4) func(T5) T.Tuple5[T1, T2, T3, T4, T5]] + HKT_F_T3_T4_T5, // HKT[func(T3) func(T4) func(T5) T.Tuple5[T1, T2, T3, T4, T5]] + HKT_F_T4_T5, // HKT[func(T4) func(T5) T.Tuple5[T1, T2, T3, T4, T5]] + HKT_F_T5, // HKT[func(T5) T.Tuple5[T1, T2, T3, T4, T5]] + HKT_TUPLE5 any, // HKT[Tuple5[T1, T2, T3, T4, T5]] +]( + fmap MAP, + fap1 AP1, + fap2 AP2, + fap3 AP3, + fap4 AP4, + t1 HKT_T1, + t2 HKT_T2, + t3 HKT_T3, + t4 HKT_T4, + t5 HKT_T5, +) HKT_TUPLE5 { + return F.Pipe5( + t1, + fmap(tupleConstructor5[T1, T2, T3, T4, T5]()), + fap1(t2), + fap2(t3), + fap3(t4), + fap4(t5), +) +} + +// SequenceTuple5 is a utility function used to implement the sequence operation for higher kinded types based only on map and ap. +// The function takes a [Tuple5] of higher higher kinded types and returns a higher kinded type of a [Tuple5] with the resolved values. +func SequenceTuple5[ + MAP ~func(func(T1) func(T2) func(T3) func(T4) func(T5) T.Tuple5[T1, T2, T3, T4, T5]) func(HKT_T1) HKT_F_T2_T3_T4_T5, + AP1 ~func(HKT_T2) func(HKT_F_T2_T3_T4_T5) HKT_F_T3_T4_T5, + AP2 ~func(HKT_T3) func(HKT_F_T3_T4_T5) HKT_F_T4_T5, + AP3 ~func(HKT_T4) func(HKT_F_T4_T5) HKT_F_T5, + AP4 ~func(HKT_T5) func(HKT_F_T5) HKT_TUPLE5, + T1, + T2, + T3, + T4, + T5, + HKT_T1, // HKT[T1] + HKT_T2, // HKT[T2] + HKT_T3, // HKT[T3] + HKT_T4, // HKT[T4] + HKT_T5, // HKT[T5] + HKT_F_T2_T3_T4_T5, // HKT[func(T2) func(T3) func(T4) func(T5) T.Tuple5[T1, T2, T3, T4, T5]] + HKT_F_T3_T4_T5, // HKT[func(T3) func(T4) func(T5) T.Tuple5[T1, T2, T3, T4, T5]] + HKT_F_T4_T5, // HKT[func(T4) func(T5) T.Tuple5[T1, T2, T3, T4, T5]] + HKT_F_T5, // HKT[func(T5) T.Tuple5[T1, T2, T3, T4, T5]] + HKT_TUPLE5 any, // HKT[Tuple5[T1, T2, T3, T4, T5]] +]( + fmap MAP, + fap1 AP1, + fap2 AP2, + fap3 AP3, + fap4 AP4, + t T.Tuple5[HKT_T1, HKT_T2, HKT_T3, HKT_T4, HKT_T5], +) HKT_TUPLE5 { + return F.Pipe5( + t.F1, + fmap(tupleConstructor5[T1, T2, T3, T4, T5]()), + fap1(t.F2), + fap2(t.F3), + fap3(t.F4), + fap4(t.F5), +) +} + +// TraverseTuple5 is a utility function used to implement the sequence operation for higher kinded types based only on map and ap. +// The function takes a [Tuple5] of base types and 5 functions that transform these based types into higher higher kinded types. It returns a higher kinded type of a [Tuple5] with the resolved values. +func TraverseTuple5[ + MAP ~func(func(T1) func(T2) func(T3) func(T4) func(T5) T.Tuple5[T1, T2, T3, T4, T5]) func(HKT_T1) HKT_F_T2_T3_T4_T5, + AP1 ~func(HKT_T2) func(HKT_F_T2_T3_T4_T5) HKT_F_T3_T4_T5, + AP2 ~func(HKT_T3) func(HKT_F_T3_T4_T5) HKT_F_T4_T5, + AP3 ~func(HKT_T4) func(HKT_F_T4_T5) HKT_F_T5, + AP4 ~func(HKT_T5) func(HKT_F_T5) HKT_TUPLE5, + F1 ~func(A1) HKT_T1, + F2 ~func(A2) HKT_T2, + F3 ~func(A3) HKT_T3, + F4 ~func(A4) HKT_T4, + F5 ~func(A5) HKT_T5, + A1, T1, + A2, T2, + A3, T3, + A4, T4, + A5, T5, + HKT_T1, // HKT[T1] + HKT_T2, // HKT[T2] + HKT_T3, // HKT[T3] + HKT_T4, // HKT[T4] + HKT_T5, // HKT[T5] + HKT_F_T2_T3_T4_T5, // HKT[func(T2) func(T3) func(T4) func(T5) T.Tuple5[T1, T2, T3, T4, T5]] + HKT_F_T3_T4_T5, // HKT[func(T3) func(T4) func(T5) T.Tuple5[T1, T2, T3, T4, T5]] + HKT_F_T4_T5, // HKT[func(T4) func(T5) T.Tuple5[T1, T2, T3, T4, T5]] + HKT_F_T5, // HKT[func(T5) T.Tuple5[T1, T2, T3, T4, T5]] + HKT_TUPLE5 any, // HKT[Tuple5[T1, T2, T3, T4, T5]] +]( + fmap MAP, + fap1 AP1, + fap2 AP2, + fap3 AP3, + fap4 AP4, + f1 F1, + f2 F2, + f3 F3, + f4 F4, + f5 F5, + t T.Tuple5[A1, A2, A3, A4, A5], +) HKT_TUPLE5 { + return F.Pipe5( + f1(t.F1), + fmap(tupleConstructor5[T1, T2, T3, T4, T5]()), + fap1(f2(t.F2)), + fap2(f3(t.F3)), + fap3(f4(t.F4)), + fap4(f5(t.F5)), +) +} + +// tupleConstructor6 returns a curried version of [T.MakeTuple6] +func tupleConstructor6[T1, T2, T3, T4, T5, T6 any]() func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6] { + return F.Curry6(T.MakeTuple6[T1, T2, T3, T4, T5, T6]) +} + +// SequenceT6 is a utility function used to implement the sequence operation for higher kinded types based only on map and ap. +// The function takes 6 higher higher kinded types and returns a higher kinded type of a [Tuple6] with the resolved values. +func SequenceT6[ + MAP ~func(func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6]) func(HKT_T1) HKT_F_T2_T3_T4_T5_T6, + AP1 ~func(HKT_T2) func(HKT_F_T2_T3_T4_T5_T6) HKT_F_T3_T4_T5_T6, + AP2 ~func(HKT_T3) func(HKT_F_T3_T4_T5_T6) HKT_F_T4_T5_T6, + AP3 ~func(HKT_T4) func(HKT_F_T4_T5_T6) HKT_F_T5_T6, + AP4 ~func(HKT_T5) func(HKT_F_T5_T6) HKT_F_T6, + AP5 ~func(HKT_T6) func(HKT_F_T6) HKT_TUPLE6, + T1, + T2, + T3, + T4, + T5, + T6, + HKT_T1, // HKT[T1] + HKT_T2, // HKT[T2] + HKT_T3, // HKT[T3] + HKT_T4, // HKT[T4] + HKT_T5, // HKT[T5] + HKT_T6, // HKT[T6] + HKT_F_T2_T3_T4_T5_T6, // HKT[func(T2) func(T3) func(T4) func(T5) func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6]] + HKT_F_T3_T4_T5_T6, // HKT[func(T3) func(T4) func(T5) func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6]] + HKT_F_T4_T5_T6, // HKT[func(T4) func(T5) func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6]] + HKT_F_T5_T6, // HKT[func(T5) func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6]] + HKT_F_T6, // HKT[func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6]] + HKT_TUPLE6 any, // HKT[Tuple6[T1, T2, T3, T4, T5, T6]] +]( + fmap MAP, + fap1 AP1, + fap2 AP2, + fap3 AP3, + fap4 AP4, + fap5 AP5, + t1 HKT_T1, + t2 HKT_T2, + t3 HKT_T3, + t4 HKT_T4, + t5 HKT_T5, + t6 HKT_T6, +) HKT_TUPLE6 { + return F.Pipe6( + t1, + fmap(tupleConstructor6[T1, T2, T3, T4, T5, T6]()), + fap1(t2), + fap2(t3), + fap3(t4), + fap4(t5), + fap5(t6), +) +} + +// SequenceTuple6 is a utility function used to implement the sequence operation for higher kinded types based only on map and ap. +// The function takes a [Tuple6] of higher higher kinded types and returns a higher kinded type of a [Tuple6] with the resolved values. +func SequenceTuple6[ + MAP ~func(func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6]) func(HKT_T1) HKT_F_T2_T3_T4_T5_T6, + AP1 ~func(HKT_T2) func(HKT_F_T2_T3_T4_T5_T6) HKT_F_T3_T4_T5_T6, + AP2 ~func(HKT_T3) func(HKT_F_T3_T4_T5_T6) HKT_F_T4_T5_T6, + AP3 ~func(HKT_T4) func(HKT_F_T4_T5_T6) HKT_F_T5_T6, + AP4 ~func(HKT_T5) func(HKT_F_T5_T6) HKT_F_T6, + AP5 ~func(HKT_T6) func(HKT_F_T6) HKT_TUPLE6, + T1, + T2, + T3, + T4, + T5, + T6, + HKT_T1, // HKT[T1] + HKT_T2, // HKT[T2] + HKT_T3, // HKT[T3] + HKT_T4, // HKT[T4] + HKT_T5, // HKT[T5] + HKT_T6, // HKT[T6] + HKT_F_T2_T3_T4_T5_T6, // HKT[func(T2) func(T3) func(T4) func(T5) func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6]] + HKT_F_T3_T4_T5_T6, // HKT[func(T3) func(T4) func(T5) func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6]] + HKT_F_T4_T5_T6, // HKT[func(T4) func(T5) func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6]] + HKT_F_T5_T6, // HKT[func(T5) func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6]] + HKT_F_T6, // HKT[func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6]] + HKT_TUPLE6 any, // HKT[Tuple6[T1, T2, T3, T4, T5, T6]] +]( + fmap MAP, + fap1 AP1, + fap2 AP2, + fap3 AP3, + fap4 AP4, + fap5 AP5, + t T.Tuple6[HKT_T1, HKT_T2, HKT_T3, HKT_T4, HKT_T5, HKT_T6], +) HKT_TUPLE6 { + return F.Pipe6( + t.F1, + fmap(tupleConstructor6[T1, T2, T3, T4, T5, T6]()), + fap1(t.F2), + fap2(t.F3), + fap3(t.F4), + fap4(t.F5), + fap5(t.F6), +) +} + +// TraverseTuple6 is a utility function used to implement the sequence operation for higher kinded types based only on map and ap. +// The function takes a [Tuple6] of base types and 6 functions that transform these based types into higher higher kinded types. It returns a higher kinded type of a [Tuple6] with the resolved values. +func TraverseTuple6[ + MAP ~func(func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6]) func(HKT_T1) HKT_F_T2_T3_T4_T5_T6, + AP1 ~func(HKT_T2) func(HKT_F_T2_T3_T4_T5_T6) HKT_F_T3_T4_T5_T6, + AP2 ~func(HKT_T3) func(HKT_F_T3_T4_T5_T6) HKT_F_T4_T5_T6, + AP3 ~func(HKT_T4) func(HKT_F_T4_T5_T6) HKT_F_T5_T6, + AP4 ~func(HKT_T5) func(HKT_F_T5_T6) HKT_F_T6, + AP5 ~func(HKT_T6) func(HKT_F_T6) HKT_TUPLE6, + F1 ~func(A1) HKT_T1, + F2 ~func(A2) HKT_T2, + F3 ~func(A3) HKT_T3, + F4 ~func(A4) HKT_T4, + F5 ~func(A5) HKT_T5, + F6 ~func(A6) HKT_T6, + A1, T1, + A2, T2, + A3, T3, + A4, T4, + A5, T5, + A6, T6, + HKT_T1, // HKT[T1] + HKT_T2, // HKT[T2] + HKT_T3, // HKT[T3] + HKT_T4, // HKT[T4] + HKT_T5, // HKT[T5] + HKT_T6, // HKT[T6] + HKT_F_T2_T3_T4_T5_T6, // HKT[func(T2) func(T3) func(T4) func(T5) func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6]] + HKT_F_T3_T4_T5_T6, // HKT[func(T3) func(T4) func(T5) func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6]] + HKT_F_T4_T5_T6, // HKT[func(T4) func(T5) func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6]] + HKT_F_T5_T6, // HKT[func(T5) func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6]] + HKT_F_T6, // HKT[func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6]] + HKT_TUPLE6 any, // HKT[Tuple6[T1, T2, T3, T4, T5, T6]] +]( + fmap MAP, + fap1 AP1, + fap2 AP2, + fap3 AP3, + fap4 AP4, + fap5 AP5, + f1 F1, + f2 F2, + f3 F3, + f4 F4, + f5 F5, + f6 F6, + t T.Tuple6[A1, A2, A3, A4, A5, A6], +) HKT_TUPLE6 { + return F.Pipe6( + f1(t.F1), + fmap(tupleConstructor6[T1, T2, T3, T4, T5, T6]()), + fap1(f2(t.F2)), + fap2(f3(t.F3)), + fap3(f4(t.F4)), + fap4(f5(t.F5)), + fap5(f6(t.F6)), +) +} + +// tupleConstructor7 returns a curried version of [T.MakeTuple7] +func tupleConstructor7[T1, T2, T3, T4, T5, T6, T7 any]() func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7] { + return F.Curry7(T.MakeTuple7[T1, T2, T3, T4, T5, T6, T7]) +} + +// SequenceT7 is a utility function used to implement the sequence operation for higher kinded types based only on map and ap. +// The function takes 7 higher higher kinded types and returns a higher kinded type of a [Tuple7] with the resolved values. +func SequenceT7[ + MAP ~func(func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7]) func(HKT_T1) HKT_F_T2_T3_T4_T5_T6_T7, + AP1 ~func(HKT_T2) func(HKT_F_T2_T3_T4_T5_T6_T7) HKT_F_T3_T4_T5_T6_T7, + AP2 ~func(HKT_T3) func(HKT_F_T3_T4_T5_T6_T7) HKT_F_T4_T5_T6_T7, + AP3 ~func(HKT_T4) func(HKT_F_T4_T5_T6_T7) HKT_F_T5_T6_T7, + AP4 ~func(HKT_T5) func(HKT_F_T5_T6_T7) HKT_F_T6_T7, + AP5 ~func(HKT_T6) func(HKT_F_T6_T7) HKT_F_T7, + AP6 ~func(HKT_T7) func(HKT_F_T7) HKT_TUPLE7, + T1, + T2, + T3, + T4, + T5, + T6, + T7, + HKT_T1, // HKT[T1] + HKT_T2, // HKT[T2] + HKT_T3, // HKT[T3] + HKT_T4, // HKT[T4] + HKT_T5, // HKT[T5] + HKT_T6, // HKT[T6] + HKT_T7, // HKT[T7] + HKT_F_T2_T3_T4_T5_T6_T7, // HKT[func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7]] + HKT_F_T3_T4_T5_T6_T7, // HKT[func(T3) func(T4) func(T5) func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7]] + HKT_F_T4_T5_T6_T7, // HKT[func(T4) func(T5) func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7]] + HKT_F_T5_T6_T7, // HKT[func(T5) func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7]] + HKT_F_T6_T7, // HKT[func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7]] + HKT_F_T7, // HKT[func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7]] + HKT_TUPLE7 any, // HKT[Tuple7[T1, T2, T3, T4, T5, T6, T7]] +]( + fmap MAP, + fap1 AP1, + fap2 AP2, + fap3 AP3, + fap4 AP4, + fap5 AP5, + fap6 AP6, + t1 HKT_T1, + t2 HKT_T2, + t3 HKT_T3, + t4 HKT_T4, + t5 HKT_T5, + t6 HKT_T6, + t7 HKT_T7, +) HKT_TUPLE7 { + return F.Pipe7( + t1, + fmap(tupleConstructor7[T1, T2, T3, T4, T5, T6, T7]()), + fap1(t2), + fap2(t3), + fap3(t4), + fap4(t5), + fap5(t6), + fap6(t7), +) +} + +// SequenceTuple7 is a utility function used to implement the sequence operation for higher kinded types based only on map and ap. +// The function takes a [Tuple7] of higher higher kinded types and returns a higher kinded type of a [Tuple7] with the resolved values. +func SequenceTuple7[ + MAP ~func(func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7]) func(HKT_T1) HKT_F_T2_T3_T4_T5_T6_T7, + AP1 ~func(HKT_T2) func(HKT_F_T2_T3_T4_T5_T6_T7) HKT_F_T3_T4_T5_T6_T7, + AP2 ~func(HKT_T3) func(HKT_F_T3_T4_T5_T6_T7) HKT_F_T4_T5_T6_T7, + AP3 ~func(HKT_T4) func(HKT_F_T4_T5_T6_T7) HKT_F_T5_T6_T7, + AP4 ~func(HKT_T5) func(HKT_F_T5_T6_T7) HKT_F_T6_T7, + AP5 ~func(HKT_T6) func(HKT_F_T6_T7) HKT_F_T7, + AP6 ~func(HKT_T7) func(HKT_F_T7) HKT_TUPLE7, + T1, + T2, + T3, + T4, + T5, + T6, + T7, + HKT_T1, // HKT[T1] + HKT_T2, // HKT[T2] + HKT_T3, // HKT[T3] + HKT_T4, // HKT[T4] + HKT_T5, // HKT[T5] + HKT_T6, // HKT[T6] + HKT_T7, // HKT[T7] + HKT_F_T2_T3_T4_T5_T6_T7, // HKT[func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7]] + HKT_F_T3_T4_T5_T6_T7, // HKT[func(T3) func(T4) func(T5) func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7]] + HKT_F_T4_T5_T6_T7, // HKT[func(T4) func(T5) func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7]] + HKT_F_T5_T6_T7, // HKT[func(T5) func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7]] + HKT_F_T6_T7, // HKT[func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7]] + HKT_F_T7, // HKT[func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7]] + HKT_TUPLE7 any, // HKT[Tuple7[T1, T2, T3, T4, T5, T6, T7]] +]( + fmap MAP, + fap1 AP1, + fap2 AP2, + fap3 AP3, + fap4 AP4, + fap5 AP5, + fap6 AP6, + t T.Tuple7[HKT_T1, HKT_T2, HKT_T3, HKT_T4, HKT_T5, HKT_T6, HKT_T7], +) HKT_TUPLE7 { + return F.Pipe7( + t.F1, + fmap(tupleConstructor7[T1, T2, T3, T4, T5, T6, T7]()), + fap1(t.F2), + fap2(t.F3), + fap3(t.F4), + fap4(t.F5), + fap5(t.F6), + fap6(t.F7), +) +} + +// TraverseTuple7 is a utility function used to implement the sequence operation for higher kinded types based only on map and ap. +// The function takes a [Tuple7] of base types and 7 functions that transform these based types into higher higher kinded types. It returns a higher kinded type of a [Tuple7] with the resolved values. +func TraverseTuple7[ + MAP ~func(func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7]) func(HKT_T1) HKT_F_T2_T3_T4_T5_T6_T7, + AP1 ~func(HKT_T2) func(HKT_F_T2_T3_T4_T5_T6_T7) HKT_F_T3_T4_T5_T6_T7, + AP2 ~func(HKT_T3) func(HKT_F_T3_T4_T5_T6_T7) HKT_F_T4_T5_T6_T7, + AP3 ~func(HKT_T4) func(HKT_F_T4_T5_T6_T7) HKT_F_T5_T6_T7, + AP4 ~func(HKT_T5) func(HKT_F_T5_T6_T7) HKT_F_T6_T7, + AP5 ~func(HKT_T6) func(HKT_F_T6_T7) HKT_F_T7, + AP6 ~func(HKT_T7) func(HKT_F_T7) HKT_TUPLE7, + F1 ~func(A1) HKT_T1, + F2 ~func(A2) HKT_T2, + F3 ~func(A3) HKT_T3, + F4 ~func(A4) HKT_T4, + F5 ~func(A5) HKT_T5, + F6 ~func(A6) HKT_T6, + F7 ~func(A7) HKT_T7, + A1, T1, + A2, T2, + A3, T3, + A4, T4, + A5, T5, + A6, T6, + A7, T7, + HKT_T1, // HKT[T1] + HKT_T2, // HKT[T2] + HKT_T3, // HKT[T3] + HKT_T4, // HKT[T4] + HKT_T5, // HKT[T5] + HKT_T6, // HKT[T6] + HKT_T7, // HKT[T7] + HKT_F_T2_T3_T4_T5_T6_T7, // HKT[func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7]] + HKT_F_T3_T4_T5_T6_T7, // HKT[func(T3) func(T4) func(T5) func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7]] + HKT_F_T4_T5_T6_T7, // HKT[func(T4) func(T5) func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7]] + HKT_F_T5_T6_T7, // HKT[func(T5) func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7]] + HKT_F_T6_T7, // HKT[func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7]] + HKT_F_T7, // HKT[func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7]] + HKT_TUPLE7 any, // HKT[Tuple7[T1, T2, T3, T4, T5, T6, T7]] +]( + fmap MAP, + fap1 AP1, + fap2 AP2, + fap3 AP3, + fap4 AP4, + fap5 AP5, + fap6 AP6, + f1 F1, + f2 F2, + f3 F3, + f4 F4, + f5 F5, + f6 F6, + f7 F7, + t T.Tuple7[A1, A2, A3, A4, A5, A6, A7], +) HKT_TUPLE7 { + return F.Pipe7( + f1(t.F1), + fmap(tupleConstructor7[T1, T2, T3, T4, T5, T6, T7]()), + fap1(f2(t.F2)), + fap2(f3(t.F3)), + fap3(f4(t.F4)), + fap4(f5(t.F5)), + fap5(f6(t.F6)), + fap6(f7(t.F7)), +) +} + +// tupleConstructor8 returns a curried version of [T.MakeTuple8] +func tupleConstructor8[T1, T2, T3, T4, T5, T6, T7, T8 any]() func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8] { + return F.Curry8(T.MakeTuple8[T1, T2, T3, T4, T5, T6, T7, T8]) +} + +// SequenceT8 is a utility function used to implement the sequence operation for higher kinded types based only on map and ap. +// The function takes 8 higher higher kinded types and returns a higher kinded type of a [Tuple8] with the resolved values. +func SequenceT8[ + MAP ~func(func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]) func(HKT_T1) HKT_F_T2_T3_T4_T5_T6_T7_T8, + AP1 ~func(HKT_T2) func(HKT_F_T2_T3_T4_T5_T6_T7_T8) HKT_F_T3_T4_T5_T6_T7_T8, + AP2 ~func(HKT_T3) func(HKT_F_T3_T4_T5_T6_T7_T8) HKT_F_T4_T5_T6_T7_T8, + AP3 ~func(HKT_T4) func(HKT_F_T4_T5_T6_T7_T8) HKT_F_T5_T6_T7_T8, + AP4 ~func(HKT_T5) func(HKT_F_T5_T6_T7_T8) HKT_F_T6_T7_T8, + AP5 ~func(HKT_T6) func(HKT_F_T6_T7_T8) HKT_F_T7_T8, + AP6 ~func(HKT_T7) func(HKT_F_T7_T8) HKT_F_T8, + AP7 ~func(HKT_T8) func(HKT_F_T8) HKT_TUPLE8, + T1, + T2, + T3, + T4, + T5, + T6, + T7, + T8, + HKT_T1, // HKT[T1] + HKT_T2, // HKT[T2] + HKT_T3, // HKT[T3] + HKT_T4, // HKT[T4] + HKT_T5, // HKT[T5] + HKT_T6, // HKT[T6] + HKT_T7, // HKT[T7] + HKT_T8, // HKT[T8] + HKT_F_T2_T3_T4_T5_T6_T7_T8, // HKT[func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] + HKT_F_T3_T4_T5_T6_T7_T8, // HKT[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] + HKT_F_T4_T5_T6_T7_T8, // HKT[func(T4) func(T5) func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] + HKT_F_T5_T6_T7_T8, // HKT[func(T5) func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] + HKT_F_T6_T7_T8, // HKT[func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] + HKT_F_T7_T8, // HKT[func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] + HKT_F_T8, // HKT[func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] + HKT_TUPLE8 any, // HKT[Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] +]( + fmap MAP, + fap1 AP1, + fap2 AP2, + fap3 AP3, + fap4 AP4, + fap5 AP5, + fap6 AP6, + fap7 AP7, + t1 HKT_T1, + t2 HKT_T2, + t3 HKT_T3, + t4 HKT_T4, + t5 HKT_T5, + t6 HKT_T6, + t7 HKT_T7, + t8 HKT_T8, +) HKT_TUPLE8 { + return F.Pipe8( + t1, + fmap(tupleConstructor8[T1, T2, T3, T4, T5, T6, T7, T8]()), + fap1(t2), + fap2(t3), + fap3(t4), + fap4(t5), + fap5(t6), + fap6(t7), + fap7(t8), +) +} + +// SequenceTuple8 is a utility function used to implement the sequence operation for higher kinded types based only on map and ap. +// The function takes a [Tuple8] of higher higher kinded types and returns a higher kinded type of a [Tuple8] with the resolved values. +func SequenceTuple8[ + MAP ~func(func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]) func(HKT_T1) HKT_F_T2_T3_T4_T5_T6_T7_T8, + AP1 ~func(HKT_T2) func(HKT_F_T2_T3_T4_T5_T6_T7_T8) HKT_F_T3_T4_T5_T6_T7_T8, + AP2 ~func(HKT_T3) func(HKT_F_T3_T4_T5_T6_T7_T8) HKT_F_T4_T5_T6_T7_T8, + AP3 ~func(HKT_T4) func(HKT_F_T4_T5_T6_T7_T8) HKT_F_T5_T6_T7_T8, + AP4 ~func(HKT_T5) func(HKT_F_T5_T6_T7_T8) HKT_F_T6_T7_T8, + AP5 ~func(HKT_T6) func(HKT_F_T6_T7_T8) HKT_F_T7_T8, + AP6 ~func(HKT_T7) func(HKT_F_T7_T8) HKT_F_T8, + AP7 ~func(HKT_T8) func(HKT_F_T8) HKT_TUPLE8, + T1, + T2, + T3, + T4, + T5, + T6, + T7, + T8, + HKT_T1, // HKT[T1] + HKT_T2, // HKT[T2] + HKT_T3, // HKT[T3] + HKT_T4, // HKT[T4] + HKT_T5, // HKT[T5] + HKT_T6, // HKT[T6] + HKT_T7, // HKT[T7] + HKT_T8, // HKT[T8] + HKT_F_T2_T3_T4_T5_T6_T7_T8, // HKT[func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] + HKT_F_T3_T4_T5_T6_T7_T8, // HKT[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] + HKT_F_T4_T5_T6_T7_T8, // HKT[func(T4) func(T5) func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] + HKT_F_T5_T6_T7_T8, // HKT[func(T5) func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] + HKT_F_T6_T7_T8, // HKT[func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] + HKT_F_T7_T8, // HKT[func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] + HKT_F_T8, // HKT[func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] + HKT_TUPLE8 any, // HKT[Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] +]( + fmap MAP, + fap1 AP1, + fap2 AP2, + fap3 AP3, + fap4 AP4, + fap5 AP5, + fap6 AP6, + fap7 AP7, + t T.Tuple8[HKT_T1, HKT_T2, HKT_T3, HKT_T4, HKT_T5, HKT_T6, HKT_T7, HKT_T8], +) HKT_TUPLE8 { + return F.Pipe8( + t.F1, + fmap(tupleConstructor8[T1, T2, T3, T4, T5, T6, T7, T8]()), + fap1(t.F2), + fap2(t.F3), + fap3(t.F4), + fap4(t.F5), + fap5(t.F6), + fap6(t.F7), + fap7(t.F8), +) +} + +// TraverseTuple8 is a utility function used to implement the sequence operation for higher kinded types based only on map and ap. +// The function takes a [Tuple8] of base types and 8 functions that transform these based types into higher higher kinded types. It returns a higher kinded type of a [Tuple8] with the resolved values. +func TraverseTuple8[ + MAP ~func(func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]) func(HKT_T1) HKT_F_T2_T3_T4_T5_T6_T7_T8, + AP1 ~func(HKT_T2) func(HKT_F_T2_T3_T4_T5_T6_T7_T8) HKT_F_T3_T4_T5_T6_T7_T8, + AP2 ~func(HKT_T3) func(HKT_F_T3_T4_T5_T6_T7_T8) HKT_F_T4_T5_T6_T7_T8, + AP3 ~func(HKT_T4) func(HKT_F_T4_T5_T6_T7_T8) HKT_F_T5_T6_T7_T8, + AP4 ~func(HKT_T5) func(HKT_F_T5_T6_T7_T8) HKT_F_T6_T7_T8, + AP5 ~func(HKT_T6) func(HKT_F_T6_T7_T8) HKT_F_T7_T8, + AP6 ~func(HKT_T7) func(HKT_F_T7_T8) HKT_F_T8, + AP7 ~func(HKT_T8) func(HKT_F_T8) HKT_TUPLE8, + F1 ~func(A1) HKT_T1, + F2 ~func(A2) HKT_T2, + F3 ~func(A3) HKT_T3, + F4 ~func(A4) HKT_T4, + F5 ~func(A5) HKT_T5, + F6 ~func(A6) HKT_T6, + F7 ~func(A7) HKT_T7, + F8 ~func(A8) HKT_T8, + A1, T1, + A2, T2, + A3, T3, + A4, T4, + A5, T5, + A6, T6, + A7, T7, + A8, T8, + HKT_T1, // HKT[T1] + HKT_T2, // HKT[T2] + HKT_T3, // HKT[T3] + HKT_T4, // HKT[T4] + HKT_T5, // HKT[T5] + HKT_T6, // HKT[T6] + HKT_T7, // HKT[T7] + HKT_T8, // HKT[T8] + HKT_F_T2_T3_T4_T5_T6_T7_T8, // HKT[func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] + HKT_F_T3_T4_T5_T6_T7_T8, // HKT[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] + HKT_F_T4_T5_T6_T7_T8, // HKT[func(T4) func(T5) func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] + HKT_F_T5_T6_T7_T8, // HKT[func(T5) func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] + HKT_F_T6_T7_T8, // HKT[func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] + HKT_F_T7_T8, // HKT[func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] + HKT_F_T8, // HKT[func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] + HKT_TUPLE8 any, // HKT[Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] +]( + fmap MAP, + fap1 AP1, + fap2 AP2, + fap3 AP3, + fap4 AP4, + fap5 AP5, + fap6 AP6, + fap7 AP7, + f1 F1, + f2 F2, + f3 F3, + f4 F4, + f5 F5, + f6 F6, + f7 F7, + f8 F8, + t T.Tuple8[A1, A2, A3, A4, A5, A6, A7, A8], +) HKT_TUPLE8 { + return F.Pipe8( + f1(t.F1), + fmap(tupleConstructor8[T1, T2, T3, T4, T5, T6, T7, T8]()), + fap1(f2(t.F2)), + fap2(f3(t.F3)), + fap3(f4(t.F4)), + fap4(f5(t.F5)), + fap5(f6(t.F6)), + fap6(f7(t.F7)), + fap7(f8(t.F8)), +) +} + +// tupleConstructor9 returns a curried version of [T.MakeTuple9] +func tupleConstructor9[T1, T2, T3, T4, T5, T6, T7, T8, T9 any]() func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9] { + return F.Curry9(T.MakeTuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]) +} + +// SequenceT9 is a utility function used to implement the sequence operation for higher kinded types based only on map and ap. +// The function takes 9 higher higher kinded types and returns a higher kinded type of a [Tuple9] with the resolved values. +func SequenceT9[ + MAP ~func(func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]) func(HKT_T1) HKT_F_T2_T3_T4_T5_T6_T7_T8_T9, + AP1 ~func(HKT_T2) func(HKT_F_T2_T3_T4_T5_T6_T7_T8_T9) HKT_F_T3_T4_T5_T6_T7_T8_T9, + AP2 ~func(HKT_T3) func(HKT_F_T3_T4_T5_T6_T7_T8_T9) HKT_F_T4_T5_T6_T7_T8_T9, + AP3 ~func(HKT_T4) func(HKT_F_T4_T5_T6_T7_T8_T9) HKT_F_T5_T6_T7_T8_T9, + AP4 ~func(HKT_T5) func(HKT_F_T5_T6_T7_T8_T9) HKT_F_T6_T7_T8_T9, + AP5 ~func(HKT_T6) func(HKT_F_T6_T7_T8_T9) HKT_F_T7_T8_T9, + AP6 ~func(HKT_T7) func(HKT_F_T7_T8_T9) HKT_F_T8_T9, + AP7 ~func(HKT_T8) func(HKT_F_T8_T9) HKT_F_T9, + AP8 ~func(HKT_T9) func(HKT_F_T9) HKT_TUPLE9, + T1, + T2, + T3, + T4, + T5, + T6, + T7, + T8, + T9, + HKT_T1, // HKT[T1] + HKT_T2, // HKT[T2] + HKT_T3, // HKT[T3] + HKT_T4, // HKT[T4] + HKT_T5, // HKT[T5] + HKT_T6, // HKT[T6] + HKT_T7, // HKT[T7] + HKT_T8, // HKT[T8] + HKT_T9, // HKT[T9] + HKT_F_T2_T3_T4_T5_T6_T7_T8_T9, // HKT[func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] + HKT_F_T3_T4_T5_T6_T7_T8_T9, // HKT[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] + HKT_F_T4_T5_T6_T7_T8_T9, // HKT[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] + HKT_F_T5_T6_T7_T8_T9, // HKT[func(T5) func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] + HKT_F_T6_T7_T8_T9, // HKT[func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] + HKT_F_T7_T8_T9, // HKT[func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] + HKT_F_T8_T9, // HKT[func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] + HKT_F_T9, // HKT[func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] + HKT_TUPLE9 any, // HKT[Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] +]( + fmap MAP, + fap1 AP1, + fap2 AP2, + fap3 AP3, + fap4 AP4, + fap5 AP5, + fap6 AP6, + fap7 AP7, + fap8 AP8, + t1 HKT_T1, + t2 HKT_T2, + t3 HKT_T3, + t4 HKT_T4, + t5 HKT_T5, + t6 HKT_T6, + t7 HKT_T7, + t8 HKT_T8, + t9 HKT_T9, +) HKT_TUPLE9 { + return F.Pipe9( + t1, + fmap(tupleConstructor9[T1, T2, T3, T4, T5, T6, T7, T8, T9]()), + fap1(t2), + fap2(t3), + fap3(t4), + fap4(t5), + fap5(t6), + fap6(t7), + fap7(t8), + fap8(t9), +) +} + +// SequenceTuple9 is a utility function used to implement the sequence operation for higher kinded types based only on map and ap. +// The function takes a [Tuple9] of higher higher kinded types and returns a higher kinded type of a [Tuple9] with the resolved values. +func SequenceTuple9[ + MAP ~func(func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]) func(HKT_T1) HKT_F_T2_T3_T4_T5_T6_T7_T8_T9, + AP1 ~func(HKT_T2) func(HKT_F_T2_T3_T4_T5_T6_T7_T8_T9) HKT_F_T3_T4_T5_T6_T7_T8_T9, + AP2 ~func(HKT_T3) func(HKT_F_T3_T4_T5_T6_T7_T8_T9) HKT_F_T4_T5_T6_T7_T8_T9, + AP3 ~func(HKT_T4) func(HKT_F_T4_T5_T6_T7_T8_T9) HKT_F_T5_T6_T7_T8_T9, + AP4 ~func(HKT_T5) func(HKT_F_T5_T6_T7_T8_T9) HKT_F_T6_T7_T8_T9, + AP5 ~func(HKT_T6) func(HKT_F_T6_T7_T8_T9) HKT_F_T7_T8_T9, + AP6 ~func(HKT_T7) func(HKT_F_T7_T8_T9) HKT_F_T8_T9, + AP7 ~func(HKT_T8) func(HKT_F_T8_T9) HKT_F_T9, + AP8 ~func(HKT_T9) func(HKT_F_T9) HKT_TUPLE9, + T1, + T2, + T3, + T4, + T5, + T6, + T7, + T8, + T9, + HKT_T1, // HKT[T1] + HKT_T2, // HKT[T2] + HKT_T3, // HKT[T3] + HKT_T4, // HKT[T4] + HKT_T5, // HKT[T5] + HKT_T6, // HKT[T6] + HKT_T7, // HKT[T7] + HKT_T8, // HKT[T8] + HKT_T9, // HKT[T9] + HKT_F_T2_T3_T4_T5_T6_T7_T8_T9, // HKT[func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] + HKT_F_T3_T4_T5_T6_T7_T8_T9, // HKT[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] + HKT_F_T4_T5_T6_T7_T8_T9, // HKT[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] + HKT_F_T5_T6_T7_T8_T9, // HKT[func(T5) func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] + HKT_F_T6_T7_T8_T9, // HKT[func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] + HKT_F_T7_T8_T9, // HKT[func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] + HKT_F_T8_T9, // HKT[func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] + HKT_F_T9, // HKT[func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] + HKT_TUPLE9 any, // HKT[Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] +]( + fmap MAP, + fap1 AP1, + fap2 AP2, + fap3 AP3, + fap4 AP4, + fap5 AP5, + fap6 AP6, + fap7 AP7, + fap8 AP8, + t T.Tuple9[HKT_T1, HKT_T2, HKT_T3, HKT_T4, HKT_T5, HKT_T6, HKT_T7, HKT_T8, HKT_T9], +) HKT_TUPLE9 { + return F.Pipe9( + t.F1, + fmap(tupleConstructor9[T1, T2, T3, T4, T5, T6, T7, T8, T9]()), + fap1(t.F2), + fap2(t.F3), + fap3(t.F4), + fap4(t.F5), + fap5(t.F6), + fap6(t.F7), + fap7(t.F8), + fap8(t.F9), +) +} + +// TraverseTuple9 is a utility function used to implement the sequence operation for higher kinded types based only on map and ap. +// The function takes a [Tuple9] of base types and 9 functions that transform these based types into higher higher kinded types. It returns a higher kinded type of a [Tuple9] with the resolved values. +func TraverseTuple9[ + MAP ~func(func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]) func(HKT_T1) HKT_F_T2_T3_T4_T5_T6_T7_T8_T9, + AP1 ~func(HKT_T2) func(HKT_F_T2_T3_T4_T5_T6_T7_T8_T9) HKT_F_T3_T4_T5_T6_T7_T8_T9, + AP2 ~func(HKT_T3) func(HKT_F_T3_T4_T5_T6_T7_T8_T9) HKT_F_T4_T5_T6_T7_T8_T9, + AP3 ~func(HKT_T4) func(HKT_F_T4_T5_T6_T7_T8_T9) HKT_F_T5_T6_T7_T8_T9, + AP4 ~func(HKT_T5) func(HKT_F_T5_T6_T7_T8_T9) HKT_F_T6_T7_T8_T9, + AP5 ~func(HKT_T6) func(HKT_F_T6_T7_T8_T9) HKT_F_T7_T8_T9, + AP6 ~func(HKT_T7) func(HKT_F_T7_T8_T9) HKT_F_T8_T9, + AP7 ~func(HKT_T8) func(HKT_F_T8_T9) HKT_F_T9, + AP8 ~func(HKT_T9) func(HKT_F_T9) HKT_TUPLE9, + F1 ~func(A1) HKT_T1, + F2 ~func(A2) HKT_T2, + F3 ~func(A3) HKT_T3, + F4 ~func(A4) HKT_T4, + F5 ~func(A5) HKT_T5, + F6 ~func(A6) HKT_T6, + F7 ~func(A7) HKT_T7, + F8 ~func(A8) HKT_T8, + F9 ~func(A9) HKT_T9, + A1, T1, + A2, T2, + A3, T3, + A4, T4, + A5, T5, + A6, T6, + A7, T7, + A8, T8, + A9, T9, + HKT_T1, // HKT[T1] + HKT_T2, // HKT[T2] + HKT_T3, // HKT[T3] + HKT_T4, // HKT[T4] + HKT_T5, // HKT[T5] + HKT_T6, // HKT[T6] + HKT_T7, // HKT[T7] + HKT_T8, // HKT[T8] + HKT_T9, // HKT[T9] + HKT_F_T2_T3_T4_T5_T6_T7_T8_T9, // HKT[func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] + HKT_F_T3_T4_T5_T6_T7_T8_T9, // HKT[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] + HKT_F_T4_T5_T6_T7_T8_T9, // HKT[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] + HKT_F_T5_T6_T7_T8_T9, // HKT[func(T5) func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] + HKT_F_T6_T7_T8_T9, // HKT[func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] + HKT_F_T7_T8_T9, // HKT[func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] + HKT_F_T8_T9, // HKT[func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] + HKT_F_T9, // HKT[func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] + HKT_TUPLE9 any, // HKT[Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] +]( + fmap MAP, + fap1 AP1, + fap2 AP2, + fap3 AP3, + fap4 AP4, + fap5 AP5, + fap6 AP6, + fap7 AP7, + fap8 AP8, + f1 F1, + f2 F2, + f3 F3, + f4 F4, + f5 F5, + f6 F6, + f7 F7, + f8 F8, + f9 F9, + t T.Tuple9[A1, A2, A3, A4, A5, A6, A7, A8, A9], +) HKT_TUPLE9 { + return F.Pipe9( + f1(t.F1), + fmap(tupleConstructor9[T1, T2, T3, T4, T5, T6, T7, T8, T9]()), + fap1(f2(t.F2)), + fap2(f3(t.F3)), + fap3(f4(t.F4)), + fap4(f5(t.F5)), + fap5(f6(t.F6)), + fap6(f7(t.F7)), + fap7(f8(t.F8)), + fap8(f9(t.F9)), +) +} + +// tupleConstructor10 returns a curried version of [T.MakeTuple10] +func tupleConstructor10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any]() func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10] { + return F.Curry10(T.MakeTuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]) +} + +// SequenceT10 is a utility function used to implement the sequence operation for higher kinded types based only on map and ap. +// The function takes 10 higher higher kinded types and returns a higher kinded type of a [Tuple10] with the resolved values. +func SequenceT10[ + MAP ~func(func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]) func(HKT_T1) HKT_F_T2_T3_T4_T5_T6_T7_T8_T9_T10, + AP1 ~func(HKT_T2) func(HKT_F_T2_T3_T4_T5_T6_T7_T8_T9_T10) HKT_F_T3_T4_T5_T6_T7_T8_T9_T10, + AP2 ~func(HKT_T3) func(HKT_F_T3_T4_T5_T6_T7_T8_T9_T10) HKT_F_T4_T5_T6_T7_T8_T9_T10, + AP3 ~func(HKT_T4) func(HKT_F_T4_T5_T6_T7_T8_T9_T10) HKT_F_T5_T6_T7_T8_T9_T10, + AP4 ~func(HKT_T5) func(HKT_F_T5_T6_T7_T8_T9_T10) HKT_F_T6_T7_T8_T9_T10, + AP5 ~func(HKT_T6) func(HKT_F_T6_T7_T8_T9_T10) HKT_F_T7_T8_T9_T10, + AP6 ~func(HKT_T7) func(HKT_F_T7_T8_T9_T10) HKT_F_T8_T9_T10, + AP7 ~func(HKT_T8) func(HKT_F_T8_T9_T10) HKT_F_T9_T10, + AP8 ~func(HKT_T9) func(HKT_F_T9_T10) HKT_F_T10, + AP9 ~func(HKT_T10) func(HKT_F_T10) HKT_TUPLE10, + T1, + T2, + T3, + T4, + T5, + T6, + T7, + T8, + T9, + T10, + HKT_T1, // HKT[T1] + HKT_T2, // HKT[T2] + HKT_T3, // HKT[T3] + HKT_T4, // HKT[T4] + HKT_T5, // HKT[T5] + HKT_T6, // HKT[T6] + HKT_T7, // HKT[T7] + HKT_T8, // HKT[T8] + HKT_T9, // HKT[T9] + HKT_T10, // HKT[T10] + HKT_F_T2_T3_T4_T5_T6_T7_T8_T9_T10, // HKT[func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] + HKT_F_T3_T4_T5_T6_T7_T8_T9_T10, // HKT[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] + HKT_F_T4_T5_T6_T7_T8_T9_T10, // HKT[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] + HKT_F_T5_T6_T7_T8_T9_T10, // HKT[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] + HKT_F_T6_T7_T8_T9_T10, // HKT[func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] + HKT_F_T7_T8_T9_T10, // HKT[func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] + HKT_F_T8_T9_T10, // HKT[func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] + HKT_F_T9_T10, // HKT[func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] + HKT_F_T10, // HKT[func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] + HKT_TUPLE10 any, // HKT[Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] +]( + fmap MAP, + fap1 AP1, + fap2 AP2, + fap3 AP3, + fap4 AP4, + fap5 AP5, + fap6 AP6, + fap7 AP7, + fap8 AP8, + fap9 AP9, + t1 HKT_T1, + t2 HKT_T2, + t3 HKT_T3, + t4 HKT_T4, + t5 HKT_T5, + t6 HKT_T6, + t7 HKT_T7, + t8 HKT_T8, + t9 HKT_T9, + t10 HKT_T10, +) HKT_TUPLE10 { + return F.Pipe10( + t1, + fmap(tupleConstructor10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]()), + fap1(t2), + fap2(t3), + fap3(t4), + fap4(t5), + fap5(t6), + fap6(t7), + fap7(t8), + fap8(t9), + fap9(t10), +) +} + +// SequenceTuple10 is a utility function used to implement the sequence operation for higher kinded types based only on map and ap. +// The function takes a [Tuple10] of higher higher kinded types and returns a higher kinded type of a [Tuple10] with the resolved values. +func SequenceTuple10[ + MAP ~func(func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]) func(HKT_T1) HKT_F_T2_T3_T4_T5_T6_T7_T8_T9_T10, + AP1 ~func(HKT_T2) func(HKT_F_T2_T3_T4_T5_T6_T7_T8_T9_T10) HKT_F_T3_T4_T5_T6_T7_T8_T9_T10, + AP2 ~func(HKT_T3) func(HKT_F_T3_T4_T5_T6_T7_T8_T9_T10) HKT_F_T4_T5_T6_T7_T8_T9_T10, + AP3 ~func(HKT_T4) func(HKT_F_T4_T5_T6_T7_T8_T9_T10) HKT_F_T5_T6_T7_T8_T9_T10, + AP4 ~func(HKT_T5) func(HKT_F_T5_T6_T7_T8_T9_T10) HKT_F_T6_T7_T8_T9_T10, + AP5 ~func(HKT_T6) func(HKT_F_T6_T7_T8_T9_T10) HKT_F_T7_T8_T9_T10, + AP6 ~func(HKT_T7) func(HKT_F_T7_T8_T9_T10) HKT_F_T8_T9_T10, + AP7 ~func(HKT_T8) func(HKT_F_T8_T9_T10) HKT_F_T9_T10, + AP8 ~func(HKT_T9) func(HKT_F_T9_T10) HKT_F_T10, + AP9 ~func(HKT_T10) func(HKT_F_T10) HKT_TUPLE10, + T1, + T2, + T3, + T4, + T5, + T6, + T7, + T8, + T9, + T10, + HKT_T1, // HKT[T1] + HKT_T2, // HKT[T2] + HKT_T3, // HKT[T3] + HKT_T4, // HKT[T4] + HKT_T5, // HKT[T5] + HKT_T6, // HKT[T6] + HKT_T7, // HKT[T7] + HKT_T8, // HKT[T8] + HKT_T9, // HKT[T9] + HKT_T10, // HKT[T10] + HKT_F_T2_T3_T4_T5_T6_T7_T8_T9_T10, // HKT[func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] + HKT_F_T3_T4_T5_T6_T7_T8_T9_T10, // HKT[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] + HKT_F_T4_T5_T6_T7_T8_T9_T10, // HKT[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] + HKT_F_T5_T6_T7_T8_T9_T10, // HKT[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] + HKT_F_T6_T7_T8_T9_T10, // HKT[func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] + HKT_F_T7_T8_T9_T10, // HKT[func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] + HKT_F_T8_T9_T10, // HKT[func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] + HKT_F_T9_T10, // HKT[func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] + HKT_F_T10, // HKT[func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] + HKT_TUPLE10 any, // HKT[Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] +]( + fmap MAP, + fap1 AP1, + fap2 AP2, + fap3 AP3, + fap4 AP4, + fap5 AP5, + fap6 AP6, + fap7 AP7, + fap8 AP8, + fap9 AP9, + t T.Tuple10[HKT_T1, HKT_T2, HKT_T3, HKT_T4, HKT_T5, HKT_T6, HKT_T7, HKT_T8, HKT_T9, HKT_T10], +) HKT_TUPLE10 { + return F.Pipe10( + t.F1, + fmap(tupleConstructor10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]()), + fap1(t.F2), + fap2(t.F3), + fap3(t.F4), + fap4(t.F5), + fap5(t.F6), + fap6(t.F7), + fap7(t.F8), + fap8(t.F9), + fap9(t.F10), +) +} + +// TraverseTuple10 is a utility function used to implement the sequence operation for higher kinded types based only on map and ap. +// The function takes a [Tuple10] of base types and 10 functions that transform these based types into higher higher kinded types. It returns a higher kinded type of a [Tuple10] with the resolved values. +func TraverseTuple10[ + MAP ~func(func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]) func(HKT_T1) HKT_F_T2_T3_T4_T5_T6_T7_T8_T9_T10, + AP1 ~func(HKT_T2) func(HKT_F_T2_T3_T4_T5_T6_T7_T8_T9_T10) HKT_F_T3_T4_T5_T6_T7_T8_T9_T10, + AP2 ~func(HKT_T3) func(HKT_F_T3_T4_T5_T6_T7_T8_T9_T10) HKT_F_T4_T5_T6_T7_T8_T9_T10, + AP3 ~func(HKT_T4) func(HKT_F_T4_T5_T6_T7_T8_T9_T10) HKT_F_T5_T6_T7_T8_T9_T10, + AP4 ~func(HKT_T5) func(HKT_F_T5_T6_T7_T8_T9_T10) HKT_F_T6_T7_T8_T9_T10, + AP5 ~func(HKT_T6) func(HKT_F_T6_T7_T8_T9_T10) HKT_F_T7_T8_T9_T10, + AP6 ~func(HKT_T7) func(HKT_F_T7_T8_T9_T10) HKT_F_T8_T9_T10, + AP7 ~func(HKT_T8) func(HKT_F_T8_T9_T10) HKT_F_T9_T10, + AP8 ~func(HKT_T9) func(HKT_F_T9_T10) HKT_F_T10, + AP9 ~func(HKT_T10) func(HKT_F_T10) HKT_TUPLE10, + F1 ~func(A1) HKT_T1, + F2 ~func(A2) HKT_T2, + F3 ~func(A3) HKT_T3, + F4 ~func(A4) HKT_T4, + F5 ~func(A5) HKT_T5, + F6 ~func(A6) HKT_T6, + F7 ~func(A7) HKT_T7, + F8 ~func(A8) HKT_T8, + F9 ~func(A9) HKT_T9, + F10 ~func(A10) HKT_T10, + A1, T1, + A2, T2, + A3, T3, + A4, T4, + A5, T5, + A6, T6, + A7, T7, + A8, T8, + A9, T9, + A10, T10, + HKT_T1, // HKT[T1] + HKT_T2, // HKT[T2] + HKT_T3, // HKT[T3] + HKT_T4, // HKT[T4] + HKT_T5, // HKT[T5] + HKT_T6, // HKT[T6] + HKT_T7, // HKT[T7] + HKT_T8, // HKT[T8] + HKT_T9, // HKT[T9] + HKT_T10, // HKT[T10] + HKT_F_T2_T3_T4_T5_T6_T7_T8_T9_T10, // HKT[func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] + HKT_F_T3_T4_T5_T6_T7_T8_T9_T10, // HKT[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] + HKT_F_T4_T5_T6_T7_T8_T9_T10, // HKT[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] + HKT_F_T5_T6_T7_T8_T9_T10, // HKT[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] + HKT_F_T6_T7_T8_T9_T10, // HKT[func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] + HKT_F_T7_T8_T9_T10, // HKT[func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] + HKT_F_T8_T9_T10, // HKT[func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] + HKT_F_T9_T10, // HKT[func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] + HKT_F_T10, // HKT[func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] + HKT_TUPLE10 any, // HKT[Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] +]( + fmap MAP, + fap1 AP1, + fap2 AP2, + fap3 AP3, + fap4 AP4, + fap5 AP5, + fap6 AP6, + fap7 AP7, + fap8 AP8, + fap9 AP9, + f1 F1, + f2 F2, + f3 F3, + f4 F4, + f5 F5, + f6 F6, + f7 F7, + f8 F8, + f9 F9, + f10 F10, + t T.Tuple10[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10], +) HKT_TUPLE10 { + return F.Pipe10( + f1(t.F1), + fmap(tupleConstructor10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]()), + fap1(f2(t.F2)), + fap2(f3(t.F3)), + fap3(f4(t.F4)), + fap4(f5(t.F5)), + fap5(f6(t.F6)), + fap6(f7(t.F7)), + fap7(f8(t.F8)), + fap8(f9(t.F9)), + fap9(f10(t.F10)), +) +} + +// tupleConstructor11 returns a curried version of [T.MakeTuple11] +func tupleConstructor11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11 any]() func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11] { + return F.Curry11(T.MakeTuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]) +} + +// SequenceT11 is a utility function used to implement the sequence operation for higher kinded types based only on map and ap. +// The function takes 11 higher higher kinded types and returns a higher kinded type of a [Tuple11] with the resolved values. +func SequenceT11[ + MAP ~func(func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]) func(HKT_T1) HKT_F_T2_T3_T4_T5_T6_T7_T8_T9_T10_T11, + AP1 ~func(HKT_T2) func(HKT_F_T2_T3_T4_T5_T6_T7_T8_T9_T10_T11) HKT_F_T3_T4_T5_T6_T7_T8_T9_T10_T11, + AP2 ~func(HKT_T3) func(HKT_F_T3_T4_T5_T6_T7_T8_T9_T10_T11) HKT_F_T4_T5_T6_T7_T8_T9_T10_T11, + AP3 ~func(HKT_T4) func(HKT_F_T4_T5_T6_T7_T8_T9_T10_T11) HKT_F_T5_T6_T7_T8_T9_T10_T11, + AP4 ~func(HKT_T5) func(HKT_F_T5_T6_T7_T8_T9_T10_T11) HKT_F_T6_T7_T8_T9_T10_T11, + AP5 ~func(HKT_T6) func(HKT_F_T6_T7_T8_T9_T10_T11) HKT_F_T7_T8_T9_T10_T11, + AP6 ~func(HKT_T7) func(HKT_F_T7_T8_T9_T10_T11) HKT_F_T8_T9_T10_T11, + AP7 ~func(HKT_T8) func(HKT_F_T8_T9_T10_T11) HKT_F_T9_T10_T11, + AP8 ~func(HKT_T9) func(HKT_F_T9_T10_T11) HKT_F_T10_T11, + AP9 ~func(HKT_T10) func(HKT_F_T10_T11) HKT_F_T11, + AP10 ~func(HKT_T11) func(HKT_F_T11) HKT_TUPLE11, + T1, + T2, + T3, + T4, + T5, + T6, + T7, + T8, + T9, + T10, + T11, + HKT_T1, // HKT[T1] + HKT_T2, // HKT[T2] + HKT_T3, // HKT[T3] + HKT_T4, // HKT[T4] + HKT_T5, // HKT[T5] + HKT_T6, // HKT[T6] + HKT_T7, // HKT[T7] + HKT_T8, // HKT[T8] + HKT_T9, // HKT[T9] + HKT_T10, // HKT[T10] + HKT_T11, // HKT[T11] + HKT_F_T2_T3_T4_T5_T6_T7_T8_T9_T10_T11, // HKT[func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]] + HKT_F_T3_T4_T5_T6_T7_T8_T9_T10_T11, // HKT[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]] + HKT_F_T4_T5_T6_T7_T8_T9_T10_T11, // HKT[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]] + HKT_F_T5_T6_T7_T8_T9_T10_T11, // HKT[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]] + HKT_F_T6_T7_T8_T9_T10_T11, // HKT[func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]] + HKT_F_T7_T8_T9_T10_T11, // HKT[func(T7) func(T8) func(T9) func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]] + HKT_F_T8_T9_T10_T11, // HKT[func(T8) func(T9) func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]] + HKT_F_T9_T10_T11, // HKT[func(T9) func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]] + HKT_F_T10_T11, // HKT[func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]] + HKT_F_T11, // HKT[func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]] + HKT_TUPLE11 any, // HKT[Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]] +]( + fmap MAP, + fap1 AP1, + fap2 AP2, + fap3 AP3, + fap4 AP4, + fap5 AP5, + fap6 AP6, + fap7 AP7, + fap8 AP8, + fap9 AP9, + fap10 AP10, + t1 HKT_T1, + t2 HKT_T2, + t3 HKT_T3, + t4 HKT_T4, + t5 HKT_T5, + t6 HKT_T6, + t7 HKT_T7, + t8 HKT_T8, + t9 HKT_T9, + t10 HKT_T10, + t11 HKT_T11, +) HKT_TUPLE11 { + return F.Pipe11( + t1, + fmap(tupleConstructor11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]()), + fap1(t2), + fap2(t3), + fap3(t4), + fap4(t5), + fap5(t6), + fap6(t7), + fap7(t8), + fap8(t9), + fap9(t10), + fap10(t11), +) +} + +// SequenceTuple11 is a utility function used to implement the sequence operation for higher kinded types based only on map and ap. +// The function takes a [Tuple11] of higher higher kinded types and returns a higher kinded type of a [Tuple11] with the resolved values. +func SequenceTuple11[ + MAP ~func(func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]) func(HKT_T1) HKT_F_T2_T3_T4_T5_T6_T7_T8_T9_T10_T11, + AP1 ~func(HKT_T2) func(HKT_F_T2_T3_T4_T5_T6_T7_T8_T9_T10_T11) HKT_F_T3_T4_T5_T6_T7_T8_T9_T10_T11, + AP2 ~func(HKT_T3) func(HKT_F_T3_T4_T5_T6_T7_T8_T9_T10_T11) HKT_F_T4_T5_T6_T7_T8_T9_T10_T11, + AP3 ~func(HKT_T4) func(HKT_F_T4_T5_T6_T7_T8_T9_T10_T11) HKT_F_T5_T6_T7_T8_T9_T10_T11, + AP4 ~func(HKT_T5) func(HKT_F_T5_T6_T7_T8_T9_T10_T11) HKT_F_T6_T7_T8_T9_T10_T11, + AP5 ~func(HKT_T6) func(HKT_F_T6_T7_T8_T9_T10_T11) HKT_F_T7_T8_T9_T10_T11, + AP6 ~func(HKT_T7) func(HKT_F_T7_T8_T9_T10_T11) HKT_F_T8_T9_T10_T11, + AP7 ~func(HKT_T8) func(HKT_F_T8_T9_T10_T11) HKT_F_T9_T10_T11, + AP8 ~func(HKT_T9) func(HKT_F_T9_T10_T11) HKT_F_T10_T11, + AP9 ~func(HKT_T10) func(HKT_F_T10_T11) HKT_F_T11, + AP10 ~func(HKT_T11) func(HKT_F_T11) HKT_TUPLE11, + T1, + T2, + T3, + T4, + T5, + T6, + T7, + T8, + T9, + T10, + T11, + HKT_T1, // HKT[T1] + HKT_T2, // HKT[T2] + HKT_T3, // HKT[T3] + HKT_T4, // HKT[T4] + HKT_T5, // HKT[T5] + HKT_T6, // HKT[T6] + HKT_T7, // HKT[T7] + HKT_T8, // HKT[T8] + HKT_T9, // HKT[T9] + HKT_T10, // HKT[T10] + HKT_T11, // HKT[T11] + HKT_F_T2_T3_T4_T5_T6_T7_T8_T9_T10_T11, // HKT[func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]] + HKT_F_T3_T4_T5_T6_T7_T8_T9_T10_T11, // HKT[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]] + HKT_F_T4_T5_T6_T7_T8_T9_T10_T11, // HKT[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]] + HKT_F_T5_T6_T7_T8_T9_T10_T11, // HKT[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]] + HKT_F_T6_T7_T8_T9_T10_T11, // HKT[func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]] + HKT_F_T7_T8_T9_T10_T11, // HKT[func(T7) func(T8) func(T9) func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]] + HKT_F_T8_T9_T10_T11, // HKT[func(T8) func(T9) func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]] + HKT_F_T9_T10_T11, // HKT[func(T9) func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]] + HKT_F_T10_T11, // HKT[func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]] + HKT_F_T11, // HKT[func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]] + HKT_TUPLE11 any, // HKT[Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]] +]( + fmap MAP, + fap1 AP1, + fap2 AP2, + fap3 AP3, + fap4 AP4, + fap5 AP5, + fap6 AP6, + fap7 AP7, + fap8 AP8, + fap9 AP9, + fap10 AP10, + t T.Tuple11[HKT_T1, HKT_T2, HKT_T3, HKT_T4, HKT_T5, HKT_T6, HKT_T7, HKT_T8, HKT_T9, HKT_T10, HKT_T11], +) HKT_TUPLE11 { + return F.Pipe11( + t.F1, + fmap(tupleConstructor11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]()), + fap1(t.F2), + fap2(t.F3), + fap3(t.F4), + fap4(t.F5), + fap5(t.F6), + fap6(t.F7), + fap7(t.F8), + fap8(t.F9), + fap9(t.F10), + fap10(t.F11), +) +} + +// TraverseTuple11 is a utility function used to implement the sequence operation for higher kinded types based only on map and ap. +// The function takes a [Tuple11] of base types and 11 functions that transform these based types into higher higher kinded types. It returns a higher kinded type of a [Tuple11] with the resolved values. +func TraverseTuple11[ + MAP ~func(func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]) func(HKT_T1) HKT_F_T2_T3_T4_T5_T6_T7_T8_T9_T10_T11, + AP1 ~func(HKT_T2) func(HKT_F_T2_T3_T4_T5_T6_T7_T8_T9_T10_T11) HKT_F_T3_T4_T5_T6_T7_T8_T9_T10_T11, + AP2 ~func(HKT_T3) func(HKT_F_T3_T4_T5_T6_T7_T8_T9_T10_T11) HKT_F_T4_T5_T6_T7_T8_T9_T10_T11, + AP3 ~func(HKT_T4) func(HKT_F_T4_T5_T6_T7_T8_T9_T10_T11) HKT_F_T5_T6_T7_T8_T9_T10_T11, + AP4 ~func(HKT_T5) func(HKT_F_T5_T6_T7_T8_T9_T10_T11) HKT_F_T6_T7_T8_T9_T10_T11, + AP5 ~func(HKT_T6) func(HKT_F_T6_T7_T8_T9_T10_T11) HKT_F_T7_T8_T9_T10_T11, + AP6 ~func(HKT_T7) func(HKT_F_T7_T8_T9_T10_T11) HKT_F_T8_T9_T10_T11, + AP7 ~func(HKT_T8) func(HKT_F_T8_T9_T10_T11) HKT_F_T9_T10_T11, + AP8 ~func(HKT_T9) func(HKT_F_T9_T10_T11) HKT_F_T10_T11, + AP9 ~func(HKT_T10) func(HKT_F_T10_T11) HKT_F_T11, + AP10 ~func(HKT_T11) func(HKT_F_T11) HKT_TUPLE11, + F1 ~func(A1) HKT_T1, + F2 ~func(A2) HKT_T2, + F3 ~func(A3) HKT_T3, + F4 ~func(A4) HKT_T4, + F5 ~func(A5) HKT_T5, + F6 ~func(A6) HKT_T6, + F7 ~func(A7) HKT_T7, + F8 ~func(A8) HKT_T8, + F9 ~func(A9) HKT_T9, + F10 ~func(A10) HKT_T10, + F11 ~func(A11) HKT_T11, + A1, T1, + A2, T2, + A3, T3, + A4, T4, + A5, T5, + A6, T6, + A7, T7, + A8, T8, + A9, T9, + A10, T10, + A11, T11, + HKT_T1, // HKT[T1] + HKT_T2, // HKT[T2] + HKT_T3, // HKT[T3] + HKT_T4, // HKT[T4] + HKT_T5, // HKT[T5] + HKT_T6, // HKT[T6] + HKT_T7, // HKT[T7] + HKT_T8, // HKT[T8] + HKT_T9, // HKT[T9] + HKT_T10, // HKT[T10] + HKT_T11, // HKT[T11] + HKT_F_T2_T3_T4_T5_T6_T7_T8_T9_T10_T11, // HKT[func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]] + HKT_F_T3_T4_T5_T6_T7_T8_T9_T10_T11, // HKT[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]] + HKT_F_T4_T5_T6_T7_T8_T9_T10_T11, // HKT[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]] + HKT_F_T5_T6_T7_T8_T9_T10_T11, // HKT[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]] + HKT_F_T6_T7_T8_T9_T10_T11, // HKT[func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]] + HKT_F_T7_T8_T9_T10_T11, // HKT[func(T7) func(T8) func(T9) func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]] + HKT_F_T8_T9_T10_T11, // HKT[func(T8) func(T9) func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]] + HKT_F_T9_T10_T11, // HKT[func(T9) func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]] + HKT_F_T10_T11, // HKT[func(T10) func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]] + HKT_F_T11, // HKT[func(T11) T.Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]] + HKT_TUPLE11 any, // HKT[Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]] +]( + fmap MAP, + fap1 AP1, + fap2 AP2, + fap3 AP3, + fap4 AP4, + fap5 AP5, + fap6 AP6, + fap7 AP7, + fap8 AP8, + fap9 AP9, + fap10 AP10, + f1 F1, + f2 F2, + f3 F3, + f4 F4, + f5 F5, + f6 F6, + f7 F7, + f8 F8, + f9 F9, + f10 F10, + f11 F11, + t T.Tuple11[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11], +) HKT_TUPLE11 { + return F.Pipe11( + f1(t.F1), + fmap(tupleConstructor11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]()), + fap1(f2(t.F2)), + fap2(f3(t.F3)), + fap3(f4(t.F4)), + fap4(f5(t.F5)), + fap5(f6(t.F6)), + fap6(f7(t.F7)), + fap7(f8(t.F8)), + fap8(f9(t.F9)), + fap9(f10(t.F10)), + fap10(f11(t.F11)), +) +} + +// tupleConstructor12 returns a curried version of [T.MakeTuple12] +func tupleConstructor12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12 any]() func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12] { + return F.Curry12(T.MakeTuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]) +} + +// SequenceT12 is a utility function used to implement the sequence operation for higher kinded types based only on map and ap. +// The function takes 12 higher higher kinded types and returns a higher kinded type of a [Tuple12] with the resolved values. +func SequenceT12[ + MAP ~func(func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]) func(HKT_T1) HKT_F_T2_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12, + AP1 ~func(HKT_T2) func(HKT_F_T2_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12) HKT_F_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12, + AP2 ~func(HKT_T3) func(HKT_F_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12) HKT_F_T4_T5_T6_T7_T8_T9_T10_T11_T12, + AP3 ~func(HKT_T4) func(HKT_F_T4_T5_T6_T7_T8_T9_T10_T11_T12) HKT_F_T5_T6_T7_T8_T9_T10_T11_T12, + AP4 ~func(HKT_T5) func(HKT_F_T5_T6_T7_T8_T9_T10_T11_T12) HKT_F_T6_T7_T8_T9_T10_T11_T12, + AP5 ~func(HKT_T6) func(HKT_F_T6_T7_T8_T9_T10_T11_T12) HKT_F_T7_T8_T9_T10_T11_T12, + AP6 ~func(HKT_T7) func(HKT_F_T7_T8_T9_T10_T11_T12) HKT_F_T8_T9_T10_T11_T12, + AP7 ~func(HKT_T8) func(HKT_F_T8_T9_T10_T11_T12) HKT_F_T9_T10_T11_T12, + AP8 ~func(HKT_T9) func(HKT_F_T9_T10_T11_T12) HKT_F_T10_T11_T12, + AP9 ~func(HKT_T10) func(HKT_F_T10_T11_T12) HKT_F_T11_T12, + AP10 ~func(HKT_T11) func(HKT_F_T11_T12) HKT_F_T12, + AP11 ~func(HKT_T12) func(HKT_F_T12) HKT_TUPLE12, + T1, + T2, + T3, + T4, + T5, + T6, + T7, + T8, + T9, + T10, + T11, + T12, + HKT_T1, // HKT[T1] + HKT_T2, // HKT[T2] + HKT_T3, // HKT[T3] + HKT_T4, // HKT[T4] + HKT_T5, // HKT[T5] + HKT_T6, // HKT[T6] + HKT_T7, // HKT[T7] + HKT_T8, // HKT[T8] + HKT_T9, // HKT[T9] + HKT_T10, // HKT[T10] + HKT_T11, // HKT[T11] + HKT_T12, // HKT[T12] + HKT_F_T2_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12, // HKT[func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]] + HKT_F_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12, // HKT[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]] + HKT_F_T4_T5_T6_T7_T8_T9_T10_T11_T12, // HKT[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]] + HKT_F_T5_T6_T7_T8_T9_T10_T11_T12, // HKT[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]] + HKT_F_T6_T7_T8_T9_T10_T11_T12, // HKT[func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]] + HKT_F_T7_T8_T9_T10_T11_T12, // HKT[func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]] + HKT_F_T8_T9_T10_T11_T12, // HKT[func(T8) func(T9) func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]] + HKT_F_T9_T10_T11_T12, // HKT[func(T9) func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]] + HKT_F_T10_T11_T12, // HKT[func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]] + HKT_F_T11_T12, // HKT[func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]] + HKT_F_T12, // HKT[func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]] + HKT_TUPLE12 any, // HKT[Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]] +]( + fmap MAP, + fap1 AP1, + fap2 AP2, + fap3 AP3, + fap4 AP4, + fap5 AP5, + fap6 AP6, + fap7 AP7, + fap8 AP8, + fap9 AP9, + fap10 AP10, + fap11 AP11, + t1 HKT_T1, + t2 HKT_T2, + t3 HKT_T3, + t4 HKT_T4, + t5 HKT_T5, + t6 HKT_T6, + t7 HKT_T7, + t8 HKT_T8, + t9 HKT_T9, + t10 HKT_T10, + t11 HKT_T11, + t12 HKT_T12, +) HKT_TUPLE12 { + return F.Pipe12( + t1, + fmap(tupleConstructor12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]()), + fap1(t2), + fap2(t3), + fap3(t4), + fap4(t5), + fap5(t6), + fap6(t7), + fap7(t8), + fap8(t9), + fap9(t10), + fap10(t11), + fap11(t12), +) +} + +// SequenceTuple12 is a utility function used to implement the sequence operation for higher kinded types based only on map and ap. +// The function takes a [Tuple12] of higher higher kinded types and returns a higher kinded type of a [Tuple12] with the resolved values. +func SequenceTuple12[ + MAP ~func(func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]) func(HKT_T1) HKT_F_T2_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12, + AP1 ~func(HKT_T2) func(HKT_F_T2_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12) HKT_F_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12, + AP2 ~func(HKT_T3) func(HKT_F_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12) HKT_F_T4_T5_T6_T7_T8_T9_T10_T11_T12, + AP3 ~func(HKT_T4) func(HKT_F_T4_T5_T6_T7_T8_T9_T10_T11_T12) HKT_F_T5_T6_T7_T8_T9_T10_T11_T12, + AP4 ~func(HKT_T5) func(HKT_F_T5_T6_T7_T8_T9_T10_T11_T12) HKT_F_T6_T7_T8_T9_T10_T11_T12, + AP5 ~func(HKT_T6) func(HKT_F_T6_T7_T8_T9_T10_T11_T12) HKT_F_T7_T8_T9_T10_T11_T12, + AP6 ~func(HKT_T7) func(HKT_F_T7_T8_T9_T10_T11_T12) HKT_F_T8_T9_T10_T11_T12, + AP7 ~func(HKT_T8) func(HKT_F_T8_T9_T10_T11_T12) HKT_F_T9_T10_T11_T12, + AP8 ~func(HKT_T9) func(HKT_F_T9_T10_T11_T12) HKT_F_T10_T11_T12, + AP9 ~func(HKT_T10) func(HKT_F_T10_T11_T12) HKT_F_T11_T12, + AP10 ~func(HKT_T11) func(HKT_F_T11_T12) HKT_F_T12, + AP11 ~func(HKT_T12) func(HKT_F_T12) HKT_TUPLE12, + T1, + T2, + T3, + T4, + T5, + T6, + T7, + T8, + T9, + T10, + T11, + T12, + HKT_T1, // HKT[T1] + HKT_T2, // HKT[T2] + HKT_T3, // HKT[T3] + HKT_T4, // HKT[T4] + HKT_T5, // HKT[T5] + HKT_T6, // HKT[T6] + HKT_T7, // HKT[T7] + HKT_T8, // HKT[T8] + HKT_T9, // HKT[T9] + HKT_T10, // HKT[T10] + HKT_T11, // HKT[T11] + HKT_T12, // HKT[T12] + HKT_F_T2_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12, // HKT[func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]] + HKT_F_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12, // HKT[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]] + HKT_F_T4_T5_T6_T7_T8_T9_T10_T11_T12, // HKT[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]] + HKT_F_T5_T6_T7_T8_T9_T10_T11_T12, // HKT[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]] + HKT_F_T6_T7_T8_T9_T10_T11_T12, // HKT[func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]] + HKT_F_T7_T8_T9_T10_T11_T12, // HKT[func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]] + HKT_F_T8_T9_T10_T11_T12, // HKT[func(T8) func(T9) func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]] + HKT_F_T9_T10_T11_T12, // HKT[func(T9) func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]] + HKT_F_T10_T11_T12, // HKT[func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]] + HKT_F_T11_T12, // HKT[func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]] + HKT_F_T12, // HKT[func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]] + HKT_TUPLE12 any, // HKT[Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]] +]( + fmap MAP, + fap1 AP1, + fap2 AP2, + fap3 AP3, + fap4 AP4, + fap5 AP5, + fap6 AP6, + fap7 AP7, + fap8 AP8, + fap9 AP9, + fap10 AP10, + fap11 AP11, + t T.Tuple12[HKT_T1, HKT_T2, HKT_T3, HKT_T4, HKT_T5, HKT_T6, HKT_T7, HKT_T8, HKT_T9, HKT_T10, HKT_T11, HKT_T12], +) HKT_TUPLE12 { + return F.Pipe12( + t.F1, + fmap(tupleConstructor12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]()), + fap1(t.F2), + fap2(t.F3), + fap3(t.F4), + fap4(t.F5), + fap5(t.F6), + fap6(t.F7), + fap7(t.F8), + fap8(t.F9), + fap9(t.F10), + fap10(t.F11), + fap11(t.F12), +) +} + +// TraverseTuple12 is a utility function used to implement the sequence operation for higher kinded types based only on map and ap. +// The function takes a [Tuple12] of base types and 12 functions that transform these based types into higher higher kinded types. It returns a higher kinded type of a [Tuple12] with the resolved values. +func TraverseTuple12[ + MAP ~func(func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]) func(HKT_T1) HKT_F_T2_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12, + AP1 ~func(HKT_T2) func(HKT_F_T2_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12) HKT_F_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12, + AP2 ~func(HKT_T3) func(HKT_F_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12) HKT_F_T4_T5_T6_T7_T8_T9_T10_T11_T12, + AP3 ~func(HKT_T4) func(HKT_F_T4_T5_T6_T7_T8_T9_T10_T11_T12) HKT_F_T5_T6_T7_T8_T9_T10_T11_T12, + AP4 ~func(HKT_T5) func(HKT_F_T5_T6_T7_T8_T9_T10_T11_T12) HKT_F_T6_T7_T8_T9_T10_T11_T12, + AP5 ~func(HKT_T6) func(HKT_F_T6_T7_T8_T9_T10_T11_T12) HKT_F_T7_T8_T9_T10_T11_T12, + AP6 ~func(HKT_T7) func(HKT_F_T7_T8_T9_T10_T11_T12) HKT_F_T8_T9_T10_T11_T12, + AP7 ~func(HKT_T8) func(HKT_F_T8_T9_T10_T11_T12) HKT_F_T9_T10_T11_T12, + AP8 ~func(HKT_T9) func(HKT_F_T9_T10_T11_T12) HKT_F_T10_T11_T12, + AP9 ~func(HKT_T10) func(HKT_F_T10_T11_T12) HKT_F_T11_T12, + AP10 ~func(HKT_T11) func(HKT_F_T11_T12) HKT_F_T12, + AP11 ~func(HKT_T12) func(HKT_F_T12) HKT_TUPLE12, + F1 ~func(A1) HKT_T1, + F2 ~func(A2) HKT_T2, + F3 ~func(A3) HKT_T3, + F4 ~func(A4) HKT_T4, + F5 ~func(A5) HKT_T5, + F6 ~func(A6) HKT_T6, + F7 ~func(A7) HKT_T7, + F8 ~func(A8) HKT_T8, + F9 ~func(A9) HKT_T9, + F10 ~func(A10) HKT_T10, + F11 ~func(A11) HKT_T11, + F12 ~func(A12) HKT_T12, + A1, T1, + A2, T2, + A3, T3, + A4, T4, + A5, T5, + A6, T6, + A7, T7, + A8, T8, + A9, T9, + A10, T10, + A11, T11, + A12, T12, + HKT_T1, // HKT[T1] + HKT_T2, // HKT[T2] + HKT_T3, // HKT[T3] + HKT_T4, // HKT[T4] + HKT_T5, // HKT[T5] + HKT_T6, // HKT[T6] + HKT_T7, // HKT[T7] + HKT_T8, // HKT[T8] + HKT_T9, // HKT[T9] + HKT_T10, // HKT[T10] + HKT_T11, // HKT[T11] + HKT_T12, // HKT[T12] + HKT_F_T2_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12, // HKT[func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]] + HKT_F_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12, // HKT[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]] + HKT_F_T4_T5_T6_T7_T8_T9_T10_T11_T12, // HKT[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]] + HKT_F_T5_T6_T7_T8_T9_T10_T11_T12, // HKT[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]] + HKT_F_T6_T7_T8_T9_T10_T11_T12, // HKT[func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]] + HKT_F_T7_T8_T9_T10_T11_T12, // HKT[func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]] + HKT_F_T8_T9_T10_T11_T12, // HKT[func(T8) func(T9) func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]] + HKT_F_T9_T10_T11_T12, // HKT[func(T9) func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]] + HKT_F_T10_T11_T12, // HKT[func(T10) func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]] + HKT_F_T11_T12, // HKT[func(T11) func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]] + HKT_F_T12, // HKT[func(T12) T.Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]] + HKT_TUPLE12 any, // HKT[Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]] +]( + fmap MAP, + fap1 AP1, + fap2 AP2, + fap3 AP3, + fap4 AP4, + fap5 AP5, + fap6 AP6, + fap7 AP7, + fap8 AP8, + fap9 AP9, + fap10 AP10, + fap11 AP11, + f1 F1, + f2 F2, + f3 F3, + f4 F4, + f5 F5, + f6 F6, + f7 F7, + f8 F8, + f9 F9, + f10 F10, + f11 F11, + f12 F12, + t T.Tuple12[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12], +) HKT_TUPLE12 { + return F.Pipe12( + f1(t.F1), + fmap(tupleConstructor12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]()), + fap1(f2(t.F2)), + fap2(f3(t.F3)), + fap3(f4(t.F4)), + fap4(f5(t.F5)), + fap5(f6(t.F6)), + fap6(f7(t.F7)), + fap7(f8(t.F8)), + fap8(f9(t.F9)), + fap9(f10(t.F10)), + fap10(f11(t.F11)), + fap11(f12(t.F12)), +) +} + +// tupleConstructor13 returns a curried version of [T.MakeTuple13] +func tupleConstructor13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13 any]() func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13] { + return F.Curry13(T.MakeTuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]) +} + +// SequenceT13 is a utility function used to implement the sequence operation for higher kinded types based only on map and ap. +// The function takes 13 higher higher kinded types and returns a higher kinded type of a [Tuple13] with the resolved values. +func SequenceT13[ + MAP ~func(func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]) func(HKT_T1) HKT_F_T2_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13, + AP1 ~func(HKT_T2) func(HKT_F_T2_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13) HKT_F_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13, + AP2 ~func(HKT_T3) func(HKT_F_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13) HKT_F_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13, + AP3 ~func(HKT_T4) func(HKT_F_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13) HKT_F_T5_T6_T7_T8_T9_T10_T11_T12_T13, + AP4 ~func(HKT_T5) func(HKT_F_T5_T6_T7_T8_T9_T10_T11_T12_T13) HKT_F_T6_T7_T8_T9_T10_T11_T12_T13, + AP5 ~func(HKT_T6) func(HKT_F_T6_T7_T8_T9_T10_T11_T12_T13) HKT_F_T7_T8_T9_T10_T11_T12_T13, + AP6 ~func(HKT_T7) func(HKT_F_T7_T8_T9_T10_T11_T12_T13) HKT_F_T8_T9_T10_T11_T12_T13, + AP7 ~func(HKT_T8) func(HKT_F_T8_T9_T10_T11_T12_T13) HKT_F_T9_T10_T11_T12_T13, + AP8 ~func(HKT_T9) func(HKT_F_T9_T10_T11_T12_T13) HKT_F_T10_T11_T12_T13, + AP9 ~func(HKT_T10) func(HKT_F_T10_T11_T12_T13) HKT_F_T11_T12_T13, + AP10 ~func(HKT_T11) func(HKT_F_T11_T12_T13) HKT_F_T12_T13, + AP11 ~func(HKT_T12) func(HKT_F_T12_T13) HKT_F_T13, + AP12 ~func(HKT_T13) func(HKT_F_T13) HKT_TUPLE13, + T1, + T2, + T3, + T4, + T5, + T6, + T7, + T8, + T9, + T10, + T11, + T12, + T13, + HKT_T1, // HKT[T1] + HKT_T2, // HKT[T2] + HKT_T3, // HKT[T3] + HKT_T4, // HKT[T4] + HKT_T5, // HKT[T5] + HKT_T6, // HKT[T6] + HKT_T7, // HKT[T7] + HKT_T8, // HKT[T8] + HKT_T9, // HKT[T9] + HKT_T10, // HKT[T10] + HKT_T11, // HKT[T11] + HKT_T12, // HKT[T12] + HKT_T13, // HKT[T13] + HKT_F_T2_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13, // HKT[func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]] + HKT_F_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13, // HKT[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]] + HKT_F_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13, // HKT[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]] + HKT_F_T5_T6_T7_T8_T9_T10_T11_T12_T13, // HKT[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]] + HKT_F_T6_T7_T8_T9_T10_T11_T12_T13, // HKT[func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]] + HKT_F_T7_T8_T9_T10_T11_T12_T13, // HKT[func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]] + HKT_F_T8_T9_T10_T11_T12_T13, // HKT[func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]] + HKT_F_T9_T10_T11_T12_T13, // HKT[func(T9) func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]] + HKT_F_T10_T11_T12_T13, // HKT[func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]] + HKT_F_T11_T12_T13, // HKT[func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]] + HKT_F_T12_T13, // HKT[func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]] + HKT_F_T13, // HKT[func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]] + HKT_TUPLE13 any, // HKT[Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]] +]( + fmap MAP, + fap1 AP1, + fap2 AP2, + fap3 AP3, + fap4 AP4, + fap5 AP5, + fap6 AP6, + fap7 AP7, + fap8 AP8, + fap9 AP9, + fap10 AP10, + fap11 AP11, + fap12 AP12, + t1 HKT_T1, + t2 HKT_T2, + t3 HKT_T3, + t4 HKT_T4, + t5 HKT_T5, + t6 HKT_T6, + t7 HKT_T7, + t8 HKT_T8, + t9 HKT_T9, + t10 HKT_T10, + t11 HKT_T11, + t12 HKT_T12, + t13 HKT_T13, +) HKT_TUPLE13 { + return F.Pipe13( + t1, + fmap(tupleConstructor13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]()), + fap1(t2), + fap2(t3), + fap3(t4), + fap4(t5), + fap5(t6), + fap6(t7), + fap7(t8), + fap8(t9), + fap9(t10), + fap10(t11), + fap11(t12), + fap12(t13), +) +} + +// SequenceTuple13 is a utility function used to implement the sequence operation for higher kinded types based only on map and ap. +// The function takes a [Tuple13] of higher higher kinded types and returns a higher kinded type of a [Tuple13] with the resolved values. +func SequenceTuple13[ + MAP ~func(func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]) func(HKT_T1) HKT_F_T2_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13, + AP1 ~func(HKT_T2) func(HKT_F_T2_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13) HKT_F_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13, + AP2 ~func(HKT_T3) func(HKT_F_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13) HKT_F_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13, + AP3 ~func(HKT_T4) func(HKT_F_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13) HKT_F_T5_T6_T7_T8_T9_T10_T11_T12_T13, + AP4 ~func(HKT_T5) func(HKT_F_T5_T6_T7_T8_T9_T10_T11_T12_T13) HKT_F_T6_T7_T8_T9_T10_T11_T12_T13, + AP5 ~func(HKT_T6) func(HKT_F_T6_T7_T8_T9_T10_T11_T12_T13) HKT_F_T7_T8_T9_T10_T11_T12_T13, + AP6 ~func(HKT_T7) func(HKT_F_T7_T8_T9_T10_T11_T12_T13) HKT_F_T8_T9_T10_T11_T12_T13, + AP7 ~func(HKT_T8) func(HKT_F_T8_T9_T10_T11_T12_T13) HKT_F_T9_T10_T11_T12_T13, + AP8 ~func(HKT_T9) func(HKT_F_T9_T10_T11_T12_T13) HKT_F_T10_T11_T12_T13, + AP9 ~func(HKT_T10) func(HKT_F_T10_T11_T12_T13) HKT_F_T11_T12_T13, + AP10 ~func(HKT_T11) func(HKT_F_T11_T12_T13) HKT_F_T12_T13, + AP11 ~func(HKT_T12) func(HKT_F_T12_T13) HKT_F_T13, + AP12 ~func(HKT_T13) func(HKT_F_T13) HKT_TUPLE13, + T1, + T2, + T3, + T4, + T5, + T6, + T7, + T8, + T9, + T10, + T11, + T12, + T13, + HKT_T1, // HKT[T1] + HKT_T2, // HKT[T2] + HKT_T3, // HKT[T3] + HKT_T4, // HKT[T4] + HKT_T5, // HKT[T5] + HKT_T6, // HKT[T6] + HKT_T7, // HKT[T7] + HKT_T8, // HKT[T8] + HKT_T9, // HKT[T9] + HKT_T10, // HKT[T10] + HKT_T11, // HKT[T11] + HKT_T12, // HKT[T12] + HKT_T13, // HKT[T13] + HKT_F_T2_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13, // HKT[func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]] + HKT_F_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13, // HKT[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]] + HKT_F_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13, // HKT[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]] + HKT_F_T5_T6_T7_T8_T9_T10_T11_T12_T13, // HKT[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]] + HKT_F_T6_T7_T8_T9_T10_T11_T12_T13, // HKT[func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]] + HKT_F_T7_T8_T9_T10_T11_T12_T13, // HKT[func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]] + HKT_F_T8_T9_T10_T11_T12_T13, // HKT[func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]] + HKT_F_T9_T10_T11_T12_T13, // HKT[func(T9) func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]] + HKT_F_T10_T11_T12_T13, // HKT[func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]] + HKT_F_T11_T12_T13, // HKT[func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]] + HKT_F_T12_T13, // HKT[func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]] + HKT_F_T13, // HKT[func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]] + HKT_TUPLE13 any, // HKT[Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]] +]( + fmap MAP, + fap1 AP1, + fap2 AP2, + fap3 AP3, + fap4 AP4, + fap5 AP5, + fap6 AP6, + fap7 AP7, + fap8 AP8, + fap9 AP9, + fap10 AP10, + fap11 AP11, + fap12 AP12, + t T.Tuple13[HKT_T1, HKT_T2, HKT_T3, HKT_T4, HKT_T5, HKT_T6, HKT_T7, HKT_T8, HKT_T9, HKT_T10, HKT_T11, HKT_T12, HKT_T13], +) HKT_TUPLE13 { + return F.Pipe13( + t.F1, + fmap(tupleConstructor13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]()), + fap1(t.F2), + fap2(t.F3), + fap3(t.F4), + fap4(t.F5), + fap5(t.F6), + fap6(t.F7), + fap7(t.F8), + fap8(t.F9), + fap9(t.F10), + fap10(t.F11), + fap11(t.F12), + fap12(t.F13), +) +} + +// TraverseTuple13 is a utility function used to implement the sequence operation for higher kinded types based only on map and ap. +// The function takes a [Tuple13] of base types and 13 functions that transform these based types into higher higher kinded types. It returns a higher kinded type of a [Tuple13] with the resolved values. +func TraverseTuple13[ + MAP ~func(func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]) func(HKT_T1) HKT_F_T2_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13, + AP1 ~func(HKT_T2) func(HKT_F_T2_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13) HKT_F_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13, + AP2 ~func(HKT_T3) func(HKT_F_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13) HKT_F_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13, + AP3 ~func(HKT_T4) func(HKT_F_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13) HKT_F_T5_T6_T7_T8_T9_T10_T11_T12_T13, + AP4 ~func(HKT_T5) func(HKT_F_T5_T6_T7_T8_T9_T10_T11_T12_T13) HKT_F_T6_T7_T8_T9_T10_T11_T12_T13, + AP5 ~func(HKT_T6) func(HKT_F_T6_T7_T8_T9_T10_T11_T12_T13) HKT_F_T7_T8_T9_T10_T11_T12_T13, + AP6 ~func(HKT_T7) func(HKT_F_T7_T8_T9_T10_T11_T12_T13) HKT_F_T8_T9_T10_T11_T12_T13, + AP7 ~func(HKT_T8) func(HKT_F_T8_T9_T10_T11_T12_T13) HKT_F_T9_T10_T11_T12_T13, + AP8 ~func(HKT_T9) func(HKT_F_T9_T10_T11_T12_T13) HKT_F_T10_T11_T12_T13, + AP9 ~func(HKT_T10) func(HKT_F_T10_T11_T12_T13) HKT_F_T11_T12_T13, + AP10 ~func(HKT_T11) func(HKT_F_T11_T12_T13) HKT_F_T12_T13, + AP11 ~func(HKT_T12) func(HKT_F_T12_T13) HKT_F_T13, + AP12 ~func(HKT_T13) func(HKT_F_T13) HKT_TUPLE13, + F1 ~func(A1) HKT_T1, + F2 ~func(A2) HKT_T2, + F3 ~func(A3) HKT_T3, + F4 ~func(A4) HKT_T4, + F5 ~func(A5) HKT_T5, + F6 ~func(A6) HKT_T6, + F7 ~func(A7) HKT_T7, + F8 ~func(A8) HKT_T8, + F9 ~func(A9) HKT_T9, + F10 ~func(A10) HKT_T10, + F11 ~func(A11) HKT_T11, + F12 ~func(A12) HKT_T12, + F13 ~func(A13) HKT_T13, + A1, T1, + A2, T2, + A3, T3, + A4, T4, + A5, T5, + A6, T6, + A7, T7, + A8, T8, + A9, T9, + A10, T10, + A11, T11, + A12, T12, + A13, T13, + HKT_T1, // HKT[T1] + HKT_T2, // HKT[T2] + HKT_T3, // HKT[T3] + HKT_T4, // HKT[T4] + HKT_T5, // HKT[T5] + HKT_T6, // HKT[T6] + HKT_T7, // HKT[T7] + HKT_T8, // HKT[T8] + HKT_T9, // HKT[T9] + HKT_T10, // HKT[T10] + HKT_T11, // HKT[T11] + HKT_T12, // HKT[T12] + HKT_T13, // HKT[T13] + HKT_F_T2_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13, // HKT[func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]] + HKT_F_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13, // HKT[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]] + HKT_F_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13, // HKT[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]] + HKT_F_T5_T6_T7_T8_T9_T10_T11_T12_T13, // HKT[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]] + HKT_F_T6_T7_T8_T9_T10_T11_T12_T13, // HKT[func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]] + HKT_F_T7_T8_T9_T10_T11_T12_T13, // HKT[func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]] + HKT_F_T8_T9_T10_T11_T12_T13, // HKT[func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]] + HKT_F_T9_T10_T11_T12_T13, // HKT[func(T9) func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]] + HKT_F_T10_T11_T12_T13, // HKT[func(T10) func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]] + HKT_F_T11_T12_T13, // HKT[func(T11) func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]] + HKT_F_T12_T13, // HKT[func(T12) func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]] + HKT_F_T13, // HKT[func(T13) T.Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]] + HKT_TUPLE13 any, // HKT[Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]] +]( + fmap MAP, + fap1 AP1, + fap2 AP2, + fap3 AP3, + fap4 AP4, + fap5 AP5, + fap6 AP6, + fap7 AP7, + fap8 AP8, + fap9 AP9, + fap10 AP10, + fap11 AP11, + fap12 AP12, + f1 F1, + f2 F2, + f3 F3, + f4 F4, + f5 F5, + f6 F6, + f7 F7, + f8 F8, + f9 F9, + f10 F10, + f11 F11, + f12 F12, + f13 F13, + t T.Tuple13[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13], +) HKT_TUPLE13 { + return F.Pipe13( + f1(t.F1), + fmap(tupleConstructor13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]()), + fap1(f2(t.F2)), + fap2(f3(t.F3)), + fap3(f4(t.F4)), + fap4(f5(t.F5)), + fap5(f6(t.F6)), + fap6(f7(t.F7)), + fap7(f8(t.F8)), + fap8(f9(t.F9)), + fap9(f10(t.F10)), + fap10(f11(t.F11)), + fap11(f12(t.F12)), + fap12(f13(t.F13)), +) +} + +// tupleConstructor14 returns a curried version of [T.MakeTuple14] +func tupleConstructor14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14 any]() func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14] { + return F.Curry14(T.MakeTuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]) +} + +// SequenceT14 is a utility function used to implement the sequence operation for higher kinded types based only on map and ap. +// The function takes 14 higher higher kinded types and returns a higher kinded type of a [Tuple14] with the resolved values. +func SequenceT14[ + MAP ~func(func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]) func(HKT_T1) HKT_F_T2_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14, + AP1 ~func(HKT_T2) func(HKT_F_T2_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14) HKT_F_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14, + AP2 ~func(HKT_T3) func(HKT_F_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14) HKT_F_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14, + AP3 ~func(HKT_T4) func(HKT_F_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14) HKT_F_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14, + AP4 ~func(HKT_T5) func(HKT_F_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14) HKT_F_T6_T7_T8_T9_T10_T11_T12_T13_T14, + AP5 ~func(HKT_T6) func(HKT_F_T6_T7_T8_T9_T10_T11_T12_T13_T14) HKT_F_T7_T8_T9_T10_T11_T12_T13_T14, + AP6 ~func(HKT_T7) func(HKT_F_T7_T8_T9_T10_T11_T12_T13_T14) HKT_F_T8_T9_T10_T11_T12_T13_T14, + AP7 ~func(HKT_T8) func(HKT_F_T8_T9_T10_T11_T12_T13_T14) HKT_F_T9_T10_T11_T12_T13_T14, + AP8 ~func(HKT_T9) func(HKT_F_T9_T10_T11_T12_T13_T14) HKT_F_T10_T11_T12_T13_T14, + AP9 ~func(HKT_T10) func(HKT_F_T10_T11_T12_T13_T14) HKT_F_T11_T12_T13_T14, + AP10 ~func(HKT_T11) func(HKT_F_T11_T12_T13_T14) HKT_F_T12_T13_T14, + AP11 ~func(HKT_T12) func(HKT_F_T12_T13_T14) HKT_F_T13_T14, + AP12 ~func(HKT_T13) func(HKT_F_T13_T14) HKT_F_T14, + AP13 ~func(HKT_T14) func(HKT_F_T14) HKT_TUPLE14, + T1, + T2, + T3, + T4, + T5, + T6, + T7, + T8, + T9, + T10, + T11, + T12, + T13, + T14, + HKT_T1, // HKT[T1] + HKT_T2, // HKT[T2] + HKT_T3, // HKT[T3] + HKT_T4, // HKT[T4] + HKT_T5, // HKT[T5] + HKT_T6, // HKT[T6] + HKT_T7, // HKT[T7] + HKT_T8, // HKT[T8] + HKT_T9, // HKT[T9] + HKT_T10, // HKT[T10] + HKT_T11, // HKT[T11] + HKT_T12, // HKT[T12] + HKT_T13, // HKT[T13] + HKT_T14, // HKT[T14] + HKT_F_T2_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14, // HKT[func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]] + HKT_F_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14, // HKT[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]] + HKT_F_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14, // HKT[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]] + HKT_F_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14, // HKT[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]] + HKT_F_T6_T7_T8_T9_T10_T11_T12_T13_T14, // HKT[func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]] + HKT_F_T7_T8_T9_T10_T11_T12_T13_T14, // HKT[func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]] + HKT_F_T8_T9_T10_T11_T12_T13_T14, // HKT[func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]] + HKT_F_T9_T10_T11_T12_T13_T14, // HKT[func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]] + HKT_F_T10_T11_T12_T13_T14, // HKT[func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]] + HKT_F_T11_T12_T13_T14, // HKT[func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]] + HKT_F_T12_T13_T14, // HKT[func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]] + HKT_F_T13_T14, // HKT[func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]] + HKT_F_T14, // HKT[func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]] + HKT_TUPLE14 any, // HKT[Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]] +]( + fmap MAP, + fap1 AP1, + fap2 AP2, + fap3 AP3, + fap4 AP4, + fap5 AP5, + fap6 AP6, + fap7 AP7, + fap8 AP8, + fap9 AP9, + fap10 AP10, + fap11 AP11, + fap12 AP12, + fap13 AP13, + t1 HKT_T1, + t2 HKT_T2, + t3 HKT_T3, + t4 HKT_T4, + t5 HKT_T5, + t6 HKT_T6, + t7 HKT_T7, + t8 HKT_T8, + t9 HKT_T9, + t10 HKT_T10, + t11 HKT_T11, + t12 HKT_T12, + t13 HKT_T13, + t14 HKT_T14, +) HKT_TUPLE14 { + return F.Pipe14( + t1, + fmap(tupleConstructor14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]()), + fap1(t2), + fap2(t3), + fap3(t4), + fap4(t5), + fap5(t6), + fap6(t7), + fap7(t8), + fap8(t9), + fap9(t10), + fap10(t11), + fap11(t12), + fap12(t13), + fap13(t14), +) +} + +// SequenceTuple14 is a utility function used to implement the sequence operation for higher kinded types based only on map and ap. +// The function takes a [Tuple14] of higher higher kinded types and returns a higher kinded type of a [Tuple14] with the resolved values. +func SequenceTuple14[ + MAP ~func(func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]) func(HKT_T1) HKT_F_T2_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14, + AP1 ~func(HKT_T2) func(HKT_F_T2_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14) HKT_F_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14, + AP2 ~func(HKT_T3) func(HKT_F_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14) HKT_F_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14, + AP3 ~func(HKT_T4) func(HKT_F_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14) HKT_F_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14, + AP4 ~func(HKT_T5) func(HKT_F_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14) HKT_F_T6_T7_T8_T9_T10_T11_T12_T13_T14, + AP5 ~func(HKT_T6) func(HKT_F_T6_T7_T8_T9_T10_T11_T12_T13_T14) HKT_F_T7_T8_T9_T10_T11_T12_T13_T14, + AP6 ~func(HKT_T7) func(HKT_F_T7_T8_T9_T10_T11_T12_T13_T14) HKT_F_T8_T9_T10_T11_T12_T13_T14, + AP7 ~func(HKT_T8) func(HKT_F_T8_T9_T10_T11_T12_T13_T14) HKT_F_T9_T10_T11_T12_T13_T14, + AP8 ~func(HKT_T9) func(HKT_F_T9_T10_T11_T12_T13_T14) HKT_F_T10_T11_T12_T13_T14, + AP9 ~func(HKT_T10) func(HKT_F_T10_T11_T12_T13_T14) HKT_F_T11_T12_T13_T14, + AP10 ~func(HKT_T11) func(HKT_F_T11_T12_T13_T14) HKT_F_T12_T13_T14, + AP11 ~func(HKT_T12) func(HKT_F_T12_T13_T14) HKT_F_T13_T14, + AP12 ~func(HKT_T13) func(HKT_F_T13_T14) HKT_F_T14, + AP13 ~func(HKT_T14) func(HKT_F_T14) HKT_TUPLE14, + T1, + T2, + T3, + T4, + T5, + T6, + T7, + T8, + T9, + T10, + T11, + T12, + T13, + T14, + HKT_T1, // HKT[T1] + HKT_T2, // HKT[T2] + HKT_T3, // HKT[T3] + HKT_T4, // HKT[T4] + HKT_T5, // HKT[T5] + HKT_T6, // HKT[T6] + HKT_T7, // HKT[T7] + HKT_T8, // HKT[T8] + HKT_T9, // HKT[T9] + HKT_T10, // HKT[T10] + HKT_T11, // HKT[T11] + HKT_T12, // HKT[T12] + HKT_T13, // HKT[T13] + HKT_T14, // HKT[T14] + HKT_F_T2_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14, // HKT[func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]] + HKT_F_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14, // HKT[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]] + HKT_F_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14, // HKT[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]] + HKT_F_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14, // HKT[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]] + HKT_F_T6_T7_T8_T9_T10_T11_T12_T13_T14, // HKT[func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]] + HKT_F_T7_T8_T9_T10_T11_T12_T13_T14, // HKT[func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]] + HKT_F_T8_T9_T10_T11_T12_T13_T14, // HKT[func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]] + HKT_F_T9_T10_T11_T12_T13_T14, // HKT[func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]] + HKT_F_T10_T11_T12_T13_T14, // HKT[func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]] + HKT_F_T11_T12_T13_T14, // HKT[func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]] + HKT_F_T12_T13_T14, // HKT[func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]] + HKT_F_T13_T14, // HKT[func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]] + HKT_F_T14, // HKT[func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]] + HKT_TUPLE14 any, // HKT[Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]] +]( + fmap MAP, + fap1 AP1, + fap2 AP2, + fap3 AP3, + fap4 AP4, + fap5 AP5, + fap6 AP6, + fap7 AP7, + fap8 AP8, + fap9 AP9, + fap10 AP10, + fap11 AP11, + fap12 AP12, + fap13 AP13, + t T.Tuple14[HKT_T1, HKT_T2, HKT_T3, HKT_T4, HKT_T5, HKT_T6, HKT_T7, HKT_T8, HKT_T9, HKT_T10, HKT_T11, HKT_T12, HKT_T13, HKT_T14], +) HKT_TUPLE14 { + return F.Pipe14( + t.F1, + fmap(tupleConstructor14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]()), + fap1(t.F2), + fap2(t.F3), + fap3(t.F4), + fap4(t.F5), + fap5(t.F6), + fap6(t.F7), + fap7(t.F8), + fap8(t.F9), + fap9(t.F10), + fap10(t.F11), + fap11(t.F12), + fap12(t.F13), + fap13(t.F14), +) +} + +// TraverseTuple14 is a utility function used to implement the sequence operation for higher kinded types based only on map and ap. +// The function takes a [Tuple14] of base types and 14 functions that transform these based types into higher higher kinded types. It returns a higher kinded type of a [Tuple14] with the resolved values. +func TraverseTuple14[ + MAP ~func(func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]) func(HKT_T1) HKT_F_T2_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14, + AP1 ~func(HKT_T2) func(HKT_F_T2_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14) HKT_F_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14, + AP2 ~func(HKT_T3) func(HKT_F_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14) HKT_F_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14, + AP3 ~func(HKT_T4) func(HKT_F_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14) HKT_F_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14, + AP4 ~func(HKT_T5) func(HKT_F_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14) HKT_F_T6_T7_T8_T9_T10_T11_T12_T13_T14, + AP5 ~func(HKT_T6) func(HKT_F_T6_T7_T8_T9_T10_T11_T12_T13_T14) HKT_F_T7_T8_T9_T10_T11_T12_T13_T14, + AP6 ~func(HKT_T7) func(HKT_F_T7_T8_T9_T10_T11_T12_T13_T14) HKT_F_T8_T9_T10_T11_T12_T13_T14, + AP7 ~func(HKT_T8) func(HKT_F_T8_T9_T10_T11_T12_T13_T14) HKT_F_T9_T10_T11_T12_T13_T14, + AP8 ~func(HKT_T9) func(HKT_F_T9_T10_T11_T12_T13_T14) HKT_F_T10_T11_T12_T13_T14, + AP9 ~func(HKT_T10) func(HKT_F_T10_T11_T12_T13_T14) HKT_F_T11_T12_T13_T14, + AP10 ~func(HKT_T11) func(HKT_F_T11_T12_T13_T14) HKT_F_T12_T13_T14, + AP11 ~func(HKT_T12) func(HKT_F_T12_T13_T14) HKT_F_T13_T14, + AP12 ~func(HKT_T13) func(HKT_F_T13_T14) HKT_F_T14, + AP13 ~func(HKT_T14) func(HKT_F_T14) HKT_TUPLE14, + F1 ~func(A1) HKT_T1, + F2 ~func(A2) HKT_T2, + F3 ~func(A3) HKT_T3, + F4 ~func(A4) HKT_T4, + F5 ~func(A5) HKT_T5, + F6 ~func(A6) HKT_T6, + F7 ~func(A7) HKT_T7, + F8 ~func(A8) HKT_T8, + F9 ~func(A9) HKT_T9, + F10 ~func(A10) HKT_T10, + F11 ~func(A11) HKT_T11, + F12 ~func(A12) HKT_T12, + F13 ~func(A13) HKT_T13, + F14 ~func(A14) HKT_T14, + A1, T1, + A2, T2, + A3, T3, + A4, T4, + A5, T5, + A6, T6, + A7, T7, + A8, T8, + A9, T9, + A10, T10, + A11, T11, + A12, T12, + A13, T13, + A14, T14, + HKT_T1, // HKT[T1] + HKT_T2, // HKT[T2] + HKT_T3, // HKT[T3] + HKT_T4, // HKT[T4] + HKT_T5, // HKT[T5] + HKT_T6, // HKT[T6] + HKT_T7, // HKT[T7] + HKT_T8, // HKT[T8] + HKT_T9, // HKT[T9] + HKT_T10, // HKT[T10] + HKT_T11, // HKT[T11] + HKT_T12, // HKT[T12] + HKT_T13, // HKT[T13] + HKT_T14, // HKT[T14] + HKT_F_T2_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14, // HKT[func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]] + HKT_F_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14, // HKT[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]] + HKT_F_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14, // HKT[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]] + HKT_F_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14, // HKT[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]] + HKT_F_T6_T7_T8_T9_T10_T11_T12_T13_T14, // HKT[func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]] + HKT_F_T7_T8_T9_T10_T11_T12_T13_T14, // HKT[func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]] + HKT_F_T8_T9_T10_T11_T12_T13_T14, // HKT[func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]] + HKT_F_T9_T10_T11_T12_T13_T14, // HKT[func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]] + HKT_F_T10_T11_T12_T13_T14, // HKT[func(T10) func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]] + HKT_F_T11_T12_T13_T14, // HKT[func(T11) func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]] + HKT_F_T12_T13_T14, // HKT[func(T12) func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]] + HKT_F_T13_T14, // HKT[func(T13) func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]] + HKT_F_T14, // HKT[func(T14) T.Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]] + HKT_TUPLE14 any, // HKT[Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]] +]( + fmap MAP, + fap1 AP1, + fap2 AP2, + fap3 AP3, + fap4 AP4, + fap5 AP5, + fap6 AP6, + fap7 AP7, + fap8 AP8, + fap9 AP9, + fap10 AP10, + fap11 AP11, + fap12 AP12, + fap13 AP13, + f1 F1, + f2 F2, + f3 F3, + f4 F4, + f5 F5, + f6 F6, + f7 F7, + f8 F8, + f9 F9, + f10 F10, + f11 F11, + f12 F12, + f13 F13, + f14 F14, + t T.Tuple14[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14], +) HKT_TUPLE14 { + return F.Pipe14( + f1(t.F1), + fmap(tupleConstructor14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]()), + fap1(f2(t.F2)), + fap2(f3(t.F3)), + fap3(f4(t.F4)), + fap4(f5(t.F5)), + fap5(f6(t.F6)), + fap6(f7(t.F7)), + fap7(f8(t.F8)), + fap8(f9(t.F9)), + fap9(f10(t.F10)), + fap10(f11(t.F11)), + fap11(f12(t.F12)), + fap12(f13(t.F13)), + fap13(f14(t.F14)), +) +} + +// tupleConstructor15 returns a curried version of [T.MakeTuple15] +func tupleConstructor15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15 any]() func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15] { + return F.Curry15(T.MakeTuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]) +} + +// SequenceT15 is a utility function used to implement the sequence operation for higher kinded types based only on map and ap. +// The function takes 15 higher higher kinded types and returns a higher kinded type of a [Tuple15] with the resolved values. +func SequenceT15[ + MAP ~func(func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]) func(HKT_T1) HKT_F_T2_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14_T15, + AP1 ~func(HKT_T2) func(HKT_F_T2_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14_T15) HKT_F_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14_T15, + AP2 ~func(HKT_T3) func(HKT_F_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14_T15) HKT_F_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14_T15, + AP3 ~func(HKT_T4) func(HKT_F_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14_T15) HKT_F_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14_T15, + AP4 ~func(HKT_T5) func(HKT_F_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14_T15) HKT_F_T6_T7_T8_T9_T10_T11_T12_T13_T14_T15, + AP5 ~func(HKT_T6) func(HKT_F_T6_T7_T8_T9_T10_T11_T12_T13_T14_T15) HKT_F_T7_T8_T9_T10_T11_T12_T13_T14_T15, + AP6 ~func(HKT_T7) func(HKT_F_T7_T8_T9_T10_T11_T12_T13_T14_T15) HKT_F_T8_T9_T10_T11_T12_T13_T14_T15, + AP7 ~func(HKT_T8) func(HKT_F_T8_T9_T10_T11_T12_T13_T14_T15) HKT_F_T9_T10_T11_T12_T13_T14_T15, + AP8 ~func(HKT_T9) func(HKT_F_T9_T10_T11_T12_T13_T14_T15) HKT_F_T10_T11_T12_T13_T14_T15, + AP9 ~func(HKT_T10) func(HKT_F_T10_T11_T12_T13_T14_T15) HKT_F_T11_T12_T13_T14_T15, + AP10 ~func(HKT_T11) func(HKT_F_T11_T12_T13_T14_T15) HKT_F_T12_T13_T14_T15, + AP11 ~func(HKT_T12) func(HKT_F_T12_T13_T14_T15) HKT_F_T13_T14_T15, + AP12 ~func(HKT_T13) func(HKT_F_T13_T14_T15) HKT_F_T14_T15, + AP13 ~func(HKT_T14) func(HKT_F_T14_T15) HKT_F_T15, + AP14 ~func(HKT_T15) func(HKT_F_T15) HKT_TUPLE15, + T1, + T2, + T3, + T4, + T5, + T6, + T7, + T8, + T9, + T10, + T11, + T12, + T13, + T14, + T15, + HKT_T1, // HKT[T1] + HKT_T2, // HKT[T2] + HKT_T3, // HKT[T3] + HKT_T4, // HKT[T4] + HKT_T5, // HKT[T5] + HKT_T6, // HKT[T6] + HKT_T7, // HKT[T7] + HKT_T8, // HKT[T8] + HKT_T9, // HKT[T9] + HKT_T10, // HKT[T10] + HKT_T11, // HKT[T11] + HKT_T12, // HKT[T12] + HKT_T13, // HKT[T13] + HKT_T14, // HKT[T14] + HKT_T15, // HKT[T15] + HKT_F_T2_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14_T15, // HKT[func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] + HKT_F_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14_T15, // HKT[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] + HKT_F_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14_T15, // HKT[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] + HKT_F_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14_T15, // HKT[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] + HKT_F_T6_T7_T8_T9_T10_T11_T12_T13_T14_T15, // HKT[func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] + HKT_F_T7_T8_T9_T10_T11_T12_T13_T14_T15, // HKT[func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] + HKT_F_T8_T9_T10_T11_T12_T13_T14_T15, // HKT[func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] + HKT_F_T9_T10_T11_T12_T13_T14_T15, // HKT[func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] + HKT_F_T10_T11_T12_T13_T14_T15, // HKT[func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] + HKT_F_T11_T12_T13_T14_T15, // HKT[func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] + HKT_F_T12_T13_T14_T15, // HKT[func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] + HKT_F_T13_T14_T15, // HKT[func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] + HKT_F_T14_T15, // HKT[func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] + HKT_F_T15, // HKT[func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] + HKT_TUPLE15 any, // HKT[Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] +]( + fmap MAP, + fap1 AP1, + fap2 AP2, + fap3 AP3, + fap4 AP4, + fap5 AP5, + fap6 AP6, + fap7 AP7, + fap8 AP8, + fap9 AP9, + fap10 AP10, + fap11 AP11, + fap12 AP12, + fap13 AP13, + fap14 AP14, + t1 HKT_T1, + t2 HKT_T2, + t3 HKT_T3, + t4 HKT_T4, + t5 HKT_T5, + t6 HKT_T6, + t7 HKT_T7, + t8 HKT_T8, + t9 HKT_T9, + t10 HKT_T10, + t11 HKT_T11, + t12 HKT_T12, + t13 HKT_T13, + t14 HKT_T14, + t15 HKT_T15, +) HKT_TUPLE15 { + return F.Pipe15( + t1, + fmap(tupleConstructor15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]()), + fap1(t2), + fap2(t3), + fap3(t4), + fap4(t5), + fap5(t6), + fap6(t7), + fap7(t8), + fap8(t9), + fap9(t10), + fap10(t11), + fap11(t12), + fap12(t13), + fap13(t14), + fap14(t15), +) +} + +// SequenceTuple15 is a utility function used to implement the sequence operation for higher kinded types based only on map and ap. +// The function takes a [Tuple15] of higher higher kinded types and returns a higher kinded type of a [Tuple15] with the resolved values. +func SequenceTuple15[ + MAP ~func(func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]) func(HKT_T1) HKT_F_T2_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14_T15, + AP1 ~func(HKT_T2) func(HKT_F_T2_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14_T15) HKT_F_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14_T15, + AP2 ~func(HKT_T3) func(HKT_F_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14_T15) HKT_F_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14_T15, + AP3 ~func(HKT_T4) func(HKT_F_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14_T15) HKT_F_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14_T15, + AP4 ~func(HKT_T5) func(HKT_F_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14_T15) HKT_F_T6_T7_T8_T9_T10_T11_T12_T13_T14_T15, + AP5 ~func(HKT_T6) func(HKT_F_T6_T7_T8_T9_T10_T11_T12_T13_T14_T15) HKT_F_T7_T8_T9_T10_T11_T12_T13_T14_T15, + AP6 ~func(HKT_T7) func(HKT_F_T7_T8_T9_T10_T11_T12_T13_T14_T15) HKT_F_T8_T9_T10_T11_T12_T13_T14_T15, + AP7 ~func(HKT_T8) func(HKT_F_T8_T9_T10_T11_T12_T13_T14_T15) HKT_F_T9_T10_T11_T12_T13_T14_T15, + AP8 ~func(HKT_T9) func(HKT_F_T9_T10_T11_T12_T13_T14_T15) HKT_F_T10_T11_T12_T13_T14_T15, + AP9 ~func(HKT_T10) func(HKT_F_T10_T11_T12_T13_T14_T15) HKT_F_T11_T12_T13_T14_T15, + AP10 ~func(HKT_T11) func(HKT_F_T11_T12_T13_T14_T15) HKT_F_T12_T13_T14_T15, + AP11 ~func(HKT_T12) func(HKT_F_T12_T13_T14_T15) HKT_F_T13_T14_T15, + AP12 ~func(HKT_T13) func(HKT_F_T13_T14_T15) HKT_F_T14_T15, + AP13 ~func(HKT_T14) func(HKT_F_T14_T15) HKT_F_T15, + AP14 ~func(HKT_T15) func(HKT_F_T15) HKT_TUPLE15, + T1, + T2, + T3, + T4, + T5, + T6, + T7, + T8, + T9, + T10, + T11, + T12, + T13, + T14, + T15, + HKT_T1, // HKT[T1] + HKT_T2, // HKT[T2] + HKT_T3, // HKT[T3] + HKT_T4, // HKT[T4] + HKT_T5, // HKT[T5] + HKT_T6, // HKT[T6] + HKT_T7, // HKT[T7] + HKT_T8, // HKT[T8] + HKT_T9, // HKT[T9] + HKT_T10, // HKT[T10] + HKT_T11, // HKT[T11] + HKT_T12, // HKT[T12] + HKT_T13, // HKT[T13] + HKT_T14, // HKT[T14] + HKT_T15, // HKT[T15] + HKT_F_T2_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14_T15, // HKT[func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] + HKT_F_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14_T15, // HKT[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] + HKT_F_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14_T15, // HKT[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] + HKT_F_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14_T15, // HKT[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] + HKT_F_T6_T7_T8_T9_T10_T11_T12_T13_T14_T15, // HKT[func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] + HKT_F_T7_T8_T9_T10_T11_T12_T13_T14_T15, // HKT[func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] + HKT_F_T8_T9_T10_T11_T12_T13_T14_T15, // HKT[func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] + HKT_F_T9_T10_T11_T12_T13_T14_T15, // HKT[func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] + HKT_F_T10_T11_T12_T13_T14_T15, // HKT[func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] + HKT_F_T11_T12_T13_T14_T15, // HKT[func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] + HKT_F_T12_T13_T14_T15, // HKT[func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] + HKT_F_T13_T14_T15, // HKT[func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] + HKT_F_T14_T15, // HKT[func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] + HKT_F_T15, // HKT[func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] + HKT_TUPLE15 any, // HKT[Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] +]( + fmap MAP, + fap1 AP1, + fap2 AP2, + fap3 AP3, + fap4 AP4, + fap5 AP5, + fap6 AP6, + fap7 AP7, + fap8 AP8, + fap9 AP9, + fap10 AP10, + fap11 AP11, + fap12 AP12, + fap13 AP13, + fap14 AP14, + t T.Tuple15[HKT_T1, HKT_T2, HKT_T3, HKT_T4, HKT_T5, HKT_T6, HKT_T7, HKT_T8, HKT_T9, HKT_T10, HKT_T11, HKT_T12, HKT_T13, HKT_T14, HKT_T15], +) HKT_TUPLE15 { + return F.Pipe15( + t.F1, + fmap(tupleConstructor15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]()), + fap1(t.F2), + fap2(t.F3), + fap3(t.F4), + fap4(t.F5), + fap5(t.F6), + fap6(t.F7), + fap7(t.F8), + fap8(t.F9), + fap9(t.F10), + fap10(t.F11), + fap11(t.F12), + fap12(t.F13), + fap13(t.F14), + fap14(t.F15), +) +} + +// TraverseTuple15 is a utility function used to implement the sequence operation for higher kinded types based only on map and ap. +// The function takes a [Tuple15] of base types and 15 functions that transform these based types into higher higher kinded types. It returns a higher kinded type of a [Tuple15] with the resolved values. +func TraverseTuple15[ + MAP ~func(func(T1) func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]) func(HKT_T1) HKT_F_T2_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14_T15, + AP1 ~func(HKT_T2) func(HKT_F_T2_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14_T15) HKT_F_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14_T15, + AP2 ~func(HKT_T3) func(HKT_F_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14_T15) HKT_F_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14_T15, + AP3 ~func(HKT_T4) func(HKT_F_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14_T15) HKT_F_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14_T15, + AP4 ~func(HKT_T5) func(HKT_F_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14_T15) HKT_F_T6_T7_T8_T9_T10_T11_T12_T13_T14_T15, + AP5 ~func(HKT_T6) func(HKT_F_T6_T7_T8_T9_T10_T11_T12_T13_T14_T15) HKT_F_T7_T8_T9_T10_T11_T12_T13_T14_T15, + AP6 ~func(HKT_T7) func(HKT_F_T7_T8_T9_T10_T11_T12_T13_T14_T15) HKT_F_T8_T9_T10_T11_T12_T13_T14_T15, + AP7 ~func(HKT_T8) func(HKT_F_T8_T9_T10_T11_T12_T13_T14_T15) HKT_F_T9_T10_T11_T12_T13_T14_T15, + AP8 ~func(HKT_T9) func(HKT_F_T9_T10_T11_T12_T13_T14_T15) HKT_F_T10_T11_T12_T13_T14_T15, + AP9 ~func(HKT_T10) func(HKT_F_T10_T11_T12_T13_T14_T15) HKT_F_T11_T12_T13_T14_T15, + AP10 ~func(HKT_T11) func(HKT_F_T11_T12_T13_T14_T15) HKT_F_T12_T13_T14_T15, + AP11 ~func(HKT_T12) func(HKT_F_T12_T13_T14_T15) HKT_F_T13_T14_T15, + AP12 ~func(HKT_T13) func(HKT_F_T13_T14_T15) HKT_F_T14_T15, + AP13 ~func(HKT_T14) func(HKT_F_T14_T15) HKT_F_T15, + AP14 ~func(HKT_T15) func(HKT_F_T15) HKT_TUPLE15, + F1 ~func(A1) HKT_T1, + F2 ~func(A2) HKT_T2, + F3 ~func(A3) HKT_T3, + F4 ~func(A4) HKT_T4, + F5 ~func(A5) HKT_T5, + F6 ~func(A6) HKT_T6, + F7 ~func(A7) HKT_T7, + F8 ~func(A8) HKT_T8, + F9 ~func(A9) HKT_T9, + F10 ~func(A10) HKT_T10, + F11 ~func(A11) HKT_T11, + F12 ~func(A12) HKT_T12, + F13 ~func(A13) HKT_T13, + F14 ~func(A14) HKT_T14, + F15 ~func(A15) HKT_T15, + A1, T1, + A2, T2, + A3, T3, + A4, T4, + A5, T5, + A6, T6, + A7, T7, + A8, T8, + A9, T9, + A10, T10, + A11, T11, + A12, T12, + A13, T13, + A14, T14, + A15, T15, + HKT_T1, // HKT[T1] + HKT_T2, // HKT[T2] + HKT_T3, // HKT[T3] + HKT_T4, // HKT[T4] + HKT_T5, // HKT[T5] + HKT_T6, // HKT[T6] + HKT_T7, // HKT[T7] + HKT_T8, // HKT[T8] + HKT_T9, // HKT[T9] + HKT_T10, // HKT[T10] + HKT_T11, // HKT[T11] + HKT_T12, // HKT[T12] + HKT_T13, // HKT[T13] + HKT_T14, // HKT[T14] + HKT_T15, // HKT[T15] + HKT_F_T2_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14_T15, // HKT[func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] + HKT_F_T3_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14_T15, // HKT[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] + HKT_F_T4_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14_T15, // HKT[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] + HKT_F_T5_T6_T7_T8_T9_T10_T11_T12_T13_T14_T15, // HKT[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] + HKT_F_T6_T7_T8_T9_T10_T11_T12_T13_T14_T15, // HKT[func(T6) func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] + HKT_F_T7_T8_T9_T10_T11_T12_T13_T14_T15, // HKT[func(T7) func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] + HKT_F_T8_T9_T10_T11_T12_T13_T14_T15, // HKT[func(T8) func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] + HKT_F_T9_T10_T11_T12_T13_T14_T15, // HKT[func(T9) func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] + HKT_F_T10_T11_T12_T13_T14_T15, // HKT[func(T10) func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] + HKT_F_T11_T12_T13_T14_T15, // HKT[func(T11) func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] + HKT_F_T12_T13_T14_T15, // HKT[func(T12) func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] + HKT_F_T13_T14_T15, // HKT[func(T13) func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] + HKT_F_T14_T15, // HKT[func(T14) func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] + HKT_F_T15, // HKT[func(T15) T.Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] + HKT_TUPLE15 any, // HKT[Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] +]( + fmap MAP, + fap1 AP1, + fap2 AP2, + fap3 AP3, + fap4 AP4, + fap5 AP5, + fap6 AP6, + fap7 AP7, + fap8 AP8, + fap9 AP9, + fap10 AP10, + fap11 AP11, + fap12 AP12, + fap13 AP13, + fap14 AP14, + f1 F1, + f2 F2, + f3 F3, + f4 F4, + f5 F5, + f6 F6, + f7 F7, + f8 F8, + f9 F9, + f10 F10, + f11 F11, + f12 F12, + f13 F13, + f14 F14, + f15 F15, + t T.Tuple15[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15], +) HKT_TUPLE15 { + return F.Pipe15( + f1(t.F1), + fmap(tupleConstructor15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]()), + fap1(f2(t.F2)), + fap2(f3(t.F3)), + fap3(f4(t.F4)), + fap4(f5(t.F5)), + fap5(f6(t.F6)), + fap6(f7(t.F7)), + fap7(f8(t.F8)), + fap8(f9(t.F9)), + fap9(f10(t.F10)), + fap10(f11(t.F11)), + fap11(f12(t.F12)), + fap12(f13(t.F13)), + fap13(f14(t.F14)), + fap14(f15(t.F15)), +) +} diff --git a/v2/internal/apply/testing/laws.go b/v2/internal/apply/testing/laws.go new file mode 100644 index 0000000..2e0a377 --- /dev/null +++ b/v2/internal/apply/testing/laws.go @@ -0,0 +1,180 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package testing + +import ( + "testing" + + E "github.com/IBM/fp-go/v2/eq" + "github.com/IBM/fp-go/v2/internal/apply" + "github.com/IBM/fp-go/v2/internal/functor" + FCT "github.com/IBM/fp-go/v2/internal/functor/testing" + "github.com/IBM/fp-go/v2/internal/pointed" + "github.com/stretchr/testify/assert" +) + +// Apply associative composition law +// +// F.ap(F.ap(F.map(fbc, bc => ab => a => bc(ab(a))), fab), fa) <-> F.ap(fbc, F.ap(fab, fa)) +// +// Deprecated: use [ApplyAssertAssociativeComposition] instead +func AssertAssociativeComposition[HKTA, HKTB, HKTC, HKTAB, HKTBC, HKTAC, HKTABAC, A, B, C any](t *testing.T, + eq E.Eq[HKTC], + + fofab func(func(A) B) HKTAB, + fofbc func(func(B) C) HKTBC, + + fmap func(HKTBC, func(func(B) C) func(func(A) B) func(A) C) HKTABAC, + + fapab func(HKTAB, HKTA) HKTB, + fapbc func(HKTBC, HKTB) HKTC, + fapac func(HKTAC, HKTA) HKTC, + + fapabac func(HKTABAC, HKTAB) HKTAC, + + ab func(A) B, + bc func(B) C, +) func(fa HKTA) bool { + t.Helper() + return func(fa HKTA) bool { + + fab := fofab(ab) + fbc := fofbc(bc) + + left := fapac(fapabac(fmap(fbc, func(bc func(B) C) func(func(A) B) func(A) C { + return func(ab func(A) B) func(A) C { + return func(a A) C { + return bc(ab(a)) + } + } + }), fab), fa) + + right := fapbc(fbc, fapab(fab, fa)) + + return assert.True(t, eq.Equals(left, right), "Apply associative composition") + } +} + +// Apply associative composition law +// +// F.ap(F.ap(F.map(fbc, bc => ab => a => bc(ab(a))), fab), fa) <-> F.ap(fbc, F.ap(fab, fa)) +func ApplyAssertAssociativeComposition[HKTA, HKTB, HKTC, HKTAB, HKTBC, HKTAC, HKTABAC, A, B, C any](t *testing.T, + eq E.Eq[HKTC], + + fofab pointed.Pointed[func(A) B, HKTAB], + fofbc pointed.Pointed[func(B) C, HKTBC], + + fmap functor.Functor[func(B) C, func(func(A) B) func(A) C, HKTBC, HKTABAC], + + fapab apply.Apply[A, B, HKTA, HKTB, HKTAB], + fapbc apply.Apply[B, C, HKTB, HKTC, HKTBC], + fapac apply.Apply[A, C, HKTA, HKTC, HKTAC], + + fapabac apply.Apply[func(A) B, func(A) C, HKTAB, HKTAC, HKTABAC], + + ab func(A) B, + bc func(B) C, +) func(fa HKTA) bool { + t.Helper() + return func(fa HKTA) bool { + + fab := fofab.Of(ab) + fbc := fofbc.Of(bc) + + left := fapac.Ap(fa)(fapabac.Ap(fab)(fmap.Map(func(bc func(B) C) func(func(A) B) func(A) C { + return func(ab func(A) B) func(A) C { + return func(a A) C { + return bc(ab(a)) + } + } + })(fbc))) + + right := fapbc.Ap(fapab.Ap(fa)(fab))(fbc) + + return assert.True(t, eq.Equals(left, right), "Apply associative composition") + } +} + +// AssertLaws asserts the apply laws `identity`, `composition` and `associative composition` +// +// Deprecated: use [ApplyAssertLaws] instead +func AssertLaws[HKTA, HKTB, HKTC, HKTAB, HKTBC, HKTAC, HKTABAC, A, B, C any](t *testing.T, + eqa E.Eq[HKTA], + eqc E.Eq[HKTC], + + fofab func(func(A) B) HKTAB, + fofbc func(func(B) C) HKTBC, + + faa func(HKTA, func(A) A) HKTA, + fab func(HKTA, func(A) B) HKTB, + fac func(HKTA, func(A) C) HKTC, + fbc func(HKTB, func(B) C) HKTC, + + fmap func(HKTBC, func(func(B) C) func(func(A) B) func(A) C) HKTABAC, + + fapab func(HKTAB, HKTA) HKTB, + fapbc func(HKTBC, HKTB) HKTC, + fapac func(HKTAC, HKTA) HKTC, + + fapabac func(HKTABAC, HKTAB) HKTAC, + + ab func(A) B, + bc func(B) C, +) func(fa HKTA) bool { + // mark as test helper + t.Helper() + // functor laws + functor := FCT.AssertLaws(t, eqa, eqc, faa, fab, fac, fbc, ab, bc) + // associative composition laws + composition := AssertAssociativeComposition(t, eqc, fofab, fofbc, fmap, fapab, fapbc, fapac, fapabac, ab, bc) + + return func(fa HKTA) bool { + return functor(fa) && composition(fa) + } +} + +// ApplyAssertLaws asserts the apply laws `identity`, `composition` and `associative composition` +func ApplyAssertLaws[HKTA, HKTB, HKTC, HKTAB, HKTBC, HKTAC, HKTABAC, A, B, C any](t *testing.T, + eqa E.Eq[HKTA], + eqc E.Eq[HKTC], + + fofab pointed.Pointed[func(A) B, HKTAB], + fofbc pointed.Pointed[func(B) C, HKTBC], + + faa functor.Functor[A, A, HKTA, HKTA], + + fmap functor.Functor[func(B) C, func(func(A) B) func(A) C, HKTBC, HKTABAC], + + fapab apply.Apply[A, B, HKTA, HKTB, HKTAB], + fapbc apply.Apply[B, C, HKTB, HKTC, HKTBC], + fapac apply.Apply[A, C, HKTA, HKTC, HKTAC], + + fapabac apply.Apply[func(A) B, func(A) C, HKTAB, HKTAC, HKTABAC], + + ab func(A) B, + bc func(B) C, +) func(fa HKTA) bool { + // mark as test helper + t.Helper() + // functor laws + functor := FCT.FunctorAssertLaws(t, eqa, eqc, faa, apply.ToFunctor(fapab), apply.ToFunctor(fapac), apply.ToFunctor(fapbc), ab, bc) + // associative composition laws + composition := ApplyAssertAssociativeComposition(t, eqc, fofab, fofbc, fmap, fapab, fapbc, fapac, fapabac, ab, bc) + + return func(fa HKTA) bool { + return functor(fa) && composition(fa) + } +} diff --git a/v2/internal/apply/types.go b/v2/internal/apply/types.go new file mode 100644 index 0000000..26286d2 --- /dev/null +++ b/v2/internal/apply/types.go @@ -0,0 +1,30 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package apply + +import ( + "github.com/IBM/fp-go/v2/internal/functor" +) + +type Apply[A, B, HKTA, HKTB, HKTFAB any] interface { + functor.Functor[A, B, HKTA, HKTB] + Ap(HKTA) func(HKTFAB) HKTB +} + +// ToFunctor converts from [Apply] to [functor.Functor] +func ToFunctor[A, B, HKTA, HKTB, HKTFAB any](ap Apply[A, B, HKTA, HKTB, HKTFAB]) functor.Functor[A, B, HKTA, HKTB] { + return ap +} diff --git a/v2/internal/array/array.go b/v2/internal/array/array.go new file mode 100644 index 0000000..8e0690d --- /dev/null +++ b/v2/internal/array/array.go @@ -0,0 +1,126 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package array + +func Slice[GA ~[]A, A any](low, high int) func(as GA) GA { + return func(as GA) GA { + return as[low:high] + } +} + +func IsEmpty[GA ~[]A, A any](as GA) bool { + return len(as) == 0 +} + +func IsNil[GA ~[]A, A any](as GA) bool { + return as == nil +} + +func IsNonNil[GA ~[]A, A any](as GA) bool { + return as != nil +} + +func Reduce[GA ~[]A, A, B any](fa GA, f func(B, A) B, initial B) B { + current := initial + count := len(fa) + for i := 0; i < count; i++ { + current = f(current, fa[i]) + } + return current +} + +func ReduceWithIndex[GA ~[]A, A, B any](fa GA, f func(int, B, A) B, initial B) B { + current := initial + count := len(fa) + for i := 0; i < count; i++ { + current = f(i, current, fa[i]) + } + return current +} + +func ReduceRight[GA ~[]A, A, B any](fa GA, f func(A, B) B, initial B) B { + current := initial + count := len(fa) + for i := count - 1; i >= 0; i-- { + current = f(fa[i], current) + } + return current +} + +func ReduceRightWithIndex[GA ~[]A, A, B any](fa GA, f func(int, A, B) B, initial B) B { + current := initial + count := len(fa) + for i := count - 1; i >= 0; i-- { + current = f(i, fa[i], current) + } + return current +} + +func Append[GA ~[]A, A any](as GA, a A) GA { + return append(as, a) +} + +func Push[GA ~[]A, A any](as GA, a A) GA { + l := len(as) + cpy := make(GA, l+1) + copy(cpy, as) + cpy[l] = a + return cpy +} + +func Empty[GA ~[]A, A any]() GA { + return make(GA, 0) +} + +func upsertAt[GA ~[]A, A any](fa GA, a A) GA { + buf := make(GA, len(fa)+1) + buf[copy(buf, fa)] = a + return buf +} + +func UpsertAt[GA ~[]A, A any](a A) func(GA) GA { + return func(ma GA) GA { + return upsertAt(ma, a) + } +} + +func MonadMap[GA ~[]A, GB ~[]B, A, B any](as GA, f func(a A) B) GB { + count := len(as) + bs := make(GB, count) + for i := count - 1; i >= 0; i-- { + bs[i] = f(as[i]) + } + return bs +} + +func Map[GA ~[]A, GB ~[]B, A, B any](f func(a A) B) func(GA) GB { + return func(as GA) GB { + return MonadMap[GA, GB](as, f) + } +} + +func MonadMapWithIndex[GA ~[]A, GB ~[]B, A, B any](as GA, f func(idx int, a A) B) GB { + count := len(as) + bs := make(GB, count) + for i := count - 1; i >= 0; i-- { + bs[i] = f(i, as[i]) + } + return bs +} + +func ConstNil[GA ~[]A, A any]() GA { + return (GA)(nil) +} diff --git a/v2/internal/array/prepend.go b/v2/internal/array/prepend.go new file mode 100644 index 0000000..200de22 --- /dev/null +++ b/v2/internal/array/prepend.go @@ -0,0 +1,28 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package array + +// Prepend prepends a single value to an array +func Prepend[ENDO ~func(AS) AS, AS ~[]A, A any](head A) ENDO { + return func(as AS) AS { + l := len(as) + cpy := make(AS, l+1) + copy(cpy[1:], as) + cpy[0] = head + return cpy + + } +} diff --git a/v2/internal/array/traverse.go b/v2/internal/array/traverse.go new file mode 100644 index 0000000..5eba0da --- /dev/null +++ b/v2/internal/array/traverse.go @@ -0,0 +1,152 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package array + +import ( + F "github.com/IBM/fp-go/v2/function" +) + +/* +* +We need to pass the members of the applicative explicitly, because golang does neither support higher kinded types nor template methods on structs or interfaces + +HKTRB = HKT +HKTB = HKT +HKTAB = HKT +*/ +func MonadTraverse[GA ~[]A, GB ~[]B, A, B, HKTB, HKTAB, HKTRB any]( + fof func(GB) HKTRB, + fmap func(func(GB) func(B) GB) func(HKTRB) HKTAB, + fap func(HKTB) func(HKTAB) HKTRB, + + ta GA, + f func(A) HKTB) HKTRB { + return MonadTraverseReduce(fof, fmap, fap, ta, f, Append[GB, B], Empty[GB]()) +} + +/* +* +We need to pass the members of the applicative explicitly, because golang does neither support higher kinded types nor template methods on structs or interfaces + +HKTRB = HKT +HKTB = HKT +HKTAB = HKT +*/ +func MonadTraverseWithIndex[GA ~[]A, GB ~[]B, A, B, HKTB, HKTAB, HKTRB any]( + fof func(GB) HKTRB, + fmap func(func(GB) func(B) GB) func(HKTRB) HKTAB, + fap func(HKTB) func(HKTAB) HKTRB, + + ta GA, + f func(int, A) HKTB) HKTRB { + return MonadTraverseReduceWithIndex(fof, fmap, fap, ta, f, Append[GB, B], Empty[GB]()) +} + +func Traverse[GA ~[]A, GB ~[]B, A, B, HKTB, HKTAB, HKTRB any]( + fof func(GB) HKTRB, + fmap func(func(GB) func(B) GB) func(HKTRB) HKTAB, + fap func(HKTB) func(HKTAB) HKTRB, + + f func(A) HKTB) func(GA) HKTRB { + + return func(ma GA) HKTRB { + return MonadTraverse(fof, fmap, fap, ma, f) + } +} + +func TraverseWithIndex[GA ~[]A, GB ~[]B, A, B, HKTB, HKTAB, HKTRB any]( + fof func(GB) HKTRB, + fmap func(func(GB) func(B) GB) func(HKTRB) HKTAB, + fap func(HKTB) func(HKTAB) HKTRB, + + f func(int, A) HKTB) func(GA) HKTRB { + + return func(ma GA) HKTRB { + return MonadTraverseWithIndex(fof, fmap, fap, ma, f) + } +} + +func MonadTraverseReduce[GA ~[]A, GB, A, B, HKTB, HKTAB, HKTRB any]( + fof func(GB) HKTRB, + fmap func(func(GB) func(B) GB) func(HKTRB) HKTAB, + fap func(HKTB) func(HKTAB) HKTRB, + + ta GA, + + transform func(A) HKTB, + reduce func(GB, B) GB, + initial GB, +) HKTRB { + mmap := fmap(F.Curry2(reduce)) + + return Reduce(ta, func(r HKTRB, a A) HKTRB { + return F.Pipe2( + r, + mmap, + fap(transform(a)), + ) + }, fof(initial)) +} + +func MonadTraverseReduceWithIndex[GA ~[]A, GB, A, B, HKTB, HKTAB, HKTRB any]( + fof func(GB) HKTRB, + fmap func(func(GB) func(B) GB) func(HKTRB) HKTAB, + fap func(HKTB) func(HKTAB) HKTRB, + + ta GA, + + transform func(int, A) HKTB, + reduce func(GB, B) GB, + initial GB, +) HKTRB { + mmap := fmap(F.Curry2(reduce)) + + return ReduceWithIndex(ta, func(idx int, r HKTRB, a A) HKTRB { + return F.Pipe2( + r, + mmap, + fap(transform(idx, a)), + ) + }, fof(initial)) +} + +func TraverseReduce[GA ~[]A, GB, A, B, HKTB, HKTAB, HKTRB any]( + fof func(GB) HKTRB, + fmap func(func(GB) func(B) GB) func(HKTRB) HKTAB, + fap func(HKTB) func(HKTAB) HKTRB, + + transform func(A) HKTB, + reduce func(GB, B) GB, + initial GB, +) func(GA) HKTRB { + return func(ta GA) HKTRB { + return MonadTraverseReduce(fof, fmap, fap, ta, transform, reduce, initial) + } +} + +func TraverseReduceWithIndex[GA ~[]A, GB, A, B, HKTB, HKTAB, HKTRB any]( + fof func(GB) HKTRB, + fmap func(func(GB) func(B) GB) func(HKTRB) HKTAB, + fap func(HKTB) func(HKTAB) HKTRB, + + transform func(int, A) HKTB, + reduce func(GB, B) GB, + initial GB, +) func(GA) HKTRB { + return func(ta GA) HKTRB { + return MonadTraverseReduceWithIndex(fof, fmap, fap, ta, transform, reduce, initial) + } +} diff --git a/v2/internal/bracket/bracket.go b/v2/internal/bracket/bracket.go new file mode 100644 index 0000000..af34fc0 --- /dev/null +++ b/v2/internal/bracket/bracket.go @@ -0,0 +1,52 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package bracket + +import ( + F "github.com/IBM/fp-go/v2/function" +) + +// Bracket makes sure that a resource is cleaned up in the event of an error. The release action is called regardless of +// whether the body action returns and error or not. +func Bracket[ + GA, // IOEither[E, A] + GB, // IOEither[E, A] + GANY, // IOEither[E, ANY] + + EB, // Either[E, B] + + A, B, ANY any]( + + ofeb func(EB) GB, + + chainab func(GA, func(A) GB) GB, + chainebb func(GB, func(EB) GB) GB, + chainany func(GANY, func(ANY) GB) GB, + + acquire GA, + use func(A) GB, + release func(A, EB) GANY, +) GB { + return chainab(acquire, + func(a A) GB { + return chainebb(use(a), func(eb EB) GB { + return chainany( + release(a, eb), + F.Constant1[ANY](ofeb(eb)), + ) + }) + }) +} diff --git a/v2/internal/chain/chain.go b/v2/internal/chain/chain.go new file mode 100644 index 0000000..f4df60b --- /dev/null +++ b/v2/internal/chain/chain.go @@ -0,0 +1,110 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package chain + +import ( + F "github.com/IBM/fp-go/v2/function" +) + +// HKTA=HKT[A] +// HKTB=HKT[B] +func MonadChainFirst[A, B, HKTA, HKTB any]( + mchain func(HKTA, func(A) HKTA) HKTA, + mmap func(HKTB, func(B) A) HKTA, + first HKTA, + f func(A) HKTB, +) HKTA { + return mchain(first, func(a A) HKTA { + return mmap(f(a), F.Constant1[B](a)) + }) +} + +func MonadChain[A, B, HKTA, HKTB any]( + mchain func(HKTA, func(A) HKTB) HKTB, + first HKTA, + f func(A) HKTB, +) HKTB { + return mchain(first, f) +} + +// HKTA=HKT[A] +// HKTB=HKT[B] +func ChainFirst[A, B, HKTA, HKTB any]( + mchain func(func(A) HKTA) func(HKTA) HKTA, + mmap func(func(B) A) func(HKTB) HKTA, + f func(A) HKTB) func(HKTA) HKTA { + return mchain(func(a A) HKTA { + return mmap(F.Constant1[B](a))(f(a)) + }) +} + +func Chain[A, B, HKTA, HKTB any]( + mchain func(func(A) HKTB) func(HKTA) HKTB, + f func(A) HKTB, +) func(HKTA) HKTB { + return mchain(f) +} + +func MonadBind[S1, S2, B, HKTS1, HKTS2, HKTB any]( + mchain func(HKTS1, func(S1) HKTS2) HKTS2, + mmap func(HKTB, func(B) S2) HKTS2, + first HKTS1, + key func(B) func(S1) S2, + f func(S1) HKTB, +) HKTS2 { + return mchain(first, func(s1 S1) HKTS2 { + return mmap(f(s1), func(b B) S2 { + return key(b)(s1) + }) + }) +} + +func Bind[S1, S2, B, HKTS1, HKTS2, HKTB any]( + mchain func(func(S1) HKTS2) func(HKTS1) HKTS2, + mmap func(func(B) S2) func(HKTB) HKTS2, + key func(B) func(S1) S2, + f func(S1) HKTB, +) func(HKTS1) HKTS2 { + mapb := F.Flow2( + F.Flip(key), + mmap, + ) + return mchain(func(s1 S1) HKTS2 { + return F.Pipe2( + s1, + f, + F.Pipe1( + s1, + mapb, + ), + ) + }) +} + +func BindTo[S1, B, HKTS1, HKTB any]( + mmap func(func(B) S1) func(HKTB) HKTS1, + key func(B) S1, +) func(fa HKTB) HKTS1 { + return mmap(key) +} + +func MonadBindTo[S1, B, HKTS1, HKTB any]( + mmap func(HKTB, func(B) S1) HKTS1, + first HKTB, + key func(B) S1, +) HKTS1 { + return mmap(first, key) +} diff --git a/v2/internal/chain/testing/laws.go b/v2/internal/chain/testing/laws.go new file mode 100644 index 0000000..52a24e8 --- /dev/null +++ b/v2/internal/chain/testing/laws.go @@ -0,0 +1,170 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package testing + +import ( + "testing" + + E "github.com/IBM/fp-go/v2/eq" + F "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/internal/apply" + L "github.com/IBM/fp-go/v2/internal/apply/testing" + "github.com/IBM/fp-go/v2/internal/chain" + "github.com/IBM/fp-go/v2/internal/functor" + "github.com/IBM/fp-go/v2/internal/pointed" + "github.com/stretchr/testify/assert" +) + +// Chain associativity law +// +// F.chain(F.chain(fa, afb), bfc) <-> F.chain(fa, a => F.chain(afb(a), bfc)) +// +// Deprecated: use [ChainAssertAssociativity] instead +func AssertAssociativity[HKTA, HKTB, HKTC, A, B, C any](t *testing.T, + eq E.Eq[HKTC], + + fofb func(B) HKTB, + fofc func(C) HKTC, + + chainab func(HKTA, func(A) HKTB) HKTB, + chainac func(HKTA, func(A) HKTC) HKTC, + chainbc func(HKTB, func(B) HKTC) HKTC, + + ab func(A) B, + bc func(B) C, +) func(fa HKTA) bool { + return func(fa HKTA) bool { + + afb := F.Flow2(ab, fofb) + bfc := F.Flow2(bc, fofc) + + left := chainbc(chainab(fa, afb), bfc) + + right := chainac(fa, func(a A) HKTC { + return chainbc(afb(a), bfc) + }) + + return assert.True(t, eq.Equals(left, right), "Chain associativity") + } +} + +// Chain associativity law +// +// F.chain(F.chain(fa, afb), bfc) <-> F.chain(fa, a => F.chain(afb(a), bfc)) +func ChainAssertAssociativity[HKTA, HKTB, HKTC, HKTAB, HKTAC, HKTBC, A, B, C any](t *testing.T, + eq E.Eq[HKTC], + + fofb pointed.Pointed[B, HKTB], + fofc pointed.Pointed[C, HKTC], + + chainab chain.Chainable[A, B, HKTA, HKTB, HKTAB], + chainac chain.Chainable[A, C, HKTA, HKTC, HKTAC], + chainbc chain.Chainable[B, C, HKTB, HKTC, HKTBC], + + ab func(A) B, + bc func(B) C, +) func(fa HKTA) bool { + return func(fa HKTA) bool { + + afb := F.Flow2(ab, fofb.Of) + bfc := F.Flow2(bc, fofc.Of) + + left := chainbc.Chain(bfc)(chainab.Chain(afb)(fa)) + + right := chainac.Chain(func(a A) HKTC { + return chainbc.Chain(bfc)(afb(a)) + })(fa) + + return assert.True(t, eq.Equals(left, right), "Chain associativity") + } +} + +// AssertLaws asserts the apply laws `identity`, `composition`, `associative composition` and `associativity` +// +// Deprecated: use [ChainAssertLaws] instead +func AssertLaws[HKTA, HKTB, HKTC, HKTAB, HKTBC, HKTAC, HKTABAC, A, B, C any](t *testing.T, + eqa E.Eq[HKTA], + eqc E.Eq[HKTC], + + fofb func(B) HKTB, + fofc func(C) HKTC, + + fofab func(func(A) B) HKTAB, + fofbc func(func(B) C) HKTBC, + + faa func(HKTA, func(A) A) HKTA, + fab func(HKTA, func(A) B) HKTB, + fac func(HKTA, func(A) C) HKTC, + fbc func(HKTB, func(B) C) HKTC, + + fmap func(HKTBC, func(func(B) C) func(func(A) B) func(A) C) HKTABAC, + + chainab func(HKTA, func(A) HKTB) HKTB, + chainac func(HKTA, func(A) HKTC) HKTC, + chainbc func(HKTB, func(B) HKTC) HKTC, + + fapab func(HKTAB, HKTA) HKTB, + fapbc func(HKTBC, HKTB) HKTC, + fapac func(HKTAC, HKTA) HKTC, + + fapabac func(HKTABAC, HKTAB) HKTAC, + + ab func(A) B, + bc func(B) C, +) func(fa HKTA) bool { + // apply laws + apply := L.AssertLaws(t, eqa, eqc, fofab, fofbc, faa, fab, fac, fbc, fmap, fapab, fapbc, fapac, fapabac, ab, bc) + // chain laws + associativity := AssertAssociativity(t, eqc, fofb, fofc, chainab, chainac, chainbc, ab, bc) + + return func(fa HKTA) bool { + return apply(fa) && associativity(fa) + } +} + +// ChainAssertLaws asserts the apply laws `identity`, `composition`, `associative composition` and `associativity` +func ChainAssertLaws[HKTA, HKTB, HKTC, HKTAB, HKTBC, HKTAC, HKTABAC, A, B, C any](t *testing.T, + eqa E.Eq[HKTA], + eqc E.Eq[HKTC], + + fofb pointed.Pointed[B, HKTB], + fofc pointed.Pointed[C, HKTC], + + fofab pointed.Pointed[func(A) B, HKTAB], + fofbc pointed.Pointed[func(B) C, HKTBC], + + faa functor.Functor[A, A, HKTA, HKTA], + + fmap functor.Functor[func(B) C, func(func(A) B) func(A) C, HKTBC, HKTABAC], + + chainab chain.Chainable[A, B, HKTA, HKTB, HKTAB], + chainac chain.Chainable[A, C, HKTA, HKTC, HKTAC], + chainbc chain.Chainable[B, C, HKTB, HKTC, HKTBC], + + fapabac apply.Apply[func(A) B, func(A) C, HKTAB, HKTAC, HKTABAC], + + ab func(A) B, + bc func(B) C, +) func(fa HKTA) bool { + // apply laws + apply := L.ApplyAssertLaws(t, eqa, eqc, fofab, fofbc, faa, fmap, chain.ToApply(chainab), chain.ToApply(chainbc), chain.ToApply(chainac), fapabac, ab, bc) + // chain laws + associativity := ChainAssertAssociativity(t, eqc, fofb, fofc, chainab, chainac, chainbc, ab, bc) + + return func(fa HKTA) bool { + return apply(fa) && associativity(fa) + } +} diff --git a/v2/internal/chain/types.go b/v2/internal/chain/types.go new file mode 100644 index 0000000..8d89090 --- /dev/null +++ b/v2/internal/chain/types.go @@ -0,0 +1,36 @@ +// Copyright (c) 2024 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package chain + +import ( + "github.com/IBM/fp-go/v2/internal/apply" + "github.com/IBM/fp-go/v2/internal/functor" +) + +type Chainable[A, B, HKTA, HKTB, HKTFAB any] interface { + apply.Apply[A, B, HKTA, HKTB, HKTFAB] + Chain(func(A) HKTB) func(HKTA) HKTB +} + +// ToFunctor converts from [Chainable] to [functor.Functor] +func ToFunctor[A, B, HKTA, HKTB, HKTFAB any](ap Chainable[A, B, HKTA, HKTB, HKTFAB]) functor.Functor[A, B, HKTA, HKTB] { + return ap +} + +// ToApply converts from [Chainable] to [functor.Functor] +func ToApply[A, B, HKTA, HKTB, HKTFAB any](ap Chainable[A, B, HKTA, HKTB, HKTFAB]) apply.Apply[A, B, HKTA, HKTB, HKTFAB] { + return ap +} diff --git a/v2/internal/eithert/either.go b/v2/internal/eithert/either.go new file mode 100644 index 0000000..6b5d214 --- /dev/null +++ b/v2/internal/eithert/either.go @@ -0,0 +1,165 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package eithert + +import ( + ET "github.com/IBM/fp-go/v2/either" + F "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/internal/apply" + FC "github.com/IBM/fp-go/v2/internal/functor" +) + +func MonadAlt[LAZY ~func() HKTFA, E, A, HKTFA any]( + fof func(ET.Either[E, A]) HKTFA, + fchain func(HKTFA, func(ET.Either[E, A]) HKTFA) HKTFA, + + first HKTFA, + second LAZY) HKTFA { + + return fchain(first, ET.Fold(F.Ignore1of1[E](second), F.Flow2(ET.Of[E, A], fof))) +} + +func Alt[LAZY ~func() HKTFA, E, A, HKTFA any]( + fof func(ET.Either[E, A]) HKTFA, + fchain func(HKTFA, func(ET.Either[E, A]) HKTFA) HKTFA, + + second LAZY) func(HKTFA) HKTFA { + + return func(fa HKTFA) HKTFA { + return MonadAlt(fof, fchain, fa, second) + } +} + +// HKTFA = HKT> +// HKTFB = HKT> +func MonadMap[E, A, B, HKTFA, HKTFB any](fmap func(HKTFA, func(ET.Either[E, A]) ET.Either[E, B]) HKTFB, fa HKTFA, f func(A) B) HKTFB { + // HKTGA = Either[E, A] + // HKTGB = Either[E, B] + return FC.MonadMap(fmap, ET.MonadMap[E, A, B], fa, f) +} + +func Map[E, A, B, HKTFA, HKTFB any]( + fmap func(func(ET.Either[E, A]) ET.Either[E, B]) func(HKTFA) HKTFB, + f func(A) B) func(HKTFA) HKTFB { + // HKTGA = Either[E, A] + // HKTGB = Either[E, B] + return FC.Map(fmap, ET.Map[E, A, B], f) +} + +// HKTFA = HKT> +// HKTFB = HKT> +func MonadBiMap[E1, E2, A, B, HKTFA, HKTFB any](fmap func(HKTFA, func(ET.Either[E1, A]) ET.Either[E2, B]) HKTFB, fa HKTFA, f func(E1) E2, g func(A) B) HKTFB { + // HKTGA = Either[E, A] + // HKTGB = Either[E, B] + return fmap(fa, ET.BiMap(f, g)) +} + +// HKTFA = HKT> +// HKTFB = HKT> +func BiMap[E1, E2, A, B, HKTFA, HKTFB any]( + fmap func(func(ET.Either[E1, A]) ET.Either[E2, B]) func(HKTFA) HKTFB, + f func(E1) E2, g func(A) B) func(HKTFA) HKTFB { + // HKTGA = Either[E, A] + // HKTGB = Either[E, B] + return fmap(ET.BiMap(f, g)) +} + +// HKTFA = HKT> +// HKTFB = HKT> +func MonadChain[E, A, B, HKTFA, HKTFB any]( + fchain func(HKTFA, func(ET.Either[E, A]) HKTFB) HKTFB, + fof func(ET.Either[E, B]) HKTFB, + ma HKTFA, + f func(A) HKTFB) HKTFB { + // dispatch to the even more generic implementation + return fchain(ma, ET.Fold(F.Flow2(ET.Left[B, E], fof), f)) +} + +func Chain[E, A, B, HKTFA, HKTFB any]( + fchain func(func(ET.Either[E, A]) HKTFB) func(HKTFA) HKTFB, + fof func(ET.Either[E, B]) HKTFB, + f func(A) HKTFB) func(HKTFA) HKTFB { + // dispatch to the even more generic implementation + return fchain(ET.Fold(F.Flow2(ET.Left[B, E], fof), f)) +} + +func MonadAp[E, A, B, HKTFAB, HKTFGAB, HKTFA, HKTFB any]( + fap func(HKTFGAB, HKTFA) HKTFB, + fmap func(HKTFAB, func(ET.Either[E, func(A) B]) func(ET.Either[E, A]) ET.Either[E, B]) HKTFGAB, + fab HKTFAB, + fa HKTFA) HKTFB { + return apply.MonadAp(fap, fmap, ET.MonadAp[B, E, A], fab, fa) +} + +func Ap[E, A, B, HKTFAB, HKTFGAB, HKTFA, HKTFB any]( + fap func(HKTFA) func(HKTFGAB) HKTFB, + fmap func(func(ET.Either[E, func(A) B]) func(ET.Either[E, A]) ET.Either[E, B]) func(HKTFAB) HKTFGAB, + fa HKTFA) func(HKTFAB) HKTFB { + return apply.Ap(fap, fmap, ET.Ap[B, E, A], fa) +} + +func Right[E, A, HKTA any](fof func(ET.Either[E, A]) HKTA, a A) HKTA { + return F.Pipe2(a, ET.Right[E, A], fof) +} + +func Left[E, A, HKTA any](fof func(ET.Either[E, A]) HKTA, e E) HKTA { + return F.Pipe2(e, ET.Left[A, E], fof) +} + +// HKTA = HKT[A] +// HKTEA = HKT[Either[E, A]] +func RightF[E, A, HKTA, HKTEA any](fmap func(HKTA, func(A) ET.Either[E, A]) HKTEA, fa HKTA) HKTEA { + return fmap(fa, ET.Right[E, A]) +} + +// HKTE = HKT[E] +// HKTEA = HKT[Either[E, A]] +func LeftF[E, A, HKTE, HKTEA any](fmap func(HKTE, func(E) ET.Either[E, A]) HKTEA, fe HKTE) HKTEA { + return fmap(fe, ET.Left[A, E]) +} + +func FoldE[E, A, HKTEA, HKTB any](mchain func(HKTEA, func(ET.Either[E, A]) HKTB) HKTB, ma HKTEA, onLeft func(E) HKTB, onRight func(A) HKTB) HKTB { + return mchain(ma, ET.Fold(onLeft, onRight)) +} + +func MatchE[E, A, HKTEA, HKTB any](mchain func(HKTEA, func(ET.Either[E, A]) HKTB) HKTB, onLeft func(E) HKTB, onRight func(A) HKTB) func(HKTEA) HKTB { + return F.Bind2nd(mchain, ET.Fold(onLeft, onRight)) +} + +func GetOrElse[E, A, HKTEA, HKTA any](mchain func(HKTEA, func(ET.Either[E, A]) HKTA) HKTA, mof func(A) HKTA, onLeft func(E) HKTA) func(HKTEA) HKTA { + return MatchE(mchain, onLeft, mof) +} + +func OrElse[E1, E2, A, HKTE1A, HKTE2A any](mchain func(HKTE1A, func(ET.Either[E1, A]) HKTE2A) HKTE2A, mof func(ET.Either[E2, A]) HKTE2A, onLeft func(E1) HKTE2A) func(HKTE1A) HKTE2A { + return MatchE(mchain, onLeft, F.Flow2(ET.Right[E2, A], mof)) +} + +func OrLeft[E1, E2, A, HKTE1A, HKTE2, HKTE2A any]( + mchain func(HKTE1A, func(ET.Either[E1, A]) HKTE2A) HKTE2A, + mmap func(HKTE2, func(E2) ET.Either[E2, A]) HKTE2A, + mof func(ET.Either[E2, A]) HKTE2A, + onLeft func(E1) HKTE2) func(HKTE1A) HKTE2A { + + return F.Bind2nd(mchain, ET.Fold(F.Flow2(onLeft, F.Bind2nd(mmap, ET.Left[A, E2])), F.Flow2(ET.Right[E2, A], mof))) +} + +func MonadMapLeft[E, A, B, HKTFA, HKTFB any](fmap func(HKTFA, func(ET.Either[E, A]) ET.Either[B, A]) HKTFB, fa HKTFA, f func(E) B) HKTFB { + return FC.MonadMap(fmap, ET.MonadMapLeft[E, A, B], fa, f) +} + +func MapLeft[E, A, B, HKTFA, HKTFB any](fmap func(func(ET.Either[E, A]) ET.Either[B, A]) func(HKTFA) HKTFB, f func(E) B) func(HKTFA) HKTFB { + return FC.Map(fmap, ET.MapLeft[A, E, B], f) +} diff --git a/v2/internal/eq/eq.go b/v2/internal/eq/eq.go new file mode 100644 index 0000000..c4610cd --- /dev/null +++ b/v2/internal/eq/eq.go @@ -0,0 +1,34 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package eq + +import ( + EQ "github.com/IBM/fp-go/v2/eq" + F "github.com/IBM/fp-go/v2/function" +) + +// Eq implements an equals predicate on the basis of `map` and `ap` +func Eq[HKTA, HKTABOOL, HKTBOOL, A any]( + fmap func(HKTA, func(A) func(A) bool) HKTABOOL, + fap func(HKTABOOL, HKTA) HKTBOOL, + + e EQ.Eq[A], +) func(l, r HKTA) HKTBOOL { + c := F.Curry2(e.Equals) + return func(fl, fr HKTA) HKTBOOL { + return fap(fmap(fl, c), fr) + } +} diff --git a/v2/internal/exec/exec.go b/v2/internal/exec/exec.go new file mode 100644 index 0000000..b2a8566 --- /dev/null +++ b/v2/internal/exec/exec.go @@ -0,0 +1,46 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package exec + +import ( + "bytes" + "context" + "fmt" + "os/exec" + + EX "github.com/IBM/fp-go/v2/exec" + + P "github.com/IBM/fp-go/v2/pair" +) + +func Exec(ctx context.Context, name string, args []string, in []byte) (EX.CommandOutput, error) { + // command input + cmd := exec.CommandContext(ctx, name, args...) + cmd.Stdin = bytes.NewReader(in) + // command result + var stdOut bytes.Buffer + var stdErr bytes.Buffer + + cmd.Stdout = &stdOut + cmd.Stderr = &stdErr + // execute the command + err := cmd.Run() + if err != nil { + err = fmt.Errorf("command execution of [%s][%s] failed, stdout [%s], stderr [%s], cause [%w]", name, args, stdOut.String(), stdErr.String(), err) + } + // return the outputs + return P.MakePair(stdOut.Bytes(), stdErr.Bytes()), err +} diff --git a/v2/internal/file/file.go b/v2/internal/file/file.go new file mode 100644 index 0000000..8236ff5 --- /dev/null +++ b/v2/internal/file/file.go @@ -0,0 +1,52 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package file + +import ( + "bytes" + "context" + "io" + + E "github.com/IBM/fp-go/v2/either" +) + +type ( + readerWithContext struct { + ctx context.Context + delegate io.Reader + } +) + +func (rdr *readerWithContext) Read(p []byte) (int, error) { + // check for cancellarion + if err := rdr.ctx.Err(); err != nil { + return 0, err + } + // simply dispatch + return rdr.delegate.Read(p) +} + +// MakeReader creates a context aware reader +func MakeReader(ctx context.Context, rdr io.Reader) io.Reader { + return &readerWithContext{ctx, rdr} +} + +// ReadAll reads the content of a reader and allows it to be canceled +func ReadAll(ctx context.Context, rdr io.Reader) E.Either[error, []byte] { + var buffer bytes.Buffer + _, err := io.Copy(&buffer, MakeReader(ctx, rdr)) + return E.TryCatchError(buffer.Bytes(), err) +} diff --git a/v2/internal/file/resource.go b/v2/internal/file/resource.go new file mode 100644 index 0000000..d33f543 --- /dev/null +++ b/v2/internal/file/resource.go @@ -0,0 +1,64 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package file + +import ( + F "github.com/IBM/fp-go/v2/function" +) + +// WithResource constructs a function that creates a resource, then operates on it and then releases the resource +func WithResource[ + GA, + GR, + GANY, + E, R, A, ANY any]( + mchain func(GR, func(R) GA) GA, + mfold1 func(GA, func(E) GA, func(A) GA) GA, + mfold2 func(GANY, func(E) GA, func(ANY) GA) GA, + mmap func(GANY, func(ANY) A) GA, + left func(E) GA, +) func(onCreate func() GR, onRelease func(R) GANY) func(func(R) GA) GA { + + return func(onCreate func() GR, onRelease func(R) GANY) func(func(R) GA) GA { + + return func(f func(R) GA) GA { + return mchain( + onCreate(), func(r R) GA { + // handle errors + return mfold1( + f(r), + func(e E) GA { + // the original error + err := left(e) + // if resource processing produced and error, still release the resource but return the first error + return mfold2( + onRelease(r), + F.Constant1[E](err), + F.Constant1[ANY](err), + ) + }, + func(a A) GA { + // if resource processing succeeded, release the resource. If this fails return failure, else the original error + return F.Pipe1( + onRelease(r), + F.Bind2nd(mmap, F.Constant1[ANY](a)), + ) + }) + }, + ) + } + } +} diff --git a/v2/internal/foldable/types.go b/v2/internal/foldable/types.go new file mode 100644 index 0000000..2d198a8 --- /dev/null +++ b/v2/internal/foldable/types.go @@ -0,0 +1,26 @@ +// Copyright (c) 2024 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package foldable + +import ( + M "github.com/IBM/fp-go/v2/monoid" +) + +type Foldable[A, B, HKTA any] interface { + Reduce(func(B, A) B, B) func(HKTA) B + ReduceRight(func(B, A) B, B) func(HKTA) B + FoldMap(m M.Monoid[B]) func(func(A) B) func(HKTA) B +} diff --git a/v2/internal/fromeither/either.go b/v2/internal/fromeither/either.go new file mode 100644 index 0000000..163eeeb --- /dev/null +++ b/v2/internal/fromeither/either.go @@ -0,0 +1,93 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package fromeither + +import ( + ET "github.com/IBM/fp-go/v2/either" + F "github.com/IBM/fp-go/v2/function" + C "github.com/IBM/fp-go/v2/internal/chain" + O "github.com/IBM/fp-go/v2/option" +) + +func FromOption[A, HKTEA, E any](fromEither func(ET.Either[E, A]) HKTEA, onNone func() E) func(ma O.Option[A]) HKTEA { + return F.Flow2(ET.FromOption[A](onNone), fromEither) +} + +func FromPredicate[E, A, HKTEA any](fromEither func(ET.Either[E, A]) HKTEA, pred func(A) bool, onFalse func(A) E) func(A) HKTEA { + return F.Flow2(ET.FromPredicate(pred, onFalse), fromEither) +} + +func MonadFromOption[E, A, HKTEA any]( + fromEither func(ET.Either[E, A]) HKTEA, + onNone func() E, + ma O.Option[A], +) HKTEA { + return F.Pipe1( + O.MonadFold( + ma, + F.Nullary2(onNone, ET.Left[A, E]), + ET.Right[E, A], + ), + fromEither, + ) +} + +func FromOptionK[A, E, B, HKTEB any]( + fromEither func(ET.Either[E, B]) HKTEB, + onNone func() E) func(f func(A) O.Option[B]) func(A) HKTEB { + // helper + return F.Bind2nd(F.Flow2[func(A) O.Option[B], func(O.Option[B]) HKTEB, A, O.Option[B], HKTEB], FromOption(fromEither, onNone)) +} + +func MonadChainEitherK[A, E, B, HKTEA, HKTEB any]( + mchain func(HKTEA, func(A) HKTEB) HKTEB, + fromEither func(ET.Either[E, B]) HKTEB, + ma HKTEA, + f func(A) ET.Either[E, B]) HKTEB { + return mchain(ma, F.Flow2(f, fromEither)) +} + +func ChainEitherK[A, E, B, HKTEA, HKTEB any]( + mchain func(func(A) HKTEB) func(HKTEA) HKTEB, + fromEither func(ET.Either[E, B]) HKTEB, + f func(A) ET.Either[E, B]) func(HKTEA) HKTEB { + return mchain(F.Flow2(f, fromEither)) +} + +func ChainOptionK[A, E, B, HKTEA, HKTEB any]( + mchain func(HKTEA, func(A) HKTEB) HKTEB, + fromEither func(ET.Either[E, B]) HKTEB, + onNone func() E, +) func(f func(A) O.Option[B]) func(ma HKTEA) HKTEB { + return F.Flow2(FromOptionK[A](fromEither, onNone), F.Bind1st(F.Bind2nd[HKTEA, func(A) HKTEB, HKTEB], mchain)) +} + +func MonadChainFirstEitherK[A, E, B, HKTEA, HKTEB any]( + mchain func(HKTEA, func(A) HKTEA) HKTEA, + mmap func(HKTEB, func(B) A) HKTEA, + fromEither func(ET.Either[E, B]) HKTEB, + ma HKTEA, + f func(A) ET.Either[E, B]) HKTEA { + return C.MonadChainFirst(mchain, mmap, ma, F.Flow2(f, fromEither)) +} + +func ChainFirstEitherK[A, E, B, HKTEA, HKTEB any]( + mchain func(func(A) HKTEA) func(HKTEA) HKTEA, + mmap func(func(B) A) func(HKTEB) HKTEA, + fromEither func(ET.Either[E, B]) HKTEB, + f func(A) ET.Either[E, B]) func(HKTEA) HKTEA { + return C.ChainFirst(mchain, mmap, F.Flow2(f, fromEither)) +} diff --git a/v2/internal/fromeither/types.go b/v2/internal/fromeither/types.go new file mode 100644 index 0000000..be8d753 --- /dev/null +++ b/v2/internal/fromeither/types.go @@ -0,0 +1,24 @@ +// Copyright (c) 2024 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package fromeither + +import ( + ET "github.com/IBM/fp-go/v2/either" +) + +type FromEither[E, A, HKTA any] interface { + FromEither(ET.Either[E, A]) HKTA +} diff --git a/v2/internal/fromio/io.go b/v2/internal/fromio/io.go new file mode 100644 index 0000000..a54de7d --- /dev/null +++ b/v2/internal/fromio/io.go @@ -0,0 +1,55 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package fromio + +import ( + F "github.com/IBM/fp-go/v2/function" + C "github.com/IBM/fp-go/v2/internal/chain" +) + +func MonadChainFirstIOK[A, B, HKTA, HKTB any, GIOB ~func() B]( + mchain func(HKTA, func(A) HKTA) HKTA, + mmap func(HKTB, func(B) A) HKTA, + fromio func(GIOB) HKTB, + first HKTA, f func(A) GIOB) HKTA { + // chain + return C.MonadChainFirst(mchain, mmap, first, F.Flow2(f, fromio)) +} + +func ChainFirstIOK[A, B, HKTA, HKTB any, GIOB ~func() B]( + mchain func(func(A) HKTA) func(HKTA) HKTA, + mmap func(func(B) A) func(HKTB) HKTA, + fromio func(GIOB) HKTB, + f func(A) GIOB) func(HKTA) HKTA { + // chain + return C.ChainFirst(mchain, mmap, F.Flow2(f, fromio)) +} + +func MonadChainIOK[GR ~func() B, A, B, HKTA, HKTB any]( + mchain func(HKTA, func(A) HKTB) HKTB, + fromio func(GR) HKTB, + first HKTA, f func(A) GR) HKTB { + // chain + return C.MonadChain[A, B](mchain, first, F.Flow2(f, fromio)) +} + +func ChainIOK[GR ~func() B, A, B, HKTA, HKTB any]( + mchain func(func(A) HKTB) func(HKTA) HKTB, + fromio func(GR) HKTB, + f func(A) GR) func(HKTA) HKTB { + // chain + return C.Chain[A, B](mchain, F.Flow2(f, fromio)) +} diff --git a/v2/internal/fromio/types.go b/v2/internal/fromio/types.go new file mode 100644 index 0000000..8f7ffa9 --- /dev/null +++ b/v2/internal/fromio/types.go @@ -0,0 +1,20 @@ +// Copyright (c) 2024 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package fromio + +type FromIO[A, GA ~func() A, HKTA any] interface { + FromIO(GA) HKTA +} diff --git a/v2/internal/fromioeither/ioeither.go b/v2/internal/fromioeither/ioeither.go new file mode 100644 index 0000000..e200327 --- /dev/null +++ b/v2/internal/fromioeither/ioeither.go @@ -0,0 +1,56 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package fromioeither + +import ( + ET "github.com/IBM/fp-go/v2/either" + F "github.com/IBM/fp-go/v2/function" + C "github.com/IBM/fp-go/v2/internal/chain" +) + +func MonadChainFirstIOEitherK[GIOB ~func() ET.Either[E, B], E, A, B, HKTA, HKTB any]( + mchain func(HKTA, func(A) HKTA) HKTA, + mmap func(HKTB, func(B) A) HKTA, + fromio func(GIOB) HKTB, + first HKTA, f func(A) GIOB) HKTA { + // chain + return C.MonadChainFirst(mchain, mmap, first, F.Flow2(f, fromio)) +} + +func ChainFirstIOEitherK[GIOB ~func() ET.Either[E, B], E, A, B, HKTA, HKTB any]( + mchain func(func(A) HKTA) func(HKTA) HKTA, + mmap func(func(B) A) func(HKTB) HKTA, + fromio func(GIOB) HKTB, + f func(A) GIOB) func(HKTA) HKTA { + // chain + return C.ChainFirst(mchain, mmap, F.Flow2(f, fromio)) +} + +func MonadChainIOEitherK[GIOB ~func() ET.Either[E, B], E, A, B, HKTA, HKTB any]( + mchain func(HKTA, func(A) HKTB) HKTB, + fromio func(GIOB) HKTB, + first HKTA, f func(A) GIOB) HKTB { + // chain + return C.MonadChain[A, B](mchain, first, F.Flow2(f, fromio)) +} + +func ChainIOEitherK[GIOB ~func() ET.Either[E, B], E, A, B, HKTA, HKTB any]( + mchain func(func(A) HKTB) func(HKTA) HKTB, + fromio func(GIOB) HKTB, + f func(A) GIOB) func(HKTA) HKTB { + // chain + return C.Chain[A, B](mchain, F.Flow2(f, fromio)) +} diff --git a/v2/internal/fromreader/reader.go b/v2/internal/fromreader/reader.go new file mode 100644 index 0000000..d654fcf --- /dev/null +++ b/v2/internal/fromreader/reader.go @@ -0,0 +1,54 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package fromreader + +import ( + F "github.com/IBM/fp-go/v2/function" + G "github.com/IBM/fp-go/v2/reader/generic" +) + +func Ask[GR ~func(R) R, R, HKTRA any](fromReader func(GR) HKTRA) func() HKTRA { + return func() HKTRA { + return fromReader(G.Ask[GR]()) + } +} + +func Asks[GA ~func(R) A, R, A, HKTRA any](fromReader func(GA) HKTRA) func(GA) HKTRA { + return fromReader +} + +func FromReaderK[GB ~func(R) B, R, A, B, HKTRB any]( + fromReader func(GB) HKTRB, + f func(A) GB) func(A) HKTRB { + return F.Flow2(f, fromReader) +} + +func MonadChainReaderK[GB ~func(R) B, R, A, B, HKTRA, HKTRB any]( + mchain func(HKTRA, func(A) HKTRB) HKTRB, + fromReader func(GB) HKTRB, + ma HKTRA, + f func(A) GB, +) HKTRB { + return mchain(ma, FromReaderK(fromReader, f)) +} + +func ChainReaderK[GB ~func(R) B, R, A, B, HKTRA, HKTRB any]( + mchain func(HKTRA, func(A) HKTRB) HKTRB, + fromReader func(GB) HKTRB, + f func(A) GB, +) func(HKTRA) HKTRB { + return F.Bind2nd(mchain, FromReaderK(fromReader, f)) +} diff --git a/v2/internal/functor/flap.go b/v2/internal/functor/flap.go new file mode 100644 index 0000000..3ec843e --- /dev/null +++ b/v2/internal/functor/flap.go @@ -0,0 +1,38 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package functor + +func flap[FAB ~func(A) B, A, B any](a A) func(FAB) B { + return func(f FAB) B { + return f(a) + } +} + +func MonadFlap[FAB ~func(A) B, A, B, HKTFAB, HKTB any]( + fmap func(HKTFAB, func(FAB) B) HKTB, + + fab HKTFAB, + a A, +) HKTB { + return fmap(fab, flap[FAB, A, B](a)) +} + +func Flap[FAB ~func(A) B, A, B, HKTFAB, HKTB any]( + fmap func(func(FAB) B) func(HKTFAB) HKTB, + a A, +) func(HKTFAB) HKTB { + return fmap(flap[FAB, A, B](a)) +} diff --git a/v2/internal/functor/functor.go b/v2/internal/functor/functor.go new file mode 100644 index 0000000..815faa2 --- /dev/null +++ b/v2/internal/functor/functor.go @@ -0,0 +1,66 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package functor + +import ( + F "github.com/IBM/fp-go/v2/function" +) + +// HKTFGA = HKT[F, HKT[G, A]] +// HKTFGB = HKT[F, HKT[G, B]] +func MonadMap[A, B, HKTGA, HKTGB, HKTFGA, HKTFGB any]( + fmap func(HKTFGA, func(HKTGA) HKTGB) HKTFGB, + gmap func(HKTGA, func(A) B) HKTGB, + fa HKTFGA, + f func(A) B) HKTFGB { + return fmap(fa, F.Bind2nd(gmap, f)) +} + +func Map[A, B, HKTGA, HKTGB, HKTFGA, HKTFGB any]( + fmap func(func(HKTGA) HKTGB) func(HKTFGA) HKTFGB, + gmap func(func(A) B) func(HKTGA) HKTGB, + f func(A) B) func(HKTFGA) HKTFGB { + return fmap(gmap(f)) +} + +func MonadLet[S1, S2, B, HKTS1, HKTS2 any]( + mmap func(HKTS1, func(S1) S2) HKTS2, + first HKTS1, + key func(B) func(S1) S2, + f func(S1) B, +) HKTS2 { + return mmap(first, func(s1 S1) S2 { + return key(f(s1))(s1) + }) +} + +func Let[S1, S2, B, HKTS1, HKTS2 any]( + mmap func(func(S1) S2) func(HKTS1) HKTS2, + key func(B) func(S1) S2, + f func(S1) B, +) func(HKTS1) HKTS2 { + return mmap(func(s1 S1) S2 { + return key(f(s1))(s1) + }) +} + +func LetTo[S1, S2, B, HKTS1, HKTS2 any]( + mmap func(func(S1) S2) func(HKTS1) HKTS2, + key func(B) func(S1) S2, + b B, +) func(HKTS1) HKTS2 { + return mmap(key(b)) +} diff --git a/v2/internal/functor/testing/laws.go b/v2/internal/functor/testing/laws.go new file mode 100644 index 0000000..13c4754 --- /dev/null +++ b/v2/internal/functor/testing/laws.go @@ -0,0 +1,143 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package testing + +import ( + "testing" + + E "github.com/IBM/fp-go/v2/eq" + F "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/internal/functor" + "github.com/stretchr/testify/assert" +) + +// Functor identity law +// +// F.map(fa, a => a) <-> fa +// +// Deprecated: use [FunctorAssertIdentity] +func AssertIdentity[HKTA, A any](t *testing.T, eq E.Eq[HKTA], fmap func(HKTA, func(A) A) HKTA) func(fa HKTA) bool { + t.Helper() + return func(fa HKTA) bool { + return assert.True(t, eq.Equals(fa, fmap(fa, F.Identity[A])), "Functor identity law") + } +} + +// Functor identity law +// +// F.map(fa, a => a) <-> fa +func FunctorAssertIdentity[HKTA, A any]( + t *testing.T, + eq E.Eq[HKTA], + + fca functor.Functor[A, A, HKTA, HKTA], +) func(fa HKTA) bool { + + t.Helper() + return func(fa HKTA) bool { + + return assert.True(t, eq.Equals(fa, fca.Map(F.Identity[A])(fa)), "Functor identity law") + } +} + +// Functor composition law +// +// F.map(fa, a => bc(ab(a))) <-> F.map(F.map(fa, ab), bc) +// +// Deprecated: use [FunctorAssertComposition] instead +func AssertComposition[HKTA, HKTB, HKTC, A, B, C any]( + t *testing.T, + + eq E.Eq[HKTC], + + fab func(HKTA, func(A) B) HKTB, + fac func(HKTA, func(A) C) HKTC, + fbc func(HKTB, func(B) C) HKTC, + ab func(A) B, + bc func(B) C, +) func(fa HKTA) bool { + t.Helper() + return func(fa HKTA) bool { + return assert.True(t, eq.Equals(fac(fa, F.Flow2(ab, bc)), fbc(fab(fa, ab), bc)), "Functor composition law") + } +} + +// Functor composition law +// +// F.map(fa, a => bc(ab(a))) <-> F.map(F.map(fa, ab), bc) +func FunctorAssertComposition[HKTA, HKTB, HKTC, A, B, C any]( + t *testing.T, + + eq E.Eq[HKTC], + + fab functor.Functor[A, B, HKTA, HKTB], + fac functor.Functor[A, C, HKTA, HKTC], + fbc functor.Functor[B, C, HKTB, HKTC], + + ab func(A) B, + bc func(B) C, +) func(fa HKTA) bool { + t.Helper() + return func(fa HKTA) bool { + return assert.True(t, eq.Equals(fac.Map(F.Flow2(ab, bc))(fa), fbc.Map(bc)(fab.Map(ab)(fa))), "Functor composition law") + } +} + +// AssertLaws asserts the functor laws `identity` and `composition` +// +// Deprecated: use [FunctorAssertLaws] instead +func AssertLaws[HKTA, HKTB, HKTC, A, B, C any](t *testing.T, + eqa E.Eq[HKTA], + eqc E.Eq[HKTC], + + faa func(HKTA, func(A) A) HKTA, + fab func(HKTA, func(A) B) HKTB, + fac func(HKTA, func(A) C) HKTC, + fbc func(HKTB, func(B) C) HKTC, + + ab func(A) B, + bc func(B) C, +) func(fa HKTA) bool { + t.Helper() + identity := AssertIdentity(t, eqa, faa) + composition := AssertComposition(t, eqc, fab, fac, fbc, ab, bc) + + return func(fa HKTA) bool { + return identity(fa) && composition(fa) + } +} + +// FunctorAssertLaws asserts the functor laws `identity` and `composition` +func FunctorAssertLaws[HKTA, HKTB, HKTC, A, B, C any](t *testing.T, + eqa E.Eq[HKTA], + eqc E.Eq[HKTC], + + faa functor.Functor[A, A, HKTA, HKTA], + fab functor.Functor[A, B, HKTA, HKTB], + fac functor.Functor[A, C, HKTA, HKTC], + fbc functor.Functor[B, C, HKTB, HKTC], + + ab func(A) B, + bc func(B) C, +) func(fa HKTA) bool { + t.Helper() + identity := FunctorAssertIdentity(t, eqa, faa) + composition := FunctorAssertComposition(t, eqc, fab, fac, fbc, ab, bc) + + return func(fa HKTA) bool { + return identity(fa) && composition(fa) + } +} diff --git a/v2/internal/functor/types.go b/v2/internal/functor/types.go new file mode 100644 index 0000000..3abac59 --- /dev/null +++ b/v2/internal/functor/types.go @@ -0,0 +1,20 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package functor + +type Functor[A, B, HKTA, HKTB any] interface { + Map(func(A) B) func(HKTA) HKTB +} diff --git a/v2/internal/lazy/memoize.go b/v2/internal/lazy/memoize.go new file mode 100644 index 0000000..9ca3f2e --- /dev/null +++ b/v2/internal/lazy/memoize.go @@ -0,0 +1,34 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package lazy + +import "sync" + +// Memoize computes the value of the provided IO monad lazily but exactly once +func Memoize[GA ~func() A, A any](ma GA) GA { + // synchronization primitives + var once sync.Once + var result A + // callback + gen := func() { + result = ma() + } + // returns our memoized wrapper + return func() A { + once.Do(gen) + return result + } +} diff --git a/v2/internal/monad/monad.go b/v2/internal/monad/monad.go new file mode 100644 index 0000000..82c9626 --- /dev/null +++ b/v2/internal/monad/monad.go @@ -0,0 +1,54 @@ +// Copyright (c) 2024 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package monad + +import ( + "github.com/IBM/fp-go/v2/internal/applicative" + "github.com/IBM/fp-go/v2/internal/apply" + "github.com/IBM/fp-go/v2/internal/chain" + "github.com/IBM/fp-go/v2/internal/functor" + "github.com/IBM/fp-go/v2/internal/pointed" +) + +type Monad[A, B, HKTA, HKTB, HKTFAB any] interface { + applicative.Applicative[A, B, HKTA, HKTB, HKTFAB] + chain.Chainable[A, B, HKTA, HKTB, HKTFAB] +} + +// ToFunctor converts from [Monad] to [functor.Functor] +func ToFunctor[A, B, HKTA, HKTB, HKTFAB any](ap Monad[A, B, HKTA, HKTB, HKTFAB]) functor.Functor[A, B, HKTA, HKTB] { + return ap +} + +// ToApply converts from [Monad] to [apply.Apply] +func ToApply[A, B, HKTA, HKTB, HKTFAB any](ap Monad[A, B, HKTA, HKTB, HKTFAB]) apply.Apply[A, B, HKTA, HKTB, HKTFAB] { + return ap +} + +// ToPointed converts from [Monad] to [pointed.Pointed] +func ToPointed[A, B, HKTA, HKTB, HKTFAB any](ap Monad[A, B, HKTA, HKTB, HKTFAB]) pointed.Pointed[A, HKTA] { + return ap +} + +// ToApplicative converts from [Monad] to [applicative.Applicative] +func ToApplicative[A, B, HKTA, HKTB, HKTFAB any](ap Monad[A, B, HKTA, HKTB, HKTFAB]) applicative.Applicative[A, B, HKTA, HKTB, HKTFAB] { + return ap +} + +// ToChainable converts from [Monad] to [chain.Chainable] +func ToChainable[A, B, HKTA, HKTB, HKTFAB any](ap Monad[A, B, HKTA, HKTB, HKTFAB]) chain.Chainable[A, B, HKTA, HKTB, HKTFAB] { + return ap +} diff --git a/v2/internal/monad/testing/laws.go b/v2/internal/monad/testing/laws.go new file mode 100644 index 0000000..eefd47a --- /dev/null +++ b/v2/internal/monad/testing/laws.go @@ -0,0 +1,228 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package testing + +import ( + "testing" + + E "github.com/IBM/fp-go/v2/eq" + "github.com/IBM/fp-go/v2/internal/applicative" + LA "github.com/IBM/fp-go/v2/internal/applicative/testing" + "github.com/IBM/fp-go/v2/internal/chain" + LC "github.com/IBM/fp-go/v2/internal/chain/testing" + "github.com/IBM/fp-go/v2/internal/functor" + "github.com/IBM/fp-go/v2/internal/monad" + "github.com/IBM/fp-go/v2/internal/pointed" + "github.com/stretchr/testify/assert" +) + +// Apply monad left identity law +// +// M.chain(M.of(a), f) <-> f(a) +// +// Deprecated: use [MonadAssertLeftIdentity] instead +func AssertLeftIdentity[HKTA, HKTB, A, B any](t *testing.T, + eq E.Eq[HKTB], + + fofa func(A) HKTA, + fofb func(B) HKTB, + + fchain func(HKTA, func(A) HKTB) HKTB, + + ab func(A) B, +) func(a A) bool { + return func(a A) bool { + + f := func(a A) HKTB { + return fofb(ab(a)) + } + + left := fchain(fofa(a), f) + right := f(a) + + return assert.True(t, eq.Equals(left, right), "Monad left identity") + } +} + +// Apply monad left identity law +// +// M.chain(M.of(a), f) <-> f(a) +func MonadAssertLeftIdentity[HKTA, HKTB, HKTFAB, A, B any](t *testing.T, + eq E.Eq[HKTB], + + fofb pointed.Pointed[B, HKTB], + + ma monad.Monad[A, B, HKTA, HKTB, HKTFAB], + + ab func(A) B, +) func(a A) bool { + return func(a A) bool { + + f := func(a A) HKTB { + return fofb.Of(ab(a)) + } + + left := ma.Chain(f)(ma.Of(a)) + right := f(a) + + return assert.True(t, eq.Equals(left, right), "Monad left identity") + } +} + +// Apply monad right identity law +// +// M.chain(fa, M.of) <-> fa +// +// Deprecated: use [MonadAssertRightIdentity] instead +func AssertRightIdentity[HKTA, A any](t *testing.T, + eq E.Eq[HKTA], + + fofa func(A) HKTA, + + fchain func(HKTA, func(A) HKTA) HKTA, +) func(fa HKTA) bool { + return func(fa HKTA) bool { + + left := fchain(fa, fofa) + right := fa + + return assert.True(t, eq.Equals(left, right), "Monad right identity") + } +} + +// Apply monad right identity law +// +// M.chain(fa, M.of) <-> fa +func MonadAssertRightIdentity[HKTA, HKTAA, A any](t *testing.T, + eq E.Eq[HKTA], + + ma monad.Monad[A, A, HKTA, HKTA, HKTAA], + +) func(fa HKTA) bool { + return func(fa HKTA) bool { + + left := ma.Chain(ma.Of)(fa) + right := fa + + return assert.True(t, eq.Equals(left, right), "Monad right identity") + } +} + +// AssertLaws asserts the apply laws `identity`, `composition`, `associative composition`, 'applicative identity', 'homomorphism', 'interchange', `associativity`, `left identity`, `right identity` +// +// Deprecated: use [MonadAssertLaws] instead +func AssertLaws[HKTA, HKTB, HKTC, HKTAA, HKTAB, HKTBC, HKTAC, HKTABB, HKTABAC, A, B, C any](t *testing.T, + eqa E.Eq[HKTA], + eqb E.Eq[HKTB], + eqc E.Eq[HKTC], + + fofa func(A) HKTA, + fofb func(B) HKTB, + fofc func(C) HKTC, + + fofaa func(func(A) A) HKTAA, + fofab func(func(A) B) HKTAB, + fofbc func(func(B) C) HKTBC, + fofabb func(func(func(A) B) B) HKTABB, + + faa func(HKTA, func(A) A) HKTA, + fab func(HKTA, func(A) B) HKTB, + fac func(HKTA, func(A) C) HKTC, + fbc func(HKTB, func(B) C) HKTC, + + fmap func(HKTBC, func(func(B) C) func(func(A) B) func(A) C) HKTABAC, + + chainaa func(HKTA, func(A) HKTA) HKTA, + chainab func(HKTA, func(A) HKTB) HKTB, + chainac func(HKTA, func(A) HKTC) HKTC, + chainbc func(HKTB, func(B) HKTC) HKTC, + + fapaa func(HKTAA, HKTA) HKTA, + fapab func(HKTAB, HKTA) HKTB, + fapbc func(HKTBC, HKTB) HKTC, + fapac func(HKTAC, HKTA) HKTC, + + fapabb func(HKTABB, HKTAB) HKTB, + fapabac func(HKTABAC, HKTAB) HKTAC, + + ab func(A) B, + bc func(B) C, +) func(a A) bool { + // applicative laws + applicative := LA.AssertLaws(t, eqa, eqb, eqc, fofa, fofb, fofaa, fofab, fofbc, fofabb, faa, fab, fac, fbc, fmap, fapaa, fapab, fapbc, fapac, fapabb, fapabac, ab, bc) + // chain laws + chain := LC.AssertLaws(t, eqa, eqc, fofb, fofc, fofab, fofbc, faa, fab, fac, fbc, fmap, chainab, chainac, chainbc, fapab, fapbc, fapac, fapabac, ab, bc) + // monad laws + leftIdentity := AssertLeftIdentity(t, eqb, fofa, fofb, chainab, ab) + rightIdentity := AssertRightIdentity(t, eqa, fofa, chainaa) + + return func(a A) bool { + fa := fofa(a) + return applicative(a) && chain(fa) && leftIdentity(a) && rightIdentity(fa) + } +} + +// MonadAssertLaws asserts the apply laws `identity`, `composition`, `associative composition`, 'applicative identity', 'homomorphism', 'interchange', `associativity`, `left identity`, `right identity` +func MonadAssertLaws[HKTA, HKTB, HKTC, HKTAA, HKTAB, HKTBC, HKTAC, HKTABB, HKTABAC, A, B, C any](t *testing.T, + eqa E.Eq[HKTA], + eqb E.Eq[HKTB], + eqc E.Eq[HKTC], + + fofc pointed.Pointed[C, HKTC], + fofaa pointed.Pointed[func(A) A, HKTAA], + fofbc pointed.Pointed[func(B) C, HKTBC], + fofabb pointed.Pointed[func(func(A) B) B, HKTABB], + + fmap functor.Functor[func(B) C, func(func(A) B) func(A) C, HKTBC, HKTABAC], + + fapabb applicative.Applicative[func(A) B, B, HKTAB, HKTB, HKTABB], + fapabac applicative.Applicative[func(A) B, func(A) C, HKTAB, HKTAC, HKTABAC], + + maa monad.Monad[A, A, HKTA, HKTA, HKTAA], + mab monad.Monad[A, B, HKTA, HKTB, HKTAB], + mac monad.Monad[A, C, HKTA, HKTC, HKTAC], + mbc monad.Monad[B, C, HKTB, HKTC, HKTBC], + + ab func(A) B, + bc func(B) C, +) func(a A) bool { + // derivations + fofa := monad.ToPointed(maa) + fofb := monad.ToPointed(mbc) + fofab := applicative.ToPointed(fapabb) + fapaa := monad.ToApplicative(maa) + fapab := monad.ToApplicative(mab) + chainab := monad.ToChainable(mab) + chainac := monad.ToChainable(mac) + chainbc := monad.ToChainable(mbc) + fapbc := chain.ToApply(chainbc) + fapac := chain.ToApply(chainac) + + faa := monad.ToFunctor(maa) + + // applicative laws + apLaw := LA.ApplicativeAssertLaws(t, eqa, eqb, eqc, fofb, fofaa, fofbc, fofabb, faa, fmap, fapaa, fapab, fapbc, fapac, fapabb, fapabac, ab, bc) + // chain laws + chainLaw := LC.ChainAssertLaws(t, eqa, eqc, fofb, fofc, fofab, fofbc, faa, fmap, chainab, chainac, chainbc, applicative.ToApply(fapabac), ab, bc) + // monad laws + leftIdentity := MonadAssertLeftIdentity(t, eqb, fofb, mab, ab) + rightIdentity := MonadAssertRightIdentity(t, eqa, maa) + + return func(a A) bool { + fa := fofa.Of(a) + return apLaw(a) && chainLaw(fa) && leftIdentity(a) && rightIdentity(fa) + } +} diff --git a/v2/internal/optiont/option.go b/v2/internal/optiont/option.go new file mode 100644 index 0000000..9b9dad0 --- /dev/null +++ b/v2/internal/optiont/option.go @@ -0,0 +1,125 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package optiont + +import ( + F "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/internal/apply" + FC "github.com/IBM/fp-go/v2/internal/functor" + O "github.com/IBM/fp-go/v2/option" +) + +func Of[A, HKTA any](fof func(O.Option[A]) HKTA, a A) HKTA { + return F.Pipe2(a, O.Of[A], fof) +} + +func None[A, HKTA any](fof func(O.Option[A]) HKTA) HKTA { + return F.Pipe1(O.None[A](), fof) +} + +func OfF[A, HKTA, HKTEA any](fmap func(HKTA, func(A) O.Option[A]) HKTEA, fa HKTA) HKTEA { + return fmap(fa, O.Of[A]) +} + +func MonadMap[A, B, HKTFA, HKTFB any](fmap func(HKTFA, func(O.Option[A]) O.Option[B]) HKTFB, fa HKTFA, f func(A) B) HKTFB { + // HKTGA = Either[E, A] + // HKTGB = Either[E, B] + return FC.MonadMap(fmap, O.MonadMap[A, B], fa, f) +} + +func Map[A, B, HKTFA, HKTFB any](fmap func(func(O.Option[A]) O.Option[B]) func(HKTFA) HKTFB, f func(A) B) func(HKTFA) HKTFB { + // HKTGA = Either[E, A] + // HKTGB = Either[E, B] + return FC.Map(fmap, O.Map[A, B], f) +} + +func MonadChain[A, B, HKTFA, HKTFB any]( + fchain func(HKTFA, func(O.Option[A]) HKTFB) HKTFB, + fof func(O.Option[B]) HKTFB, + ma HKTFA, + f func(A) HKTFB) HKTFB { + // dispatch to the even more generic implementation + return fchain(ma, O.Fold(F.Nullary2(O.None[B], fof), f)) +} + +func Chain[A, B, HKTFA, HKTFB any]( + fchain func(func(O.Option[A]) HKTFB) func(HKTFA) HKTFB, + fof func(O.Option[B]) HKTFB, + f func(A) HKTFB) func(ma HKTFA) HKTFB { + // dispatch to the even more generic implementation + return fchain(O.Fold(F.Nullary2(O.None[B], fof), f)) +} + +func MonadAp[A, B, HKTFAB, HKTFGAB, HKTFA, HKTFB any]( + fap func(HKTFGAB, HKTFA) HKTFB, + fmap func(HKTFAB, func(O.Option[func(A) B]) func(O.Option[A]) O.Option[B]) HKTFGAB, + fab HKTFAB, + fa HKTFA) HKTFB { + return apply.MonadAp(fap, fmap, O.MonadAp[B, A], fab, fa) +} + +func Ap[A, B, HKTFAB, HKTFGAB, HKTFA, HKTFB any]( + fap func(HKTFA) func(HKTFGAB) HKTFB, + fmap func(func(O.Option[func(A) B]) func(O.Option[A]) O.Option[B]) func(HKTFAB) HKTFGAB, + fa HKTFA) func(HKTFAB) HKTFB { + return apply.Ap(fap, fmap, O.Ap[B, A], fa) +} + +func MatchE[A, HKTEA, HKTB any](mchain func(HKTEA, func(O.Option[A]) HKTB) HKTB, onNone func() HKTB, onSome func(A) HKTB) func(HKTEA) HKTB { + return F.Bind2nd(mchain, O.Fold(onNone, onSome)) +} + +func FromOptionK[A, B, HKTB any]( + fof func(O.Option[B]) HKTB, + f func(A) O.Option[B]) func(A) HKTB { + return F.Flow2(f, fof) +} + +func MonadChainOptionK[A, B, HKTA, HKTB any]( + fchain func(HKTA, func(O.Option[A]) HKTB) HKTB, + fof func(O.Option[B]) HKTB, + ma HKTA, + f func(A) O.Option[B], +) HKTB { + return MonadChain(fchain, fof, ma, FromOptionK(fof, f)) +} + +func ChainOptionK[A, B, HKTA, HKTB any]( + fchain func(func(O.Option[A]) HKTB) func(HKTA) HKTB, + fof func(O.Option[B]) HKTB, + f func(A) O.Option[B], +) func(HKTA) HKTB { + return Chain(fchain, fof, FromOptionK(fof, f)) +} + +func MonadAlt[LAZY ~func() HKTFA, A, HKTFA any]( + fof func(O.Option[A]) HKTFA, + fchain func(HKTFA, func(O.Option[A]) HKTFA) HKTFA, + + first HKTFA, + second LAZY) HKTFA { + + return fchain(first, O.Fold(second, F.Flow2(O.Of[A], fof))) +} + +func Alt[LAZY ~func() HKTFA, A, HKTFA any]( + fof func(O.Option[A]) HKTFA, + fchain func(func(O.Option[A]) HKTFA) func(HKTFA) HKTFA, + + second LAZY) func(HKTFA) HKTFA { + + return fchain(O.Fold(second, F.Flow2(O.Of[A], fof))) +} diff --git a/v2/internal/pointed/types.go b/v2/internal/pointed/types.go new file mode 100644 index 0000000..8f126a6 --- /dev/null +++ b/v2/internal/pointed/types.go @@ -0,0 +1,21 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package pointed + +type Pointed[A, HKTA any] interface { + // Of lifts a value into its higher kinded type + Of(A) HKTA +} diff --git a/v2/internal/readert/reader.go b/v2/internal/readert/reader.go new file mode 100644 index 0000000..cc459bb --- /dev/null +++ b/v2/internal/readert/reader.go @@ -0,0 +1,92 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readert + +import ( + F "github.com/IBM/fp-go/v2/function" + R "github.com/IBM/fp-go/v2/reader/generic" +) + +// here we implement the monadic operations using callbacks from +// higher kinded types, as good a golang allows use to do this + +func MonadMap[GEA ~func(E) HKTA, GEB ~func(E) HKTB, E, A, B, HKTA, HKTB any]( + fmap func(HKTA, func(A) B) HKTB, + fa GEA, + f func(A) B, +) GEB { + return R.MonadMap[GEA, GEB](fa, F.Bind2nd(fmap, f)) +} + +func Map[GEA ~func(E) HKTA, GEB ~func(E) HKTB, E, A, B, HKTA, HKTB any]( + fmap func(func(A) B) func(HKTA) HKTB, + f func(A) B, +) func(GEA) GEB { + return F.Pipe2( + f, + fmap, + R.Map[GEA, GEB, E, HKTA, HKTB], + ) +} + +func MonadChain[GEA ~func(E) HKTA, GEB ~func(E) HKTB, A, E, HKTA, HKTB any](fchain func(HKTA, func(A) HKTB) HKTB, ma GEA, f func(A) GEB) GEB { + return R.MakeReader(func(r E) HKTB { + return fchain(ma(r), func(a A) HKTB { + return f(a)(r) + }) + }) +} + +func Chain[GEA ~func(E) HKTA, GEB ~func(E) HKTB, A, E, HKTA, HKTB any](fchain func(func(A) HKTB) func(HKTA) HKTB, f func(A) GEB) func(GEA) GEB { + return func(ma GEA) GEB { + return R.MakeReader(func(r E) HKTB { + return fchain(func(a A) HKTB { + return f(a)(r) + })(ma(r)) + }) + } +} + +func MonadOf[GEA ~func(E) HKTA, E, A, HKTA any](fof func(A) HKTA, a A) GEA { + return R.MakeReader(func(_ E) HKTA { + return fof(a) + }) +} + +// HKTFAB = HKT[func(A)B] +func MonadAp[GEA ~func(E) HKTA, GEB ~func(E) HKTB, GEFAB ~func(E) HKTFAB, E, A, HKTA, HKTB, HKTFAB any](fap func(HKTFAB, HKTA) HKTB, fab GEFAB, fa GEA) GEB { + return R.MakeReader(func(r E) HKTB { + return fap(fab(r), fa(r)) + }) +} + +func Ap[GEA ~func(E) HKTA, GEB ~func(E) HKTB, GEFAB ~func(E) HKTFAB, E, A, HKTA, HKTB, HKTFAB any](fap func(HKTA) func(HKTFAB) HKTB, fa GEA) func(GEFAB) GEB { + return func(fab GEFAB) GEB { + return func(r E) HKTB { + return fap(fa(r))(fab(r)) + } + } +} + +func MonadFromReader[GA ~func(E) A, GEA ~func(E) HKTA, E, A, HKTA any]( + fof func(A) HKTA, ma GA) GEA { + return R.MakeReader(F.Flow2(ma, fof)) +} + +func FromReader[GA ~func(E) A, GEA ~func(E) HKTA, E, A, HKTA any]( + fof func(A) HKTA) func(ma GA) GEA { + return F.Bind1st(MonadFromReader[GA, GEA, E, A, HKTA], fof) +} diff --git a/v2/internal/record/record.go b/v2/internal/record/record.go new file mode 100644 index 0000000..f9eb184 --- /dev/null +++ b/v2/internal/record/record.go @@ -0,0 +1,48 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package record + +func Reduce[M ~map[K]V, K comparable, V, R any](r M, f func(R, V) R, initial R) R { + current := initial + for _, v := range r { + current = f(current, v) + } + return current +} + +func ReduceWithIndex[M ~map[K]V, K comparable, V, R any](r M, f func(K, R, V) R, initial R) R { + current := initial + for k, v := range r { + current = f(k, current, v) + } + return current +} + +func ReduceRef[M ~map[K]V, K comparable, V, R any](r M, f func(R, *V) R, initial R) R { + current := initial + for _, v := range r { + current = f(current, &v) // #nosec G601 + } + return current +} + +func ReduceRefWithIndex[M ~map[K]V, K comparable, V, R any](r M, f func(K, R, *V) R, initial R) R { + current := initial + for k, v := range r { + current = f(k, current, &v) // #nosec G601 + } + return current +} diff --git a/v2/internal/record/traverse.go b/v2/internal/record/traverse.go new file mode 100644 index 0000000..14575eb --- /dev/null +++ b/v2/internal/record/traverse.go @@ -0,0 +1,109 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package record + +import ( + F "github.com/IBM/fp-go/v2/function" +) + +// createEmpty creates a new empty, read-write map +// this is different to Empty which creates a new read-only empty map +func createEmpty[N ~map[K]A, K comparable, A any]() N { + return make(N) +} + +// inserts the key/value pair into a read-write map for performance +// order of parameters is adjusted to be curryable +func addKey[N ~map[K]A, K comparable, A any](key K, m N, value A) N { + m[key] = value + return m +} + +/* +* +We need to pass the members of the applicative explicitly, because golang does neither support higher kinded types nor template methods on structs or interfaces + +HKTRB = HKT +HKTA = HKT +HKTB = HKT +HKTAB = HKT +*/ +func traverseWithIndex[MA ~map[K]A, MB ~map[K]B, K comparable, A, B, HKTB, HKTAB, HKTRB any]( + fof func(MB) HKTRB, + fmap func(func(MB) func(B) MB) func(HKTRB) HKTAB, + fap func(HKTB) func(HKTAB) HKTRB, + + ta MA, f func(K, A) HKTB) HKTRB { + // this function inserts a value into a map with a given key + mmap := F.Flow2(F.Curry3(addKey[MB, K, B]), fmap) + + return ReduceWithIndex(ta, func(k K, r HKTRB, a A) HKTRB { + return F.Pipe2( + r, + mmap(k), + fap(f(k, a)), + ) + }, fof(createEmpty[MB]())) +} + +func MonadTraverse[MA ~map[K]A, MB ~map[K]B, K comparable, A, B, HKTB, HKTAB, HKTRB any]( + fof func(MB) HKTRB, + fmap func(func(MB) func(B) MB) func(HKTRB) HKTAB, + fap func(HKTB) func(HKTAB) HKTRB, + + r MA, f func(A) HKTB) HKTRB { + return traverseWithIndex(fof, fmap, fap, r, F.Ignore1of2[K](f)) +} + +func TraverseWithIndex[MA ~map[K]A, MB ~map[K]B, K comparable, A, B, HKTB, HKTAB, HKTRB any]( + fof func(MB) HKTRB, + fmap func(func(MB) func(B) MB) func(HKTRB) HKTAB, + fap func(HKTB) func(HKTAB) HKTRB, + + f func(K, A) HKTB) func(MA) HKTRB { + + return func(ma MA) HKTRB { + return traverseWithIndex(fof, fmap, fap, ma, f) + } +} + +// HKTA = HKT +// HKTB = HKT +// HKTAB = HKT +// HKTRB = HKT +func Traverse[MA ~map[K]A, MB ~map[K]B, K comparable, A, B, HKTB, HKTAB, HKTRB any]( + fof func(MB) HKTRB, + fmap func(func(MB) func(B) MB) func(HKTRB) HKTAB, + fap func(HKTB) func(HKTAB) HKTRB, + + f func(A) HKTB) func(MA) HKTRB { + + return func(ma MA) HKTRB { + return MonadTraverse(fof, fmap, fap, ma, f) + } +} + +// HKTA = HKT[A] +// HKTAA = HKT[func(A)MA] +// HKTRA = HKT[MA] +func Sequence[MA ~map[K]A, MKTA ~map[K]HKTA, K comparable, A, HKTA, HKTAA, HKTRA any]( + fof func(MA) HKTRA, + fmap func(func(MA) func(A) MA) func(HKTRA) HKTAA, + fap func(HKTA) func(HKTAA) HKTRA, + + ma MKTA) HKTRA { + return MonadTraverse(fof, fmap, fap, ma, F.Identity[HKTA]) +} diff --git a/v2/internal/statet/state.go b/v2/internal/statet/state.go new file mode 100644 index 0000000..3cdfafa --- /dev/null +++ b/v2/internal/statet/state.go @@ -0,0 +1,198 @@ +// Copyright (c) 2024 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package statet + +import ( + "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/pair" +) + +func Of[ + HKTSA ~func(S) HKTA, + HKTA, + S, A any, +]( + fof func(pair.Pair[S, A]) HKTA, + + a A) HKTSA { + + return function.Flow2( + function.Bind2nd(pair.MakePair[S, A], a), + fof, + ) +} + +func MonadMap[ + HKTSA ~func(S) HKTA, + HKTSB ~func(S) HKTB, + HKTA, + HKTB, + S, A, B any, +]( + fmap func(HKTA, func(pair.Pair[S, A]) pair.Pair[S, B]) HKTB, + + fa HKTSA, + f func(A) B, +) HKTSB { + + return function.Flow2( + fa, + function.Bind2nd(fmap, pair.Map[S](f)), + ) +} + +func Map[ + HKTSA ~func(S) HKTA, + HKTSB ~func(S) HKTB, + HKTA, + HKTB, + S, A, B any, +]( + fmap func(func(pair.Pair[S, A]) pair.Pair[S, B]) func(HKTA) HKTB, + + f func(A) B, +) func(HKTSA) HKTSB { + mp := fmap(pair.Map[S](f)) + + return func(fa HKTSA) HKTSB { + return function.Flow2( + fa, + mp, + ) + } +} + +func MonadChain[ + HKTSA ~func(S) HKTA, + HKTSB ~func(S) HKTB, + HKTA, + HKTB, + S, A any, +]( + fchain func(HKTA, func(pair.Pair[S, A]) HKTB) HKTB, + + fa HKTSA, + f func(A) HKTSB, +) HKTSB { + return function.Flow2( + fa, + function.Bind2nd(fchain, func(a pair.Pair[S, A]) HKTB { + return f(pair.Tail(a))(pair.Head(a)) + }), + ) +} + +func Chain[ + HKTSA ~func(S) HKTA, + HKTSB ~func(S) HKTB, + HKTA, + HKTB, + S, A any, +]( + fchain func(func(pair.Pair[S, A]) HKTB) func(HKTA) HKTB, + + f func(A) HKTSB, +) func(HKTSA) HKTSB { + mp := fchain(func(a pair.Pair[S, A]) HKTB { + return f(pair.Tail(a))(pair.Head(a)) + }) + + return func(fa HKTSA) HKTSB { + return function.Flow2( + fa, + mp, + ) + } +} + +func MonadAp[ + HKTSA ~func(S) HKTA, + HKTSB ~func(S) HKTB, + HKTSAB ~func(S) HKTAB, + HKTA, + HKTB, + HKTAB, + + S, A, B any, +]( + fmap func(HKTA, func(pair.Pair[S, A]) pair.Pair[S, B]) HKTB, + fchain func(HKTAB, func(pair.Pair[S, func(A) B]) HKTB) HKTB, + + fab HKTSAB, + fa HKTSA, +) HKTSB { + return func(s S) HKTB { + return fchain(fab(s), func(ab pair.Pair[S, func(A) B]) HKTB { + return fmap(fa(pair.Head(ab)), pair.Map[S](pair.Tail(ab))) + }) + } +} + +func Ap[ + HKTSA ~func(S) HKTA, + HKTSB ~func(S) HKTB, + HKTSAB ~func(S) HKTAB, + HKTA, + HKTB, + HKTAB, + + S, A, B any, +]( + fmap func(func(pair.Pair[S, A]) pair.Pair[S, B]) func(HKTA) HKTB, + fchain func(func(pair.Pair[S, func(A) B]) HKTB) func(HKTAB) HKTB, + + fa HKTSA, +) func(HKTSAB) HKTSB { + return func(fab HKTSAB) HKTSB { + return function.Flow2( + fab, + fchain(func(ab pair.Pair[S, func(A) B]) HKTB { + return fmap(pair.Map[S](pair.Tail(ab)))(fa(pair.Head(ab))) + }), + ) + } +} + +func FromF[ + HKTSA ~func(S) HKTA, + HKTA, + + HKTFA, + + S, A any, +]( + fmap func(HKTFA, func(A) pair.Pair[S, A]) HKTA, + ma HKTFA) HKTSA { + + f1 := function.Bind1st(fmap, ma) + + return func(s S) HKTA { + return f1(function.Bind1st(pair.MakePair[S, A], s)) + } +} + +func FromState[ + HKTSA ~func(S) HKTA, + ST ~func(S) pair.Pair[S, A], + HKTA, + + S, A any, +]( + fof func(pair.Pair[S, A]) HKTA, + sa ST, +) HKTSA { + return function.Flow2(sa, fof) +} diff --git a/v2/internal/testing/sequence.go b/v2/internal/testing/sequence.go new file mode 100644 index 0000000..eee803c --- /dev/null +++ b/v2/internal/testing/sequence.go @@ -0,0 +1,183 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package testing + +import ( + "fmt" + "testing" + + EQ "github.com/IBM/fp-go/v2/eq" + F "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/internal/functor" + "github.com/IBM/fp-go/v2/internal/pointed" + "github.com/stretchr/testify/assert" +) + +// SequenceArrayTest tests if the sequence operation works in case the operation cannot error +func SequenceArrayTest[ + HKTA, + HKTB, + HKTAA any, // HKT[[]A] +]( + eq EQ.Eq[HKTB], + + pa pointed.Pointed[string, HKTA], + pb pointed.Pointed[bool, HKTB], + faa functor.Functor[[]string, bool, HKTAA, HKTB], + seq func([]HKTA) HKTAA, +) func(count int) func(t *testing.T) { + + return func(count int) func(t *testing.T) { + + exp := make([]string, count) + good := make([]HKTA, count) + for i := 0; i < count; i++ { + val := fmt.Sprintf("TestData %d", i) + exp[i] = val + good[i] = pa.Of(val) + } + + return func(t *testing.T) { + res := F.Pipe2( + good, + seq, + faa.Map(func(act []string) bool { + return assert.Equal(t, exp, act) + }), + ) + assert.True(t, eq.Equals(res, pb.Of(true))) + } + } +} + +// SequenceArrayErrorTest tests if the sequence operation works in case the operation can error +func SequenceArrayErrorTest[ + HKTA, + HKTB, + HKTAA any, // HKT[[]A] +]( + eq EQ.Eq[HKTB], + + left func(error) HKTA, + leftB func(error) HKTB, + pa pointed.Pointed[string, HKTA], + pb pointed.Pointed[bool, HKTB], + faa functor.Functor[[]string, bool, HKTAA, HKTB], + seq func([]HKTA) HKTAA, +) func(count int) func(t *testing.T) { + + return func(count int) func(t *testing.T) { + + expGood := make([]string, count) + good := make([]HKTA, count) + expBad := make([]error, count) + bad := make([]HKTA, count) + + for i := 0; i < count; i++ { + goodVal := fmt.Sprintf("TestData %d", i) + badVal := fmt.Errorf("ErrorData %d", i) + expGood[i] = goodVal + good[i] = pa.Of(goodVal) + expBad[i] = badVal + bad[i] = left(badVal) + } + + total := 1 << count + + return func(t *testing.T) { + // test the good case + res := F.Pipe2( + good, + seq, + faa.Map(func(act []string) bool { + return assert.Equal(t, expGood, act) + }), + ) + assert.True(t, eq.Equals(res, pb.Of(true))) + // iterate and test the bad cases + for i := 1; i < total; i++ { + // run the test + t.Run(fmt.Sprintf("Bitmask test %d", i), func(t1 *testing.T) { + // the actual + act := make([]HKTA, count) + // the expected error + var exp error + // prepare the values bases on the bit mask + mask := 1 + for j := 0; j < count; j++ { + if (i & mask) == 0 { + act[j] = good[j] + } else { + act[j] = bad[j] + if exp == nil { + exp = expBad[j] + } + } + mask <<= 1 + } + // test the good case + res := F.Pipe2( + act, + seq, + faa.Map(func(act []string) bool { + return assert.Equal(t, expGood, act) + }), + ) + // validate the error + assert.True(t, eq.Equals(res, leftB(exp))) + }) + } + } + } +} + +// SequenceRecordTest tests if the sequence operation works in case the operation cannot error +func SequenceRecordTest[ + HKTA, + HKTB, + HKTAA any, // HKT[map[string]string] +]( + eq EQ.Eq[HKTB], + + pa pointed.Pointed[string, HKTA], + pb pointed.Pointed[bool, HKTB], + faa functor.Functor[map[string]string, bool, HKTAA, HKTB], + seq func(map[string]HKTA) HKTAA, +) func(count int) func(t *testing.T) { + + return func(count int) func(t *testing.T) { + + exp := make(map[string]string) + good := make(map[string]HKTA) + for i := 0; i < count; i++ { + key := fmt.Sprintf("KeyData %d", i) + val := fmt.Sprintf("ValueData %d", i) + exp[key] = val + good[key] = pa.Of(val) + } + + return func(t *testing.T) { + res := F.Pipe2( + good, + seq, + faa.Map(func(act map[string]string) bool { + return assert.Equal(t, exp, act) + }), + ) + assert.True(t, eq.Equals(res, pb.Of(true))) + } + } +} diff --git a/v2/internal/utils/do.go b/v2/internal/utils/do.go new file mode 100644 index 0000000..4c075a1 --- /dev/null +++ b/v2/internal/utils/do.go @@ -0,0 +1,59 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package utils + +import ( + "fmt" + + F "github.com/IBM/fp-go/v2/function" +) + +type ( + Initial struct { + } + + WithLastName struct { + Initial + LastName string + } + + WithGivenName struct { + WithLastName + GivenName string + } +) + +var ( + Empty = Initial{} + + SetLastName = F.Curry2(func(name string, s1 Initial) WithLastName { + return WithLastName{ + Initial: s1, + LastName: name, + } + }) + + SetGivenName = F.Curry2(func(name string, s1 WithLastName) WithGivenName { + return WithGivenName{ + WithLastName: s1, + GivenName: name, + } + }) +) + +func GetFullName(s WithGivenName) string { + return fmt.Sprintf("%s %s", s.GivenName, s.LastName) +} diff --git a/v2/internal/utils/utils.go b/v2/internal/utils/utils.go new file mode 100644 index 0000000..8d6f078 --- /dev/null +++ b/v2/internal/utils/utils.go @@ -0,0 +1,51 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package utils + +import ( + "errors" + "strings" +) + +var Upper = strings.ToUpper + +func Inc(i int) int { + return i + 1 +} + +func Dec(i int) int { + return i - 1 +} + +func Sum(left, right int) int { + return left + right +} + +func Double(value int) int { + return value * 2 +} + +func Triple(value int) int { + return value * 3 +} + +func StringLen(value string) int { + return len(value) +} + +func Error() (int, error) { + return 0, errors.New("some error") +} diff --git a/v2/io/applicative.go b/v2/io/applicative.go new file mode 100644 index 0000000..40eba56 --- /dev/null +++ b/v2/io/applicative.go @@ -0,0 +1,52 @@ +// Copyright (c) 2024 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package io + +import ( + "github.com/IBM/fp-go/v2/internal/applicative" +) + +type ( + ioApplicative[A, B any] struct{} + // IOApplicative represents the applicative functor type class for IO. + // It combines the capabilities of Functor (Map) and Pointed (Of) with + // the ability to apply wrapped functions to wrapped values (Ap). + IOApplicative[A, B any] = applicative.Applicative[A, B, IO[A], IO[B], IO[func(A) B]] +) + +func (o *ioApplicative[A, B]) Of(a A) IO[A] { + return Of(a) +} + +func (o *ioApplicative[A, B]) Map(f func(A) B) Operator[A, B] { + return Map(f) +} + +func (o *ioApplicative[A, B]) Ap(fa IO[A]) Operator[func(A) B, B] { + return Ap[B](fa) +} + +// Applicative returns an instance of the Applicative type class for IO. +// This provides a structured way to access applicative operations (Of, Map, Ap) +// for IO computations. +// +// Example: +// +// app := io.Applicative[int, string]() +// result := app.Map(strconv.Itoa)(app.Of(42)) +func Applicative[A, B any]() IOApplicative[A, B] { + return &ioApplicative[A, B]{} +} diff --git a/v2/io/apply.go b/v2/io/apply.go new file mode 100644 index 0000000..1f24d1c --- /dev/null +++ b/v2/io/apply.go @@ -0,0 +1,47 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package io + +import ( + M "github.com/IBM/fp-go/v2/monoid" + S "github.com/IBM/fp-go/v2/semigroup" +) + +// ApplySemigroup lifts a Semigroup[A] into a Semigroup[IO[A]]. +// This allows combining IO computations using the semigroup operation on their results. +// +// Example: +// +// intAdd := semigroup.MakeSemigroup(func(a, b int) int { return a + b }) +// ioAdd := io.ApplySemigroup(intAdd) +// result := ioAdd.Concat(io.Of(1), io.Of(2)) // IO[3] +func ApplySemigroup[A any](s S.Semigroup[A]) Semigroup[A] { + return S.ApplySemigroup(MonadMap[A, func(A) A], MonadAp[A, A], s) +} + +// ApplicativeMonoid lifts a Monoid[A] into a Monoid[IO[A]]. +// This allows combining IO computations using the monoid operation on their results, +// including an empty/identity element. +// +// Example: +// +// intAdd := monoid.MakeMonoid(func(a, b int) int { return a + b }, 0) +// ioAdd := io.ApplicativeMonoid(intAdd) +// result := ioAdd.Concat(io.Of(1), io.Of(2)) // IO[3] +// empty := ioAdd.Empty() // IO[0] +func ApplicativeMonoid[A any](m M.Monoid[A]) Monoid[A] { + return M.ApplicativeMonoid(Of[A], MonadMap[A, func(A) A], MonadAp[A, A], m) +} diff --git a/v2/io/bind.go b/v2/io/bind.go new file mode 100644 index 0000000..e648f75 --- /dev/null +++ b/v2/io/bind.go @@ -0,0 +1,154 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package io + +import ( + INTA "github.com/IBM/fp-go/v2/internal/apply" + INTC "github.com/IBM/fp-go/v2/internal/chain" + INTF "github.com/IBM/fp-go/v2/internal/functor" +) + +// Do creates an empty context of type S to be used with the Bind operation. +// This is the starting point for do-notation style composition. +// +// Example: +// +// type State struct { +// user User +// posts []Post +// } +// result := pipe.Pipe2( +// io.Do(State{}), +// io.Bind("user", fetchUser), +// io.Bind("posts", func(s State) io.IO[[]Post] { +// return fetchPosts(s.user.Id) +// }), +// ) +func Do[S any]( + empty S, +) IO[S] { + return Of(empty) +} + +// Bind attaches the result of an IO computation to a context S1 to produce a context S2. +// This is used in do-notation style composition to build up state incrementally. +// +// The setter function takes the result T and returns a function that updates S1 to S2. +// +// Example: +// +// io.Bind(func(user User) func(s State) State { +// return func(s State) State { +// s.user = user +// return s +// } +// }, fetchUser) +func Bind[S1, S2, T any]( + setter func(T) func(S1) S2, + f func(S1) IO[T], +) Operator[S1, S2] { + return INTC.Bind( + Chain[S1, S2], + Map[T, S2], + setter, + f, + ) +} + +// Let attaches the result of a pure computation to a context S1 to produce a context S2. +// Similar to Bind, but for pure (non-IO) computations. +// +// Example: +// +// io.Let(func(count int) func(s State) State { +// return func(s State) State { +// s.count = count +// return s +// } +// }, func(s State) int { return len(s.items) }) +func Let[S1, S2, T any]( + setter func(T) func(S1) S2, + f func(S1) T, +) Operator[S1, S2] { + return INTF.Let( + Map[S1, S2], + setter, + f, + ) +} + +// LetTo attaches a constant value to a context S1 to produce a context S2. +// Similar to Let, but with a constant value instead of a computation. +// +// Example: +// +// io.LetTo(func(status string) func(s State) State { +// return func(s State) State { +// s.status = status +// return s +// } +// }, "ready") +func LetTo[S1, S2, T any]( + setter func(T) func(S1) S2, + b T, +) Operator[S1, S2] { + return INTF.LetTo( + Map[S1, S2], + setter, + b, + ) +} + +// BindTo initializes a new state S1 from a value T. +// This is typically used to start a do-notation chain from a single value. +// +// Example: +// +// io.BindTo(func(user User) State { +// return State{user: user} +// }) +func BindTo[S1, T any]( + setter func(T) S1, +) Operator[T, S1] { + return INTC.BindTo( + Map[T, S1], + setter, + ) +} + +// ApS attaches a value to a context S1 to produce a context S2 by considering +// the context and the value concurrently (using applicative operations). +// This allows parallel execution of independent computations. +// +// Example: +// +// io.ApS(func(posts []Post) func(s State) State { +// return func(s State) State { +// s.posts = posts +// return s +// } +// }, fetchPosts()) +func ApS[S1, S2, T any]( + setter func(T) func(S1) S2, + fa IO[T], +) Operator[S1, S2] { + return INTA.ApS( + Ap[S2, T], + Map[S1, func(T) S2], + setter, + fa, + ) +} diff --git a/v2/io/bind_test.go b/v2/io/bind_test.go new file mode 100644 index 0000000..d54d5f8 --- /dev/null +++ b/v2/io/bind_test.go @@ -0,0 +1,56 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package io + +import ( + "testing" + + F "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/internal/utils" + "github.com/stretchr/testify/assert" +) + +func getLastName(s utils.Initial) IO[string] { + return Of("Doe") +} + +func getGivenName(s utils.WithLastName) IO[string] { + return Of("John") +} + +func TestBind(t *testing.T) { + + res := F.Pipe3( + Do(utils.Empty), + Bind(utils.SetLastName, getLastName), + Bind(utils.SetGivenName, getGivenName), + Map(utils.GetFullName), + ) + + assert.Equal(t, res(), "John Doe") +} + +func TestApS(t *testing.T) { + + res := F.Pipe3( + Do(utils.Empty), + ApS(utils.SetLastName, Of("Doe")), + ApS(utils.SetGivenName, Of("John")), + Map(utils.GetFullName), + ) + + assert.Equal(t, res(), "John Doe") +} diff --git a/v2/io/bracket.go b/v2/io/bracket.go new file mode 100644 index 0000000..ad3bc53 --- /dev/null +++ b/v2/io/bracket.go @@ -0,0 +1,39 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package io + +import ( + INTB "github.com/IBM/fp-go/v2/internal/bracket" +) + +// Bracket makes sure that a resource is cleaned up in the event of an error. The release action is called regardless of +// whether the body action returns and error or not. +func Bracket[A, B, ANY any]( + acquire IO[A], + use func(A) IO[B], + release func(A, B) IO[ANY], +) IO[B] { + return INTB.Bracket[IO[A], IO[B], IO[ANY], B, A, B]( + Of[B], + MonadChain[A, B], + MonadChain[B, B], + MonadChain[ANY, B], + + acquire, + use, + release, + ) +} diff --git a/v2/io/coverage_test.go b/v2/io/coverage_test.go new file mode 100644 index 0000000..653f8b7 --- /dev/null +++ b/v2/io/coverage_test.go @@ -0,0 +1,1516 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package io + +import ( + "context" + "sync" + "testing" + "time" + + F "github.com/IBM/fp-go/v2/function" + M "github.com/IBM/fp-go/v2/monoid" + S "github.com/IBM/fp-go/v2/semigroup" + T "github.com/IBM/fp-go/v2/tuple" + "github.com/stretchr/testify/assert" +) + +// Test FromIO +func TestFromIO(t *testing.T) { + io := Of(42) + result := FromIO(io) + assert.Equal(t, 42, result()) +} + +// Test MonadOf +func TestMonadOf(t *testing.T) { + result := MonadOf(42) + assert.Equal(t, 42, result()) +} + +// Test MonadMapTo +func TestMonadMapTo(t *testing.T) { + result := MonadMapTo(Of(1), "hello") + assert.Equal(t, "hello", result()) +} + +// Test MapTo +func TestMapTo(t *testing.T) { + result := F.Pipe1(Of(1), MapTo[int, string]("hello")) + assert.Equal(t, "hello", result()) +} + +// Test MonadApSeq +func TestMonadApSeq(t *testing.T) { + f := Of(func(x int) int { return x * 2 }) + result := MonadApSeq(f, Of(21)) + assert.Equal(t, 42, result()) +} + +// Test ApPar +func TestApPar(t *testing.T) { + f := Of(func(x int) int { return x * 2 }) + result := F.Pipe1(f, ApPar[int](Of(21))) + assert.Equal(t, 42, result()) +} + +// Test MonadChainFirst +func TestMonadChainFirst(t *testing.T) { + sideEffect := 0 + result := MonadChainFirst(Of(42), func(x int) IO[string] { + sideEffect = x + return Of("ignored") + }) + assert.Equal(t, 42, result()) + assert.Equal(t, 42, sideEffect) +} + +// Test ChainFirst +func TestChainFirst(t *testing.T) { + sideEffect := 0 + result := F.Pipe1(Of(42), ChainFirst(func(x int) IO[string] { + sideEffect = x + return Of("ignored") + })) + assert.Equal(t, 42, result()) + assert.Equal(t, 42, sideEffect) +} + +// Test MonadApFirst +func TestMonadApFirst(t *testing.T) { + result := MonadApFirst(Of(42), Of("ignored")) + assert.Equal(t, 42, result()) +} + +// Test MonadApSecond +func TestMonadApSecond(t *testing.T) { + result := MonadApSecond(Of("ignored"), Of(42)) + assert.Equal(t, 42, result()) +} + +// Test MonadChainTo +func TestMonadChainTo(t *testing.T) { + result := MonadChainTo(Of(1), Of(42)) + assert.Equal(t, 42, result()) +} + +// Test ChainTo +func TestChainTo(t *testing.T) { + result := F.Pipe1(Of(1), ChainTo[int](Of(42))) + assert.Equal(t, 42, result()) +} + +// Test Defer +func TestDefer(t *testing.T) { + counter := 0 + deferred := Defer(func() IO[int] { + counter++ + return Of(counter) + }) + + assert.Equal(t, 1, deferred()) + assert.Equal(t, 2, deferred()) + assert.Equal(t, 3, deferred()) +} + +// Test MonadFlap +func TestMonadFlap(t *testing.T) { + f := Of(func(x int) int { return x * 2 }) + result := MonadFlap(f, 21) + assert.Equal(t, 42, result()) +} + +// Test Flap +func TestFlap(t *testing.T) { + f := Of(func(x int) int { return x * 2 }) + result := F.Pipe1(f, Flap[int](21)) + assert.Equal(t, 42, result()) +} + +// Test After +func TestAfter(t *testing.T) { + future := time.Now().Add(50 * time.Millisecond) + start := time.Now() + result := F.Pipe1(Of(42), After[int](future)) + value := result() + elapsed := time.Since(start) + + assert.Equal(t, 42, value) + assert.True(t, elapsed >= 50*time.Millisecond) +} + +// Test WithTime +func TestWithTime(t *testing.T) { + result := WithTime(Of(42)) + tuple := result() + + assert.Equal(t, 42, tuple.F1) + assert.True(t, tuple.F2.Before(tuple.F3) || tuple.F2.Equal(tuple.F3)) +} + +// Test WithDuration +func TestWithDuration(t *testing.T) { + result := WithDuration(func() int { + time.Sleep(10 * time.Millisecond) + return 42 + }) + tuple := result() + + assert.Equal(t, 42, tuple.F1) + assert.True(t, tuple.F2 >= 10*time.Millisecond) +} + +// Test Let +func TestLet(t *testing.T) { + type State struct { + value int + } + + result := F.Pipe2( + Of(State{value: 10}), + Let(func(doubled int) func(s State) State { + return func(s State) State { + s.value = doubled + return s + } + }, func(s State) int { + return s.value * 2 + }), + Map(func(s State) int { return s.value }), + ) + + assert.Equal(t, 20, result()) +} + +// Test LetTo +func TestLetTo(t *testing.T) { + type State struct { + value int + } + + result := F.Pipe2( + Of(State{value: 10}), + LetTo(func(newVal int) func(s State) State { + return func(s State) State { + s.value = newVal + return s + } + }, 42), + Map(func(s State) int { return s.value }), + ) + + assert.Equal(t, 42, result()) +} + +// Test BindTo +func TestBindTo(t *testing.T) { + type State struct { + value int + } + + result := F.Pipe2( + Of(42), + BindTo(func(v int) State { + return State{value: v} + }), + Map(func(s State) int { return s.value }), + ) + + assert.Equal(t, 42, result()) +} + +// Test Bracket +func TestBracket(t *testing.T) { + acquired := false + released := false + + acquire := func() int { + acquired = true + return 42 + } + + use := func(x int) IO[int] { + return Of(x * 2) + } + + release := func(x int, result int) IO[any] { + return FromImpure(func() { + released = true + }) + } + + result := Bracket(Of(acquire()), use, release) + value := result() + + assert.Equal(t, 84, value) + assert.True(t, acquired) + assert.True(t, released) +} + +// Test WithResource +func TestWithResource(t *testing.T) { + acquired := false + released := false + + onCreate := func() int { + acquired = true + return 42 + } + + onRelease := func(x int) IO[any] { + return FromImpure(func() { + released = true + }) + } + + withRes := WithResource[int, int, any](Of(onCreate()), onRelease) + + result := withRes(func(x int) IO[int] { + return Of(x * 2) + }) + + value := result() + + assert.Equal(t, 84, value) + assert.True(t, acquired) + assert.True(t, released) +} + +// Test WithLock - simplified test +func TestWithLock(t *testing.T) { + var mu sync.Mutex + counter := 0 + + // Create a lock IO that acquires the lock and returns a release function + lock := func() func() { + mu.Lock() + return func() { mu.Unlock() } + } + + // Create operation that increments counter + operation := func() int { + counter++ + return counter + } + + // Apply lock to operation - cast to context.CancelFunc + var lockIO IO[context.CancelFunc] = func() context.CancelFunc { + return lock() + } + safeOp := WithLock[int](lockIO)(operation) + + // Run the operation + result := safeOp() + + assert.Equal(t, 1, result) + assert.Equal(t, 1, counter) +} + +// Test Printf +func TestPrintf(t *testing.T) { + result := F.Pipe1(Of(42), ChainFirst(Printf[int]("Value: %d\n"))) + assert.Equal(t, 42, result()) +} + +// Test ApplySemigroup +func TestApplySemigroup(t *testing.T) { + intAdd := S.MakeSemigroup(func(a, b int) int { return a + b }) + ioSemigroup := ApplySemigroup(intAdd) + + result := ioSemigroup.Concat(Of(10), Of(32)) + assert.Equal(t, 42, result()) +} + +// Test ApplicativeMonoid +func TestApplicativeMonoid(t *testing.T) { + intAdd := M.MakeMonoid(func(a, b int) int { return a + b }, 0) + ioMonoid := ApplicativeMonoid(intAdd) + + result := ioMonoid.Concat(Of(10), Of(32)) + assert.Equal(t, 42, result()) + + empty := ioMonoid.Empty() + assert.Equal(t, 0, empty()) +} + +// Test Applicative type class +func TestApplicativeTypeClass(t *testing.T) { + app := Applicative[int, int]() + + // Test Of + io1 := app.Of(21) + assert.Equal(t, 21, io1()) + + // Test Map + io2 := app.Map(func(x int) int { return x * 2 })(io1) + assert.Equal(t, 42, io2()) +} + +// Test Monad type class +func TestMonadTypeClass(t *testing.T) { + m := Monad[int, int]() + + result := F.Pipe2( + m.Of(21), + m.Chain(func(x int) IO[int] { + return m.Of(x * 2) + }), + m.Map(func(x int) int { return x + 1 }), + ) + + assert.Equal(t, 43, result()) +} + +// Test TraverseArrayWithIndex +func TestTraverseArrayWithIndex(t *testing.T) { + src := []string{"a", "b", "c"} + + result := F.Pipe1( + src, + TraverseArrayWithIndex(func(i int, s string) IO[string] { + return Of(F.Pipe1(i, func(idx int) string { + return s + string(rune('0'+idx)) + })) + }), + ) + + assert.Equal(t, []string{"a0", "b1", "c2"}, result()) +} + +// Test TraverseRecord +func TestTraverseRecord(t *testing.T) { + src := map[string]int{"a": 1, "b": 2} + + result := F.Pipe1( + src, + TraverseRecord[string](func(x int) IO[int] { + return Of(x * 2) + }), + ) + + values := result() + assert.Equal(t, 2, values["a"]) + assert.Equal(t, 4, values["b"]) +} + +// Test TraverseRecordWithIndex +func TestTraverseRecordWithIndex(t *testing.T) { + src := map[string]int{"a": 1, "b": 2} + + result := F.Pipe1( + src, + TraverseRecordWithIndex(func(k string, x int) IO[string] { + return Of(k) + }), + ) + + values := result() + assert.Equal(t, "a", values["a"]) + assert.Equal(t, "b", values["b"]) +} + +// Test SequenceRecord +func TestSequenceRecord(t *testing.T) { + src := map[string]IO[int]{ + "a": Of(1), + "b": Of(2), + } + + result := SequenceRecord(src) + values := result() + + assert.Equal(t, 1, values["a"]) + assert.Equal(t, 2, values["b"]) +} + +// Test MonadTraverseRecord +func TestMonadTraverseRecord(t *testing.T) { + src := map[string]int{"a": 1, "b": 2} + + result := MonadTraverseRecord(src, func(x int) IO[int] { + return Of(x * 2) + }) + + values := result() + assert.Equal(t, 2, values["a"]) + assert.Equal(t, 4, values["b"]) +} + +// Test TraverseArrayWithIndexSeq +func TestTraverseArrayWithIndexSeq(t *testing.T) { + var order []int + src := []int{1, 2, 3} + + result := F.Pipe1( + src, + TraverseArrayWithIndexSeq(func(i int, x int) IO[int] { + return func() int { + order = append(order, i) + return x * 2 + } + }), + ) + + values := result() + assert.Equal(t, []int{2, 4, 6}, values) + assert.Equal(t, []int{0, 1, 2}, order) // Sequential order +} + +// Test SequenceArraySeq +func TestSequenceArraySeq(t *testing.T) { + var order []int + src := []IO[int]{ + func() int { order = append(order, 0); return 1 }, + func() int { order = append(order, 1); return 2 }, + func() int { order = append(order, 2); return 3 }, + } + + result := SequenceArraySeq(src) + values := result() + + assert.Equal(t, []int{1, 2, 3}, values) + assert.Equal(t, []int{0, 1, 2}, order) // Sequential order +} + +// Test MonadTraverseArraySeq +func TestMonadTraverseArraySeq(t *testing.T) { + var order []int + src := []int{1, 2, 3} + + result := MonadTraverseArraySeq(src, func(x int) IO[int] { + return func() int { + order = append(order, x) + return x * 2 + } + }) + + values := result() + assert.Equal(t, []int{2, 4, 6}, values) + assert.Equal(t, []int{1, 2, 3}, order) // Sequential order +} + +// Test TraverseRecordSeq +func TestTraverseRecordSeq(t *testing.T) { + src := map[string]int{"a": 1, "b": 2} + + result := F.Pipe1( + src, + TraverseRecordSeq[string](func(x int) IO[int] { + return Of(x * 2) + }), + ) + + values := result() + assert.Equal(t, 2, values["a"]) + assert.Equal(t, 4, values["b"]) +} + +// Test TraverseRecordWithIndeSeq (note the typo in the function name) +func TestTraverseRecordWithIndeSeq(t *testing.T) { + src := map[string]int{"a": 1, "b": 2} + + result := F.Pipe1( + src, + TraverseRecordWithIndeSeq(func(k string, x int) IO[string] { + return Of(k) + }), + ) + + values := result() + assert.Equal(t, "a", values["a"]) + assert.Equal(t, "b", values["b"]) +} + +// Test SequenceRecordSeq +func TestSequenceRecordSeq(t *testing.T) { + src := map[string]IO[int]{ + "a": Of(1), + "b": Of(2), + } + + result := SequenceRecordSeq(src) + values := result() + + assert.Equal(t, 1, values["a"]) + assert.Equal(t, 2, values["b"]) +} + +// Test MonadTraverseRecordSeq +func TestMonadTraverseRecordSeq(t *testing.T) { + src := map[string]int{"a": 1, "b": 2} + + result := MonadTraverseRecordSeq(src, func(x int) IO[int] { + return Of(x * 2) + }) + + values := result() + assert.Equal(t, 2, values["a"]) + assert.Equal(t, 4, values["b"]) +} + +// Test SequenceTuple2 +func TestSequenceTuple2(t *testing.T) { + io1 := Of(10) + io2 := Of(32) + + tup := T.MakeTuple2(io1, io2) + result := SequenceTuple2(tup) + tuple := result() + + assert.Equal(t, 10, tuple.F1) + assert.Equal(t, 32, tuple.F2) +} + +// Test SequenceT2 +func TestSequenceT2(t *testing.T) { + result := SequenceT2(Of(10), Of(32)) + tuple := result() + + assert.Equal(t, 10, tuple.F1) + assert.Equal(t, 32, tuple.F2) +} + +// Test SequenceTuple3 +func TestSequenceTuple3(t *testing.T) { + io1 := Of(10) + io2 := Of(20) + io3 := Of(30) + + tup := T.MakeTuple3(io1, io2, io3) + result := SequenceTuple3(tup) + tuple := result() + + assert.Equal(t, 10, tuple.F1) + assert.Equal(t, 20, tuple.F2) + assert.Equal(t, 30, tuple.F3) +} + +// Test SequenceSeqTuple2 +func TestSequenceSeqTuple2(t *testing.T) { + var order []int + io1 := func() int { order = append(order, 1); return 10 } + io2 := func() int { order = append(order, 2); return 20 } + + tup := T.MakeTuple2[IO[int], IO[int]](io1, io2) + result := SequenceSeqTuple2(tup) + tuple := result() + + assert.Equal(t, 10, tuple.F1) + assert.Equal(t, 20, tuple.F2) + assert.Equal(t, []int{1, 2}, order) // Sequential execution +} + +// Test SequenceParTuple2 +func TestSequenceParTuple2(t *testing.T) { + io1 := Of(10) + io2 := Of(20) + + tup := T.MakeTuple2(io1, io2) + result := SequenceParTuple2(tup) + tuple := result() + + assert.Equal(t, 10, tuple.F1) + assert.Equal(t, 20, tuple.F2) +} + +// Test SequenceT3 +func TestSequenceT3(t *testing.T) { + result := SequenceT3(Of(10), Of(20), Of(30)) + tuple := result() + + assert.Equal(t, 10, tuple.F1) + assert.Equal(t, 20, tuple.F2) + assert.Equal(t, 30, tuple.F3) +} + +// Test SequenceSeqT2 +func TestSequenceSeqT2(t *testing.T) { + var order []int + io1 := func() int { order = append(order, 1); return 10 } + io2 := func() int { order = append(order, 2); return 20 } + + result := SequenceSeqT2(io1, io2) + tuple := result() + + assert.Equal(t, 10, tuple.F1) + assert.Equal(t, 20, tuple.F2) + assert.Equal(t, []int{1, 2}, order) +} + +// Test SequenceParT2 +func TestSequenceParT2(t *testing.T) { + result := SequenceParT2(Of(10), Of(20)) + tuple := result() + + assert.Equal(t, 10, tuple.F1) + assert.Equal(t, 20, tuple.F2) +} + +// Test SequenceT4 +func TestSequenceT4(t *testing.T) { + result := SequenceT4(Of(1), Of(2), Of(3), Of(4)) + tuple := result() + + assert.Equal(t, 1, tuple.F1) + assert.Equal(t, 2, tuple.F2) + assert.Equal(t, 3, tuple.F3) + assert.Equal(t, 4, tuple.F4) +} + +// Test SequenceTuple4 +func TestSequenceTuple4(t *testing.T) { + tup := T.MakeTuple4(Of(1), Of(2), Of(3), Of(4)) + result := SequenceTuple4(tup) + tuple := result() + + assert.Equal(t, 1, tuple.F1) + assert.Equal(t, 2, tuple.F2) + assert.Equal(t, 3, tuple.F3) + assert.Equal(t, 4, tuple.F4) +} + +// Test SequenceT5 +func TestSequenceT5(t *testing.T) { + result := SequenceT5(Of(1), Of(2), Of(3), Of(4), Of(5)) + tuple := result() + + assert.Equal(t, 1, tuple.F1) + assert.Equal(t, 2, tuple.F2) + assert.Equal(t, 3, tuple.F3) + assert.Equal(t, 4, tuple.F4) + assert.Equal(t, 5, tuple.F5) +} + +// Test TraverseTuple2 +func TestTraverseTuple2(t *testing.T) { + inputTuple := T.MakeTuple2(10, 20) + result := TraverseTuple2( + func(x int) IO[int] { return Of(x * 2) }, + func(x int) IO[int] { return Of(x + 1) }, + )(inputTuple) + + tuple := result() + assert.Equal(t, 20, tuple.F1) + assert.Equal(t, 21, tuple.F2) +} + +// Test TraverseTuple3 +func TestTraverseTuple3(t *testing.T) { + inputTuple := T.MakeTuple3(10, 20, 30) + result := TraverseTuple3( + func(x int) IO[int] { return Of(x * 2) }, + func(x int) IO[int] { return Of(x + 1) }, + func(x int) IO[int] { return Of(x - 1) }, + )(inputTuple) + + tuple := result() + assert.Equal(t, 20, tuple.F1) + assert.Equal(t, 21, tuple.F2) + assert.Equal(t, 29, tuple.F3) +} + +// Test SequenceTuple1 +func TestSequenceTuple1(t *testing.T) { + tup := T.MakeTuple1(Of(42)) + result := SequenceTuple1(tup) + tuple := result() + + assert.Equal(t, 42, tuple.F1) +} + +// Test SequenceT1 +func TestSequenceT1(t *testing.T) { + result := SequenceT1(Of(42)) + tuple := result() + + assert.Equal(t, 42, tuple.F1) +} + +// Test SequenceSeqT3 +func TestSequenceSeqT3(t *testing.T) { + var order []int + io1 := func() int { order = append(order, 1); return 10 } + io2 := func() int { order = append(order, 2); return 20 } + io3 := func() int { order = append(order, 3); return 30 } + + result := SequenceSeqT3(io1, io2, io3) + tuple := result() + + assert.Equal(t, 10, tuple.F1) + assert.Equal(t, 20, tuple.F2) + assert.Equal(t, 30, tuple.F3) + assert.Equal(t, []int{1, 2, 3}, order) +} + +// Test SequenceParT3 +func TestSequenceParT3(t *testing.T) { + result := SequenceParT3(Of(10), Of(20), Of(30)) + tuple := result() + + assert.Equal(t, 10, tuple.F1) + assert.Equal(t, 20, tuple.F2) + assert.Equal(t, 30, tuple.F3) +} + +// Test TraverseSeqTuple3 +func TestTraverseSeqTuple3(t *testing.T) { + var order []int + inputTuple := T.MakeTuple3(10, 20, 30) + result := TraverseSeqTuple3( + func(x int) IO[int] { return func() int { order = append(order, 1); return x * 2 } }, + func(x int) IO[int] { return func() int { order = append(order, 2); return x + 1 } }, + func(x int) IO[int] { return func() int { order = append(order, 3); return x - 1 } }, + )(inputTuple) + + tuple := result() + assert.Equal(t, 20, tuple.F1) + assert.Equal(t, 21, tuple.F2) + assert.Equal(t, 29, tuple.F3) + assert.Equal(t, []int{1, 2, 3}, order) +} + +// Test TraverseParTuple3 +func TestTraverseParTuple3(t *testing.T) { + inputTuple := T.MakeTuple3(10, 20, 30) + result := TraverseParTuple3( + func(x int) IO[int] { return Of(x * 2) }, + func(x int) IO[int] { return Of(x + 1) }, + func(x int) IO[int] { return Of(x - 1) }, + )(inputTuple) + + tuple := result() + assert.Equal(t, 20, tuple.F1) + assert.Equal(t, 21, tuple.F2) + assert.Equal(t, 29, tuple.F3) +} + +// Test SequenceSeqTuple3 +func TestSequenceSeqTuple3(t *testing.T) { + var order []int + io1 := func() int { order = append(order, 1); return 10 } + io2 := func() int { order = append(order, 2); return 20 } + io3 := func() int { order = append(order, 3); return 30 } + + tup := T.MakeTuple3[IO[int], IO[int], IO[int]](io1, io2, io3) + result := SequenceSeqTuple3(tup) + tuple := result() + + assert.Equal(t, 10, tuple.F1) + assert.Equal(t, 20, tuple.F2) + assert.Equal(t, 30, tuple.F3) + assert.Equal(t, []int{1, 2, 3}, order) +} + +// Test SequenceParTuple3 +func TestSequenceParTuple3(t *testing.T) { + tup := T.MakeTuple3(Of(10), Of(20), Of(30)) + result := SequenceParTuple3(tup) + tuple := result() + + assert.Equal(t, 10, tuple.F1) + assert.Equal(t, 20, tuple.F2) + assert.Equal(t, 30, tuple.F3) +} + +// Test SequenceSeqT4 +func TestSequenceSeqT4(t *testing.T) { + var order []int + io1 := func() int { order = append(order, 1); return 1 } + io2 := func() int { order = append(order, 2); return 2 } + io3 := func() int { order = append(order, 3); return 3 } + io4 := func() int { order = append(order, 4); return 4 } + + result := SequenceSeqT4(io1, io2, io3, io4) + tuple := result() + + assert.Equal(t, 1, tuple.F1) + assert.Equal(t, 2, tuple.F2) + assert.Equal(t, 3, tuple.F3) + assert.Equal(t, 4, tuple.F4) + assert.Equal(t, []int{1, 2, 3, 4}, order) +} + +// Test SequenceParT4 +func TestSequenceParT4(t *testing.T) { + result := SequenceParT4(Of(1), Of(2), Of(3), Of(4)) + tuple := result() + + assert.Equal(t, 1, tuple.F1) + assert.Equal(t, 2, tuple.F2) + assert.Equal(t, 3, tuple.F3) + assert.Equal(t, 4, tuple.F4) +} + +// Test SequenceSeqTuple4 +func TestSequenceSeqTuple4(t *testing.T) { + var order []int + io1 := func() int { order = append(order, 1); return 1 } + io2 := func() int { order = append(order, 2); return 2 } + io3 := func() int { order = append(order, 3); return 3 } + io4 := func() int { order = append(order, 4); return 4 } + + tup := T.MakeTuple4[IO[int], IO[int], IO[int], IO[int]](io1, io2, io3, io4) + result := SequenceSeqTuple4(tup) + tuple := result() + + assert.Equal(t, 1, tuple.F1) + assert.Equal(t, 2, tuple.F2) + assert.Equal(t, 3, tuple.F3) + assert.Equal(t, 4, tuple.F4) + assert.Equal(t, []int{1, 2, 3, 4}, order) +} + +// Test SequenceParTuple4 +func TestSequenceParTuple4(t *testing.T) { + tup := T.MakeTuple4(Of(1), Of(2), Of(3), Of(4)) + result := SequenceParTuple4(tup) + tuple := result() + + assert.Equal(t, 1, tuple.F1) + assert.Equal(t, 2, tuple.F2) + assert.Equal(t, 3, tuple.F3) + assert.Equal(t, 4, tuple.F4) +} + +// Test TraverseTuple4 +func TestTraverseTuple4(t *testing.T) { + inputTuple := T.MakeTuple4(1, 2, 3, 4) + result := TraverseTuple4( + func(x int) IO[int] { return Of(x * 10) }, + func(x int) IO[int] { return Of(x * 20) }, + func(x int) IO[int] { return Of(x * 30) }, + func(x int) IO[int] { return Of(x * 40) }, + )(inputTuple) + + tuple := result() + assert.Equal(t, 10, tuple.F1) + assert.Equal(t, 40, tuple.F2) + assert.Equal(t, 90, tuple.F3) + assert.Equal(t, 160, tuple.F4) +} + +// Test SequenceSeqT5 +func TestSequenceSeqT5(t *testing.T) { + var order []int + io1 := func() int { order = append(order, 1); return 1 } + io2 := func() int { order = append(order, 2); return 2 } + io3 := func() int { order = append(order, 3); return 3 } + io4 := func() int { order = append(order, 4); return 4 } + io5 := func() int { order = append(order, 5); return 5 } + + result := SequenceSeqT5(io1, io2, io3, io4, io5) + tuple := result() + + assert.Equal(t, 1, tuple.F1) + assert.Equal(t, 2, tuple.F2) + assert.Equal(t, 3, tuple.F3) + assert.Equal(t, 4, tuple.F4) + assert.Equal(t, 5, tuple.F5) + assert.Equal(t, []int{1, 2, 3, 4, 5}, order) +} + +// Test SequenceParT5 +func TestSequenceParT5(t *testing.T) { + result := SequenceParT5(Of(1), Of(2), Of(3), Of(4), Of(5)) + tuple := result() + + assert.Equal(t, 1, tuple.F1) + assert.Equal(t, 2, tuple.F2) + assert.Equal(t, 3, tuple.F3) + assert.Equal(t, 4, tuple.F4) + assert.Equal(t, 5, tuple.F5) +} + +// Test SequenceSeqTuple1 +func TestSequenceSeqTuple1(t *testing.T) { + var executed bool + io1 := func() int { executed = true; return 42 } + + tup := T.MakeTuple1[IO[int]](io1) + result := SequenceSeqTuple1(tup) + tuple := result() + + assert.Equal(t, 42, tuple.F1) + assert.True(t, executed) +} + +// Test SequenceParTuple1 +func TestSequenceParTuple1(t *testing.T) { + tup := T.MakeTuple1(Of(42)) + result := SequenceParTuple1(tup) + tuple := result() + + assert.Equal(t, 42, tuple.F1) +} + +// Test TraverseTuple1 +func TestTraverseTuple1(t *testing.T) { + inputTuple := T.MakeTuple1(21) + result := TraverseTuple1( + func(x int) IO[int] { return Of(x * 2) }, + )(inputTuple) + + tuple := result() + assert.Equal(t, 42, tuple.F1) +} + +// Test TraverseSeqTuple1 +func TestTraverseSeqTuple1(t *testing.T) { + var executed bool + inputTuple := T.MakeTuple1(21) + result := TraverseSeqTuple1( + func(x int) IO[int] { return func() int { executed = true; return x * 2 } }, + )(inputTuple) + + tuple := result() + assert.Equal(t, 42, tuple.F1) + assert.True(t, executed) +} + +// Test TraverseParTuple1 +func TestTraverseParTuple1(t *testing.T) { + inputTuple := T.MakeTuple1(21) + result := TraverseParTuple1( + func(x int) IO[int] { return Of(x * 2) }, + )(inputTuple) + + tuple := result() + assert.Equal(t, 42, tuple.F1) +} + +// Test SequenceTuple5 +func TestSequenceTuple5(t *testing.T) { + tup := T.MakeTuple5(Of(1), Of(2), Of(3), Of(4), Of(5)) + result := SequenceTuple5(tup) + tuple := result() + + assert.Equal(t, 1, tuple.F1) + assert.Equal(t, 2, tuple.F2) + assert.Equal(t, 3, tuple.F3) + assert.Equal(t, 4, tuple.F4) + assert.Equal(t, 5, tuple.F5) +} + +// Test SequenceSeqTuple5 +func TestSequenceSeqTuple5(t *testing.T) { + var order []int + io1 := func() int { order = append(order, 1); return 1 } + io2 := func() int { order = append(order, 2); return 2 } + io3 := func() int { order = append(order, 3); return 3 } + io4 := func() int { order = append(order, 4); return 4 } + io5 := func() int { order = append(order, 5); return 5 } + + tup := T.MakeTuple5[IO[int], IO[int], IO[int], IO[int], IO[int]](io1, io2, io3, io4, io5) + result := SequenceSeqTuple5(tup) + tuple := result() + + assert.Equal(t, 1, tuple.F1) + assert.Equal(t, 2, tuple.F2) + assert.Equal(t, 3, tuple.F3) + assert.Equal(t, 4, tuple.F4) + assert.Equal(t, 5, tuple.F5) + assert.Equal(t, []int{1, 2, 3, 4, 5}, order) +} + +// Test SequenceParTuple5 +func TestSequenceParTuple5(t *testing.T) { + tup := T.MakeTuple5(Of(1), Of(2), Of(3), Of(4), Of(5)) + result := SequenceParTuple5(tup) + tuple := result() + + assert.Equal(t, 1, tuple.F1) + assert.Equal(t, 2, tuple.F2) + assert.Equal(t, 3, tuple.F3) + assert.Equal(t, 4, tuple.F4) + assert.Equal(t, 5, tuple.F5) +} + +// Test TraverseTuple5 +func TestTraverseTuple5(t *testing.T) { + inputTuple := T.MakeTuple5(1, 2, 3, 4, 5) + result := TraverseTuple5( + func(x int) IO[int] { return Of(x * 10) }, + func(x int) IO[int] { return Of(x * 20) }, + func(x int) IO[int] { return Of(x * 30) }, + func(x int) IO[int] { return Of(x * 40) }, + func(x int) IO[int] { return Of(x * 50) }, + )(inputTuple) + + tuple := result() + assert.Equal(t, 10, tuple.F1) + assert.Equal(t, 40, tuple.F2) + assert.Equal(t, 90, tuple.F3) + assert.Equal(t, 160, tuple.F4) + assert.Equal(t, 250, tuple.F5) +} + +// Test TraverseSeqTuple5 +func TestTraverseSeqTuple5(t *testing.T) { + var order []int + inputTuple := T.MakeTuple5(1, 2, 3, 4, 5) + result := TraverseSeqTuple5( + func(x int) IO[int] { return func() int { order = append(order, 1); return x * 10 } }, + func(x int) IO[int] { return func() int { order = append(order, 2); return x * 20 } }, + func(x int) IO[int] { return func() int { order = append(order, 3); return x * 30 } }, + func(x int) IO[int] { return func() int { order = append(order, 4); return x * 40 } }, + func(x int) IO[int] { return func() int { order = append(order, 5); return x * 50 } }, + )(inputTuple) + + tuple := result() + assert.Equal(t, 10, tuple.F1) + assert.Equal(t, 40, tuple.F2) + assert.Equal(t, 90, tuple.F3) + assert.Equal(t, 160, tuple.F4) + assert.Equal(t, 250, tuple.F5) + assert.Equal(t, []int{1, 2, 3, 4, 5}, order) +} + +// Test TraverseParTuple5 +func TestTraverseParTuple5(t *testing.T) { + inputTuple := T.MakeTuple5(1, 2, 3, 4, 5) + result := TraverseParTuple5( + func(x int) IO[int] { return Of(x * 10) }, + func(x int) IO[int] { return Of(x * 20) }, + func(x int) IO[int] { return Of(x * 30) }, + func(x int) IO[int] { return Of(x * 40) }, + func(x int) IO[int] { return Of(x * 50) }, + )(inputTuple) + + tuple := result() + assert.Equal(t, 10, tuple.F1) + assert.Equal(t, 40, tuple.F2) + assert.Equal(t, 90, tuple.F3) + assert.Equal(t, 160, tuple.F4) + assert.Equal(t, 250, tuple.F5) +} + +// Test SequenceT6 +func TestSequenceT6(t *testing.T) { + result := SequenceT6(Of(1), Of(2), Of(3), Of(4), Of(5), Of(6)) + tuple := result() + + assert.Equal(t, 1, tuple.F1) + assert.Equal(t, 2, tuple.F2) + assert.Equal(t, 3, tuple.F3) + assert.Equal(t, 4, tuple.F4) + assert.Equal(t, 5, tuple.F5) + assert.Equal(t, 6, tuple.F6) +} + +// Test SequenceSeqT6 +func TestSequenceSeqT6(t *testing.T) { + var order []int + io1 := func() int { order = append(order, 1); return 1 } + io2 := func() int { order = append(order, 2); return 2 } + io3 := func() int { order = append(order, 3); return 3 } + io4 := func() int { order = append(order, 4); return 4 } + io5 := func() int { order = append(order, 5); return 5 } + io6 := func() int { order = append(order, 6); return 6 } + + result := SequenceSeqT6(io1, io2, io3, io4, io5, io6) + tuple := result() + + assert.Equal(t, 1, tuple.F1) + assert.Equal(t, 2, tuple.F2) + assert.Equal(t, 3, tuple.F3) + assert.Equal(t, 4, tuple.F4) + assert.Equal(t, 5, tuple.F5) + assert.Equal(t, 6, tuple.F6) + assert.Equal(t, []int{1, 2, 3, 4, 5, 6}, order) +} + +// Test SequenceParT6 +func TestSequenceParT6(t *testing.T) { + result := SequenceParT6(Of(1), Of(2), Of(3), Of(4), Of(5), Of(6)) + tuple := result() + + assert.Equal(t, 1, tuple.F1) + assert.Equal(t, 2, tuple.F2) + assert.Equal(t, 3, tuple.F3) + assert.Equal(t, 4, tuple.F4) + assert.Equal(t, 5, tuple.F5) + assert.Equal(t, 6, tuple.F6) +} + +// Test SequenceT7 +func TestSequenceT7(t *testing.T) { + result := SequenceT7(Of(1), Of(2), Of(3), Of(4), Of(5), Of(6), Of(7)) + tuple := result() + + assert.Equal(t, 1, tuple.F1) + assert.Equal(t, 2, tuple.F2) + assert.Equal(t, 3, tuple.F3) + assert.Equal(t, 4, tuple.F4) + assert.Equal(t, 5, tuple.F5) + assert.Equal(t, 6, tuple.F6) + assert.Equal(t, 7, tuple.F7) +} + +// Test SequenceT8 +func TestSequenceT8(t *testing.T) { + result := SequenceT8(Of(1), Of(2), Of(3), Of(4), Of(5), Of(6), Of(7), Of(8)) + tuple := result() + + assert.Equal(t, 1, tuple.F1) + assert.Equal(t, 2, tuple.F2) + assert.Equal(t, 3, tuple.F3) + assert.Equal(t, 4, tuple.F4) + assert.Equal(t, 5, tuple.F5) + assert.Equal(t, 6, tuple.F6) + assert.Equal(t, 7, tuple.F7) + assert.Equal(t, 8, tuple.F8) +} + +// Test SequenceT9 +func TestSequenceT9(t *testing.T) { + result := SequenceT9(Of(1), Of(2), Of(3), Of(4), Of(5), Of(6), Of(7), Of(8), Of(9)) + tuple := result() + + assert.Equal(t, 1, tuple.F1) + assert.Equal(t, 2, tuple.F2) + assert.Equal(t, 3, tuple.F3) + assert.Equal(t, 4, tuple.F4) + assert.Equal(t, 5, tuple.F5) + assert.Equal(t, 6, tuple.F6) + assert.Equal(t, 7, tuple.F7) + assert.Equal(t, 8, tuple.F8) + assert.Equal(t, 9, tuple.F9) +} + +// Test SequenceT10 +func TestSequenceT10(t *testing.T) { + result := SequenceT10(Of(1), Of(2), Of(3), Of(4), Of(5), Of(6), Of(7), Of(8), Of(9), Of(10)) + tuple := result() + + assert.Equal(t, 1, tuple.F1) + assert.Equal(t, 2, tuple.F2) + assert.Equal(t, 3, tuple.F3) + assert.Equal(t, 4, tuple.F4) + assert.Equal(t, 5, tuple.F5) + assert.Equal(t, 6, tuple.F6) + assert.Equal(t, 7, tuple.F7) + assert.Equal(t, 8, tuple.F8) + assert.Equal(t, 9, tuple.F9) + assert.Equal(t, 10, tuple.F10) +} + +// Test SequenceTuple6 +func TestSequenceTuple6(t *testing.T) { + tup := T.MakeTuple6(Of(1), Of(2), Of(3), Of(4), Of(5), Of(6)) + result := SequenceTuple6(tup) + tuple := result() + + assert.Equal(t, 1, tuple.F1) + assert.Equal(t, 2, tuple.F2) + assert.Equal(t, 3, tuple.F3) + assert.Equal(t, 4, tuple.F4) + assert.Equal(t, 5, tuple.F5) + assert.Equal(t, 6, tuple.F6) +} + +// Test SequenceSeqTuple6 +func TestSequenceSeqTuple6(t *testing.T) { + var order []int + io1 := func() int { order = append(order, 1); return 1 } + io2 := func() int { order = append(order, 2); return 2 } + io3 := func() int { order = append(order, 3); return 3 } + io4 := func() int { order = append(order, 4); return 4 } + io5 := func() int { order = append(order, 5); return 5 } + io6 := func() int { order = append(order, 6); return 6 } + + tup := T.MakeTuple6[IO[int], IO[int], IO[int], IO[int], IO[int], IO[int]](io1, io2, io3, io4, io5, io6) + result := SequenceSeqTuple6(tup) + tuple := result() + + assert.Equal(t, 1, tuple.F1) + assert.Equal(t, 2, tuple.F2) + assert.Equal(t, 3, tuple.F3) + assert.Equal(t, 4, tuple.F4) + assert.Equal(t, 5, tuple.F5) + assert.Equal(t, 6, tuple.F6) + assert.Equal(t, []int{1, 2, 3, 4, 5, 6}, order) +} + +// Test SequenceParTuple6 +func TestSequenceParTuple6(t *testing.T) { + tup := T.MakeTuple6(Of(1), Of(2), Of(3), Of(4), Of(5), Of(6)) + result := SequenceParTuple6(tup) + tuple := result() + + assert.Equal(t, 1, tuple.F1) + assert.Equal(t, 2, tuple.F2) + assert.Equal(t, 3, tuple.F3) + assert.Equal(t, 4, tuple.F4) + assert.Equal(t, 5, tuple.F5) + assert.Equal(t, 6, tuple.F6) +} + +// Test TraverseTuple6 +func TestTraverseTuple6(t *testing.T) { + inputTuple := T.MakeTuple6(1, 2, 3, 4, 5, 6) + result := TraverseTuple6( + func(x int) IO[int] { return Of(x * 1) }, + func(x int) IO[int] { return Of(x * 2) }, + func(x int) IO[int] { return Of(x * 3) }, + func(x int) IO[int] { return Of(x * 4) }, + func(x int) IO[int] { return Of(x * 5) }, + func(x int) IO[int] { return Of(x * 6) }, + )(inputTuple) + + tuple := result() + assert.Equal(t, 1, tuple.F1) + assert.Equal(t, 4, tuple.F2) + assert.Equal(t, 9, tuple.F3) + assert.Equal(t, 16, tuple.F4) + assert.Equal(t, 25, tuple.F5) + assert.Equal(t, 36, tuple.F6) +} + +// Test SequenceTuple7 +func TestSequenceTuple7(t *testing.T) { + tup := T.MakeTuple7(Of(1), Of(2), Of(3), Of(4), Of(5), Of(6), Of(7)) + result := SequenceTuple7(tup) + tuple := result() + + assert.Equal(t, 1, tuple.F1) + assert.Equal(t, 2, tuple.F2) + assert.Equal(t, 3, tuple.F3) + assert.Equal(t, 4, tuple.F4) + assert.Equal(t, 5, tuple.F5) + assert.Equal(t, 6, tuple.F6) + assert.Equal(t, 7, tuple.F7) +} + +// Test SequenceSeqT7 +func TestSequenceSeqT7(t *testing.T) { + var order []int + io1 := func() int { order = append(order, 1); return 1 } + io2 := func() int { order = append(order, 2); return 2 } + io3 := func() int { order = append(order, 3); return 3 } + io4 := func() int { order = append(order, 4); return 4 } + io5 := func() int { order = append(order, 5); return 5 } + io6 := func() int { order = append(order, 6); return 6 } + io7 := func() int { order = append(order, 7); return 7 } + + result := SequenceSeqT7(io1, io2, io3, io4, io5, io6, io7) + tuple := result() + + assert.Equal(t, 1, tuple.F1) + assert.Equal(t, 2, tuple.F2) + assert.Equal(t, 3, tuple.F3) + assert.Equal(t, 4, tuple.F4) + assert.Equal(t, 5, tuple.F5) + assert.Equal(t, 6, tuple.F6) + assert.Equal(t, 7, tuple.F7) + assert.Equal(t, []int{1, 2, 3, 4, 5, 6, 7}, order) +} + +// Test SequenceParT7 +func TestSequenceParT7(t *testing.T) { + result := SequenceParT7(Of(1), Of(2), Of(3), Of(4), Of(5), Of(6), Of(7)) + tuple := result() + + assert.Equal(t, 1, tuple.F1) + assert.Equal(t, 2, tuple.F2) + assert.Equal(t, 3, tuple.F3) + assert.Equal(t, 4, tuple.F4) + assert.Equal(t, 5, tuple.F5) + assert.Equal(t, 6, tuple.F6) + assert.Equal(t, 7, tuple.F7) +} + +// Test SequenceTuple8 +func TestSequenceTuple8(t *testing.T) { + tup := T.MakeTuple8(Of(1), Of(2), Of(3), Of(4), Of(5), Of(6), Of(7), Of(8)) + result := SequenceTuple8(tup) + tuple := result() + + assert.Equal(t, 1, tuple.F1) + assert.Equal(t, 2, tuple.F2) + assert.Equal(t, 3, tuple.F3) + assert.Equal(t, 4, tuple.F4) + assert.Equal(t, 5, tuple.F5) + assert.Equal(t, 6, tuple.F6) + assert.Equal(t, 7, tuple.F7) + assert.Equal(t, 8, tuple.F8) +} + +// Test SequenceSeqT8 +func TestSequenceSeqT8(t *testing.T) { + var order []int + io1 := func() int { order = append(order, 1); return 1 } + io2 := func() int { order = append(order, 2); return 2 } + io3 := func() int { order = append(order, 3); return 3 } + io4 := func() int { order = append(order, 4); return 4 } + io5 := func() int { order = append(order, 5); return 5 } + io6 := func() int { order = append(order, 6); return 6 } + io7 := func() int { order = append(order, 7); return 7 } + io8 := func() int { order = append(order, 8); return 8 } + + result := SequenceSeqT8(io1, io2, io3, io4, io5, io6, io7, io8) + tuple := result() + + assert.Equal(t, 1, tuple.F1) + assert.Equal(t, 2, tuple.F2) + assert.Equal(t, 3, tuple.F3) + assert.Equal(t, 4, tuple.F4) + assert.Equal(t, 5, tuple.F5) + assert.Equal(t, 6, tuple.F6) + assert.Equal(t, 7, tuple.F7) + assert.Equal(t, 8, tuple.F8) + assert.Equal(t, []int{1, 2, 3, 4, 5, 6, 7, 8}, order) +} + +// Test SequenceParT8 +func TestSequenceParT8(t *testing.T) { + result := SequenceParT8(Of(1), Of(2), Of(3), Of(4), Of(5), Of(6), Of(7), Of(8)) + tuple := result() + + assert.Equal(t, 1, tuple.F1) + assert.Equal(t, 2, tuple.F2) + assert.Equal(t, 3, tuple.F3) + assert.Equal(t, 4, tuple.F4) + assert.Equal(t, 5, tuple.F5) + assert.Equal(t, 6, tuple.F6) + assert.Equal(t, 7, tuple.F7) + assert.Equal(t, 8, tuple.F8) +} + +// Test SequenceTuple9 +func TestSequenceTuple9(t *testing.T) { + tup := T.MakeTuple9(Of(1), Of(2), Of(3), Of(4), Of(5), Of(6), Of(7), Of(8), Of(9)) + result := SequenceTuple9(tup) + tuple := result() + + assert.Equal(t, 1, tuple.F1) + assert.Equal(t, 2, tuple.F2) + assert.Equal(t, 3, tuple.F3) + assert.Equal(t, 4, tuple.F4) + assert.Equal(t, 5, tuple.F5) + assert.Equal(t, 6, tuple.F6) + assert.Equal(t, 7, tuple.F7) + assert.Equal(t, 8, tuple.F8) + assert.Equal(t, 9, tuple.F9) +} + +// Test SequenceSeqT9 +func TestSequenceSeqT9(t *testing.T) { + var order []int + io1 := func() int { order = append(order, 1); return 1 } + io2 := func() int { order = append(order, 2); return 2 } + io3 := func() int { order = append(order, 3); return 3 } + io4 := func() int { order = append(order, 4); return 4 } + io5 := func() int { order = append(order, 5); return 5 } + io6 := func() int { order = append(order, 6); return 6 } + io7 := func() int { order = append(order, 7); return 7 } + io8 := func() int { order = append(order, 8); return 8 } + io9 := func() int { order = append(order, 9); return 9 } + + result := SequenceSeqT9(io1, io2, io3, io4, io5, io6, io7, io8, io9) + tuple := result() + + assert.Equal(t, 1, tuple.F1) + assert.Equal(t, 2, tuple.F2) + assert.Equal(t, 3, tuple.F3) + assert.Equal(t, 4, tuple.F4) + assert.Equal(t, 5, tuple.F5) + assert.Equal(t, 6, tuple.F6) + assert.Equal(t, 7, tuple.F7) + assert.Equal(t, 8, tuple.F8) + assert.Equal(t, 9, tuple.F9) + assert.Equal(t, []int{1, 2, 3, 4, 5, 6, 7, 8, 9}, order) +} + +// Test SequenceParT9 +func TestSequenceParT9(t *testing.T) { + result := SequenceParT9(Of(1), Of(2), Of(3), Of(4), Of(5), Of(6), Of(7), Of(8), Of(9)) + tuple := result() + + assert.Equal(t, 1, tuple.F1) + assert.Equal(t, 2, tuple.F2) + assert.Equal(t, 3, tuple.F3) + assert.Equal(t, 4, tuple.F4) + assert.Equal(t, 5, tuple.F5) + assert.Equal(t, 6, tuple.F6) + assert.Equal(t, 7, tuple.F7) + assert.Equal(t, 8, tuple.F8) + assert.Equal(t, 9, tuple.F9) +} + +// Test SequenceTuple10 +func TestSequenceTuple10(t *testing.T) { + tup := T.MakeTuple10(Of(1), Of(2), Of(3), Of(4), Of(5), Of(6), Of(7), Of(8), Of(9), Of(10)) + result := SequenceTuple10(tup) + tuple := result() + + assert.Equal(t, 1, tuple.F1) + assert.Equal(t, 2, tuple.F2) + assert.Equal(t, 3, tuple.F3) + assert.Equal(t, 4, tuple.F4) + assert.Equal(t, 5, tuple.F5) + assert.Equal(t, 6, tuple.F6) + assert.Equal(t, 7, tuple.F7) + assert.Equal(t, 8, tuple.F8) + assert.Equal(t, 9, tuple.F9) + assert.Equal(t, 10, tuple.F10) +} + +// Test SequenceSeqT10 +func TestSequenceSeqT10(t *testing.T) { + var order []int + io1 := func() int { order = append(order, 1); return 1 } + io2 := func() int { order = append(order, 2); return 2 } + io3 := func() int { order = append(order, 3); return 3 } + io4 := func() int { order = append(order, 4); return 4 } + io5 := func() int { order = append(order, 5); return 5 } + io6 := func() int { order = append(order, 6); return 6 } + io7 := func() int { order = append(order, 7); return 7 } + io8 := func() int { order = append(order, 8); return 8 } + io9 := func() int { order = append(order, 9); return 9 } + io10 := func() int { order = append(order, 10); return 10 } + + result := SequenceSeqT10(io1, io2, io3, io4, io5, io6, io7, io8, io9, io10) + tuple := result() + + assert.Equal(t, 1, tuple.F1) + assert.Equal(t, 2, tuple.F2) + assert.Equal(t, 3, tuple.F3) + assert.Equal(t, 4, tuple.F4) + assert.Equal(t, 5, tuple.F5) + assert.Equal(t, 6, tuple.F6) + assert.Equal(t, 7, tuple.F7) + assert.Equal(t, 8, tuple.F8) + assert.Equal(t, 9, tuple.F9) + assert.Equal(t, 10, tuple.F10) + assert.Equal(t, []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, order) +} + +// Test SequenceParT10 +func TestSequenceParT10(t *testing.T) { + result := SequenceParT10(Of(1), Of(2), Of(3), Of(4), Of(5), Of(6), Of(7), Of(8), Of(9), Of(10)) + tuple := result() + + assert.Equal(t, 1, tuple.F1) + assert.Equal(t, 2, tuple.F2) + assert.Equal(t, 3, tuple.F3) + assert.Equal(t, 4, tuple.F4) + assert.Equal(t, 5, tuple.F5) + assert.Equal(t, 6, tuple.F6) + assert.Equal(t, 7, tuple.F7) + assert.Equal(t, 8, tuple.F8) + assert.Equal(t, 9, tuple.F9) + assert.Equal(t, 10, tuple.F10) +} diff --git a/v2/io/doc.go b/v2/io/doc.go new file mode 100644 index 0000000..b61896b --- /dev/null +++ b/v2/io/doc.go @@ -0,0 +1,155 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +// Package io provides the IO monad, representing synchronous computations that cannot fail. +// +// IO is a lazy computation that encapsulates side effects and ensures referential transparency. +// Unlike functions that execute immediately, IO values describe computations that will be +// executed when explicitly invoked. +// +// # Core Concepts +// +// The IO type is defined as a function that takes no arguments and returns a value: +// +// type IO[A any] = func() A +// +// This simple definition provides powerful guarantees: +// - Lazy evaluation: computations are not executed until explicitly called +// - Composability: IO operations can be combined without executing them +// - Referential transparency: IO values can be safely reused and passed around +// +// # Basic Usage +// +// // Creating IO values +// greeting := io.Of("Hello, World!") +// timestamp := io.Now +// +// // Transforming values with Map +// upper := io.Map(strings.ToUpper)(greeting) +// +// // Chaining computations with Chain +// result := io.Chain(func(s string) io.IO[int] { +// return io.Of(len(s)) +// })(greeting) +// +// // Executing the computation +// value := result() // Only now does the computation run +// +// # Monadic Operations +// +// IO implements the Monad interface, providing: +// - Of: Wrap a pure value in IO +// - Map: Transform the result of a computation +// - Chain (FlatMap): Sequence computations that return IO +// - Ap: Apply a function wrapped in IO to a value wrapped in IO +// +// # Parallel vs Sequential Execution +// +// IO supports both parallel and sequential execution of applicative operations: +// - Ap/MonadAp: Parallel execution (default) +// - ApSeq/MonadApSeq: Sequential execution +// - ApPar/MonadApPar: Explicit parallel execution +// +// # Time-based Operations +// +// // Delay execution +// delayed := io.Delay(time.Second)(computation) +// +// // Execute after a specific time +// scheduled := io.After(timestamp)(computation) +// +// // Measure execution time +// withDuration := io.WithDuration(computation) +// withTime := io.WithTime(computation) +// +// # Resource Management +// +// IO provides utilities for safe resource management: +// +// // Bracket ensures cleanup +// result := io.Bracket( +// acquire, +// use, +// release, +// ) +// +// // WithResource simplifies resource patterns +// withFile := io.WithResource(openFile, closeFile) +// result := withFile(func(f *os.File) io.IO[Data] { +// return readData(f) +// }) +// +// # Retry Logic +// +// // Retry with exponential backoff +// result := io.Retrying( +// retry.ExponentialBackoff(time.Second, 5), +// func(status retry.RetryStatus) io.IO[Result] { +// return fetchData() +// }, +// func(r Result) bool { return r.ShouldRetry }, +// ) +// +// # Traversal Operations +// +// IO provides utilities for working with collections: +// - TraverseArray: Apply IO-returning function to array elements +// - TraverseRecord: Apply IO-returning function to map values +// - SequenceArray: Convert []IO[A] to IO[[]A] +// - SequenceRecord: Convert map[K]IO[A] to IO[map[K]A] +// +// Both parallel and sequential variants are available (e.g., TraverseArraySeq). +// +// # Do Notation +// +// IO supports do-notation style composition for imperative-looking code: +// +// result := pipe.Pipe3( +// io.Do(State{}), +// io.Bind("user", func(s State) io.IO[User] { +// return fetchUser(s.userId) +// }), +// io.Bind("posts", func(s State) io.IO[[]Post] { +// return fetchPosts(s.user.Id) +// }), +// io.Map(func(s State) Result { +// return formatResult(s.user, s.posts) +// }), +// ) +// +// # Logging and Debugging +// +// // Log values during computation +// logged := io.ChainFirst(io.Logger()("Fetched user"))(fetchUser) +// +// // Printf-style logging +// logged := io.ChainFirst(io.Printf("User: %+v"))(fetchUser) +// +// # Subpackages +// +// - io/file: File system operations returning IO +// - io/generic: Generic IO utilities and type classes +// - io/testing: Testing utilities for IO laws +// +// # Relationship to Other Monads +// +// IO is the simplest effect monad in the fp-go library: +// - IOEither: IO that can fail (combines IO with Either) +// - IOOption: IO that may not return a value (combines IO with Option) +// - ReaderIO: IO with dependency injection (combines Reader with IO) +// - ReaderIOEither: Full effect system with DI and error handling +package io + +//go:generate go run .. io --count 10 --filename gen.go diff --git a/v2/io/eq.go b/v2/io/eq.go new file mode 100644 index 0000000..f2f7dae --- /dev/null +++ b/v2/io/eq.go @@ -0,0 +1,55 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package io + +import ( + EQ "github.com/IBM/fp-go/v2/eq" + INTE "github.com/IBM/fp-go/v2/internal/eq" +) + +// Eq implements the equals predicate for values contained in the IO monad. +// It lifts an Eq[A] into an Eq[IO[A]] by executing both IO computations +// and comparing their results. +// +// Example: +// +// intEq := eq.FromStrictEquals[int]() +// ioEq := io.Eq(intEq) +// result := ioEq.Equals(io.Of(42), io.Of(42)) // true +func Eq[A any](e EQ.Eq[A]) EQ.Eq[IO[A]] { + // comparator for the monad + eq := INTE.Eq( + MonadMap[A, func(A) bool], + MonadAp[A, bool], + e, + ) + // eagerly execute + return EQ.FromEquals(func(l, r IO[A]) bool { + return eq(l, r)() + }) +} + +// FromStrictEquals constructs an Eq[IO[A]] from the canonical comparison function +// for comparable types. This is a convenience function that combines Eq with +// the standard equality operator. +// +// Example: +// +// ioEq := io.FromStrictEquals[int]() +// result := ioEq.Equals(io.Of(42), io.Of(42)) // true +func FromStrictEquals[A comparable]() EQ.Eq[IO[A]] { + return Eq(EQ.FromStrictEquals[A]()) +} diff --git a/v2/io/file/file.go b/v2/io/file/file.go new file mode 100644 index 0000000..24ac80a --- /dev/null +++ b/v2/io/file/file.go @@ -0,0 +1,39 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package file + +import ( + "io" + "os" + + IO "github.com/IBM/fp-go/v2/io" +) + +// Close closes a closeable resource and ignores a potential error +func Close[R io.Closer](r R) IO.IO[R] { + return func() R { + r.Close() // #nosec: G104 + return r + } +} + +// Remove removes a resource and ignores a potential error +func Remove(name string) IO.IO[string] { + return func() string { + os.Remove(name) // #nosec: G104 + return name + } +} diff --git a/v2/io/functor.go b/v2/io/functor.go new file mode 100644 index 0000000..b1d2aad --- /dev/null +++ b/v2/io/functor.go @@ -0,0 +1,45 @@ +// Copyright (c) 2024 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package io + +import ( + "github.com/IBM/fp-go/v2/internal/functor" +) + +type ( + ioFunctor[A, B any] struct{} + + // IOFunctor represents the functor type class for IO. + // A functor allows mapping a function over a wrapped value without + // unwrapping it, preserving the structure. + IOFunctor[A, B any] = functor.Functor[A, B, IO[A], IO[B]] +) + +func (o *ioFunctor[A, B]) Map(f func(A) B) Operator[A, B] { + return Map(f) +} + +// Functor returns an instance of the Functor type class for IO. +// This provides a structured way to access functor operations (Map) +// for IO computations. +// +// Example: +// +// f := io.Functor[int, string]() +// result := f.Map(strconv.Itoa)(io.Of(42)) +func Functor[A, B any]() IOFunctor[A, B] { + return &ioFunctor[A, B]{} +} diff --git a/v2/io/gen.go b/v2/io/gen.go new file mode 100644 index 0000000..a5c9607 --- /dev/null +++ b/v2/io/gen.go @@ -0,0 +1,1691 @@ +// Code generated by go generate; DO NOT EDIT. +// This file was generated by robots at +// 2025-03-09 23:53:05.8147342 +0100 CET m=+0.003528401 + +package io + + +import ( + "github.com/IBM/fp-go/v2/tuple" + "github.com/IBM/fp-go/v2/internal/apply" +) + +// SequenceT1 converts 1 [IO[T]] into a [IO[tuple.Tuple1[T1]]] +func SequenceT1[T1 any]( + t1 IO[T1], +) IO[tuple.Tuple1[T1]] { + return apply.SequenceT1( + Map[T1, tuple.Tuple1[T1]], + t1, + ) +} + +// SequenceSeqT1 converts 1 [IO[T]] into a [IO[tuple.Tuple1[T1]]] +func SequenceSeqT1[T1 any]( + t1 IO[T1], +) IO[tuple.Tuple1[T1]] { + return apply.SequenceT1( + Map[T1, tuple.Tuple1[T1]], + t1, + ) +} + +// SequenceParT1 converts 1 [IO[T]] into a [IO[tuple.Tuple1[T1]]] +func SequenceParT1[T1 any]( + t1 IO[T1], +) IO[tuple.Tuple1[T1]] { + return apply.SequenceT1( + Map[T1, tuple.Tuple1[T1]], + t1, + ) +} + +// SequenceTuple1 converts a [tuple.Tuple1[IO[T]]] into a [IO[tuple.Tuple1[T1]]] +func SequenceTuple1[T1 any](t tuple.Tuple1[IO[T1]]) IO[tuple.Tuple1[T1]] { + return apply.SequenceTuple1( + Map[T1, tuple.Tuple1[T1]], + t, + ) +} + +// SequenceSeqTuple1 converts a [tuple.Tuple1[IO[T]]] into a [IO[tuple.Tuple1[T1]]] +func SequenceSeqTuple1[T1 any](t tuple.Tuple1[IO[T1]]) IO[tuple.Tuple1[T1]] { + return apply.SequenceTuple1( + Map[T1, tuple.Tuple1[T1]], + t, + ) +} + +// SequenceParTuple1 converts a [tuple.Tuple1[IO[T]]] into a [IO[tuple.Tuple1[T1]]] +func SequenceParTuple1[T1 any](t tuple.Tuple1[IO[T1]]) IO[tuple.Tuple1[T1]] { + return apply.SequenceTuple1( + Map[T1, tuple.Tuple1[T1]], + t, + ) +} + +// TraverseTuple1 converts a [tuple.Tuple1[A1]] into a [IO[tuple.Tuple1[T1]]] +func TraverseTuple1[F1 ~func(A1) IO[T1], T1, A1 any](f1 F1) func(tuple.Tuple1[A1]) IO[tuple.Tuple1[T1]] { + return func(t tuple.Tuple1[A1]) IO[tuple.Tuple1[T1]] { + return apply.TraverseTuple1( + Map[T1, tuple.Tuple1[T1]], + f1, + t, + ) + } +} + +// TraverseSeqTuple1 converts a [tuple.Tuple1[A1]] into a [IO[tuple.Tuple1[T1]]] +func TraverseSeqTuple1[F1 ~func(A1) IO[T1], T1, A1 any](f1 F1) func(tuple.Tuple1[A1]) IO[tuple.Tuple1[T1]] { + return func(t tuple.Tuple1[A1]) IO[tuple.Tuple1[T1]] { + return apply.TraverseTuple1( + Map[T1, tuple.Tuple1[T1]], + f1, + t, + ) + } +} + +// TraverseParTuple1 converts a [tuple.Tuple1[A1]] into a [IO[tuple.Tuple1[T1]]] +func TraverseParTuple1[F1 ~func(A1) IO[T1], T1, A1 any](f1 F1) func(tuple.Tuple1[A1]) IO[tuple.Tuple1[T1]] { + return func(t tuple.Tuple1[A1]) IO[tuple.Tuple1[T1]] { + return apply.TraverseTuple1( + Map[T1, tuple.Tuple1[T1]], + f1, + t, + ) + } +} + +// SequenceT2 converts 2 [IO[T]] into a [IO[tuple.Tuple2[T1, T2]]] +func SequenceT2[T1, T2 any]( + t1 IO[T1], + t2 IO[T2], +) IO[tuple.Tuple2[T1, T2]] { + return apply.SequenceT2( + Map[T1, func(T2) tuple.Tuple2[T1, T2]], + Ap[tuple.Tuple2[T1, T2], T2], + t1, + t2, + ) +} + +// SequenceSeqT2 converts 2 [IO[T]] into a [IO[tuple.Tuple2[T1, T2]]] +func SequenceSeqT2[T1, T2 any]( + t1 IO[T1], + t2 IO[T2], +) IO[tuple.Tuple2[T1, T2]] { + return apply.SequenceT2( + Map[T1, func(T2) tuple.Tuple2[T1, T2]], + ApSeq[tuple.Tuple2[T1, T2], T2], + t1, + t2, + ) +} + +// SequenceParT2 converts 2 [IO[T]] into a [IO[tuple.Tuple2[T1, T2]]] +func SequenceParT2[T1, T2 any]( + t1 IO[T1], + t2 IO[T2], +) IO[tuple.Tuple2[T1, T2]] { + return apply.SequenceT2( + Map[T1, func(T2) tuple.Tuple2[T1, T2]], + ApPar[tuple.Tuple2[T1, T2], T2], + t1, + t2, + ) +} + +// SequenceTuple2 converts a [tuple.Tuple2[IO[T]]] into a [IO[tuple.Tuple2[T1, T2]]] +func SequenceTuple2[T1, T2 any](t tuple.Tuple2[IO[T1], IO[T2]]) IO[tuple.Tuple2[T1, T2]] { + return apply.SequenceTuple2( + Map[T1, func(T2) tuple.Tuple2[T1, T2]], + Ap[tuple.Tuple2[T1, T2], T2], + t, + ) +} + +// SequenceSeqTuple2 converts a [tuple.Tuple2[IO[T]]] into a [IO[tuple.Tuple2[T1, T2]]] +func SequenceSeqTuple2[T1, T2 any](t tuple.Tuple2[IO[T1], IO[T2]]) IO[tuple.Tuple2[T1, T2]] { + return apply.SequenceTuple2( + Map[T1, func(T2) tuple.Tuple2[T1, T2]], + ApSeq[tuple.Tuple2[T1, T2], T2], + t, + ) +} + +// SequenceParTuple2 converts a [tuple.Tuple2[IO[T]]] into a [IO[tuple.Tuple2[T1, T2]]] +func SequenceParTuple2[T1, T2 any](t tuple.Tuple2[IO[T1], IO[T2]]) IO[tuple.Tuple2[T1, T2]] { + return apply.SequenceTuple2( + Map[T1, func(T2) tuple.Tuple2[T1, T2]], + ApPar[tuple.Tuple2[T1, T2], T2], + t, + ) +} + +// TraverseTuple2 converts a [tuple.Tuple2[A1, A2]] into a [IO[tuple.Tuple2[T1, T2]]] +func TraverseTuple2[F1 ~func(A1) IO[T1], F2 ~func(A2) IO[T2], T1, T2, A1, A2 any](f1 F1, f2 F2) func(tuple.Tuple2[A1, A2]) IO[tuple.Tuple2[T1, T2]] { + return func(t tuple.Tuple2[A1, A2]) IO[tuple.Tuple2[T1, T2]] { + return apply.TraverseTuple2( + Map[T1, func(T2) tuple.Tuple2[T1, T2]], + Ap[tuple.Tuple2[T1, T2], T2], + f1, + f2, + t, + ) + } +} + +// TraverseSeqTuple2 converts a [tuple.Tuple2[A1, A2]] into a [IO[tuple.Tuple2[T1, T2]]] +func TraverseSeqTuple2[F1 ~func(A1) IO[T1], F2 ~func(A2) IO[T2], T1, T2, A1, A2 any](f1 F1, f2 F2) func(tuple.Tuple2[A1, A2]) IO[tuple.Tuple2[T1, T2]] { + return func(t tuple.Tuple2[A1, A2]) IO[tuple.Tuple2[T1, T2]] { + return apply.TraverseTuple2( + Map[T1, func(T2) tuple.Tuple2[T1, T2]], + ApSeq[tuple.Tuple2[T1, T2], T2], + f1, + f2, + t, + ) + } +} + +// TraverseParTuple2 converts a [tuple.Tuple2[A1, A2]] into a [IO[tuple.Tuple2[T1, T2]]] +func TraverseParTuple2[F1 ~func(A1) IO[T1], F2 ~func(A2) IO[T2], T1, T2, A1, A2 any](f1 F1, f2 F2) func(tuple.Tuple2[A1, A2]) IO[tuple.Tuple2[T1, T2]] { + return func(t tuple.Tuple2[A1, A2]) IO[tuple.Tuple2[T1, T2]] { + return apply.TraverseTuple2( + Map[T1, func(T2) tuple.Tuple2[T1, T2]], + ApPar[tuple.Tuple2[T1, T2], T2], + f1, + f2, + t, + ) + } +} + +// SequenceT3 converts 3 [IO[T]] into a [IO[tuple.Tuple3[T1, T2, T3]]] +func SequenceT3[T1, T2, T3 any]( + t1 IO[T1], + t2 IO[T2], + t3 IO[T3], +) IO[tuple.Tuple3[T1, T2, T3]] { + return apply.SequenceT3( + Map[T1, func(T2) func(T3) tuple.Tuple3[T1, T2, T3]], + Ap[func(T3) tuple.Tuple3[T1, T2, T3], T2], + Ap[tuple.Tuple3[T1, T2, T3], T3], + t1, + t2, + t3, + ) +} + +// SequenceSeqT3 converts 3 [IO[T]] into a [IO[tuple.Tuple3[T1, T2, T3]]] +func SequenceSeqT3[T1, T2, T3 any]( + t1 IO[T1], + t2 IO[T2], + t3 IO[T3], +) IO[tuple.Tuple3[T1, T2, T3]] { + return apply.SequenceT3( + Map[T1, func(T2) func(T3) tuple.Tuple3[T1, T2, T3]], + ApSeq[func(T3) tuple.Tuple3[T1, T2, T3], T2], + ApSeq[tuple.Tuple3[T1, T2, T3], T3], + t1, + t2, + t3, + ) +} + +// SequenceParT3 converts 3 [IO[T]] into a [IO[tuple.Tuple3[T1, T2, T3]]] +func SequenceParT3[T1, T2, T3 any]( + t1 IO[T1], + t2 IO[T2], + t3 IO[T3], +) IO[tuple.Tuple3[T1, T2, T3]] { + return apply.SequenceT3( + Map[T1, func(T2) func(T3) tuple.Tuple3[T1, T2, T3]], + ApPar[func(T3) tuple.Tuple3[T1, T2, T3], T2], + ApPar[tuple.Tuple3[T1, T2, T3], T3], + t1, + t2, + t3, + ) +} + +// SequenceTuple3 converts a [tuple.Tuple3[IO[T]]] into a [IO[tuple.Tuple3[T1, T2, T3]]] +func SequenceTuple3[T1, T2, T3 any](t tuple.Tuple3[IO[T1], IO[T2], IO[T3]]) IO[tuple.Tuple3[T1, T2, T3]] { + return apply.SequenceTuple3( + Map[T1, func(T2) func(T3) tuple.Tuple3[T1, T2, T3]], + Ap[func(T3) tuple.Tuple3[T1, T2, T3], T2], + Ap[tuple.Tuple3[T1, T2, T3], T3], + t, + ) +} + +// SequenceSeqTuple3 converts a [tuple.Tuple3[IO[T]]] into a [IO[tuple.Tuple3[T1, T2, T3]]] +func SequenceSeqTuple3[T1, T2, T3 any](t tuple.Tuple3[IO[T1], IO[T2], IO[T3]]) IO[tuple.Tuple3[T1, T2, T3]] { + return apply.SequenceTuple3( + Map[T1, func(T2) func(T3) tuple.Tuple3[T1, T2, T3]], + ApSeq[func(T3) tuple.Tuple3[T1, T2, T3], T2], + ApSeq[tuple.Tuple3[T1, T2, T3], T3], + t, + ) +} + +// SequenceParTuple3 converts a [tuple.Tuple3[IO[T]]] into a [IO[tuple.Tuple3[T1, T2, T3]]] +func SequenceParTuple3[T1, T2, T3 any](t tuple.Tuple3[IO[T1], IO[T2], IO[T3]]) IO[tuple.Tuple3[T1, T2, T3]] { + return apply.SequenceTuple3( + Map[T1, func(T2) func(T3) tuple.Tuple3[T1, T2, T3]], + ApPar[func(T3) tuple.Tuple3[T1, T2, T3], T2], + ApPar[tuple.Tuple3[T1, T2, T3], T3], + t, + ) +} + +// TraverseTuple3 converts a [tuple.Tuple3[A1, A2, A3]] into a [IO[tuple.Tuple3[T1, T2, T3]]] +func TraverseTuple3[F1 ~func(A1) IO[T1], F2 ~func(A2) IO[T2], F3 ~func(A3) IO[T3], T1, T2, T3, A1, A2, A3 any](f1 F1, f2 F2, f3 F3) func(tuple.Tuple3[A1, A2, A3]) IO[tuple.Tuple3[T1, T2, T3]] { + return func(t tuple.Tuple3[A1, A2, A3]) IO[tuple.Tuple3[T1, T2, T3]] { + return apply.TraverseTuple3( + Map[T1, func(T2) func(T3) tuple.Tuple3[T1, T2, T3]], + Ap[func(T3) tuple.Tuple3[T1, T2, T3], T2], + Ap[tuple.Tuple3[T1, T2, T3], T3], + f1, + f2, + f3, + t, + ) + } +} + +// TraverseSeqTuple3 converts a [tuple.Tuple3[A1, A2, A3]] into a [IO[tuple.Tuple3[T1, T2, T3]]] +func TraverseSeqTuple3[F1 ~func(A1) IO[T1], F2 ~func(A2) IO[T2], F3 ~func(A3) IO[T3], T1, T2, T3, A1, A2, A3 any](f1 F1, f2 F2, f3 F3) func(tuple.Tuple3[A1, A2, A3]) IO[tuple.Tuple3[T1, T2, T3]] { + return func(t tuple.Tuple3[A1, A2, A3]) IO[tuple.Tuple3[T1, T2, T3]] { + return apply.TraverseTuple3( + Map[T1, func(T2) func(T3) tuple.Tuple3[T1, T2, T3]], + ApSeq[func(T3) tuple.Tuple3[T1, T2, T3], T2], + ApSeq[tuple.Tuple3[T1, T2, T3], T3], + f1, + f2, + f3, + t, + ) + } +} + +// TraverseParTuple3 converts a [tuple.Tuple3[A1, A2, A3]] into a [IO[tuple.Tuple3[T1, T2, T3]]] +func TraverseParTuple3[F1 ~func(A1) IO[T1], F2 ~func(A2) IO[T2], F3 ~func(A3) IO[T3], T1, T2, T3, A1, A2, A3 any](f1 F1, f2 F2, f3 F3) func(tuple.Tuple3[A1, A2, A3]) IO[tuple.Tuple3[T1, T2, T3]] { + return func(t tuple.Tuple3[A1, A2, A3]) IO[tuple.Tuple3[T1, T2, T3]] { + return apply.TraverseTuple3( + Map[T1, func(T2) func(T3) tuple.Tuple3[T1, T2, T3]], + ApPar[func(T3) tuple.Tuple3[T1, T2, T3], T2], + ApPar[tuple.Tuple3[T1, T2, T3], T3], + f1, + f2, + f3, + t, + ) + } +} + +// SequenceT4 converts 4 [IO[T]] into a [IO[tuple.Tuple4[T1, T2, T3, T4]]] +func SequenceT4[T1, T2, T3, T4 any]( + t1 IO[T1], + t2 IO[T2], + t3 IO[T3], + t4 IO[T4], +) IO[tuple.Tuple4[T1, T2, T3, T4]] { + return apply.SequenceT4( + Map[T1, func(T2) func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4]], + Ap[func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4], T2], + Ap[func(T4) tuple.Tuple4[T1, T2, T3, T4], T3], + Ap[tuple.Tuple4[T1, T2, T3, T4], T4], + t1, + t2, + t3, + t4, + ) +} + +// SequenceSeqT4 converts 4 [IO[T]] into a [IO[tuple.Tuple4[T1, T2, T3, T4]]] +func SequenceSeqT4[T1, T2, T3, T4 any]( + t1 IO[T1], + t2 IO[T2], + t3 IO[T3], + t4 IO[T4], +) IO[tuple.Tuple4[T1, T2, T3, T4]] { + return apply.SequenceT4( + Map[T1, func(T2) func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4]], + ApSeq[func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4], T2], + ApSeq[func(T4) tuple.Tuple4[T1, T2, T3, T4], T3], + ApSeq[tuple.Tuple4[T1, T2, T3, T4], T4], + t1, + t2, + t3, + t4, + ) +} + +// SequenceParT4 converts 4 [IO[T]] into a [IO[tuple.Tuple4[T1, T2, T3, T4]]] +func SequenceParT4[T1, T2, T3, T4 any]( + t1 IO[T1], + t2 IO[T2], + t3 IO[T3], + t4 IO[T4], +) IO[tuple.Tuple4[T1, T2, T3, T4]] { + return apply.SequenceT4( + Map[T1, func(T2) func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4]], + ApPar[func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4], T2], + ApPar[func(T4) tuple.Tuple4[T1, T2, T3, T4], T3], + ApPar[tuple.Tuple4[T1, T2, T3, T4], T4], + t1, + t2, + t3, + t4, + ) +} + +// SequenceTuple4 converts a [tuple.Tuple4[IO[T]]] into a [IO[tuple.Tuple4[T1, T2, T3, T4]]] +func SequenceTuple4[T1, T2, T3, T4 any](t tuple.Tuple4[IO[T1], IO[T2], IO[T3], IO[T4]]) IO[tuple.Tuple4[T1, T2, T3, T4]] { + return apply.SequenceTuple4( + Map[T1, func(T2) func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4]], + Ap[func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4], T2], + Ap[func(T4) tuple.Tuple4[T1, T2, T3, T4], T3], + Ap[tuple.Tuple4[T1, T2, T3, T4], T4], + t, + ) +} + +// SequenceSeqTuple4 converts a [tuple.Tuple4[IO[T]]] into a [IO[tuple.Tuple4[T1, T2, T3, T4]]] +func SequenceSeqTuple4[T1, T2, T3, T4 any](t tuple.Tuple4[IO[T1], IO[T2], IO[T3], IO[T4]]) IO[tuple.Tuple4[T1, T2, T3, T4]] { + return apply.SequenceTuple4( + Map[T1, func(T2) func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4]], + ApSeq[func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4], T2], + ApSeq[func(T4) tuple.Tuple4[T1, T2, T3, T4], T3], + ApSeq[tuple.Tuple4[T1, T2, T3, T4], T4], + t, + ) +} + +// SequenceParTuple4 converts a [tuple.Tuple4[IO[T]]] into a [IO[tuple.Tuple4[T1, T2, T3, T4]]] +func SequenceParTuple4[T1, T2, T3, T4 any](t tuple.Tuple4[IO[T1], IO[T2], IO[T3], IO[T4]]) IO[tuple.Tuple4[T1, T2, T3, T4]] { + return apply.SequenceTuple4( + Map[T1, func(T2) func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4]], + ApPar[func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4], T2], + ApPar[func(T4) tuple.Tuple4[T1, T2, T3, T4], T3], + ApPar[tuple.Tuple4[T1, T2, T3, T4], T4], + t, + ) +} + +// TraverseTuple4 converts a [tuple.Tuple4[A1, A2, A3, A4]] into a [IO[tuple.Tuple4[T1, T2, T3, T4]]] +func TraverseTuple4[F1 ~func(A1) IO[T1], F2 ~func(A2) IO[T2], F3 ~func(A3) IO[T3], F4 ~func(A4) IO[T4], T1, T2, T3, T4, A1, A2, A3, A4 any](f1 F1, f2 F2, f3 F3, f4 F4) func(tuple.Tuple4[A1, A2, A3, A4]) IO[tuple.Tuple4[T1, T2, T3, T4]] { + return func(t tuple.Tuple4[A1, A2, A3, A4]) IO[tuple.Tuple4[T1, T2, T3, T4]] { + return apply.TraverseTuple4( + Map[T1, func(T2) func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4]], + Ap[func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4], T2], + Ap[func(T4) tuple.Tuple4[T1, T2, T3, T4], T3], + Ap[tuple.Tuple4[T1, T2, T3, T4], T4], + f1, + f2, + f3, + f4, + t, + ) + } +} + +// TraverseSeqTuple4 converts a [tuple.Tuple4[A1, A2, A3, A4]] into a [IO[tuple.Tuple4[T1, T2, T3, T4]]] +func TraverseSeqTuple4[F1 ~func(A1) IO[T1], F2 ~func(A2) IO[T2], F3 ~func(A3) IO[T3], F4 ~func(A4) IO[T4], T1, T2, T3, T4, A1, A2, A3, A4 any](f1 F1, f2 F2, f3 F3, f4 F4) func(tuple.Tuple4[A1, A2, A3, A4]) IO[tuple.Tuple4[T1, T2, T3, T4]] { + return func(t tuple.Tuple4[A1, A2, A3, A4]) IO[tuple.Tuple4[T1, T2, T3, T4]] { + return apply.TraverseTuple4( + Map[T1, func(T2) func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4]], + ApSeq[func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4], T2], + ApSeq[func(T4) tuple.Tuple4[T1, T2, T3, T4], T3], + ApSeq[tuple.Tuple4[T1, T2, T3, T4], T4], + f1, + f2, + f3, + f4, + t, + ) + } +} + +// TraverseParTuple4 converts a [tuple.Tuple4[A1, A2, A3, A4]] into a [IO[tuple.Tuple4[T1, T2, T3, T4]]] +func TraverseParTuple4[F1 ~func(A1) IO[T1], F2 ~func(A2) IO[T2], F3 ~func(A3) IO[T3], F4 ~func(A4) IO[T4], T1, T2, T3, T4, A1, A2, A3, A4 any](f1 F1, f2 F2, f3 F3, f4 F4) func(tuple.Tuple4[A1, A2, A3, A4]) IO[tuple.Tuple4[T1, T2, T3, T4]] { + return func(t tuple.Tuple4[A1, A2, A3, A4]) IO[tuple.Tuple4[T1, T2, T3, T4]] { + return apply.TraverseTuple4( + Map[T1, func(T2) func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4]], + ApPar[func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4], T2], + ApPar[func(T4) tuple.Tuple4[T1, T2, T3, T4], T3], + ApPar[tuple.Tuple4[T1, T2, T3, T4], T4], + f1, + f2, + f3, + f4, + t, + ) + } +} + +// SequenceT5 converts 5 [IO[T]] into a [IO[tuple.Tuple5[T1, T2, T3, T4, T5]]] +func SequenceT5[T1, T2, T3, T4, T5 any]( + t1 IO[T1], + t2 IO[T2], + t3 IO[T3], + t4 IO[T4], + t5 IO[T5], +) IO[tuple.Tuple5[T1, T2, T3, T4, T5]] { + return apply.SequenceT5( + Map[T1, func(T2) func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5]], + Ap[func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T2], + Ap[func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T3], + Ap[func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T4], + Ap[tuple.Tuple5[T1, T2, T3, T4, T5], T5], + t1, + t2, + t3, + t4, + t5, + ) +} + +// SequenceSeqT5 converts 5 [IO[T]] into a [IO[tuple.Tuple5[T1, T2, T3, T4, T5]]] +func SequenceSeqT5[T1, T2, T3, T4, T5 any]( + t1 IO[T1], + t2 IO[T2], + t3 IO[T3], + t4 IO[T4], + t5 IO[T5], +) IO[tuple.Tuple5[T1, T2, T3, T4, T5]] { + return apply.SequenceT5( + Map[T1, func(T2) func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5]], + ApSeq[func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T2], + ApSeq[func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T3], + ApSeq[func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T4], + ApSeq[tuple.Tuple5[T1, T2, T3, T4, T5], T5], + t1, + t2, + t3, + t4, + t5, + ) +} + +// SequenceParT5 converts 5 [IO[T]] into a [IO[tuple.Tuple5[T1, T2, T3, T4, T5]]] +func SequenceParT5[T1, T2, T3, T4, T5 any]( + t1 IO[T1], + t2 IO[T2], + t3 IO[T3], + t4 IO[T4], + t5 IO[T5], +) IO[tuple.Tuple5[T1, T2, T3, T4, T5]] { + return apply.SequenceT5( + Map[T1, func(T2) func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5]], + ApPar[func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T2], + ApPar[func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T3], + ApPar[func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T4], + ApPar[tuple.Tuple5[T1, T2, T3, T4, T5], T5], + t1, + t2, + t3, + t4, + t5, + ) +} + +// SequenceTuple5 converts a [tuple.Tuple5[IO[T]]] into a [IO[tuple.Tuple5[T1, T2, T3, T4, T5]]] +func SequenceTuple5[T1, T2, T3, T4, T5 any](t tuple.Tuple5[IO[T1], IO[T2], IO[T3], IO[T4], IO[T5]]) IO[tuple.Tuple5[T1, T2, T3, T4, T5]] { + return apply.SequenceTuple5( + Map[T1, func(T2) func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5]], + Ap[func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T2], + Ap[func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T3], + Ap[func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T4], + Ap[tuple.Tuple5[T1, T2, T3, T4, T5], T5], + t, + ) +} + +// SequenceSeqTuple5 converts a [tuple.Tuple5[IO[T]]] into a [IO[tuple.Tuple5[T1, T2, T3, T4, T5]]] +func SequenceSeqTuple5[T1, T2, T3, T4, T5 any](t tuple.Tuple5[IO[T1], IO[T2], IO[T3], IO[T4], IO[T5]]) IO[tuple.Tuple5[T1, T2, T3, T4, T5]] { + return apply.SequenceTuple5( + Map[T1, func(T2) func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5]], + ApSeq[func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T2], + ApSeq[func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T3], + ApSeq[func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T4], + ApSeq[tuple.Tuple5[T1, T2, T3, T4, T5], T5], + t, + ) +} + +// SequenceParTuple5 converts a [tuple.Tuple5[IO[T]]] into a [IO[tuple.Tuple5[T1, T2, T3, T4, T5]]] +func SequenceParTuple5[T1, T2, T3, T4, T5 any](t tuple.Tuple5[IO[T1], IO[T2], IO[T3], IO[T4], IO[T5]]) IO[tuple.Tuple5[T1, T2, T3, T4, T5]] { + return apply.SequenceTuple5( + Map[T1, func(T2) func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5]], + ApPar[func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T2], + ApPar[func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T3], + ApPar[func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T4], + ApPar[tuple.Tuple5[T1, T2, T3, T4, T5], T5], + t, + ) +} + +// TraverseTuple5 converts a [tuple.Tuple5[A1, A2, A3, A4, A5]] into a [IO[tuple.Tuple5[T1, T2, T3, T4, T5]]] +func TraverseTuple5[F1 ~func(A1) IO[T1], F2 ~func(A2) IO[T2], F3 ~func(A3) IO[T3], F4 ~func(A4) IO[T4], F5 ~func(A5) IO[T5], T1, T2, T3, T4, T5, A1, A2, A3, A4, A5 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5) func(tuple.Tuple5[A1, A2, A3, A4, A5]) IO[tuple.Tuple5[T1, T2, T3, T4, T5]] { + return func(t tuple.Tuple5[A1, A2, A3, A4, A5]) IO[tuple.Tuple5[T1, T2, T3, T4, T5]] { + return apply.TraverseTuple5( + Map[T1, func(T2) func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5]], + Ap[func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T2], + Ap[func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T3], + Ap[func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T4], + Ap[tuple.Tuple5[T1, T2, T3, T4, T5], T5], + f1, + f2, + f3, + f4, + f5, + t, + ) + } +} + +// TraverseSeqTuple5 converts a [tuple.Tuple5[A1, A2, A3, A4, A5]] into a [IO[tuple.Tuple5[T1, T2, T3, T4, T5]]] +func TraverseSeqTuple5[F1 ~func(A1) IO[T1], F2 ~func(A2) IO[T2], F3 ~func(A3) IO[T3], F4 ~func(A4) IO[T4], F5 ~func(A5) IO[T5], T1, T2, T3, T4, T5, A1, A2, A3, A4, A5 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5) func(tuple.Tuple5[A1, A2, A3, A4, A5]) IO[tuple.Tuple5[T1, T2, T3, T4, T5]] { + return func(t tuple.Tuple5[A1, A2, A3, A4, A5]) IO[tuple.Tuple5[T1, T2, T3, T4, T5]] { + return apply.TraverseTuple5( + Map[T1, func(T2) func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5]], + ApSeq[func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T2], + ApSeq[func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T3], + ApSeq[func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T4], + ApSeq[tuple.Tuple5[T1, T2, T3, T4, T5], T5], + f1, + f2, + f3, + f4, + f5, + t, + ) + } +} + +// TraverseParTuple5 converts a [tuple.Tuple5[A1, A2, A3, A4, A5]] into a [IO[tuple.Tuple5[T1, T2, T3, T4, T5]]] +func TraverseParTuple5[F1 ~func(A1) IO[T1], F2 ~func(A2) IO[T2], F3 ~func(A3) IO[T3], F4 ~func(A4) IO[T4], F5 ~func(A5) IO[T5], T1, T2, T3, T4, T5, A1, A2, A3, A4, A5 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5) func(tuple.Tuple5[A1, A2, A3, A4, A5]) IO[tuple.Tuple5[T1, T2, T3, T4, T5]] { + return func(t tuple.Tuple5[A1, A2, A3, A4, A5]) IO[tuple.Tuple5[T1, T2, T3, T4, T5]] { + return apply.TraverseTuple5( + Map[T1, func(T2) func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5]], + ApPar[func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T2], + ApPar[func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T3], + ApPar[func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T4], + ApPar[tuple.Tuple5[T1, T2, T3, T4, T5], T5], + f1, + f2, + f3, + f4, + f5, + t, + ) + } +} + +// SequenceT6 converts 6 [IO[T]] into a [IO[tuple.Tuple6[T1, T2, T3, T4, T5, T6]]] +func SequenceT6[T1, T2, T3, T4, T5, T6 any]( + t1 IO[T1], + t2 IO[T2], + t3 IO[T3], + t4 IO[T4], + t5 IO[T5], + t6 IO[T6], +) IO[tuple.Tuple6[T1, T2, T3, T4, T5, T6]] { + return apply.SequenceT6( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6]], + Ap[func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T2], + Ap[func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T3], + Ap[func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T4], + Ap[func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T5], + Ap[tuple.Tuple6[T1, T2, T3, T4, T5, T6], T6], + t1, + t2, + t3, + t4, + t5, + t6, + ) +} + +// SequenceSeqT6 converts 6 [IO[T]] into a [IO[tuple.Tuple6[T1, T2, T3, T4, T5, T6]]] +func SequenceSeqT6[T1, T2, T3, T4, T5, T6 any]( + t1 IO[T1], + t2 IO[T2], + t3 IO[T3], + t4 IO[T4], + t5 IO[T5], + t6 IO[T6], +) IO[tuple.Tuple6[T1, T2, T3, T4, T5, T6]] { + return apply.SequenceT6( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6]], + ApSeq[func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T2], + ApSeq[func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T3], + ApSeq[func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T4], + ApSeq[func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T5], + ApSeq[tuple.Tuple6[T1, T2, T3, T4, T5, T6], T6], + t1, + t2, + t3, + t4, + t5, + t6, + ) +} + +// SequenceParT6 converts 6 [IO[T]] into a [IO[tuple.Tuple6[T1, T2, T3, T4, T5, T6]]] +func SequenceParT6[T1, T2, T3, T4, T5, T6 any]( + t1 IO[T1], + t2 IO[T2], + t3 IO[T3], + t4 IO[T4], + t5 IO[T5], + t6 IO[T6], +) IO[tuple.Tuple6[T1, T2, T3, T4, T5, T6]] { + return apply.SequenceT6( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6]], + ApPar[func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T2], + ApPar[func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T3], + ApPar[func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T4], + ApPar[func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T5], + ApPar[tuple.Tuple6[T1, T2, T3, T4, T5, T6], T6], + t1, + t2, + t3, + t4, + t5, + t6, + ) +} + +// SequenceTuple6 converts a [tuple.Tuple6[IO[T]]] into a [IO[tuple.Tuple6[T1, T2, T3, T4, T5, T6]]] +func SequenceTuple6[T1, T2, T3, T4, T5, T6 any](t tuple.Tuple6[IO[T1], IO[T2], IO[T3], IO[T4], IO[T5], IO[T6]]) IO[tuple.Tuple6[T1, T2, T3, T4, T5, T6]] { + return apply.SequenceTuple6( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6]], + Ap[func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T2], + Ap[func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T3], + Ap[func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T4], + Ap[func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T5], + Ap[tuple.Tuple6[T1, T2, T3, T4, T5, T6], T6], + t, + ) +} + +// SequenceSeqTuple6 converts a [tuple.Tuple6[IO[T]]] into a [IO[tuple.Tuple6[T1, T2, T3, T4, T5, T6]]] +func SequenceSeqTuple6[T1, T2, T3, T4, T5, T6 any](t tuple.Tuple6[IO[T1], IO[T2], IO[T3], IO[T4], IO[T5], IO[T6]]) IO[tuple.Tuple6[T1, T2, T3, T4, T5, T6]] { + return apply.SequenceTuple6( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6]], + ApSeq[func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T2], + ApSeq[func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T3], + ApSeq[func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T4], + ApSeq[func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T5], + ApSeq[tuple.Tuple6[T1, T2, T3, T4, T5, T6], T6], + t, + ) +} + +// SequenceParTuple6 converts a [tuple.Tuple6[IO[T]]] into a [IO[tuple.Tuple6[T1, T2, T3, T4, T5, T6]]] +func SequenceParTuple6[T1, T2, T3, T4, T5, T6 any](t tuple.Tuple6[IO[T1], IO[T2], IO[T3], IO[T4], IO[T5], IO[T6]]) IO[tuple.Tuple6[T1, T2, T3, T4, T5, T6]] { + return apply.SequenceTuple6( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6]], + ApPar[func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T2], + ApPar[func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T3], + ApPar[func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T4], + ApPar[func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T5], + ApPar[tuple.Tuple6[T1, T2, T3, T4, T5, T6], T6], + t, + ) +} + +// TraverseTuple6 converts a [tuple.Tuple6[A1, A2, A3, A4, A5, A6]] into a [IO[tuple.Tuple6[T1, T2, T3, T4, T5, T6]]] +func TraverseTuple6[F1 ~func(A1) IO[T1], F2 ~func(A2) IO[T2], F3 ~func(A3) IO[T3], F4 ~func(A4) IO[T4], F5 ~func(A5) IO[T5], F6 ~func(A6) IO[T6], T1, T2, T3, T4, T5, T6, A1, A2, A3, A4, A5, A6 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6) func(tuple.Tuple6[A1, A2, A3, A4, A5, A6]) IO[tuple.Tuple6[T1, T2, T3, T4, T5, T6]] { + return func(t tuple.Tuple6[A1, A2, A3, A4, A5, A6]) IO[tuple.Tuple6[T1, T2, T3, T4, T5, T6]] { + return apply.TraverseTuple6( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6]], + Ap[func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T2], + Ap[func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T3], + Ap[func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T4], + Ap[func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T5], + Ap[tuple.Tuple6[T1, T2, T3, T4, T5, T6], T6], + f1, + f2, + f3, + f4, + f5, + f6, + t, + ) + } +} + +// TraverseSeqTuple6 converts a [tuple.Tuple6[A1, A2, A3, A4, A5, A6]] into a [IO[tuple.Tuple6[T1, T2, T3, T4, T5, T6]]] +func TraverseSeqTuple6[F1 ~func(A1) IO[T1], F2 ~func(A2) IO[T2], F3 ~func(A3) IO[T3], F4 ~func(A4) IO[T4], F5 ~func(A5) IO[T5], F6 ~func(A6) IO[T6], T1, T2, T3, T4, T5, T6, A1, A2, A3, A4, A5, A6 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6) func(tuple.Tuple6[A1, A2, A3, A4, A5, A6]) IO[tuple.Tuple6[T1, T2, T3, T4, T5, T6]] { + return func(t tuple.Tuple6[A1, A2, A3, A4, A5, A6]) IO[tuple.Tuple6[T1, T2, T3, T4, T5, T6]] { + return apply.TraverseTuple6( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6]], + ApSeq[func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T2], + ApSeq[func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T3], + ApSeq[func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T4], + ApSeq[func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T5], + ApSeq[tuple.Tuple6[T1, T2, T3, T4, T5, T6], T6], + f1, + f2, + f3, + f4, + f5, + f6, + t, + ) + } +} + +// TraverseParTuple6 converts a [tuple.Tuple6[A1, A2, A3, A4, A5, A6]] into a [IO[tuple.Tuple6[T1, T2, T3, T4, T5, T6]]] +func TraverseParTuple6[F1 ~func(A1) IO[T1], F2 ~func(A2) IO[T2], F3 ~func(A3) IO[T3], F4 ~func(A4) IO[T4], F5 ~func(A5) IO[T5], F6 ~func(A6) IO[T6], T1, T2, T3, T4, T5, T6, A1, A2, A3, A4, A5, A6 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6) func(tuple.Tuple6[A1, A2, A3, A4, A5, A6]) IO[tuple.Tuple6[T1, T2, T3, T4, T5, T6]] { + return func(t tuple.Tuple6[A1, A2, A3, A4, A5, A6]) IO[tuple.Tuple6[T1, T2, T3, T4, T5, T6]] { + return apply.TraverseTuple6( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6]], + ApPar[func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T2], + ApPar[func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T3], + ApPar[func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T4], + ApPar[func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T5], + ApPar[tuple.Tuple6[T1, T2, T3, T4, T5, T6], T6], + f1, + f2, + f3, + f4, + f5, + f6, + t, + ) + } +} + +// SequenceT7 converts 7 [IO[T]] into a [IO[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]]] +func SequenceT7[T1, T2, T3, T4, T5, T6, T7 any]( + t1 IO[T1], + t2 IO[T2], + t3 IO[T3], + t4 IO[T4], + t5 IO[T5], + t6 IO[T6], + t7 IO[T7], +) IO[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return apply.SequenceT7( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T2], + Ap[func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T3], + Ap[func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T4], + Ap[func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T5], + Ap[func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T6], + Ap[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T7], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + ) +} + +// SequenceSeqT7 converts 7 [IO[T]] into a [IO[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]]] +func SequenceSeqT7[T1, T2, T3, T4, T5, T6, T7 any]( + t1 IO[T1], + t2 IO[T2], + t3 IO[T3], + t4 IO[T4], + t5 IO[T5], + t6 IO[T6], + t7 IO[T7], +) IO[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return apply.SequenceT7( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]], + ApSeq[func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T2], + ApSeq[func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T3], + ApSeq[func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T4], + ApSeq[func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T5], + ApSeq[func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T6], + ApSeq[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T7], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + ) +} + +// SequenceParT7 converts 7 [IO[T]] into a [IO[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]]] +func SequenceParT7[T1, T2, T3, T4, T5, T6, T7 any]( + t1 IO[T1], + t2 IO[T2], + t3 IO[T3], + t4 IO[T4], + t5 IO[T5], + t6 IO[T6], + t7 IO[T7], +) IO[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return apply.SequenceT7( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]], + ApPar[func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T2], + ApPar[func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T3], + ApPar[func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T4], + ApPar[func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T5], + ApPar[func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T6], + ApPar[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T7], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + ) +} + +// SequenceTuple7 converts a [tuple.Tuple7[IO[T]]] into a [IO[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]]] +func SequenceTuple7[T1, T2, T3, T4, T5, T6, T7 any](t tuple.Tuple7[IO[T1], IO[T2], IO[T3], IO[T4], IO[T5], IO[T6], IO[T7]]) IO[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return apply.SequenceTuple7( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T2], + Ap[func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T3], + Ap[func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T4], + Ap[func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T5], + Ap[func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T6], + Ap[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T7], + t, + ) +} + +// SequenceSeqTuple7 converts a [tuple.Tuple7[IO[T]]] into a [IO[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]]] +func SequenceSeqTuple7[T1, T2, T3, T4, T5, T6, T7 any](t tuple.Tuple7[IO[T1], IO[T2], IO[T3], IO[T4], IO[T5], IO[T6], IO[T7]]) IO[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return apply.SequenceTuple7( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]], + ApSeq[func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T2], + ApSeq[func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T3], + ApSeq[func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T4], + ApSeq[func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T5], + ApSeq[func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T6], + ApSeq[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T7], + t, + ) +} + +// SequenceParTuple7 converts a [tuple.Tuple7[IO[T]]] into a [IO[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]]] +func SequenceParTuple7[T1, T2, T3, T4, T5, T6, T7 any](t tuple.Tuple7[IO[T1], IO[T2], IO[T3], IO[T4], IO[T5], IO[T6], IO[T7]]) IO[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return apply.SequenceTuple7( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]], + ApPar[func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T2], + ApPar[func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T3], + ApPar[func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T4], + ApPar[func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T5], + ApPar[func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T6], + ApPar[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T7], + t, + ) +} + +// TraverseTuple7 converts a [tuple.Tuple7[A1, A2, A3, A4, A5, A6, A7]] into a [IO[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]]] +func TraverseTuple7[F1 ~func(A1) IO[T1], F2 ~func(A2) IO[T2], F3 ~func(A3) IO[T3], F4 ~func(A4) IO[T4], F5 ~func(A5) IO[T5], F6 ~func(A6) IO[T6], F7 ~func(A7) IO[T7], T1, T2, T3, T4, T5, T6, T7, A1, A2, A3, A4, A5, A6, A7 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7) func(tuple.Tuple7[A1, A2, A3, A4, A5, A6, A7]) IO[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return func(t tuple.Tuple7[A1, A2, A3, A4, A5, A6, A7]) IO[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return apply.TraverseTuple7( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T2], + Ap[func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T3], + Ap[func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T4], + Ap[func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T5], + Ap[func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T6], + Ap[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T7], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + t, + ) + } +} + +// TraverseSeqTuple7 converts a [tuple.Tuple7[A1, A2, A3, A4, A5, A6, A7]] into a [IO[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]]] +func TraverseSeqTuple7[F1 ~func(A1) IO[T1], F2 ~func(A2) IO[T2], F3 ~func(A3) IO[T3], F4 ~func(A4) IO[T4], F5 ~func(A5) IO[T5], F6 ~func(A6) IO[T6], F7 ~func(A7) IO[T7], T1, T2, T3, T4, T5, T6, T7, A1, A2, A3, A4, A5, A6, A7 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7) func(tuple.Tuple7[A1, A2, A3, A4, A5, A6, A7]) IO[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return func(t tuple.Tuple7[A1, A2, A3, A4, A5, A6, A7]) IO[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return apply.TraverseTuple7( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]], + ApSeq[func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T2], + ApSeq[func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T3], + ApSeq[func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T4], + ApSeq[func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T5], + ApSeq[func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T6], + ApSeq[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T7], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + t, + ) + } +} + +// TraverseParTuple7 converts a [tuple.Tuple7[A1, A2, A3, A4, A5, A6, A7]] into a [IO[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]]] +func TraverseParTuple7[F1 ~func(A1) IO[T1], F2 ~func(A2) IO[T2], F3 ~func(A3) IO[T3], F4 ~func(A4) IO[T4], F5 ~func(A5) IO[T5], F6 ~func(A6) IO[T6], F7 ~func(A7) IO[T7], T1, T2, T3, T4, T5, T6, T7, A1, A2, A3, A4, A5, A6, A7 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7) func(tuple.Tuple7[A1, A2, A3, A4, A5, A6, A7]) IO[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return func(t tuple.Tuple7[A1, A2, A3, A4, A5, A6, A7]) IO[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return apply.TraverseTuple7( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]], + ApPar[func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T2], + ApPar[func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T3], + ApPar[func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T4], + ApPar[func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T5], + ApPar[func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T6], + ApPar[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T7], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + t, + ) + } +} + +// SequenceT8 converts 8 [IO[T]] into a [IO[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]]] +func SequenceT8[T1, T2, T3, T4, T5, T6, T7, T8 any]( + t1 IO[T1], + t2 IO[T2], + t3 IO[T3], + t4 IO[T4], + t5 IO[T5], + t6 IO[T6], + t7 IO[T7], + t8 IO[T8], +) IO[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return apply.SequenceT8( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T3], + Ap[func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T4], + Ap[func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T5], + Ap[func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T6], + Ap[func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T7], + Ap[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T8], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + t8, + ) +} + +// SequenceSeqT8 converts 8 [IO[T]] into a [IO[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]]] +func SequenceSeqT8[T1, T2, T3, T4, T5, T6, T7, T8 any]( + t1 IO[T1], + t2 IO[T2], + t3 IO[T3], + t4 IO[T4], + t5 IO[T5], + t6 IO[T6], + t7 IO[T7], + t8 IO[T8], +) IO[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return apply.SequenceT8( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]], + ApSeq[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T2], + ApSeq[func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T3], + ApSeq[func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T4], + ApSeq[func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T5], + ApSeq[func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T6], + ApSeq[func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T7], + ApSeq[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T8], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + t8, + ) +} + +// SequenceParT8 converts 8 [IO[T]] into a [IO[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]]] +func SequenceParT8[T1, T2, T3, T4, T5, T6, T7, T8 any]( + t1 IO[T1], + t2 IO[T2], + t3 IO[T3], + t4 IO[T4], + t5 IO[T5], + t6 IO[T6], + t7 IO[T7], + t8 IO[T8], +) IO[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return apply.SequenceT8( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]], + ApPar[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T2], + ApPar[func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T3], + ApPar[func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T4], + ApPar[func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T5], + ApPar[func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T6], + ApPar[func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T7], + ApPar[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T8], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + t8, + ) +} + +// SequenceTuple8 converts a [tuple.Tuple8[IO[T]]] into a [IO[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]]] +func SequenceTuple8[T1, T2, T3, T4, T5, T6, T7, T8 any](t tuple.Tuple8[IO[T1], IO[T2], IO[T3], IO[T4], IO[T5], IO[T6], IO[T7], IO[T8]]) IO[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return apply.SequenceTuple8( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T3], + Ap[func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T4], + Ap[func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T5], + Ap[func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T6], + Ap[func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T7], + Ap[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T8], + t, + ) +} + +// SequenceSeqTuple8 converts a [tuple.Tuple8[IO[T]]] into a [IO[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]]] +func SequenceSeqTuple8[T1, T2, T3, T4, T5, T6, T7, T8 any](t tuple.Tuple8[IO[T1], IO[T2], IO[T3], IO[T4], IO[T5], IO[T6], IO[T7], IO[T8]]) IO[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return apply.SequenceTuple8( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]], + ApSeq[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T2], + ApSeq[func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T3], + ApSeq[func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T4], + ApSeq[func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T5], + ApSeq[func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T6], + ApSeq[func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T7], + ApSeq[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T8], + t, + ) +} + +// SequenceParTuple8 converts a [tuple.Tuple8[IO[T]]] into a [IO[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]]] +func SequenceParTuple8[T1, T2, T3, T4, T5, T6, T7, T8 any](t tuple.Tuple8[IO[T1], IO[T2], IO[T3], IO[T4], IO[T5], IO[T6], IO[T7], IO[T8]]) IO[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return apply.SequenceTuple8( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]], + ApPar[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T2], + ApPar[func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T3], + ApPar[func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T4], + ApPar[func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T5], + ApPar[func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T6], + ApPar[func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T7], + ApPar[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T8], + t, + ) +} + +// TraverseTuple8 converts a [tuple.Tuple8[A1, A2, A3, A4, A5, A6, A7, A8]] into a [IO[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]]] +func TraverseTuple8[F1 ~func(A1) IO[T1], F2 ~func(A2) IO[T2], F3 ~func(A3) IO[T3], F4 ~func(A4) IO[T4], F5 ~func(A5) IO[T5], F6 ~func(A6) IO[T6], F7 ~func(A7) IO[T7], F8 ~func(A8) IO[T8], T1, T2, T3, T4, T5, T6, T7, T8, A1, A2, A3, A4, A5, A6, A7, A8 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8) func(tuple.Tuple8[A1, A2, A3, A4, A5, A6, A7, A8]) IO[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return func(t tuple.Tuple8[A1, A2, A3, A4, A5, A6, A7, A8]) IO[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return apply.TraverseTuple8( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T3], + Ap[func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T4], + Ap[func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T5], + Ap[func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T6], + Ap[func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T7], + Ap[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T8], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + f8, + t, + ) + } +} + +// TraverseSeqTuple8 converts a [tuple.Tuple8[A1, A2, A3, A4, A5, A6, A7, A8]] into a [IO[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]]] +func TraverseSeqTuple8[F1 ~func(A1) IO[T1], F2 ~func(A2) IO[T2], F3 ~func(A3) IO[T3], F4 ~func(A4) IO[T4], F5 ~func(A5) IO[T5], F6 ~func(A6) IO[T6], F7 ~func(A7) IO[T7], F8 ~func(A8) IO[T8], T1, T2, T3, T4, T5, T6, T7, T8, A1, A2, A3, A4, A5, A6, A7, A8 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8) func(tuple.Tuple8[A1, A2, A3, A4, A5, A6, A7, A8]) IO[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return func(t tuple.Tuple8[A1, A2, A3, A4, A5, A6, A7, A8]) IO[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return apply.TraverseTuple8( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]], + ApSeq[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T2], + ApSeq[func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T3], + ApSeq[func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T4], + ApSeq[func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T5], + ApSeq[func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T6], + ApSeq[func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T7], + ApSeq[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T8], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + f8, + t, + ) + } +} + +// TraverseParTuple8 converts a [tuple.Tuple8[A1, A2, A3, A4, A5, A6, A7, A8]] into a [IO[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]]] +func TraverseParTuple8[F1 ~func(A1) IO[T1], F2 ~func(A2) IO[T2], F3 ~func(A3) IO[T3], F4 ~func(A4) IO[T4], F5 ~func(A5) IO[T5], F6 ~func(A6) IO[T6], F7 ~func(A7) IO[T7], F8 ~func(A8) IO[T8], T1, T2, T3, T4, T5, T6, T7, T8, A1, A2, A3, A4, A5, A6, A7, A8 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8) func(tuple.Tuple8[A1, A2, A3, A4, A5, A6, A7, A8]) IO[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return func(t tuple.Tuple8[A1, A2, A3, A4, A5, A6, A7, A8]) IO[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return apply.TraverseTuple8( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]], + ApPar[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T2], + ApPar[func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T3], + ApPar[func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T4], + ApPar[func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T5], + ApPar[func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T6], + ApPar[func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T7], + ApPar[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T8], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + f8, + t, + ) + } +} + +// SequenceT9 converts 9 [IO[T]] into a [IO[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]]] +func SequenceT9[T1, T2, T3, T4, T5, T6, T7, T8, T9 any]( + t1 IO[T1], + t2 IO[T2], + t3 IO[T3], + t4 IO[T4], + t5 IO[T5], + t6 IO[T6], + t7 IO[T7], + t8 IO[T8], + t9 IO[T9], +) IO[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return apply.SequenceT9( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T3], + Ap[func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T4], + Ap[func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T5], + Ap[func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T6], + Ap[func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T7], + Ap[func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T8], + Ap[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T9], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + t8, + t9, + ) +} + +// SequenceSeqT9 converts 9 [IO[T]] into a [IO[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]]] +func SequenceSeqT9[T1, T2, T3, T4, T5, T6, T7, T8, T9 any]( + t1 IO[T1], + t2 IO[T2], + t3 IO[T3], + t4 IO[T4], + t5 IO[T5], + t6 IO[T6], + t7 IO[T7], + t8 IO[T8], + t9 IO[T9], +) IO[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return apply.SequenceT9( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]], + ApSeq[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T2], + ApSeq[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T3], + ApSeq[func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T4], + ApSeq[func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T5], + ApSeq[func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T6], + ApSeq[func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T7], + ApSeq[func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T8], + ApSeq[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T9], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + t8, + t9, + ) +} + +// SequenceParT9 converts 9 [IO[T]] into a [IO[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]]] +func SequenceParT9[T1, T2, T3, T4, T5, T6, T7, T8, T9 any]( + t1 IO[T1], + t2 IO[T2], + t3 IO[T3], + t4 IO[T4], + t5 IO[T5], + t6 IO[T6], + t7 IO[T7], + t8 IO[T8], + t9 IO[T9], +) IO[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return apply.SequenceT9( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]], + ApPar[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T2], + ApPar[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T3], + ApPar[func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T4], + ApPar[func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T5], + ApPar[func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T6], + ApPar[func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T7], + ApPar[func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T8], + ApPar[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T9], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + t8, + t9, + ) +} + +// SequenceTuple9 converts a [tuple.Tuple9[IO[T]]] into a [IO[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]]] +func SequenceTuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9 any](t tuple.Tuple9[IO[T1], IO[T2], IO[T3], IO[T4], IO[T5], IO[T6], IO[T7], IO[T8], IO[T9]]) IO[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return apply.SequenceTuple9( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T3], + Ap[func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T4], + Ap[func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T5], + Ap[func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T6], + Ap[func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T7], + Ap[func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T8], + Ap[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T9], + t, + ) +} + +// SequenceSeqTuple9 converts a [tuple.Tuple9[IO[T]]] into a [IO[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]]] +func SequenceSeqTuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9 any](t tuple.Tuple9[IO[T1], IO[T2], IO[T3], IO[T4], IO[T5], IO[T6], IO[T7], IO[T8], IO[T9]]) IO[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return apply.SequenceTuple9( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]], + ApSeq[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T2], + ApSeq[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T3], + ApSeq[func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T4], + ApSeq[func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T5], + ApSeq[func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T6], + ApSeq[func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T7], + ApSeq[func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T8], + ApSeq[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T9], + t, + ) +} + +// SequenceParTuple9 converts a [tuple.Tuple9[IO[T]]] into a [IO[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]]] +func SequenceParTuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9 any](t tuple.Tuple9[IO[T1], IO[T2], IO[T3], IO[T4], IO[T5], IO[T6], IO[T7], IO[T8], IO[T9]]) IO[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return apply.SequenceTuple9( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]], + ApPar[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T2], + ApPar[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T3], + ApPar[func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T4], + ApPar[func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T5], + ApPar[func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T6], + ApPar[func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T7], + ApPar[func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T8], + ApPar[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T9], + t, + ) +} + +// TraverseTuple9 converts a [tuple.Tuple9[A1, A2, A3, A4, A5, A6, A7, A8, A9]] into a [IO[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]]] +func TraverseTuple9[F1 ~func(A1) IO[T1], F2 ~func(A2) IO[T2], F3 ~func(A3) IO[T3], F4 ~func(A4) IO[T4], F5 ~func(A5) IO[T5], F6 ~func(A6) IO[T6], F7 ~func(A7) IO[T7], F8 ~func(A8) IO[T8], F9 ~func(A9) IO[T9], T1, T2, T3, T4, T5, T6, T7, T8, T9, A1, A2, A3, A4, A5, A6, A7, A8, A9 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9) func(tuple.Tuple9[A1, A2, A3, A4, A5, A6, A7, A8, A9]) IO[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return func(t tuple.Tuple9[A1, A2, A3, A4, A5, A6, A7, A8, A9]) IO[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return apply.TraverseTuple9( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T3], + Ap[func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T4], + Ap[func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T5], + Ap[func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T6], + Ap[func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T7], + Ap[func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T8], + Ap[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T9], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + f8, + f9, + t, + ) + } +} + +// TraverseSeqTuple9 converts a [tuple.Tuple9[A1, A2, A3, A4, A5, A6, A7, A8, A9]] into a [IO[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]]] +func TraverseSeqTuple9[F1 ~func(A1) IO[T1], F2 ~func(A2) IO[T2], F3 ~func(A3) IO[T3], F4 ~func(A4) IO[T4], F5 ~func(A5) IO[T5], F6 ~func(A6) IO[T6], F7 ~func(A7) IO[T7], F8 ~func(A8) IO[T8], F9 ~func(A9) IO[T9], T1, T2, T3, T4, T5, T6, T7, T8, T9, A1, A2, A3, A4, A5, A6, A7, A8, A9 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9) func(tuple.Tuple9[A1, A2, A3, A4, A5, A6, A7, A8, A9]) IO[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return func(t tuple.Tuple9[A1, A2, A3, A4, A5, A6, A7, A8, A9]) IO[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return apply.TraverseTuple9( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]], + ApSeq[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T2], + ApSeq[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T3], + ApSeq[func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T4], + ApSeq[func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T5], + ApSeq[func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T6], + ApSeq[func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T7], + ApSeq[func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T8], + ApSeq[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T9], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + f8, + f9, + t, + ) + } +} + +// TraverseParTuple9 converts a [tuple.Tuple9[A1, A2, A3, A4, A5, A6, A7, A8, A9]] into a [IO[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]]] +func TraverseParTuple9[F1 ~func(A1) IO[T1], F2 ~func(A2) IO[T2], F3 ~func(A3) IO[T3], F4 ~func(A4) IO[T4], F5 ~func(A5) IO[T5], F6 ~func(A6) IO[T6], F7 ~func(A7) IO[T7], F8 ~func(A8) IO[T8], F9 ~func(A9) IO[T9], T1, T2, T3, T4, T5, T6, T7, T8, T9, A1, A2, A3, A4, A5, A6, A7, A8, A9 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9) func(tuple.Tuple9[A1, A2, A3, A4, A5, A6, A7, A8, A9]) IO[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return func(t tuple.Tuple9[A1, A2, A3, A4, A5, A6, A7, A8, A9]) IO[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return apply.TraverseTuple9( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]], + ApPar[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T2], + ApPar[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T3], + ApPar[func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T4], + ApPar[func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T5], + ApPar[func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T6], + ApPar[func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T7], + ApPar[func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T8], + ApPar[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T9], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + f8, + f9, + t, + ) + } +} + +// SequenceT10 converts 10 [IO[T]] into a [IO[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]]] +func SequenceT10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any]( + t1 IO[T1], + t2 IO[T2], + t3 IO[T3], + t4 IO[T4], + t5 IO[T5], + t6 IO[T6], + t7 IO[T7], + t8 IO[T8], + t9 IO[T9], + t10 IO[T10], +) IO[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return apply.SequenceT10( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T3], + Ap[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T4], + Ap[func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T5], + Ap[func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T6], + Ap[func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T7], + Ap[func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T8], + Ap[func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T9], + Ap[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T10], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + t8, + t9, + t10, + ) +} + +// SequenceSeqT10 converts 10 [IO[T]] into a [IO[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]]] +func SequenceSeqT10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any]( + t1 IO[T1], + t2 IO[T2], + t3 IO[T3], + t4 IO[T4], + t5 IO[T5], + t6 IO[T6], + t7 IO[T7], + t8 IO[T8], + t9 IO[T9], + t10 IO[T10], +) IO[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return apply.SequenceT10( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]], + ApSeq[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T2], + ApSeq[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T3], + ApSeq[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T4], + ApSeq[func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T5], + ApSeq[func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T6], + ApSeq[func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T7], + ApSeq[func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T8], + ApSeq[func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T9], + ApSeq[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T10], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + t8, + t9, + t10, + ) +} + +// SequenceParT10 converts 10 [IO[T]] into a [IO[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]]] +func SequenceParT10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any]( + t1 IO[T1], + t2 IO[T2], + t3 IO[T3], + t4 IO[T4], + t5 IO[T5], + t6 IO[T6], + t7 IO[T7], + t8 IO[T8], + t9 IO[T9], + t10 IO[T10], +) IO[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return apply.SequenceT10( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]], + ApPar[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T2], + ApPar[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T3], + ApPar[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T4], + ApPar[func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T5], + ApPar[func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T6], + ApPar[func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T7], + ApPar[func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T8], + ApPar[func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T9], + ApPar[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T10], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + t8, + t9, + t10, + ) +} + +// SequenceTuple10 converts a [tuple.Tuple10[IO[T]]] into a [IO[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]]] +func SequenceTuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any](t tuple.Tuple10[IO[T1], IO[T2], IO[T3], IO[T4], IO[T5], IO[T6], IO[T7], IO[T8], IO[T9], IO[T10]]) IO[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return apply.SequenceTuple10( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T3], + Ap[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T4], + Ap[func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T5], + Ap[func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T6], + Ap[func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T7], + Ap[func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T8], + Ap[func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T9], + Ap[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T10], + t, + ) +} + +// SequenceSeqTuple10 converts a [tuple.Tuple10[IO[T]]] into a [IO[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]]] +func SequenceSeqTuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any](t tuple.Tuple10[IO[T1], IO[T2], IO[T3], IO[T4], IO[T5], IO[T6], IO[T7], IO[T8], IO[T9], IO[T10]]) IO[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return apply.SequenceTuple10( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]], + ApSeq[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T2], + ApSeq[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T3], + ApSeq[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T4], + ApSeq[func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T5], + ApSeq[func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T6], + ApSeq[func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T7], + ApSeq[func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T8], + ApSeq[func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T9], + ApSeq[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T10], + t, + ) +} + +// SequenceParTuple10 converts a [tuple.Tuple10[IO[T]]] into a [IO[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]]] +func SequenceParTuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any](t tuple.Tuple10[IO[T1], IO[T2], IO[T3], IO[T4], IO[T5], IO[T6], IO[T7], IO[T8], IO[T9], IO[T10]]) IO[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return apply.SequenceTuple10( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]], + ApPar[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T2], + ApPar[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T3], + ApPar[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T4], + ApPar[func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T5], + ApPar[func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T6], + ApPar[func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T7], + ApPar[func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T8], + ApPar[func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T9], + ApPar[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T10], + t, + ) +} + +// TraverseTuple10 converts a [tuple.Tuple10[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10]] into a [IO[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]]] +func TraverseTuple10[F1 ~func(A1) IO[T1], F2 ~func(A2) IO[T2], F3 ~func(A3) IO[T3], F4 ~func(A4) IO[T4], F5 ~func(A5) IO[T5], F6 ~func(A6) IO[T6], F7 ~func(A7) IO[T7], F8 ~func(A8) IO[T8], F9 ~func(A9) IO[T9], F10 ~func(A10) IO[T10], T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10) func(tuple.Tuple10[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10]) IO[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return func(t tuple.Tuple10[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10]) IO[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return apply.TraverseTuple10( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T3], + Ap[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T4], + Ap[func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T5], + Ap[func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T6], + Ap[func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T7], + Ap[func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T8], + Ap[func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T9], + Ap[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T10], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + f8, + f9, + f10, + t, + ) + } +} + +// TraverseSeqTuple10 converts a [tuple.Tuple10[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10]] into a [IO[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]]] +func TraverseSeqTuple10[F1 ~func(A1) IO[T1], F2 ~func(A2) IO[T2], F3 ~func(A3) IO[T3], F4 ~func(A4) IO[T4], F5 ~func(A5) IO[T5], F6 ~func(A6) IO[T6], F7 ~func(A7) IO[T7], F8 ~func(A8) IO[T8], F9 ~func(A9) IO[T9], F10 ~func(A10) IO[T10], T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10) func(tuple.Tuple10[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10]) IO[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return func(t tuple.Tuple10[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10]) IO[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return apply.TraverseTuple10( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]], + ApSeq[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T2], + ApSeq[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T3], + ApSeq[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T4], + ApSeq[func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T5], + ApSeq[func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T6], + ApSeq[func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T7], + ApSeq[func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T8], + ApSeq[func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T9], + ApSeq[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T10], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + f8, + f9, + f10, + t, + ) + } +} + +// TraverseParTuple10 converts a [tuple.Tuple10[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10]] into a [IO[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]]] +func TraverseParTuple10[F1 ~func(A1) IO[T1], F2 ~func(A2) IO[T2], F3 ~func(A3) IO[T3], F4 ~func(A4) IO[T4], F5 ~func(A5) IO[T5], F6 ~func(A6) IO[T6], F7 ~func(A7) IO[T7], F8 ~func(A8) IO[T8], F9 ~func(A9) IO[T9], F10 ~func(A10) IO[T10], T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10) func(tuple.Tuple10[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10]) IO[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return func(t tuple.Tuple10[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10]) IO[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return apply.TraverseTuple10( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]], + ApPar[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T2], + ApPar[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T3], + ApPar[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T4], + ApPar[func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T5], + ApPar[func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T6], + ApPar[func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T7], + ApPar[func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T8], + ApPar[func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T9], + ApPar[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T10], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + f8, + f9, + f10, + t, + ) + } +} diff --git a/v2/io/generic/ap.go b/v2/io/generic/ap.go new file mode 100644 index 0000000..ebb1ca2 --- /dev/null +++ b/v2/io/generic/ap.go @@ -0,0 +1,157 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + F "github.com/IBM/fp-go/v2/function" + G "github.com/IBM/fp-go/v2/internal/apply" +) + +const ( + // useParallel is the feature flag to control if we use the parallel or the sequential implementation of ap + useParallel = true +) + +// Deprecated: MonadApSeq implements the applicative on a single thread by first executing mab and the ma +func MonadApSeq[GA ~func() A, GB ~func() B, GAB ~func() func(A) B, A, B any](mab GAB, ma GA) GB { + return MonadChain(mab, F.Bind1st(MonadMap[GA, GB], ma)) +} + +// MonadApPar implements the applicative on two threads, the main thread executes mab and the actuall +// apply operation and the second thread computes ma. Communication between the threads happens via a channel +// +// Deprecated: +func MonadApPar[GA ~func() A, GB ~func() B, GAB ~func() func(A) B, A, B any](mab GAB, ma GA) GB { + return MakeIO[GB](func() B { + c := make(chan A) + go func() { + c <- ma() + close(c) + }() + return mab()(<-c) + }) +} + +// MonadAp implements the `ap` operation. Depending on a feature flag this will be sequential or parallel, the preferred implementation +// is parallel +// +// Deprecated: +func MonadAp[GA ~func() A, GB ~func() B, GAB ~func() func(A) B, A, B any](mab GAB, ma GA) GB { + if useParallel { + return MonadApPar[GA, GB](mab, ma) + } + return MonadApSeq[GA, GB](mab, ma) +} + +// MonadApFirst combines two effectful actions, keeping only the result of the first. +// +// Deprecated: +func MonadApFirst[GA ~func() A, GB ~func() B, GBA ~func() func(B) A, A, B any](first GA, second GB) GA { + return G.MonadApFirst( + MonadAp[GB, GA, GBA, B, A], + MonadMap[GA, GBA, A, func(B) A], + + first, + second, + ) +} + +// MonadApFirstPar combines two effectful actions, keeping only the result of the first. +// +// Deprecated: +func MonadApFirstPar[GA ~func() A, GB ~func() B, GBA ~func() func(B) A, A, B any](first GA, second GB) GA { + return G.MonadApFirst( + MonadApPar[GB, GA, GBA, B, A], + MonadMap[GA, GBA, A, func(B) A], + + first, + second, + ) +} + +// MonadApFirstSeq combines two effectful actions, keeping only the result of the first. +// +// Deprecated: +func MonadApFirstSeq[GA ~func() A, GB ~func() B, GBA ~func() func(B) A, A, B any](first GA, second GB) GA { + return G.MonadApFirst( + MonadApSeq[GB, GA, GBA, B, A], + MonadMap[GA, GBA, A, func(B) A], + + first, + second, + ) +} + +// ApFirst combines two effectful actions, keeping only the result of the first. +// +// Deprecated: +func ApFirst[GA ~func() A, GB ~func() B, GBA ~func() func(B) A, A, B any](second GB) func(GA) GA { + return G.ApFirst( + MonadAp[GB, GA, GBA, B, A], + MonadMap[GA, GBA, A, func(B) A], + + second, + ) +} + +// ApFirstPar combines two effectful actions, keeping only the result of the first. +// +// Deprecated: +func ApFirstPar[GA ~func() A, GB ~func() B, GBA ~func() func(B) A, A, B any](second GB) func(GA) GA { + return G.ApFirst( + MonadApPar[GB, GA, GBA, B, A], + MonadMap[GA, GBA, A, func(B) A], + + second, + ) +} + +// ApFirstSeq combines two effectful actions, keeping only the result of the first. +// +// Deprecated: +func ApFirstSeq[GA ~func() A, GB ~func() B, GBA ~func() func(B) A, A, B any](second GB) func(GA) GA { + return G.ApFirst( + MonadApSeq[GB, GA, GBA, B, A], + MonadMap[GA, GBA, A, func(B) A], + + second, + ) +} + +// MonadApSecond combines two effectful actions, keeping only the result of the second. +// +// Deprecated: +func MonadApSecond[GA ~func() A, GB ~func() B, GBB ~func() func(B) B, A, B any](first GA, second GB) GB { + return G.MonadApSecond( + MonadAp[GB, GB, GBB, B, B], + MonadMap[GA, GBB, A, func(B) B], + + first, + second, + ) +} + +// ApSecond combines two effectful actions, keeping only the result of the second. +// +// Deprecated: +func ApSecond[GA ~func() A, GB ~func() B, GBB ~func() func(B) B, A, B any](second GB) func(GA) GB { + return G.ApSecond( + MonadAp[GB, GB, GBB, B, B], + MonadMap[GA, GBB, A, func(B) B], + + second, + ) +} diff --git a/v2/io/generic/io.go b/v2/io/generic/io.go new file mode 100644 index 0000000..5cc8445 --- /dev/null +++ b/v2/io/generic/io.go @@ -0,0 +1,207 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + "time" + + F "github.com/IBM/fp-go/v2/function" + C "github.com/IBM/fp-go/v2/internal/chain" + FC "github.com/IBM/fp-go/v2/internal/functor" + L "github.com/IBM/fp-go/v2/internal/lazy" + T "github.com/IBM/fp-go/v2/tuple" +) + +var ( + // undefined represents an undefined value + undefined = struct{}{} +) + +// type IO[A any] = func() A + +func MakeIO[GA ~func() A, A any](f func() A) GA { + return f +} + +func Of[GA ~func() A, A any](a A) GA { + return MakeIO[GA](F.Constant(a)) +} + +func FromIO[GA ~func() A, A any](a GA) GA { + return a +} + +// FromImpure converts a side effect without a return value into a side effect that returns any +func FromImpure[GA ~func() any, IMP ~func()](f IMP) GA { + return MakeIO[GA](func() any { + f() + return undefined + }) +} + +func MonadOf[GA ~func() A, A any](a A) GA { + return MakeIO[GA](F.Constant(a)) +} + +func MonadMap[GA ~func() A, GB ~func() B, A, B any](fa GA, f func(A) B) GB { + return MakeIO[GB](func() B { + return f(fa()) + }) +} + +func Map[GA ~func() A, GB ~func() B, A, B any](f func(A) B) func(GA) GB { + return F.Bind2nd(MonadMap[GA, GB, A, B], f) +} + +func MonadMapTo[GA ~func() A, GB ~func() B, A, B any](fa GA, b B) GB { + return MonadMap[GA, GB](fa, F.Constant1[A](b)) +} + +func MapTo[GA ~func() A, GB ~func() B, A, B any](b B) func(GA) GB { + return Map[GA, GB](F.Constant1[A](b)) +} + +// MonadChain composes computations in sequence, using the return value of one computation to determine the next computation. +func MonadChain[GA ~func() A, GB ~func() B, A, B any](fa GA, f func(A) GB) GB { + return MakeIO[GB](func() B { + return f(fa())() + }) +} + +// Chain composes computations in sequence, using the return value of one computation to determine the next computation. +func Chain[GA ~func() A, GB ~func() B, A, B any](f func(A) GB) func(GA) GB { + return F.Bind2nd(MonadChain[GA, GB, A, B], f) +} + +// MonadChainTo composes computations in sequence, ignoring the return value of the first computation +func MonadChainTo[GA ~func() A, GB ~func() B, A, B any](fa GA, fb GB) GB { + return MonadChain(fa, F.Constant1[A](fb)) +} + +// ChainTo composes computations in sequence, ignoring the return value of the first computation +func ChainTo[GA ~func() A, GB ~func() B, A, B any](fb GB) func(GA) GB { + return Chain[GA, GB](F.Constant1[A](fb)) +} + +// MonadChainFirst composes computations in sequence, using the return value of one computation to determine the next computation and +// keeping only the result of the first. +func MonadChainFirst[GA ~func() A, GB ~func() B, A, B any](fa GA, f func(A) GB) GA { + return C.MonadChainFirst(MonadChain[GA, GA, A, A], MonadMap[GB, GA, B, A], fa, f) +} + +// ChainFirst composes computations in sequence, using the return value of one computation to determine the next computation and +// keeping only the result of the first. +func ChainFirst[GA ~func() A, GB ~func() B, A, B any](f func(A) GB) func(GA) GA { + return C.ChainFirst( + Chain[GA, GA, A, A], + Map[GB, GA, B, A], + f, + ) +} + +func ApSeq[GB ~func() B, GAB ~func() func(A) B, GA ~func() A, B, A any](ma GA) func(GAB) GB { + return F.Bind2nd(MonadApSeq[GA, GB, GAB, A, B], ma) +} + +func ApPar[GB ~func() B, GAB ~func() func(A) B, GA ~func() A, B, A any](ma GA) func(GAB) GB { + return F.Bind2nd(MonadApPar[GA, GB, GAB, A, B], ma) +} + +func Ap[GB ~func() B, GAB ~func() func(A) B, GA ~func() A, B, A any](ma GA) func(GAB) GB { + return F.Bind2nd(MonadAp[GA, GB, GAB, A, B], ma) +} + +func Flatten[GA ~func() A, GAA ~func() GA, A any](mma GAA) GA { + return mma() +} + +// Memoize computes the value of the provided IO monad lazily but exactly once +func Memoize[GA ~func() A, A any](ma GA) GA { + return L.Memoize[GA, A](ma) +} + +// Delay creates an operation that passes in the value after some delay +func Delay[GA ~func() A, A any](delay time.Duration) func(GA) GA { + return func(ga GA) GA { + return MakeIO[GA](func() A { + time.Sleep(delay) + return ga() + }) + } +} + +func after(timestamp time.Time) func() { + return func() { + // check if we need to wait + current := time.Now() + if current.Before(timestamp) { + time.Sleep(timestamp.Sub(current)) + } + } +} + +// After creates an operation that passes after the given timestamp +func After[GA ~func() A, A any](timestamp time.Time) func(GA) GA { + aft := after(timestamp) + return func(ga GA) GA { + return MakeIO[GA](func() A { + // wait as long as necessary + aft() + // execute after wait + return ga() + }) + } +} + +// Now returns the current timestamp +func Now[GA ~func() time.Time]() GA { + return MakeIO[GA](time.Now) +} + +// Defer creates an IO by creating a brand new IO via a generator function, each time +func Defer[GA ~func() A, A any](gen func() GA) GA { + return MakeIO[GA](func() A { + return gen()() + }) +} + +func MonadFlap[FAB ~func(A) B, GFAB ~func() FAB, GB ~func() B, A, B any](fab GFAB, a A) GB { + return FC.MonadFlap(MonadMap[GFAB, GB, FAB, B], fab, a) +} + +func Flap[FAB ~func(A) B, GFAB ~func() FAB, GB ~func() B, A, B any](a A) func(GFAB) GB { + return FC.Flap(Map[GFAB, GB, FAB, B], a) +} + +// WithTime returns an operation that measures the start and end timestamp of the operation +func WithTime[GTA ~func() T.Tuple3[A, time.Time, time.Time], GA ~func() A, A any](a GA) GTA { + return MakeIO[GTA](func() T.Tuple3[A, time.Time, time.Time] { + t0 := time.Now() + res := a() + t1 := time.Now() + return T.MakeTuple3(res, t0, t1) + }) +} + +// WithDuration returns an operation that measures the duration of the operation +func WithDuration[GTA ~func() T.Tuple2[A, time.Duration], GA ~func() A, A any](a GA) GTA { + return MakeIO[GTA](func() T.Tuple2[A, time.Duration] { + t0 := time.Now() + res := a() + t1 := time.Now() + return T.MakeTuple2(res, t1.Sub(t0)) + }) +} diff --git a/v2/io/io.go b/v2/io/io.go new file mode 100644 index 0000000..fc9f1b1 --- /dev/null +++ b/v2/io/io.go @@ -0,0 +1,390 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package io + +import ( + "time" + + F "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/internal/apply" + "github.com/IBM/fp-go/v2/internal/chain" + "github.com/IBM/fp-go/v2/internal/functor" + INTL "github.com/IBM/fp-go/v2/internal/lazy" + M "github.com/IBM/fp-go/v2/monoid" + R "github.com/IBM/fp-go/v2/reader" + S "github.com/IBM/fp-go/v2/semigroup" + T "github.com/IBM/fp-go/v2/tuple" +) + +const ( + // useParallel is the feature flag to control if we use the parallel or the sequential implementation of ap + useParallel = true +) + +var ( + // undefined represents an undefined value + undefined = struct{}{} +) + +type ( + // IO represents a synchronous computation that cannot fail + // refer to [https://andywhite.xyz/posts/2021-01-27-rte-foundations/#ioltagt] for more details + IO[A any] = func() A + + Operator[A, B any] = R.Reader[IO[A], IO[B]] + Monoid[A any] = M.Monoid[IO[A]] + Semigroup[A any] = S.Semigroup[IO[A]] +) + +// Of wraps a pure value in an IO context, creating a computation that returns that value. +// This is the monadic return operation for IO. +// +// Example: +// +// greeting := io.Of("Hello, World!") +// result := greeting() // returns "Hello, World!" +func Of[A any](a A) IO[A] { + return F.Constant(a) +} + +// FromIO is an identity function that returns the IO value unchanged. +// Useful for type conversions and maintaining consistency with other monad packages. +func FromIO[A any](a IO[A]) IO[A] { + return a +} + +// FromImpure converts a side effect without a return value into a side effect that returns any +func FromImpure[ANY ~func()](f ANY) IO[any] { + return func() any { + f() + return undefined + } +} + +// MonadOf wraps a pure value in an IO context. +// This is an alias for Of, following the monadic naming convention. +func MonadOf[A any](a A) IO[A] { + return F.Constant(a) +} + +// MonadMap transforms the result of an IO computation by applying a function to it. +// The function is only applied when the IO is executed. +// +// Example: +// +// doubled := io.MonadMap(io.Of(21), func(n int) int { return n * 2 }) +// result := doubled() // returns 42 +func MonadMap[A, B any](fa IO[A], f func(A) B) IO[B] { + return func() B { + return f(fa()) + } +} + +// Map returns an operator that transforms the result of an IO computation. +// This is the curried version of MonadMap. +// +// Example: +// +// double := io.Map(func(n int) int { return n * 2 }) +// doubled := double(io.Of(21)) +func Map[A, B any](f func(A) B) Operator[A, B] { + return F.Bind2nd(MonadMap[A, B], f) +} + +// MonadMapTo replaces the result of an IO computation with a constant value. +// The original computation is still executed, but its result is discarded. +// +// Example: +// +// always42 := io.MonadMapTo(sideEffect, 42) +func MonadMapTo[A, B any](fa IO[A], b B) IO[B] { + return MonadMap(fa, F.Constant1[A](b)) +} + +// MapTo returns an operator that replaces the result with a constant value. +// This is the curried version of MonadMapTo. +func MapTo[A, B any](b B) Operator[A, B] { + return Map(F.Constant1[A](b)) +} + +// MonadChain composes computations in sequence, using the return value of one computation to determine the next computation. +func MonadChain[A, B any](fa IO[A], f func(A) IO[B]) IO[B] { + return func() B { + return f(fa())() + } +} + +// Chain composes computations in sequence, using the return value of one computation to determine the next computation. +func Chain[A, B any](f func(A) IO[B]) Operator[A, B] { + return F.Bind2nd(MonadChain[A, B], f) +} + +// MonadApSeq implements the applicative on a single thread by first executing mab and the ma +func MonadApSeq[A, B any](mab IO[func(A) B], ma IO[A]) IO[B] { + return MonadChain(mab, F.Bind1st(MonadMap[A, B], ma)) +} + +// MonadApPar implements the applicative on two threads, the main thread executes mab and the actuall +// apply operation and the second thread computes ma. Communication between the threads happens via a channel +func MonadApPar[A, B any](mab IO[func(A) B], ma IO[A]) IO[B] { + return func() B { + c := make(chan A, 1) + go func() { + c <- ma() + close(c) + }() + return mab()(<-c) + } +} + +// MonadAp implements the `ap` operation. Depending on a feature flag this will be sequential or parallel, the preferred implementation +// is parallel +func MonadAp[A, B any](mab IO[func(A) B], ma IO[A]) IO[B] { + if useParallel { + return MonadApPar(mab, ma) + } + return MonadApSeq(mab, ma) +} + +// Ap returns an operator that applies a function wrapped in IO to a value wrapped in IO. +// This is the curried version of MonadAp and uses parallel execution by default. +// +// Example: +// +// add := func(a int) func(int) int { return func(b int) int { return a + b } } +// result := io.Ap(io.Of(2))(io.Of(add(3))) // parallel execution +func Ap[B, A any](ma IO[A]) Operator[func(A) B, B] { + return F.Bind2nd(MonadAp[A, B], ma) +} + +// ApSeq returns an operator that applies a function wrapped in IO to a value wrapped in IO sequentially. +// Unlike Ap, this executes the function and value computations in sequence. +func ApSeq[B, A any](ma IO[A]) Operator[func(A) B, B] { + return Chain(F.Bind1st(MonadMap[A, B], ma)) +} + +// ApPar returns an operator that applies a function wrapped in IO to a value wrapped in IO in parallel. +// This explicitly uses parallel execution (same as Ap when useParallel is true). +func ApPar[B, A any](ma IO[A]) Operator[func(A) B, B] { + return F.Bind2nd(MonadApPar[A, B], ma) +} + +// Flatten removes one level of nesting from a nested IO computation. +// Converts IO[IO[A]] to IO[A]. +// +// Example: +// +// nested := io.Of(io.Of(42)) +// flattened := io.Flatten(nested) +// result := flattened() // returns 42 +func Flatten[A any](mma IO[IO[A]]) IO[A] { + return MonadChain(mma, F.Identity) +} + +// Memoize computes the value of the provided [IO] monad lazily but exactly once +func Memoize[A any](ma IO[A]) IO[A] { + return INTL.Memoize(ma) +} + +// MonadChainFirst composes computations in sequence, using the return value of one computation to determine the next computation and +// keeping only the result of the first. +func MonadChainFirst[A, B any](fa IO[A], f func(A) IO[B]) IO[A] { + return chain.MonadChainFirst(MonadChain[A, A], MonadMap[B, A], fa, f) +} + +// ChainFirst composes computations in sequence, using the return value of one computation to determine the next computation and +// keeping only the result of the first. +func ChainFirst[A, B any](f func(A) IO[B]) Operator[A, A] { + return chain.ChainFirst( + Chain[A, A], + Map[B, A], + f, + ) +} + +// MonadApFirst combines two effectful actions, keeping only the result of the first. +func MonadApFirst[A, B any](first IO[A], second IO[B]) IO[A] { + return apply.MonadApFirst( + MonadAp[B, A], + MonadMap[A, func(B) A], + + first, + second, + ) +} + +// ApFirst combines two effectful actions, keeping only the result of the first. +func ApFirst[A, B any](second IO[B]) Operator[A, A] { + return apply.ApFirst( + MonadAp[B, A], + MonadMap[A, func(B) A], + + second, + ) +} + +// MonadApSecond combines two effectful actions, keeping only the result of the second. +func MonadApSecond[A, B any](first IO[A], second IO[B]) IO[B] { + return apply.MonadApSecond( + MonadAp[B, B], + MonadMap[A, func(B) B], + + first, + second, + ) +} + +// ApSecond combines two effectful actions, keeping only the result of the second. +func ApSecond[A, B any](second IO[B]) Operator[A, B] { + return apply.ApSecond( + MonadAp[B, B], + MonadMap[A, func(B) B], + + second, + ) +} + +// MonadChainTo composes computations in sequence, ignoring the return value of the first computation +func MonadChainTo[A, B any](fa IO[A], fb IO[B]) IO[B] { + return MonadChain(fa, F.Constant1[A](fb)) +} + +// ChainTo composes computations in sequence, ignoring the return value of the first computation +func ChainTo[A, B any](fb IO[B]) Operator[A, B] { + return Chain(F.Constant1[A](fb)) +} + +// Now is an IO computation that returns the current timestamp when executed. +// Each execution returns the current time at that moment. +// +// Example: +// +// timestamp := io.Now() +var Now IO[time.Time] = time.Now + +// Defer creates an IO by creating a brand new IO via a generator function each time. +// This allows for dynamic creation of IO computations based on runtime conditions. +// +// Example: +// +// deferred := io.Defer(func() io.IO[int] { +// if someCondition() { +// return io.Of(1) +// } +// return io.Of(2) +// }) +func Defer[A any](gen func() IO[A]) IO[A] { + return func() A { + return gen()() + } +} + +// MonadFlap applies a value to a function wrapped in IO. +// This is the reverse of Ap - instead of applying IO[func] to IO[value], +// it applies a pure value to IO[func]. +// +// Example: +// +// addFive := io.Of(func(n int) int { return n + 5 }) +// result := io.MonadFlap(addFive, 10) // returns IO[15] +func MonadFlap[B, A any](fab IO[func(A) B], a A) IO[B] { + return functor.MonadFlap(MonadMap[func(A) B, B], fab, a) +} + +// Flap returns an operator that applies a pure value to a function wrapped in IO. +// This is the curried version of MonadFlap. +func Flap[B, A any](a A) Operator[func(A) B, B] { + return functor.Flap(Map[func(A) B, B], a) +} + +// Delay creates an operator that delays execution by the specified duration. +// The delay occurs before executing the wrapped computation. +// +// Example: +// +// delayed := io.Delay(time.Second)(io.Of(42)) +// result := delayed() // waits 1 second, then returns 42 +func Delay[A any](delay time.Duration) Operator[A, A] { + return func(ga IO[A]) IO[A] { + return func() A { + time.Sleep(delay) + return ga() + } + } +} + +func after(timestamp time.Time) func() { + return func() { + // check if we need to wait + current := time.Now() + if current.Before(timestamp) { + time.Sleep(timestamp.Sub(current)) + } + } +} + +// After creates an operator that delays execution until after the given timestamp. +// If the timestamp is in the past, the computation executes immediately. +// +// Example: +// +// future := time.Now().Add(5 * time.Second) +// scheduled := io.After(future)(io.Of(42)) +// result := scheduled() // waits until future time, then returns 42 +func After[A any](timestamp time.Time) Operator[A, A] { + aft := after(timestamp) + return func(ga IO[A]) IO[A] { + return func() A { + // wait as long as necessary + aft() + // execute after wait + return ga() + } + } +} + +// WithTime returns an IO that measures the start and end time.Time of the operation. +// Returns a tuple containing the result, start time, and end time. +// +// Example: +// +// timed := io.WithTime(expensiveComputation) +// result, start, end := timed() +func WithTime[A any](a IO[A]) IO[T.Tuple3[A, time.Time, time.Time]] { + return func() T.Tuple3[A, time.Time, time.Time] { + t0 := time.Now() + res := a() + t1 := time.Now() + return T.MakeTuple3(res, t0, t1) + } +} + +// WithDuration returns an IO that measures the execution time.Duration of the operation. +// Returns a tuple containing the result and the duration. +// +// Example: +// +// timed := io.WithDuration(expensiveComputation) +// result, duration := timed() +// fmt.Printf("Took %v\n", duration) +func WithDuration[A any](a IO[A]) IO[T.Tuple2[A, time.Duration]] { + return func() T.Tuple2[A, time.Duration] { + t0 := time.Now() + res := a() + t1 := time.Now() + return T.MakeTuple2(res, t1.Sub(t0)) + } +} diff --git a/v2/io/io_test.go b/v2/io/io_test.go new file mode 100644 index 0000000..a685dc6 --- /dev/null +++ b/v2/io/io_test.go @@ -0,0 +1,73 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package io + +import ( + "math/rand" + "testing" + + F "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/internal/utils" + "github.com/stretchr/testify/assert" +) + +func TestMap(t *testing.T) { + assert.Equal(t, 2, F.Pipe1(Of(1), Map(utils.Double))()) +} + +func TestChain(t *testing.T) { + f := func(n int) IO[int] { + return Of(n * 2) + } + assert.Equal(t, 2, F.Pipe1(Of(1), Chain(f))()) +} + +func TestAp(t *testing.T) { + assert.Equal(t, 2, F.Pipe1(Of(utils.Double), Ap[int](Of(1)))()) +} + +func TestFlatten(t *testing.T) { + assert.Equal(t, 1, F.Pipe1(Of(Of(1)), Flatten[int])()) +} + +func TestMemoize(t *testing.T) { + data := Memoize(rand.Int) + + value1 := data() + value2 := data() + + assert.Equal(t, value1, value2) +} + +func TestApFirst(t *testing.T) { + + x := F.Pipe1( + Of("a"), + ApFirst[string](Of("b")), + ) + + assert.Equal(t, "a", x()) +} + +func TestApSecond(t *testing.T) { + + x := F.Pipe1( + Of("a"), + ApSecond[string](Of("b")), + ) + + assert.Equal(t, "b", x()) +} diff --git a/v2/io/logging.go b/v2/io/logging.go new file mode 100644 index 0000000..a7e6590 --- /dev/null +++ b/v2/io/logging.go @@ -0,0 +1,81 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package io + +import ( + "fmt" + "log" + + L "github.com/IBM/fp-go/v2/logging" +) + +// Logger constructs a logger function that can be used with ChainFirst or similar operations. +// It logs values using the provided loggers (or the default logger if none provided). +// +// Example: +// +// result := pipe.Pipe2( +// fetchUser(), +// io.ChainFirst(io.Logger[User]()("Fetched user")), +// processUser, +// ) +func Logger[A any](loggers ...*log.Logger) func(string) func(A) IO[any] { + _, right := L.LoggingCallbacks(loggers...) + return func(prefix string) func(A) IO[any] { + return func(a A) IO[any] { + return FromImpure(func() { + right("%s: %v", prefix, a) + }) + } + } +} + +// Logf constructs a logger function that can be used with ChainFirst or similar operations. +// The prefix string contains the format string for the log value. +// +// Example: +// +// result := pipe.Pipe2( +// fetchUser(), +// io.ChainFirst(io.Logf[User]("User: %+v")), +// processUser, +// ) +func Logf[A any](prefix string) func(A) IO[any] { + return func(a A) IO[any] { + return FromImpure(func() { + log.Printf(prefix, a) + }) + } +} + +// Printf constructs a printer function that can be used with ChainFirst or similar operations. +// The prefix string contains the format string for the printed value. +// Unlike Logf, this prints to stdout without log prefixes. +// +// Example: +// +// result := pipe.Pipe2( +// fetchUser(), +// io.ChainFirst(io.Printf[User]("User: %+v\n")), +// processUser, +// ) +func Printf[A any](prefix string) func(A) IO[any] { + return func(a A) IO[any] { + return FromImpure(func() { + fmt.Printf(prefix, a) + }) + } +} diff --git a/v2/io/logging_test.go b/v2/io/logging_test.go new file mode 100644 index 0000000..b16b42a --- /dev/null +++ b/v2/io/logging_test.go @@ -0,0 +1,40 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package io + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestLogger(t *testing.T) { + + l := Logger[int]() + + lio := l("out") + + assert.NotPanics(t, func() { lio(10)() }) +} + +func TestLogf(t *testing.T) { + + l := Logf[int] + + lio := l("Value is %d") + + assert.NotPanics(t, func() { lio(10)() }) +} diff --git a/v2/io/monad.go b/v2/io/monad.go new file mode 100644 index 0000000..b2a469b --- /dev/null +++ b/v2/io/monad.go @@ -0,0 +1,58 @@ +// Copyright (c) 2024 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package io + +import ( + "github.com/IBM/fp-go/v2/internal/monad" +) + +type ( + ioMonad[A, B any] struct{} + // IOMonad represents the monad type class for IO. + // A monad combines the capabilities of Functor, Applicative, and Pointed + // with the ability to chain computations (Chain/FlatMap). + IOMonad[A, B any] = monad.Monad[A, B, IO[A], IO[B], IO[func(A) B]] +) + +func (o *ioMonad[A, B]) Of(a A) IO[A] { + return Of(a) +} + +func (o *ioMonad[A, B]) Map(f func(A) B) Operator[A, B] { + return Map(f) +} + +func (o *ioMonad[A, B]) Chain(f func(A) IO[B]) Operator[A, B] { + return Chain(f) +} + +func (o *ioMonad[A, B]) Ap(fa IO[A]) Operator[func(A) B, B] { + return Ap[B](fa) +} + +// Monad returns an instance of the Monad type class for IO. +// This provides a structured way to access monadic operations (Of, Map, Chain, Ap) +// for IO computations. +// +// Example: +// +// m := io.Monad[int, string]() +// result := m.Chain(func(n int) io.IO[string] { +// return io.Of(strconv.Itoa(n)) +// })(m.Of(42)) +func Monad[A, B any]() IOMonad[A, B] { + return &ioMonad[A, B]{} +} diff --git a/v2/io/pointed.go b/v2/io/pointed.go new file mode 100644 index 0000000..5ed82b9 --- /dev/null +++ b/v2/io/pointed.go @@ -0,0 +1,43 @@ +// Copyright (c) 2024 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package io + +import ( + "github.com/IBM/fp-go/v2/internal/pointed" +) + +type ( + ioPointed[A any] struct{} + // IOPointed represents the pointed functor type class for IO. + // A pointed functor is a functor with the ability to lift a pure value + // into the functor context (Of operation). + IOPointed[A any] = pointed.Pointed[A, IO[A]] +) + +func (o *ioPointed[A]) Of(a A) IO[A] { + return Of(a) +} + +// Pointed returns an instance of the Pointed type class for IO. +// This provides a structured way to access the Of operation for IO computations. +// +// Example: +// +// p := io.Pointed[int]() +// result := p.Of(42) +func Pointed[A any]() IOPointed[A] { + return &ioPointed[A]{} +} diff --git a/v2/io/resource.go b/v2/io/resource.go new file mode 100644 index 0000000..e06a781 --- /dev/null +++ b/v2/io/resource.go @@ -0,0 +1,42 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package io + +import ( + "github.com/IBM/fp-go/v2/function" +) + +// WithResource constructs a function that creates a resource, operates on it, and then releases it. +// This is a higher-level abstraction over Bracket that simplifies resource management patterns. +// +// The resource is guaranteed to be released even if the operation fails or panics. +// +// Example: +// +// withFile := io.WithResource( +// io.Of(openFile("data.txt")), +// func(f *os.File) io.IO[any] { +// return io.FromImpure(func() { f.Close() }) +// }, +// ) +// result := withFile(func(f *os.File) io.IO[Data] { +// return readData(f) +// }) +func WithResource[ + R, A, ANY any](onCreate IO[R], onRelease func(R) IO[ANY]) func(func(R) IO[A]) IO[A] { + // simply map to implementation of bracket + return function.Bind13of3(Bracket[R, A, ANY])(onCreate, function.Ignore2of2[A](onRelease)) +} diff --git a/v2/io/retry.go b/v2/io/retry.go new file mode 100644 index 0000000..67bf4e2 --- /dev/null +++ b/v2/io/retry.go @@ -0,0 +1,65 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package io + +import ( + R "github.com/IBM/fp-go/v2/retry" + RG "github.com/IBM/fp-go/v2/retry/generic" +) + +type ( + // RetryStatus is an IO computation that returns retry status information. + RetryStatus = IO[R.RetryStatus] +) + +// Retrying retries an IO action according to a retry policy until it succeeds or the policy gives up. +// +// Parameters: +// - policy: The retry policy that determines delays and maximum attempts +// - action: A function that takes retry status and returns an IO computation +// - check: A predicate that determines if the result should trigger a retry (true = retry) +// +// The action receives retry status information (attempt number, cumulative delay, etc.) +// which can be used for logging or conditional behavior. +// +// Example: +// +// result := io.Retrying( +// retry.ExponentialBackoff(time.Second, 5), +// func(status retry.RetryStatus) io.IO[Response] { +// log.Printf("Attempt %d", status.IterNumber) +// return fetchData() +// }, +// func(r Response) bool { return r.StatusCode >= 500 }, +// ) +func Retrying[A any]( + policy R.RetryPolicy, + action func(R.RetryStatus) IO[A], + check func(A) bool, +) IO[A] { + // get an implementation for the types + return RG.Retrying( + Chain[A, A], + Chain[R.RetryStatus, A], + Of[A], + Of[R.RetryStatus], + Delay[R.RetryStatus], + + policy, + action, + check, + ) +} diff --git a/v2/io/retry_test.go b/v2/io/retry_test.go new file mode 100644 index 0000000..3dae293 --- /dev/null +++ b/v2/io/retry_test.go @@ -0,0 +1,47 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package io + +import ( + "fmt" + "strings" + "testing" + "time" + + R "github.com/IBM/fp-go/v2/retry" + "github.com/stretchr/testify/assert" +) + +var expLogBackoff = R.ExponentialBackoff(10) + +// our retry policy with a 1s cap +var testLogPolicy = R.CapDelay( + 2*time.Second, + R.Monoid.Concat(expLogBackoff, R.LimitRetries(20)), +) + +func TestRetry(t *testing.T) { + action := func(status R.RetryStatus) IO[string] { + return Of(fmt.Sprintf("Retrying %d", status.IterNumber)) + } + check := func(value string) bool { + return !strings.Contains(value, "5") + } + + r := Retrying(testLogPolicy, action, check) + + assert.Equal(t, "Retrying 5", r()) +} diff --git a/v2/io/sequence_test.go b/v2/io/sequence_test.go new file mode 100644 index 0000000..d1afbbf --- /dev/null +++ b/v2/io/sequence_test.go @@ -0,0 +1,65 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package io + +import ( + "fmt" + + A "github.com/IBM/fp-go/v2/array" + F "github.com/IBM/fp-go/v2/function" + TST "github.com/IBM/fp-go/v2/internal/testing" + "github.com/stretchr/testify/assert" + + "testing" +) + +func TestMapSeq(t *testing.T) { + var results []string + + handler := func(value string) IO[string] { + return func() string { + results = append(results, value) + return value + } + } + + src := A.From("a", "b", "c") + + res := F.Pipe2( + src, + TraverseArraySeq(handler), + Map(func(data []string) bool { + return assert.Equal(t, data, results) + }), + ) + + assert.True(t, res()) +} + +func TestSequenceArray(t *testing.T) { + + s := TST.SequenceArrayTest( + FromStrictEquals[bool](), + Pointed[string](), + Pointed[bool](), + Functor[[]string, bool](), + SequenceArray[string], + ) + + for i := 0; i < 10; i++ { + t.Run(fmt.Sprintf("TestSequenceArray %d", i), s(i)) + } +} diff --git a/v2/io/sync.go b/v2/io/sync.go new file mode 100644 index 0000000..553dcfa --- /dev/null +++ b/v2/io/sync.go @@ -0,0 +1,45 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package io + +import ( + "context" +) + +// WithLock executes the provided IO operation in the scope of a lock. +// The lock parameter should be an IO that acquires a lock and returns a function to release it. +// +// This ensures that the operation is executed with exclusive access to a shared resource, +// and the lock is always released even if the operation panics. +// +// Example: +// +// mutex := &sync.Mutex{} +// lock := io.FromImpure(func() context.CancelFunc { +// mutex.Lock() +// return func() { mutex.Unlock() } +// }) +// +// safeOperation := io.WithLock(lock)(dangerousOperation) +// result := safeOperation() +func WithLock[A any](lock IO[context.CancelFunc]) func(fa IO[A]) IO[A] { + return func(fa IO[A]) IO[A] { + return func() A { + defer lock()() + return fa() + } + } +} diff --git a/v2/io/testing/laws.go b/v2/io/testing/laws.go new file mode 100644 index 0000000..caff41f --- /dev/null +++ b/v2/io/testing/laws.go @@ -0,0 +1,60 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package testing + +import ( + "testing" + + EQ "github.com/IBM/fp-go/v2/eq" + L "github.com/IBM/fp-go/v2/internal/monad/testing" + "github.com/IBM/fp-go/v2/io" +) + +// AssertLaws asserts the apply monad laws for the `Either` monad +func AssertLaws[A, B, C any](t *testing.T, + eqa EQ.Eq[A], + eqb EQ.Eq[B], + eqc EQ.Eq[C], + + ab func(A) B, + bc func(B) C, +) func(a A) bool { + + return L.MonadAssertLaws(t, + io.Eq(eqa), + io.Eq(eqb), + io.Eq(eqc), + + io.Pointed[C](), + io.Pointed[func(A) A](), + io.Pointed[func(B) C](), + io.Pointed[func(func(A) B) B](), + + io.Functor[func(B) C, func(func(A) B) func(A) C](), + + io.Applicative[func(A) B, B](), + io.Applicative[func(A) B, func(A) C](), + + io.Monad[A, A](), + io.Monad[A, B](), + io.Monad[A, C](), + io.Monad[B, C](), + + ab, + bc, + ) + +} diff --git a/v2/io/testing/laws_test.go b/v2/io/testing/laws_test.go new file mode 100644 index 0000000..faff6d2 --- /dev/null +++ b/v2/io/testing/laws_test.go @@ -0,0 +1,47 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package testing + +import ( + "fmt" + "testing" + + EQ "github.com/IBM/fp-go/v2/eq" + "github.com/stretchr/testify/assert" +) + +func TestMonadLaws(t *testing.T) { + // some comparison + eqa := EQ.FromStrictEquals[bool]() + eqb := EQ.FromStrictEquals[int]() + eqc := EQ.FromStrictEquals[string]() + + ab := func(a bool) int { + if a { + return 1 + } + return 0 + } + + bc := func(b int) string { + return fmt.Sprintf("value %d", b) + } + + laws := AssertLaws(t, eqa, eqb, eqc, ab, bc) + + assert.True(t, laws(true)) + assert.True(t, laws(false)) +} diff --git a/v2/io/traverse.go b/v2/io/traverse.go new file mode 100644 index 0000000..5a1ab8b --- /dev/null +++ b/v2/io/traverse.go @@ -0,0 +1,240 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package io + +import ( + F "github.com/IBM/fp-go/v2/function" + INTA "github.com/IBM/fp-go/v2/internal/array" + INTR "github.com/IBM/fp-go/v2/internal/record" +) + +// MonadTraverseArray applies an IO-returning function to each element of an array +// and collects the results into an IO of an array. Executes in parallel by default. +// +// Example: +// +// fetchUsers := func(id int) io.IO[User] { return fetchUser(id) } +// users := io.MonadTraverseArray([]int{1, 2, 3}, fetchUsers) +// result := users() // []User with all fetched users +func MonadTraverseArray[A, B any](tas []A, f func(A) IO[B]) IO[[]B] { + return INTA.MonadTraverse( + Of[[]B], + Map[[]B, func(B) []B], + Ap[[]B, B], + + tas, + f, + ) +} + +// TraverseArray returns a function that applies an IO-returning function to each element +// of an array and collects the results. This is the curried version of MonadTraverseArray. +// Executes in parallel by default. +// +// Example: +// +// fetchUsers := io.TraverseArray(func(id int) io.IO[User] { +// return fetchUser(id) +// }) +// users := fetchUsers([]int{1, 2, 3}) +func TraverseArray[A, B any](f func(A) IO[B]) func([]A) IO[[]B] { + return INTA.Traverse[[]A]( + Of[[]B], + Map[[]B, func(B) []B], + Ap[[]B, B], + + f, + ) +} + +// TraverseArrayWithIndex is like TraverseArray but the function also receives the index. +// Executes in parallel by default. +// +// Example: +// +// numbered := io.TraverseArrayWithIndex(func(i int, s string) io.IO[string] { +// return io.Of(fmt.Sprintf("%d: %s", i, s)) +// }) +func TraverseArrayWithIndex[A, B any](f func(int, A) IO[B]) func([]A) IO[[]B] { + return INTA.TraverseWithIndex[[]A]( + Of[[]B], + Map[[]B, func(B) []B], + Ap[[]B, B], + + f, + ) +} + +// SequenceArray converts an array of IO computations into an IO of an array of results. +// All computations are executed in parallel by default. +// +// Example: +// +// operations := []io.IO[int]{fetchA(), fetchB(), fetchC()} +// results := io.SequenceArray(operations) +// values := results() // []int with all results +func SequenceArray[A any](tas []IO[A]) IO[[]A] { + return MonadTraverseArray(tas, F.Identity[IO[A]]) +} + +// MonadTraverseRecord applies an IO-returning function to each value in a map +// and collects the results into an IO of a map. Executes in parallel by default. +// +// Example: +// +// fetchData := func(url string) io.IO[Data] { return fetch(url) } +// urls := map[string]string{"a": "http://a.com", "b": "http://b.com"} +// data := io.MonadTraverseRecord(urls, fetchData) +func MonadTraverseRecord[K comparable, A, B any](tas map[K]A, f func(A) IO[B]) IO[map[K]B] { + return INTR.MonadTraverse( + Of[map[K]B], + Map[map[K]B, func(B) map[K]B], + Ap[map[K]B, B], + + tas, + f, + ) +} + +// TraverseRecord returns a function that applies an IO-returning function to each value +// in a map and collects the results. This is the curried version of MonadTraverseRecord. +// Executes in parallel by default. +func TraverseRecord[K comparable, A, B any](f func(A) IO[B]) func(map[K]A) IO[map[K]B] { + return INTR.Traverse[map[K]A]( + Of[map[K]B], + Map[map[K]B, func(B) map[K]B], + Ap[map[K]B, B], + + f, + ) +} + +// TraverseRecordWithIndex is like TraverseRecord but the function also receives the key. +// Executes in parallel by default. +func TraverseRecordWithIndex[K comparable, A, B any](f func(K, A) IO[B]) func(map[K]A) IO[map[K]B] { + return INTR.TraverseWithIndex[map[K]A]( + Of[map[K]B], + Map[map[K]B, func(B) map[K]B], + Ap[map[K]B, B], + + f, + ) +} + +// SequenceRecord converts a map of IO computations into an IO of a map of results. +// All computations are executed in parallel by default. +// +// Example: +// +// operations := map[string]io.IO[int]{"a": fetchA(), "b": fetchB()} +// results := io.SequenceRecord(operations) +// values := results() // map[string]int with all results +func SequenceRecord[K comparable, A any](tas map[K]IO[A]) IO[map[K]A] { + return MonadTraverseRecord(tas, F.Identity[IO[A]]) +} + +// MonadTraverseArraySeq applies an IO-returning function to each element of an array +// and collects the results into an IO of an array. Executes sequentially (one after another). +// +// Example: +// +// fetchUsers := func(id int) io.IO[User] { return fetchUser(id) } +// users := io.MonadTraverseArraySeq([]int{1, 2, 3}, fetchUsers) +func MonadTraverseArraySeq[A, B any](tas []A, f func(A) IO[B]) IO[[]B] { + return INTA.MonadTraverse( + Of[[]B], + Map[[]B, func(B) []B], + ApSeq[[]B, B], + + tas, + f, + ) +} + +// TraverseArraySeq returns a function that applies an IO-returning function to each element +// of an array and collects the results. Executes sequentially (one after another). +// Use this when operations must be performed in order or when parallel execution is not desired. +func TraverseArraySeq[A, B any](f func(A) IO[B]) func([]A) IO[[]B] { + return INTA.Traverse[[]A]( + Of[[]B], + Map[[]B, func(B) []B], + ApSeq[[]B, B], + + f, + ) +} + +// TraverseArrayWithIndexSeq is like TraverseArraySeq but the function also receives the index. +// Executes sequentially (one after another). +func TraverseArrayWithIndexSeq[A, B any](f func(int, A) IO[B]) func([]A) IO[[]B] { + return INTA.TraverseWithIndex[[]A]( + Of[[]B], + Map[[]B, func(B) []B], + ApSeq[[]B, B], + + f, + ) +} + +// SequenceArraySeq converts an array of IO computations into an IO of an array of results. +// All computations are executed sequentially (one after another). +func SequenceArraySeq[A any](tas []IO[A]) IO[[]A] { + return MonadTraverseArraySeq(tas, F.Identity[IO[A]]) +} + +// MonadTraverseRecordSeq applies an IO-returning function to each value in a map +// and collects the results into an IO of a map. Executes sequentially. +func MonadTraverseRecordSeq[K comparable, A, B any](tas map[K]A, f func(A) IO[B]) IO[map[K]B] { + return INTR.MonadTraverse( + Of[map[K]B], + Map[map[K]B, func(B) map[K]B], + ApSeq[map[K]B, B], + + tas, + f, + ) +} + +// TraverseRecordSeq returns a function that applies an IO-returning function to each value +// in a map and collects the results. Executes sequentially (one after another). +func TraverseRecordSeq[K comparable, A, B any](f func(A) IO[B]) func(map[K]A) IO[map[K]B] { + return INTR.Traverse[map[K]A]( + Of[map[K]B], + Map[map[K]B, func(B) map[K]B], + ApSeq[map[K]B, B], + + f, + ) +} + +// TraverseRecordWithIndeSeq is like TraverseRecordSeq but the function also receives the key. +// Executes sequentially (one after another). +// Note: There's a typo in the function name (Inde instead of Index) for backward compatibility. +func TraverseRecordWithIndeSeq[K comparable, A, B any](f func(K, A) IO[B]) func(map[K]A) IO[map[K]B] { + return INTR.TraverseWithIndex[map[K]A]( + Of[map[K]B], + Map[map[K]B, func(B) map[K]B], + ApSeq[map[K]B, B], + + f, + ) +} + +// SequenceRecordSeq converts a map of IO computations into an IO of a map of results. +// All computations are executed sequentially (one after another). +func SequenceRecordSeq[K comparable, A any](tas map[K]IO[A]) IO[map[K]A] { + return MonadTraverseRecordSeq(tas, F.Identity[IO[A]]) +} diff --git a/v2/io/traverse_test.go b/v2/io/traverse_test.go new file mode 100644 index 0000000..d7e06b6 --- /dev/null +++ b/v2/io/traverse_test.go @@ -0,0 +1,38 @@ +package io + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +func toUpper(s string) IO[string] { + return Of(strings.ToUpper(s)) +} + +func TestTraverseArray(t *testing.T) { + + src := []string{"a", "b"} + + trv := TraverseArray(toUpper) + + res := trv(src) + + assert.Equal(t, res(), []string{"A", "B"}) +} + +type ( + customSlice []string +) + +func TestTraverseCustomSlice(t *testing.T) { + + src := customSlice{"a", "b"} + + trv := TraverseArray(toUpper) + + res := trv(src) + + assert.Equal(t, res(), []string{"A", "B"}) +} diff --git a/v2/ioeither/ap.go b/v2/ioeither/ap.go new file mode 100644 index 0000000..e76d81a --- /dev/null +++ b/v2/ioeither/ap.go @@ -0,0 +1,62 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package ioeither + +import ( + "github.com/IBM/fp-go/v2/internal/apply" +) + +// MonadApFirst combines two effectful actions, keeping only the result of the first. +func MonadApFirst[A, E, B any](first IOEither[E, A], second IOEither[E, B]) IOEither[E, A] { + return apply.MonadApFirst( + MonadAp[A, E, B], + MonadMap[E, A, func(B) A], + + first, + second, + ) +} + +// ApFirst combines two effectful actions, keeping only the result of the first. +func ApFirst[A, E, B any](second IOEither[E, B]) Operator[E, A, A] { + return apply.ApFirst( + MonadAp[A, E, B], + MonadMap[E, A, func(B) A], + + second, + ) +} + +// MonadApSecond combines two effectful actions, keeping only the result of the second. +func MonadApSecond[A, E, B any](first IOEither[E, A], second IOEither[E, B]) IOEither[E, B] { + return apply.MonadApSecond( + MonadAp[B, E, B], + MonadMap[E, A, func(B) B], + + first, + second, + ) +} + +// ApSecond combines two effectful actions, keeping only the result of the second. +func ApSecond[A, E, B any](second IOEither[E, B]) Operator[E, A, B] { + return apply.ApSecond( + MonadAp[B, E, B], + MonadMap[E, A, func(B) B], + + second, + ) +} diff --git a/v2/ioeither/bind.go b/v2/ioeither/bind.go new file mode 100644 index 0000000..c7b79e6 --- /dev/null +++ b/v2/ioeither/bind.go @@ -0,0 +1,89 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package ioeither + +import ( + "github.com/IBM/fp-go/v2/internal/apply" + "github.com/IBM/fp-go/v2/internal/chain" + "github.com/IBM/fp-go/v2/internal/functor" +) + +// Bind creates an empty context of type [S] to be used with the [Bind] operation +func Do[E, S any]( + empty S, +) IOEither[E, S] { + return Of[E](empty) +} + +// Bind attaches the result of a computation to a context [S1] to produce a context [S2] +func Bind[E, S1, S2, T any]( + setter func(T) func(S1) S2, + f func(S1) IOEither[E, T], +) Operator[E, S1, S2] { + return chain.Bind( + Chain[E, S1, S2], + Map[E, T, S2], + setter, + f, + ) +} + +// Let attaches the result of a computation to a context [S1] to produce a context [S2] +func Let[E, S1, S2, T any]( + setter func(T) func(S1) S2, + f func(S1) T, +) Operator[E, S1, S2] { + return functor.Let( + Map[E, S1, S2], + setter, + f, + ) +} + +// LetTo attaches the a value to a context [S1] to produce a context [S2] +func LetTo[E, S1, S2, T any]( + setter func(T) func(S1) S2, + b T, +) Operator[E, S1, S2] { + return functor.LetTo( + Map[E, S1, S2], + setter, + b, + ) +} + +// BindTo initializes a new state [S1] from a value [T] +func BindTo[E, S1, T any]( + setter func(T) S1, +) Operator[E, T, S1] { + return chain.BindTo( + Map[E, T, S1], + setter, + ) +} + +// ApS attaches a value to a context [S1] to produce a context [S2] by considering the context and the value concurrently +func ApS[E, S1, S2, T any]( + setter func(T) func(S1) S2, + fa IOEither[E, T], +) Operator[E, S1, S2] { + return apply.ApS( + Ap[S2, E, T], + Map[E, S1, func(T) S2], + setter, + fa, + ) +} diff --git a/v2/ioeither/bind_test.go b/v2/ioeither/bind_test.go new file mode 100644 index 0000000..8e27965 --- /dev/null +++ b/v2/ioeither/bind_test.go @@ -0,0 +1,57 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package ioeither + +import ( + "testing" + + E "github.com/IBM/fp-go/v2/either" + F "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/internal/utils" + "github.com/stretchr/testify/assert" +) + +func getLastName(s utils.Initial) IOEither[error, string] { + return Of[error]("Doe") +} + +func getGivenName(s utils.WithLastName) IOEither[error, string] { + return Of[error]("John") +} + +func TestBind(t *testing.T) { + + res := F.Pipe3( + Do[error](utils.Empty), + Bind(utils.SetLastName, getLastName), + Bind(utils.SetGivenName, getGivenName), + Map[error](utils.GetFullName), + ) + + assert.Equal(t, res(), E.Of[error]("John Doe")) +} + +func TestApS(t *testing.T) { + + res := F.Pipe3( + Do[error](utils.Empty), + ApS(utils.SetLastName, Of[error]("Doe")), + ApS(utils.SetGivenName, Of[error]("John")), + Map[error](utils.GetFullName), + ) + + assert.Equal(t, res(), E.Of[error]("John Doe")) +} diff --git a/v2/ioeither/bracket.go b/v2/ioeither/bracket.go new file mode 100644 index 0000000..3669b0e --- /dev/null +++ b/v2/ioeither/bracket.go @@ -0,0 +1,40 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package ioeither + +import ( + BR "github.com/IBM/fp-go/v2/internal/bracket" + "github.com/IBM/fp-go/v2/io" +) + +// Bracket makes sure that a resource is cleaned up in the event of an error. The release action is called regardless of +// whether the body action returns and error or not. +func Bracket[E, A, B, ANY any]( + acquire IOEither[E, A], + use func(A) IOEither[E, B], + release func(A, Either[E, B]) IOEither[E, ANY], +) IOEither[E, B] { + return BR.Bracket[IOEither[E, A], IOEither[E, B], IOEither[E, ANY], Either[E, B], A, B]( + io.Of[Either[E, B]], + MonadChain[E, A, B], + io.MonadChain[Either[E, B], Either[E, B]], + MonadChain[E, ANY, B], + + acquire, + use, + release, + ) +} diff --git a/v2/ioeither/doc.go b/v2/ioeither/doc.go new file mode 100644 index 0000000..7fd35a9 --- /dev/null +++ b/v2/ioeither/doc.go @@ -0,0 +1,18 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package ioeither + +//go:generate go run .. ioeither --count 10 --filename gen.go diff --git a/v2/ioeither/eq.go b/v2/ioeither/eq.go new file mode 100644 index 0000000..dbc8727 --- /dev/null +++ b/v2/ioeither/eq.go @@ -0,0 +1,32 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package ioeither + +import ( + "github.com/IBM/fp-go/v2/either" + EQ "github.com/IBM/fp-go/v2/eq" + "github.com/IBM/fp-go/v2/io" +) + +// Eq implements the equals predicate for values contained in the IOEither monad +func Eq[E, A any](eq EQ.Eq[Either[E, A]]) EQ.Eq[IOEither[E, A]] { + return io.Eq(eq) +} + +// FromStrictEquals constructs an [EQ.Eq] from the canonical comparison function +func FromStrictEquals[E, A comparable]() EQ.Eq[IOEither[E, A]] { + return Eq(either.FromStrictEquals[E, A]()) +} diff --git a/v2/ioeither/eq_test.go b/v2/ioeither/eq_test.go new file mode 100644 index 0000000..42b9f8c --- /dev/null +++ b/v2/ioeither/eq_test.go @@ -0,0 +1,45 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package ioeither + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestEq(t *testing.T) { + + r1 := Of[string](1) + r2 := Of[string](1) + r3 := Of[string](2) + + e1 := Left[int]("a") + e2 := Left[int]("a") + e3 := Left[int]("b") + + eq := FromStrictEquals[string, int]() + + assert.True(t, eq.Equals(r1, r1)) + assert.True(t, eq.Equals(r1, r2)) + assert.False(t, eq.Equals(r1, r3)) + assert.False(t, eq.Equals(r1, e1)) + + assert.True(t, eq.Equals(e1, e1)) + assert.True(t, eq.Equals(e1, e2)) + assert.False(t, eq.Equals(e1, e3)) + assert.False(t, eq.Equals(e2, r2)) +} diff --git a/v2/ioeither/examples_create_test.go b/v2/ioeither/examples_create_test.go new file mode 100644 index 0000000..bb666a8 --- /dev/null +++ b/v2/ioeither/examples_create_test.go @@ -0,0 +1,57 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package ioeither + +import ( + "fmt" + + E "github.com/IBM/fp-go/v2/either" +) + +func ExampleIOEither_creation() { + // Build an IOEither + leftValue := Left[string](fmt.Errorf("some error")) + rightValue := Right[error]("value") + + // Convert from Either + eitherValue := E.Of[error](42) + ioFromEither := FromEither(eitherValue) + + // some predicate + isEven := func(num int) (int, error) { + if num%2 == 0 { + return num, nil + } + return 0, fmt.Errorf("%d is an odd number", num) + } + fromEven := Eitherize1(isEven) + leftFromPred := fromEven(3) + rightFromPred := fromEven(4) + + fmt.Println(leftValue()) + fmt.Println(rightValue()) + fmt.Println(ioFromEither()) + fmt.Println(leftFromPred()) + fmt.Println(rightFromPred()) + + // Output: + // Left[*errors.errorString](some error) + // Right[string](value) + // Right[int](42) + // Left[*errors.errorString](3 is an odd number) + // Right[int](4) + +} diff --git a/v2/ioeither/examples_do_test.go b/v2/ioeither/examples_do_test.go new file mode 100644 index 0000000..e2a841b --- /dev/null +++ b/v2/ioeither/examples_do_test.go @@ -0,0 +1,57 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package ioeither + +import ( + "fmt" + "log" + + F "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/io" + T "github.com/IBM/fp-go/v2/tuple" +) + +func ExampleIOEither_do() { + foo := Of[error]("foo") + bar := Of[error](1) + + // quux consumes the state of three bindings and returns an [IO] instead of an [IOEither] + quux := func(t T.Tuple3[string, int, string]) IO[any] { + return io.FromImpure(func() { + log.Printf("t1: %s, t2: %d, t3: %s", t.F1, t.F2, t.F3) + }) + } + + transform := func(t T.Tuple3[string, int, string]) int { + return len(t.F1) + t.F2 + len(t.F3) + } + + b := F.Pipe5( + foo, + BindTo[error](T.Of[string]), + ApS(T.Push1[string, int], bar), + Bind(T.Push2[string, int, string], func(t T.Tuple2[string, int]) IOEither[error, string] { + return Of[error](fmt.Sprintf("%s%d", t.F1, t.F2)) + }), + ChainFirstIOK[error](quux), + Map[error](transform), + ) + + fmt.Println(b()) + + // Output: + // Right[int](8) +} diff --git a/v2/ioeither/examples_extract_test.go b/v2/ioeither/examples_extract_test.go new file mode 100644 index 0000000..8b5bdec --- /dev/null +++ b/v2/ioeither/examples_extract_test.go @@ -0,0 +1,45 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package ioeither + +import ( + "fmt" + + E "github.com/IBM/fp-go/v2/either" + F "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/io" +) + +func ExampleIOEither_extraction() { + // IOEither + someIOEither := Right[error](42) + eitherValue := someIOEither() // E.Right(42) + value := E.GetOrElse(F.Constant1[error](0))(eitherValue) // 42 + + // Or more directly + infaillibleIO := GetOrElse(F.Constant1[error](io.Of(0)))(someIOEither) // => io.Right(42) + valueFromIO := infaillibleIO() // => 42 + + fmt.Println(eitherValue) + fmt.Println(value) + fmt.Println(valueFromIO) + + // Output: + // Right[int](42) + // 42 + // 42 + +} diff --git a/v2/ioeither/exec/exec.go b/v2/ioeither/exec/exec.go new file mode 100644 index 0000000..fa8b616 --- /dev/null +++ b/v2/ioeither/exec/exec.go @@ -0,0 +1,36 @@ +// Copyright (c) 2023 IBM Corp. +// All rights reserved. +// +// 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. + +package exec + +import ( + "context" + + "github.com/IBM/fp-go/v2/exec" + "github.com/IBM/fp-go/v2/function" + INTE "github.com/IBM/fp-go/v2/internal/exec" + "github.com/IBM/fp-go/v2/ioeither" +) + +var ( + // Command executes a command + Command = function.Curry3(command) +) + +func command(name string, args []string, in []byte) ioeither.IOEither[error, exec.CommandOutput] { + return ioeither.TryCatchError(func() (exec.CommandOutput, error) { + return INTE.Exec(context.Background(), name, args, in) + }) +} diff --git a/v2/ioeither/exec/exec_test.go b/v2/ioeither/exec/exec_test.go new file mode 100644 index 0000000..fc0c010 --- /dev/null +++ b/v2/ioeither/exec/exec_test.go @@ -0,0 +1,43 @@ +// Copyright (c) 2023 IBM Corp. +// All rights reserved. +// +// 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. + +package exec + +import ( + "strings" + "testing" + + RA "github.com/IBM/fp-go/v2/array" + B "github.com/IBM/fp-go/v2/bytes" + E "github.com/IBM/fp-go/v2/either" + "github.com/IBM/fp-go/v2/exec" + F "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/ioeither" + "github.com/stretchr/testify/assert" +) + +func TestOpenSSL(t *testing.T) { + // execute the openSSL binary + version := F.Pipe1( + Command("openssl")(RA.From("version"))(B.Monoid.Empty()), + ioeither.Map[error](F.Flow3( + exec.StdOut, + B.ToString, + strings.TrimSpace, + )), + ) + + assert.True(t, E.IsRight(version())) +} diff --git a/v2/ioeither/file/dir.go b/v2/ioeither/file/dir.go new file mode 100644 index 0000000..b641c41 --- /dev/null +++ b/v2/ioeither/file/dir.go @@ -0,0 +1,36 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package file + +import ( + "os" + + "github.com/IBM/fp-go/v2/ioeither" +) + +// MkdirAll create a sequence of directories, see [os.MkdirAll] +func MkdirAll(path string, perm os.FileMode) ioeither.IOEither[error, string] { + return ioeither.TryCatchError(func() (string, error) { + return path, os.MkdirAll(path, perm) + }) +} + +// Mkdir create a directory, see [os.Mkdir] +func Mkdir(path string, perm os.FileMode) ioeither.IOEither[error, string] { + return ioeither.TryCatchError(func() (string, error) { + return path, os.Mkdir(path, perm) + }) +} diff --git a/v2/ioeither/file/file.go b/v2/ioeither/file/file.go new file mode 100644 index 0000000..61e2f0d --- /dev/null +++ b/v2/ioeither/file/file.go @@ -0,0 +1,55 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package file + +import ( + "io" + "os" + + "github.com/IBM/fp-go/v2/ioeither" +) + +var ( + // Open opens a file for reading + Open = ioeither.Eitherize1(os.Open) + // Create opens a file for writing + Create = ioeither.Eitherize1(os.Create) + // ReadFile reads the context of a file + ReadFile = ioeither.Eitherize1(os.ReadFile) +) + +// WriteFile writes a data blob to a file +func WriteFile(dstName string, perm os.FileMode) func([]byte) ioeither.IOEither[error, []byte] { + return func(data []byte) ioeither.IOEither[error, []byte] { + return ioeither.TryCatchError(func() ([]byte, error) { + return data, os.WriteFile(dstName, data, perm) + }) + } +} + +// Remove removes a file by name +func Remove(name string) ioeither.IOEither[error, string] { + return ioeither.TryCatchError(func() (string, error) { + return name, os.Remove(name) + }) +} + +// Close closes an object +func Close[C io.Closer](c C) ioeither.IOEither[error, any] { + return ioeither.TryCatchError(func() (any, error) { + return c, c.Close() + }) +} diff --git a/v2/ioeither/file/readall.go b/v2/ioeither/file/readall.go new file mode 100644 index 0000000..c65dbf3 --- /dev/null +++ b/v2/ioeither/file/readall.go @@ -0,0 +1,40 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package file + +import ( + "io" + + FL "github.com/IBM/fp-go/v2/file" + F "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/ioeither" +) + +var ( + // readAll is the adapted version of [io.ReadAll] + readAll = ioeither.Eitherize1(io.ReadAll) +) + +// ReadAll uses a generator function to create a stream, reads it and closes it +func ReadAll[R io.ReadCloser](acquire ioeither.IOEither[error, R]) ioeither.IOEither[error, []byte] { + return F.Pipe1( + F.Flow2( + FL.ToReader[R], + readAll, + ), + ioeither.WithResource[[]byte](acquire, Close[R]), + ) +} diff --git a/v2/ioeither/file/tempfile.go b/v2/ioeither/file/tempfile.go new file mode 100644 index 0000000..5f486f5 --- /dev/null +++ b/v2/ioeither/file/tempfile.go @@ -0,0 +1,44 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package file + +import ( + "os" + + F "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/io" + IOF "github.com/IBM/fp-go/v2/io/file" + "github.com/IBM/fp-go/v2/ioeither" +) + +var ( + // CreateTemp created a temp file with proper parametrization + CreateTemp = ioeither.Eitherize2(os.CreateTemp) + // onCreateTempFile creates a temp file with sensible defaults + onCreateTempFile = CreateTemp("", "*") + // destroy handler + onReleaseTempFile = F.Flow4( + IOF.Close[*os.File], + io.Map((*os.File).Name), + ioeither.FromIO[error, string], + ioeither.Chain(Remove), + ) +) + +// WithTempFile creates a temporary file, then invokes a callback to create a resource based on the file, then close and remove the temp file +func WithTempFile[A any](f func(*os.File) ioeither.IOEither[error, A]) ioeither.IOEither[error, A] { + return ioeither.WithResource[A](onCreateTempFile, onReleaseTempFile)(f) +} diff --git a/v2/ioeither/file/tempfile_test.go b/v2/ioeither/file/tempfile_test.go new file mode 100644 index 0000000..1a25ae5 --- /dev/null +++ b/v2/ioeither/file/tempfile_test.go @@ -0,0 +1,46 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package file + +import ( + "os" + "testing" + + E "github.com/IBM/fp-go/v2/either" + F "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/ioeither" + "github.com/stretchr/testify/assert" +) + +func TestWithTempFile(t *testing.T) { + + res := WithTempFile(onWriteAll[*os.File]([]byte("Carsten"))) + + assert.Equal(t, E.Of[error]([]byte("Carsten")), res()) +} + +func TestWithTempFileOnClosedFile(t *testing.T) { + + res := WithTempFile(func(f *os.File) ioeither.IOEither[error, []byte] { + return F.Pipe2( + f, + onWriteAll[*os.File]([]byte("Carsten")), + ioeither.ChainFirst(F.Constant1[[]byte](Close(f))), + ) + }) + + assert.Equal(t, E.Of[error]([]byte("Carsten")), res()) +} diff --git a/v2/ioeither/file/write.go b/v2/ioeither/file/write.go new file mode 100644 index 0000000..74f070c --- /dev/null +++ b/v2/ioeither/file/write.go @@ -0,0 +1,50 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package file + +import ( + "io" + + "github.com/IBM/fp-go/v2/ioeither" +) + +func onWriteAll[W io.Writer](data []byte) func(w W) ioeither.IOEither[error, []byte] { + return func(w W) ioeither.IOEither[error, []byte] { + return ioeither.TryCatchError(func() ([]byte, error) { + _, err := w.Write(data) + return data, err + }) + } +} + +// WriteAll uses a generator function to create a stream, writes data to it and closes it +func WriteAll[W io.WriteCloser](data []byte) func(acquire ioeither.IOEither[error, W]) ioeither.IOEither[error, []byte] { + onWrite := onWriteAll[W](data) + return func(onCreate ioeither.IOEither[error, W]) ioeither.IOEither[error, []byte] { + return ioeither.WithResource[[]byte]( + onCreate, + Close[W])( + onWrite, + ) + } +} + +// Write uses a generator function to create a stream, writes data to it and closes it +func Write[R any, W io.WriteCloser](acquire ioeither.IOEither[error, W]) func(use func(W) ioeither.IOEither[error, R]) ioeither.IOEither[error, R] { + return ioeither.WithResource[R]( + acquire, + Close[W]) +} diff --git a/v2/ioeither/gen.go b/v2/ioeither/gen.go new file mode 100644 index 0000000..e661d84 --- /dev/null +++ b/v2/ioeither/gen.go @@ -0,0 +1,1802 @@ +// Code generated by go generate; DO NOT EDIT. +// This file was generated by robots at +// 2025-03-09 23:53:07.5974468 +0100 CET m=+0.008920801 + +package ioeither + + +import ( + G "github.com/IBM/fp-go/v2/ioeither/generic" + "github.com/IBM/fp-go/v2/internal/apply" + "github.com/IBM/fp-go/v2/tuple" +) + +// Eitherize0 converts a function with 1 parameters returning a tuple into a function with 0 parameters returning a [IOEither[error, R]] +func Eitherize0[F ~func() (R, error), R any](f F) func() IOEither[error, R] { + return G.Eitherize0[IOEither[error, R]](f) +} + +// Uneitherize0 converts a function with 1 parameters returning a tuple into a function with 0 parameters returning a [IOEither[error, R]] +func Uneitherize0[F ~func() IOEither[error, R], R any](f F) func() (R, error) { + return G.Uneitherize0[IOEither[error, R]](f) +} + +// Eitherize1 converts a function with 2 parameters returning a tuple into a function with 1 parameters returning a [IOEither[error, R]] +func Eitherize1[F ~func(T1) (R, error), T1, R any](f F) func(T1) IOEither[error, R] { + return G.Eitherize1[IOEither[error, R]](f) +} + +// Uneitherize1 converts a function with 2 parameters returning a tuple into a function with 1 parameters returning a [IOEither[error, R]] +func Uneitherize1[F ~func(T1) IOEither[error, R], T1, R any](f F) func(T1) (R, error) { + return G.Uneitherize1[IOEither[error, R]](f) +} + +// SequenceT1 converts 1 [IOEither[E, T]] into a [IOEither[E, tuple.Tuple1[T1]]] +func SequenceT1[E, T1 any]( + t1 IOEither[E, T1], +) IOEither[E, tuple.Tuple1[T1]] { + return apply.SequenceT1( + Map[E, T1, tuple.Tuple1[T1]], + t1, + ) +} + +// SequenceSeqT1 converts 1 [IOEither[E, T]] into a [IOEither[E, tuple.Tuple1[T1]]] +func SequenceSeqT1[E, T1 any]( + t1 IOEither[E, T1], +) IOEither[E, tuple.Tuple1[T1]] { + return apply.SequenceT1( + Map[E, T1, tuple.Tuple1[T1]], + t1, + ) +} + +// SequenceParT1 converts 1 [IOEither[E, T]] into a [IOEither[E, tuple.Tuple1[T1]]] +func SequenceParT1[E, T1 any]( + t1 IOEither[E, T1], +) IOEither[E, tuple.Tuple1[T1]] { + return apply.SequenceT1( + Map[E, T1, tuple.Tuple1[T1]], + t1, + ) +} + +// SequenceTuple1 converts a [tuple.Tuple1[IOEither[E, T]]] into a [IOEither[E, tuple.Tuple1[T1]]] +func SequenceTuple1[E, T1 any](t tuple.Tuple1[IOEither[E, T1]]) IOEither[E, tuple.Tuple1[T1]] { + return apply.SequenceTuple1( + Map[E, T1, tuple.Tuple1[T1]], + t, + ) +} + +// SequenceSeqTuple1 converts a [tuple.Tuple1[IOEither[E, T]]] into a [IOEither[E, tuple.Tuple1[T1]]] +func SequenceSeqTuple1[E, T1 any](t tuple.Tuple1[IOEither[E, T1]]) IOEither[E, tuple.Tuple1[T1]] { + return apply.SequenceTuple1( + Map[E, T1, tuple.Tuple1[T1]], + t, + ) +} + +// SequenceParTuple1 converts a [tuple.Tuple1[IOEither[E, T]]] into a [IOEither[E, tuple.Tuple1[T1]]] +func SequenceParTuple1[E, T1 any](t tuple.Tuple1[IOEither[E, T1]]) IOEither[E, tuple.Tuple1[T1]] { + return apply.SequenceTuple1( + Map[E, T1, tuple.Tuple1[T1]], + t, + ) +} + +// TraverseTuple1 converts a [tuple.Tuple1[A1]] into a [IOEither[E, tuple.Tuple1[T1]]] +func TraverseTuple1[E, F1 ~func(A1) IOEither[E, T1], T1, A1 any](f1 F1) func(tuple.Tuple1[A1]) IOEither[E, tuple.Tuple1[T1]] { + return func(t tuple.Tuple1[A1]) IOEither[E, tuple.Tuple1[T1]] { + return apply.TraverseTuple1( + Map[E, T1, tuple.Tuple1[T1]], + f1, + t, + ) + } +} + +// TraverseSeqTuple1 converts a [tuple.Tuple1[A1]] into a [IOEither[E, tuple.Tuple1[T1]]] +func TraverseSeqTuple1[E, F1 ~func(A1) IOEither[E, T1], T1, A1 any](f1 F1) func(tuple.Tuple1[A1]) IOEither[E, tuple.Tuple1[T1]] { + return func(t tuple.Tuple1[A1]) IOEither[E, tuple.Tuple1[T1]] { + return apply.TraverseTuple1( + Map[E, T1, tuple.Tuple1[T1]], + f1, + t, + ) + } +} + +// TraverseParTuple1 converts a [tuple.Tuple1[A1]] into a [IOEither[E, tuple.Tuple1[T1]]] +func TraverseParTuple1[E, F1 ~func(A1) IOEither[E, T1], T1, A1 any](f1 F1) func(tuple.Tuple1[A1]) IOEither[E, tuple.Tuple1[T1]] { + return func(t tuple.Tuple1[A1]) IOEither[E, tuple.Tuple1[T1]] { + return apply.TraverseTuple1( + Map[E, T1, tuple.Tuple1[T1]], + f1, + t, + ) + } +} + +// Eitherize2 converts a function with 3 parameters returning a tuple into a function with 2 parameters returning a [IOEither[error, R]] +func Eitherize2[F ~func(T1, T2) (R, error), T1, T2, R any](f F) func(T1, T2) IOEither[error, R] { + return G.Eitherize2[IOEither[error, R]](f) +} + +// Uneitherize2 converts a function with 3 parameters returning a tuple into a function with 2 parameters returning a [IOEither[error, R]] +func Uneitherize2[F ~func(T1, T2) IOEither[error, R], T1, T2, R any](f F) func(T1, T2) (R, error) { + return G.Uneitherize2[IOEither[error, R]](f) +} + +// SequenceT2 converts 2 [IOEither[E, T]] into a [IOEither[E, tuple.Tuple2[T1, T2]]] +func SequenceT2[E, T1, T2 any]( + t1 IOEither[E, T1], + t2 IOEither[E, T2], +) IOEither[E, tuple.Tuple2[T1, T2]] { + return apply.SequenceT2( + Map[E, T1, func(T2) tuple.Tuple2[T1, T2]], + Ap[tuple.Tuple2[T1, T2], E, T2], + t1, + t2, + ) +} + +// SequenceSeqT2 converts 2 [IOEither[E, T]] into a [IOEither[E, tuple.Tuple2[T1, T2]]] +func SequenceSeqT2[E, T1, T2 any]( + t1 IOEither[E, T1], + t2 IOEither[E, T2], +) IOEither[E, tuple.Tuple2[T1, T2]] { + return apply.SequenceT2( + Map[E, T1, func(T2) tuple.Tuple2[T1, T2]], + ApSeq[tuple.Tuple2[T1, T2], E, T2], + t1, + t2, + ) +} + +// SequenceParT2 converts 2 [IOEither[E, T]] into a [IOEither[E, tuple.Tuple2[T1, T2]]] +func SequenceParT2[E, T1, T2 any]( + t1 IOEither[E, T1], + t2 IOEither[E, T2], +) IOEither[E, tuple.Tuple2[T1, T2]] { + return apply.SequenceT2( + Map[E, T1, func(T2) tuple.Tuple2[T1, T2]], + ApPar[tuple.Tuple2[T1, T2], E, T2], + t1, + t2, + ) +} + +// SequenceTuple2 converts a [tuple.Tuple2[IOEither[E, T]]] into a [IOEither[E, tuple.Tuple2[T1, T2]]] +func SequenceTuple2[E, T1, T2 any](t tuple.Tuple2[IOEither[E, T1], IOEither[E, T2]]) IOEither[E, tuple.Tuple2[T1, T2]] { + return apply.SequenceTuple2( + Map[E, T1, func(T2) tuple.Tuple2[T1, T2]], + Ap[tuple.Tuple2[T1, T2], E, T2], + t, + ) +} + +// SequenceSeqTuple2 converts a [tuple.Tuple2[IOEither[E, T]]] into a [IOEither[E, tuple.Tuple2[T1, T2]]] +func SequenceSeqTuple2[E, T1, T2 any](t tuple.Tuple2[IOEither[E, T1], IOEither[E, T2]]) IOEither[E, tuple.Tuple2[T1, T2]] { + return apply.SequenceTuple2( + Map[E, T1, func(T2) tuple.Tuple2[T1, T2]], + ApSeq[tuple.Tuple2[T1, T2], E, T2], + t, + ) +} + +// SequenceParTuple2 converts a [tuple.Tuple2[IOEither[E, T]]] into a [IOEither[E, tuple.Tuple2[T1, T2]]] +func SequenceParTuple2[E, T1, T2 any](t tuple.Tuple2[IOEither[E, T1], IOEither[E, T2]]) IOEither[E, tuple.Tuple2[T1, T2]] { + return apply.SequenceTuple2( + Map[E, T1, func(T2) tuple.Tuple2[T1, T2]], + ApPar[tuple.Tuple2[T1, T2], E, T2], + t, + ) +} + +// TraverseTuple2 converts a [tuple.Tuple2[A1, A2]] into a [IOEither[E, tuple.Tuple2[T1, T2]]] +func TraverseTuple2[E, F1 ~func(A1) IOEither[E, T1], F2 ~func(A2) IOEither[E, T2], T1, T2, A1, A2 any](f1 F1, f2 F2) func(tuple.Tuple2[A1, A2]) IOEither[E, tuple.Tuple2[T1, T2]] { + return func(t tuple.Tuple2[A1, A2]) IOEither[E, tuple.Tuple2[T1, T2]] { + return apply.TraverseTuple2( + Map[E, T1, func(T2) tuple.Tuple2[T1, T2]], + Ap[tuple.Tuple2[T1, T2], E, T2], + f1, + f2, + t, + ) + } +} + +// TraverseSeqTuple2 converts a [tuple.Tuple2[A1, A2]] into a [IOEither[E, tuple.Tuple2[T1, T2]]] +func TraverseSeqTuple2[E, F1 ~func(A1) IOEither[E, T1], F2 ~func(A2) IOEither[E, T2], T1, T2, A1, A2 any](f1 F1, f2 F2) func(tuple.Tuple2[A1, A2]) IOEither[E, tuple.Tuple2[T1, T2]] { + return func(t tuple.Tuple2[A1, A2]) IOEither[E, tuple.Tuple2[T1, T2]] { + return apply.TraverseTuple2( + Map[E, T1, func(T2) tuple.Tuple2[T1, T2]], + ApSeq[tuple.Tuple2[T1, T2], E, T2], + f1, + f2, + t, + ) + } +} + +// TraverseParTuple2 converts a [tuple.Tuple2[A1, A2]] into a [IOEither[E, tuple.Tuple2[T1, T2]]] +func TraverseParTuple2[E, F1 ~func(A1) IOEither[E, T1], F2 ~func(A2) IOEither[E, T2], T1, T2, A1, A2 any](f1 F1, f2 F2) func(tuple.Tuple2[A1, A2]) IOEither[E, tuple.Tuple2[T1, T2]] { + return func(t tuple.Tuple2[A1, A2]) IOEither[E, tuple.Tuple2[T1, T2]] { + return apply.TraverseTuple2( + Map[E, T1, func(T2) tuple.Tuple2[T1, T2]], + ApPar[tuple.Tuple2[T1, T2], E, T2], + f1, + f2, + t, + ) + } +} + +// Eitherize3 converts a function with 4 parameters returning a tuple into a function with 3 parameters returning a [IOEither[error, R]] +func Eitherize3[F ~func(T1, T2, T3) (R, error), T1, T2, T3, R any](f F) func(T1, T2, T3) IOEither[error, R] { + return G.Eitherize3[IOEither[error, R]](f) +} + +// Uneitherize3 converts a function with 4 parameters returning a tuple into a function with 3 parameters returning a [IOEither[error, R]] +func Uneitherize3[F ~func(T1, T2, T3) IOEither[error, R], T1, T2, T3, R any](f F) func(T1, T2, T3) (R, error) { + return G.Uneitherize3[IOEither[error, R]](f) +} + +// SequenceT3 converts 3 [IOEither[E, T]] into a [IOEither[E, tuple.Tuple3[T1, T2, T3]]] +func SequenceT3[E, T1, T2, T3 any]( + t1 IOEither[E, T1], + t2 IOEither[E, T2], + t3 IOEither[E, T3], +) IOEither[E, tuple.Tuple3[T1, T2, T3]] { + return apply.SequenceT3( + Map[E, T1, func(T2) func(T3) tuple.Tuple3[T1, T2, T3]], + Ap[func(T3) tuple.Tuple3[T1, T2, T3], E, T2], + Ap[tuple.Tuple3[T1, T2, T3], E, T3], + t1, + t2, + t3, + ) +} + +// SequenceSeqT3 converts 3 [IOEither[E, T]] into a [IOEither[E, tuple.Tuple3[T1, T2, T3]]] +func SequenceSeqT3[E, T1, T2, T3 any]( + t1 IOEither[E, T1], + t2 IOEither[E, T2], + t3 IOEither[E, T3], +) IOEither[E, tuple.Tuple3[T1, T2, T3]] { + return apply.SequenceT3( + Map[E, T1, func(T2) func(T3) tuple.Tuple3[T1, T2, T3]], + ApSeq[func(T3) tuple.Tuple3[T1, T2, T3], E, T2], + ApSeq[tuple.Tuple3[T1, T2, T3], E, T3], + t1, + t2, + t3, + ) +} + +// SequenceParT3 converts 3 [IOEither[E, T]] into a [IOEither[E, tuple.Tuple3[T1, T2, T3]]] +func SequenceParT3[E, T1, T2, T3 any]( + t1 IOEither[E, T1], + t2 IOEither[E, T2], + t3 IOEither[E, T3], +) IOEither[E, tuple.Tuple3[T1, T2, T3]] { + return apply.SequenceT3( + Map[E, T1, func(T2) func(T3) tuple.Tuple3[T1, T2, T3]], + ApPar[func(T3) tuple.Tuple3[T1, T2, T3], E, T2], + ApPar[tuple.Tuple3[T1, T2, T3], E, T3], + t1, + t2, + t3, + ) +} + +// SequenceTuple3 converts a [tuple.Tuple3[IOEither[E, T]]] into a [IOEither[E, tuple.Tuple3[T1, T2, T3]]] +func SequenceTuple3[E, T1, T2, T3 any](t tuple.Tuple3[IOEither[E, T1], IOEither[E, T2], IOEither[E, T3]]) IOEither[E, tuple.Tuple3[T1, T2, T3]] { + return apply.SequenceTuple3( + Map[E, T1, func(T2) func(T3) tuple.Tuple3[T1, T2, T3]], + Ap[func(T3) tuple.Tuple3[T1, T2, T3], E, T2], + Ap[tuple.Tuple3[T1, T2, T3], E, T3], + t, + ) +} + +// SequenceSeqTuple3 converts a [tuple.Tuple3[IOEither[E, T]]] into a [IOEither[E, tuple.Tuple3[T1, T2, T3]]] +func SequenceSeqTuple3[E, T1, T2, T3 any](t tuple.Tuple3[IOEither[E, T1], IOEither[E, T2], IOEither[E, T3]]) IOEither[E, tuple.Tuple3[T1, T2, T3]] { + return apply.SequenceTuple3( + Map[E, T1, func(T2) func(T3) tuple.Tuple3[T1, T2, T3]], + ApSeq[func(T3) tuple.Tuple3[T1, T2, T3], E, T2], + ApSeq[tuple.Tuple3[T1, T2, T3], E, T3], + t, + ) +} + +// SequenceParTuple3 converts a [tuple.Tuple3[IOEither[E, T]]] into a [IOEither[E, tuple.Tuple3[T1, T2, T3]]] +func SequenceParTuple3[E, T1, T2, T3 any](t tuple.Tuple3[IOEither[E, T1], IOEither[E, T2], IOEither[E, T3]]) IOEither[E, tuple.Tuple3[T1, T2, T3]] { + return apply.SequenceTuple3( + Map[E, T1, func(T2) func(T3) tuple.Tuple3[T1, T2, T3]], + ApPar[func(T3) tuple.Tuple3[T1, T2, T3], E, T2], + ApPar[tuple.Tuple3[T1, T2, T3], E, T3], + t, + ) +} + +// TraverseTuple3 converts a [tuple.Tuple3[A1, A2, A3]] into a [IOEither[E, tuple.Tuple3[T1, T2, T3]]] +func TraverseTuple3[E, F1 ~func(A1) IOEither[E, T1], F2 ~func(A2) IOEither[E, T2], F3 ~func(A3) IOEither[E, T3], T1, T2, T3, A1, A2, A3 any](f1 F1, f2 F2, f3 F3) func(tuple.Tuple3[A1, A2, A3]) IOEither[E, tuple.Tuple3[T1, T2, T3]] { + return func(t tuple.Tuple3[A1, A2, A3]) IOEither[E, tuple.Tuple3[T1, T2, T3]] { + return apply.TraverseTuple3( + Map[E, T1, func(T2) func(T3) tuple.Tuple3[T1, T2, T3]], + Ap[func(T3) tuple.Tuple3[T1, T2, T3], E, T2], + Ap[tuple.Tuple3[T1, T2, T3], E, T3], + f1, + f2, + f3, + t, + ) + } +} + +// TraverseSeqTuple3 converts a [tuple.Tuple3[A1, A2, A3]] into a [IOEither[E, tuple.Tuple3[T1, T2, T3]]] +func TraverseSeqTuple3[E, F1 ~func(A1) IOEither[E, T1], F2 ~func(A2) IOEither[E, T2], F3 ~func(A3) IOEither[E, T3], T1, T2, T3, A1, A2, A3 any](f1 F1, f2 F2, f3 F3) func(tuple.Tuple3[A1, A2, A3]) IOEither[E, tuple.Tuple3[T1, T2, T3]] { + return func(t tuple.Tuple3[A1, A2, A3]) IOEither[E, tuple.Tuple3[T1, T2, T3]] { + return apply.TraverseTuple3( + Map[E, T1, func(T2) func(T3) tuple.Tuple3[T1, T2, T3]], + ApSeq[func(T3) tuple.Tuple3[T1, T2, T3], E, T2], + ApSeq[tuple.Tuple3[T1, T2, T3], E, T3], + f1, + f2, + f3, + t, + ) + } +} + +// TraverseParTuple3 converts a [tuple.Tuple3[A1, A2, A3]] into a [IOEither[E, tuple.Tuple3[T1, T2, T3]]] +func TraverseParTuple3[E, F1 ~func(A1) IOEither[E, T1], F2 ~func(A2) IOEither[E, T2], F3 ~func(A3) IOEither[E, T3], T1, T2, T3, A1, A2, A3 any](f1 F1, f2 F2, f3 F3) func(tuple.Tuple3[A1, A2, A3]) IOEither[E, tuple.Tuple3[T1, T2, T3]] { + return func(t tuple.Tuple3[A1, A2, A3]) IOEither[E, tuple.Tuple3[T1, T2, T3]] { + return apply.TraverseTuple3( + Map[E, T1, func(T2) func(T3) tuple.Tuple3[T1, T2, T3]], + ApPar[func(T3) tuple.Tuple3[T1, T2, T3], E, T2], + ApPar[tuple.Tuple3[T1, T2, T3], E, T3], + f1, + f2, + f3, + t, + ) + } +} + +// Eitherize4 converts a function with 5 parameters returning a tuple into a function with 4 parameters returning a [IOEither[error, R]] +func Eitherize4[F ~func(T1, T2, T3, T4) (R, error), T1, T2, T3, T4, R any](f F) func(T1, T2, T3, T4) IOEither[error, R] { + return G.Eitherize4[IOEither[error, R]](f) +} + +// Uneitherize4 converts a function with 5 parameters returning a tuple into a function with 4 parameters returning a [IOEither[error, R]] +func Uneitherize4[F ~func(T1, T2, T3, T4) IOEither[error, R], T1, T2, T3, T4, R any](f F) func(T1, T2, T3, T4) (R, error) { + return G.Uneitherize4[IOEither[error, R]](f) +} + +// SequenceT4 converts 4 [IOEither[E, T]] into a [IOEither[E, tuple.Tuple4[T1, T2, T3, T4]]] +func SequenceT4[E, T1, T2, T3, T4 any]( + t1 IOEither[E, T1], + t2 IOEither[E, T2], + t3 IOEither[E, T3], + t4 IOEither[E, T4], +) IOEither[E, tuple.Tuple4[T1, T2, T3, T4]] { + return apply.SequenceT4( + Map[E, T1, func(T2) func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4]], + Ap[func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4], E, T2], + Ap[func(T4) tuple.Tuple4[T1, T2, T3, T4], E, T3], + Ap[tuple.Tuple4[T1, T2, T3, T4], E, T4], + t1, + t2, + t3, + t4, + ) +} + +// SequenceSeqT4 converts 4 [IOEither[E, T]] into a [IOEither[E, tuple.Tuple4[T1, T2, T3, T4]]] +func SequenceSeqT4[E, T1, T2, T3, T4 any]( + t1 IOEither[E, T1], + t2 IOEither[E, T2], + t3 IOEither[E, T3], + t4 IOEither[E, T4], +) IOEither[E, tuple.Tuple4[T1, T2, T3, T4]] { + return apply.SequenceT4( + Map[E, T1, func(T2) func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4]], + ApSeq[func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4], E, T2], + ApSeq[func(T4) tuple.Tuple4[T1, T2, T3, T4], E, T3], + ApSeq[tuple.Tuple4[T1, T2, T3, T4], E, T4], + t1, + t2, + t3, + t4, + ) +} + +// SequenceParT4 converts 4 [IOEither[E, T]] into a [IOEither[E, tuple.Tuple4[T1, T2, T3, T4]]] +func SequenceParT4[E, T1, T2, T3, T4 any]( + t1 IOEither[E, T1], + t2 IOEither[E, T2], + t3 IOEither[E, T3], + t4 IOEither[E, T4], +) IOEither[E, tuple.Tuple4[T1, T2, T3, T4]] { + return apply.SequenceT4( + Map[E, T1, func(T2) func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4]], + ApPar[func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4], E, T2], + ApPar[func(T4) tuple.Tuple4[T1, T2, T3, T4], E, T3], + ApPar[tuple.Tuple4[T1, T2, T3, T4], E, T4], + t1, + t2, + t3, + t4, + ) +} + +// SequenceTuple4 converts a [tuple.Tuple4[IOEither[E, T]]] into a [IOEither[E, tuple.Tuple4[T1, T2, T3, T4]]] +func SequenceTuple4[E, T1, T2, T3, T4 any](t tuple.Tuple4[IOEither[E, T1], IOEither[E, T2], IOEither[E, T3], IOEither[E, T4]]) IOEither[E, tuple.Tuple4[T1, T2, T3, T4]] { + return apply.SequenceTuple4( + Map[E, T1, func(T2) func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4]], + Ap[func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4], E, T2], + Ap[func(T4) tuple.Tuple4[T1, T2, T3, T4], E, T3], + Ap[tuple.Tuple4[T1, T2, T3, T4], E, T4], + t, + ) +} + +// SequenceSeqTuple4 converts a [tuple.Tuple4[IOEither[E, T]]] into a [IOEither[E, tuple.Tuple4[T1, T2, T3, T4]]] +func SequenceSeqTuple4[E, T1, T2, T3, T4 any](t tuple.Tuple4[IOEither[E, T1], IOEither[E, T2], IOEither[E, T3], IOEither[E, T4]]) IOEither[E, tuple.Tuple4[T1, T2, T3, T4]] { + return apply.SequenceTuple4( + Map[E, T1, func(T2) func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4]], + ApSeq[func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4], E, T2], + ApSeq[func(T4) tuple.Tuple4[T1, T2, T3, T4], E, T3], + ApSeq[tuple.Tuple4[T1, T2, T3, T4], E, T4], + t, + ) +} + +// SequenceParTuple4 converts a [tuple.Tuple4[IOEither[E, T]]] into a [IOEither[E, tuple.Tuple4[T1, T2, T3, T4]]] +func SequenceParTuple4[E, T1, T2, T3, T4 any](t tuple.Tuple4[IOEither[E, T1], IOEither[E, T2], IOEither[E, T3], IOEither[E, T4]]) IOEither[E, tuple.Tuple4[T1, T2, T3, T4]] { + return apply.SequenceTuple4( + Map[E, T1, func(T2) func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4]], + ApPar[func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4], E, T2], + ApPar[func(T4) tuple.Tuple4[T1, T2, T3, T4], E, T3], + ApPar[tuple.Tuple4[T1, T2, T3, T4], E, T4], + t, + ) +} + +// TraverseTuple4 converts a [tuple.Tuple4[A1, A2, A3, A4]] into a [IOEither[E, tuple.Tuple4[T1, T2, T3, T4]]] +func TraverseTuple4[E, F1 ~func(A1) IOEither[E, T1], F2 ~func(A2) IOEither[E, T2], F3 ~func(A3) IOEither[E, T3], F4 ~func(A4) IOEither[E, T4], T1, T2, T3, T4, A1, A2, A3, A4 any](f1 F1, f2 F2, f3 F3, f4 F4) func(tuple.Tuple4[A1, A2, A3, A4]) IOEither[E, tuple.Tuple4[T1, T2, T3, T4]] { + return func(t tuple.Tuple4[A1, A2, A3, A4]) IOEither[E, tuple.Tuple4[T1, T2, T3, T4]] { + return apply.TraverseTuple4( + Map[E, T1, func(T2) func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4]], + Ap[func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4], E, T2], + Ap[func(T4) tuple.Tuple4[T1, T2, T3, T4], E, T3], + Ap[tuple.Tuple4[T1, T2, T3, T4], E, T4], + f1, + f2, + f3, + f4, + t, + ) + } +} + +// TraverseSeqTuple4 converts a [tuple.Tuple4[A1, A2, A3, A4]] into a [IOEither[E, tuple.Tuple4[T1, T2, T3, T4]]] +func TraverseSeqTuple4[E, F1 ~func(A1) IOEither[E, T1], F2 ~func(A2) IOEither[E, T2], F3 ~func(A3) IOEither[E, T3], F4 ~func(A4) IOEither[E, T4], T1, T2, T3, T4, A1, A2, A3, A4 any](f1 F1, f2 F2, f3 F3, f4 F4) func(tuple.Tuple4[A1, A2, A3, A4]) IOEither[E, tuple.Tuple4[T1, T2, T3, T4]] { + return func(t tuple.Tuple4[A1, A2, A3, A4]) IOEither[E, tuple.Tuple4[T1, T2, T3, T4]] { + return apply.TraverseTuple4( + Map[E, T1, func(T2) func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4]], + ApSeq[func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4], E, T2], + ApSeq[func(T4) tuple.Tuple4[T1, T2, T3, T4], E, T3], + ApSeq[tuple.Tuple4[T1, T2, T3, T4], E, T4], + f1, + f2, + f3, + f4, + t, + ) + } +} + +// TraverseParTuple4 converts a [tuple.Tuple4[A1, A2, A3, A4]] into a [IOEither[E, tuple.Tuple4[T1, T2, T3, T4]]] +func TraverseParTuple4[E, F1 ~func(A1) IOEither[E, T1], F2 ~func(A2) IOEither[E, T2], F3 ~func(A3) IOEither[E, T3], F4 ~func(A4) IOEither[E, T4], T1, T2, T3, T4, A1, A2, A3, A4 any](f1 F1, f2 F2, f3 F3, f4 F4) func(tuple.Tuple4[A1, A2, A3, A4]) IOEither[E, tuple.Tuple4[T1, T2, T3, T4]] { + return func(t tuple.Tuple4[A1, A2, A3, A4]) IOEither[E, tuple.Tuple4[T1, T2, T3, T4]] { + return apply.TraverseTuple4( + Map[E, T1, func(T2) func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4]], + ApPar[func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4], E, T2], + ApPar[func(T4) tuple.Tuple4[T1, T2, T3, T4], E, T3], + ApPar[tuple.Tuple4[T1, T2, T3, T4], E, T4], + f1, + f2, + f3, + f4, + t, + ) + } +} + +// Eitherize5 converts a function with 6 parameters returning a tuple into a function with 5 parameters returning a [IOEither[error, R]] +func Eitherize5[F ~func(T1, T2, T3, T4, T5) (R, error), T1, T2, T3, T4, T5, R any](f F) func(T1, T2, T3, T4, T5) IOEither[error, R] { + return G.Eitherize5[IOEither[error, R]](f) +} + +// Uneitherize5 converts a function with 6 parameters returning a tuple into a function with 5 parameters returning a [IOEither[error, R]] +func Uneitherize5[F ~func(T1, T2, T3, T4, T5) IOEither[error, R], T1, T2, T3, T4, T5, R any](f F) func(T1, T2, T3, T4, T5) (R, error) { + return G.Uneitherize5[IOEither[error, R]](f) +} + +// SequenceT5 converts 5 [IOEither[E, T]] into a [IOEither[E, tuple.Tuple5[T1, T2, T3, T4, T5]]] +func SequenceT5[E, T1, T2, T3, T4, T5 any]( + t1 IOEither[E, T1], + t2 IOEither[E, T2], + t3 IOEither[E, T3], + t4 IOEither[E, T4], + t5 IOEither[E, T5], +) IOEither[E, tuple.Tuple5[T1, T2, T3, T4, T5]] { + return apply.SequenceT5( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5]], + Ap[func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], E, T2], + Ap[func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], E, T3], + Ap[func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], E, T4], + Ap[tuple.Tuple5[T1, T2, T3, T4, T5], E, T5], + t1, + t2, + t3, + t4, + t5, + ) +} + +// SequenceSeqT5 converts 5 [IOEither[E, T]] into a [IOEither[E, tuple.Tuple5[T1, T2, T3, T4, T5]]] +func SequenceSeqT5[E, T1, T2, T3, T4, T5 any]( + t1 IOEither[E, T1], + t2 IOEither[E, T2], + t3 IOEither[E, T3], + t4 IOEither[E, T4], + t5 IOEither[E, T5], +) IOEither[E, tuple.Tuple5[T1, T2, T3, T4, T5]] { + return apply.SequenceT5( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5]], + ApSeq[func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], E, T2], + ApSeq[func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], E, T3], + ApSeq[func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], E, T4], + ApSeq[tuple.Tuple5[T1, T2, T3, T4, T5], E, T5], + t1, + t2, + t3, + t4, + t5, + ) +} + +// SequenceParT5 converts 5 [IOEither[E, T]] into a [IOEither[E, tuple.Tuple5[T1, T2, T3, T4, T5]]] +func SequenceParT5[E, T1, T2, T3, T4, T5 any]( + t1 IOEither[E, T1], + t2 IOEither[E, T2], + t3 IOEither[E, T3], + t4 IOEither[E, T4], + t5 IOEither[E, T5], +) IOEither[E, tuple.Tuple5[T1, T2, T3, T4, T5]] { + return apply.SequenceT5( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5]], + ApPar[func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], E, T2], + ApPar[func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], E, T3], + ApPar[func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], E, T4], + ApPar[tuple.Tuple5[T1, T2, T3, T4, T5], E, T5], + t1, + t2, + t3, + t4, + t5, + ) +} + +// SequenceTuple5 converts a [tuple.Tuple5[IOEither[E, T]]] into a [IOEither[E, tuple.Tuple5[T1, T2, T3, T4, T5]]] +func SequenceTuple5[E, T1, T2, T3, T4, T5 any](t tuple.Tuple5[IOEither[E, T1], IOEither[E, T2], IOEither[E, T3], IOEither[E, T4], IOEither[E, T5]]) IOEither[E, tuple.Tuple5[T1, T2, T3, T4, T5]] { + return apply.SequenceTuple5( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5]], + Ap[func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], E, T2], + Ap[func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], E, T3], + Ap[func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], E, T4], + Ap[tuple.Tuple5[T1, T2, T3, T4, T5], E, T5], + t, + ) +} + +// SequenceSeqTuple5 converts a [tuple.Tuple5[IOEither[E, T]]] into a [IOEither[E, tuple.Tuple5[T1, T2, T3, T4, T5]]] +func SequenceSeqTuple5[E, T1, T2, T3, T4, T5 any](t tuple.Tuple5[IOEither[E, T1], IOEither[E, T2], IOEither[E, T3], IOEither[E, T4], IOEither[E, T5]]) IOEither[E, tuple.Tuple5[T1, T2, T3, T4, T5]] { + return apply.SequenceTuple5( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5]], + ApSeq[func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], E, T2], + ApSeq[func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], E, T3], + ApSeq[func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], E, T4], + ApSeq[tuple.Tuple5[T1, T2, T3, T4, T5], E, T5], + t, + ) +} + +// SequenceParTuple5 converts a [tuple.Tuple5[IOEither[E, T]]] into a [IOEither[E, tuple.Tuple5[T1, T2, T3, T4, T5]]] +func SequenceParTuple5[E, T1, T2, T3, T4, T5 any](t tuple.Tuple5[IOEither[E, T1], IOEither[E, T2], IOEither[E, T3], IOEither[E, T4], IOEither[E, T5]]) IOEither[E, tuple.Tuple5[T1, T2, T3, T4, T5]] { + return apply.SequenceTuple5( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5]], + ApPar[func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], E, T2], + ApPar[func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], E, T3], + ApPar[func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], E, T4], + ApPar[tuple.Tuple5[T1, T2, T3, T4, T5], E, T5], + t, + ) +} + +// TraverseTuple5 converts a [tuple.Tuple5[A1, A2, A3, A4, A5]] into a [IOEither[E, tuple.Tuple5[T1, T2, T3, T4, T5]]] +func TraverseTuple5[E, F1 ~func(A1) IOEither[E, T1], F2 ~func(A2) IOEither[E, T2], F3 ~func(A3) IOEither[E, T3], F4 ~func(A4) IOEither[E, T4], F5 ~func(A5) IOEither[E, T5], T1, T2, T3, T4, T5, A1, A2, A3, A4, A5 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5) func(tuple.Tuple5[A1, A2, A3, A4, A5]) IOEither[E, tuple.Tuple5[T1, T2, T3, T4, T5]] { + return func(t tuple.Tuple5[A1, A2, A3, A4, A5]) IOEither[E, tuple.Tuple5[T1, T2, T3, T4, T5]] { + return apply.TraverseTuple5( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5]], + Ap[func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], E, T2], + Ap[func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], E, T3], + Ap[func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], E, T4], + Ap[tuple.Tuple5[T1, T2, T3, T4, T5], E, T5], + f1, + f2, + f3, + f4, + f5, + t, + ) + } +} + +// TraverseSeqTuple5 converts a [tuple.Tuple5[A1, A2, A3, A4, A5]] into a [IOEither[E, tuple.Tuple5[T1, T2, T3, T4, T5]]] +func TraverseSeqTuple5[E, F1 ~func(A1) IOEither[E, T1], F2 ~func(A2) IOEither[E, T2], F3 ~func(A3) IOEither[E, T3], F4 ~func(A4) IOEither[E, T4], F5 ~func(A5) IOEither[E, T5], T1, T2, T3, T4, T5, A1, A2, A3, A4, A5 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5) func(tuple.Tuple5[A1, A2, A3, A4, A5]) IOEither[E, tuple.Tuple5[T1, T2, T3, T4, T5]] { + return func(t tuple.Tuple5[A1, A2, A3, A4, A5]) IOEither[E, tuple.Tuple5[T1, T2, T3, T4, T5]] { + return apply.TraverseTuple5( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5]], + ApSeq[func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], E, T2], + ApSeq[func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], E, T3], + ApSeq[func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], E, T4], + ApSeq[tuple.Tuple5[T1, T2, T3, T4, T5], E, T5], + f1, + f2, + f3, + f4, + f5, + t, + ) + } +} + +// TraverseParTuple5 converts a [tuple.Tuple5[A1, A2, A3, A4, A5]] into a [IOEither[E, tuple.Tuple5[T1, T2, T3, T4, T5]]] +func TraverseParTuple5[E, F1 ~func(A1) IOEither[E, T1], F2 ~func(A2) IOEither[E, T2], F3 ~func(A3) IOEither[E, T3], F4 ~func(A4) IOEither[E, T4], F5 ~func(A5) IOEither[E, T5], T1, T2, T3, T4, T5, A1, A2, A3, A4, A5 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5) func(tuple.Tuple5[A1, A2, A3, A4, A5]) IOEither[E, tuple.Tuple5[T1, T2, T3, T4, T5]] { + return func(t tuple.Tuple5[A1, A2, A3, A4, A5]) IOEither[E, tuple.Tuple5[T1, T2, T3, T4, T5]] { + return apply.TraverseTuple5( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5]], + ApPar[func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], E, T2], + ApPar[func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], E, T3], + ApPar[func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], E, T4], + ApPar[tuple.Tuple5[T1, T2, T3, T4, T5], E, T5], + f1, + f2, + f3, + f4, + f5, + t, + ) + } +} + +// Eitherize6 converts a function with 7 parameters returning a tuple into a function with 6 parameters returning a [IOEither[error, R]] +func Eitherize6[F ~func(T1, T2, T3, T4, T5, T6) (R, error), T1, T2, T3, T4, T5, T6, R any](f F) func(T1, T2, T3, T4, T5, T6) IOEither[error, R] { + return G.Eitherize6[IOEither[error, R]](f) +} + +// Uneitherize6 converts a function with 7 parameters returning a tuple into a function with 6 parameters returning a [IOEither[error, R]] +func Uneitherize6[F ~func(T1, T2, T3, T4, T5, T6) IOEither[error, R], T1, T2, T3, T4, T5, T6, R any](f F) func(T1, T2, T3, T4, T5, T6) (R, error) { + return G.Uneitherize6[IOEither[error, R]](f) +} + +// SequenceT6 converts 6 [IOEither[E, T]] into a [IOEither[E, tuple.Tuple6[T1, T2, T3, T4, T5, T6]]] +func SequenceT6[E, T1, T2, T3, T4, T5, T6 any]( + t1 IOEither[E, T1], + t2 IOEither[E, T2], + t3 IOEither[E, T3], + t4 IOEither[E, T4], + t5 IOEither[E, T5], + t6 IOEither[E, T6], +) IOEither[E, tuple.Tuple6[T1, T2, T3, T4, T5, T6]] { + return apply.SequenceT6( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6]], + Ap[func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], E, T2], + Ap[func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], E, T3], + Ap[func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], E, T4], + Ap[func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], E, T5], + Ap[tuple.Tuple6[T1, T2, T3, T4, T5, T6], E, T6], + t1, + t2, + t3, + t4, + t5, + t6, + ) +} + +// SequenceSeqT6 converts 6 [IOEither[E, T]] into a [IOEither[E, tuple.Tuple6[T1, T2, T3, T4, T5, T6]]] +func SequenceSeqT6[E, T1, T2, T3, T4, T5, T6 any]( + t1 IOEither[E, T1], + t2 IOEither[E, T2], + t3 IOEither[E, T3], + t4 IOEither[E, T4], + t5 IOEither[E, T5], + t6 IOEither[E, T6], +) IOEither[E, tuple.Tuple6[T1, T2, T3, T4, T5, T6]] { + return apply.SequenceT6( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6]], + ApSeq[func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], E, T2], + ApSeq[func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], E, T3], + ApSeq[func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], E, T4], + ApSeq[func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], E, T5], + ApSeq[tuple.Tuple6[T1, T2, T3, T4, T5, T6], E, T6], + t1, + t2, + t3, + t4, + t5, + t6, + ) +} + +// SequenceParT6 converts 6 [IOEither[E, T]] into a [IOEither[E, tuple.Tuple6[T1, T2, T3, T4, T5, T6]]] +func SequenceParT6[E, T1, T2, T3, T4, T5, T6 any]( + t1 IOEither[E, T1], + t2 IOEither[E, T2], + t3 IOEither[E, T3], + t4 IOEither[E, T4], + t5 IOEither[E, T5], + t6 IOEither[E, T6], +) IOEither[E, tuple.Tuple6[T1, T2, T3, T4, T5, T6]] { + return apply.SequenceT6( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6]], + ApPar[func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], E, T2], + ApPar[func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], E, T3], + ApPar[func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], E, T4], + ApPar[func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], E, T5], + ApPar[tuple.Tuple6[T1, T2, T3, T4, T5, T6], E, T6], + t1, + t2, + t3, + t4, + t5, + t6, + ) +} + +// SequenceTuple6 converts a [tuple.Tuple6[IOEither[E, T]]] into a [IOEither[E, tuple.Tuple6[T1, T2, T3, T4, T5, T6]]] +func SequenceTuple6[E, T1, T2, T3, T4, T5, T6 any](t tuple.Tuple6[IOEither[E, T1], IOEither[E, T2], IOEither[E, T3], IOEither[E, T4], IOEither[E, T5], IOEither[E, T6]]) IOEither[E, tuple.Tuple6[T1, T2, T3, T4, T5, T6]] { + return apply.SequenceTuple6( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6]], + Ap[func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], E, T2], + Ap[func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], E, T3], + Ap[func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], E, T4], + Ap[func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], E, T5], + Ap[tuple.Tuple6[T1, T2, T3, T4, T5, T6], E, T6], + t, + ) +} + +// SequenceSeqTuple6 converts a [tuple.Tuple6[IOEither[E, T]]] into a [IOEither[E, tuple.Tuple6[T1, T2, T3, T4, T5, T6]]] +func SequenceSeqTuple6[E, T1, T2, T3, T4, T5, T6 any](t tuple.Tuple6[IOEither[E, T1], IOEither[E, T2], IOEither[E, T3], IOEither[E, T4], IOEither[E, T5], IOEither[E, T6]]) IOEither[E, tuple.Tuple6[T1, T2, T3, T4, T5, T6]] { + return apply.SequenceTuple6( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6]], + ApSeq[func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], E, T2], + ApSeq[func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], E, T3], + ApSeq[func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], E, T4], + ApSeq[func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], E, T5], + ApSeq[tuple.Tuple6[T1, T2, T3, T4, T5, T6], E, T6], + t, + ) +} + +// SequenceParTuple6 converts a [tuple.Tuple6[IOEither[E, T]]] into a [IOEither[E, tuple.Tuple6[T1, T2, T3, T4, T5, T6]]] +func SequenceParTuple6[E, T1, T2, T3, T4, T5, T6 any](t tuple.Tuple6[IOEither[E, T1], IOEither[E, T2], IOEither[E, T3], IOEither[E, T4], IOEither[E, T5], IOEither[E, T6]]) IOEither[E, tuple.Tuple6[T1, T2, T3, T4, T5, T6]] { + return apply.SequenceTuple6( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6]], + ApPar[func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], E, T2], + ApPar[func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], E, T3], + ApPar[func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], E, T4], + ApPar[func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], E, T5], + ApPar[tuple.Tuple6[T1, T2, T3, T4, T5, T6], E, T6], + t, + ) +} + +// TraverseTuple6 converts a [tuple.Tuple6[A1, A2, A3, A4, A5, A6]] into a [IOEither[E, tuple.Tuple6[T1, T2, T3, T4, T5, T6]]] +func TraverseTuple6[E, F1 ~func(A1) IOEither[E, T1], F2 ~func(A2) IOEither[E, T2], F3 ~func(A3) IOEither[E, T3], F4 ~func(A4) IOEither[E, T4], F5 ~func(A5) IOEither[E, T5], F6 ~func(A6) IOEither[E, T6], T1, T2, T3, T4, T5, T6, A1, A2, A3, A4, A5, A6 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6) func(tuple.Tuple6[A1, A2, A3, A4, A5, A6]) IOEither[E, tuple.Tuple6[T1, T2, T3, T4, T5, T6]] { + return func(t tuple.Tuple6[A1, A2, A3, A4, A5, A6]) IOEither[E, tuple.Tuple6[T1, T2, T3, T4, T5, T6]] { + return apply.TraverseTuple6( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6]], + Ap[func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], E, T2], + Ap[func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], E, T3], + Ap[func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], E, T4], + Ap[func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], E, T5], + Ap[tuple.Tuple6[T1, T2, T3, T4, T5, T6], E, T6], + f1, + f2, + f3, + f4, + f5, + f6, + t, + ) + } +} + +// TraverseSeqTuple6 converts a [tuple.Tuple6[A1, A2, A3, A4, A5, A6]] into a [IOEither[E, tuple.Tuple6[T1, T2, T3, T4, T5, T6]]] +func TraverseSeqTuple6[E, F1 ~func(A1) IOEither[E, T1], F2 ~func(A2) IOEither[E, T2], F3 ~func(A3) IOEither[E, T3], F4 ~func(A4) IOEither[E, T4], F5 ~func(A5) IOEither[E, T5], F6 ~func(A6) IOEither[E, T6], T1, T2, T3, T4, T5, T6, A1, A2, A3, A4, A5, A6 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6) func(tuple.Tuple6[A1, A2, A3, A4, A5, A6]) IOEither[E, tuple.Tuple6[T1, T2, T3, T4, T5, T6]] { + return func(t tuple.Tuple6[A1, A2, A3, A4, A5, A6]) IOEither[E, tuple.Tuple6[T1, T2, T3, T4, T5, T6]] { + return apply.TraverseTuple6( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6]], + ApSeq[func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], E, T2], + ApSeq[func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], E, T3], + ApSeq[func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], E, T4], + ApSeq[func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], E, T5], + ApSeq[tuple.Tuple6[T1, T2, T3, T4, T5, T6], E, T6], + f1, + f2, + f3, + f4, + f5, + f6, + t, + ) + } +} + +// TraverseParTuple6 converts a [tuple.Tuple6[A1, A2, A3, A4, A5, A6]] into a [IOEither[E, tuple.Tuple6[T1, T2, T3, T4, T5, T6]]] +func TraverseParTuple6[E, F1 ~func(A1) IOEither[E, T1], F2 ~func(A2) IOEither[E, T2], F3 ~func(A3) IOEither[E, T3], F4 ~func(A4) IOEither[E, T4], F5 ~func(A5) IOEither[E, T5], F6 ~func(A6) IOEither[E, T6], T1, T2, T3, T4, T5, T6, A1, A2, A3, A4, A5, A6 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6) func(tuple.Tuple6[A1, A2, A3, A4, A5, A6]) IOEither[E, tuple.Tuple6[T1, T2, T3, T4, T5, T6]] { + return func(t tuple.Tuple6[A1, A2, A3, A4, A5, A6]) IOEither[E, tuple.Tuple6[T1, T2, T3, T4, T5, T6]] { + return apply.TraverseTuple6( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6]], + ApPar[func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], E, T2], + ApPar[func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], E, T3], + ApPar[func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], E, T4], + ApPar[func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], E, T5], + ApPar[tuple.Tuple6[T1, T2, T3, T4, T5, T6], E, T6], + f1, + f2, + f3, + f4, + f5, + f6, + t, + ) + } +} + +// Eitherize7 converts a function with 8 parameters returning a tuple into a function with 7 parameters returning a [IOEither[error, R]] +func Eitherize7[F ~func(T1, T2, T3, T4, T5, T6, T7) (R, error), T1, T2, T3, T4, T5, T6, T7, R any](f F) func(T1, T2, T3, T4, T5, T6, T7) IOEither[error, R] { + return G.Eitherize7[IOEither[error, R]](f) +} + +// Uneitherize7 converts a function with 8 parameters returning a tuple into a function with 7 parameters returning a [IOEither[error, R]] +func Uneitherize7[F ~func(T1, T2, T3, T4, T5, T6, T7) IOEither[error, R], T1, T2, T3, T4, T5, T6, T7, R any](f F) func(T1, T2, T3, T4, T5, T6, T7) (R, error) { + return G.Uneitherize7[IOEither[error, R]](f) +} + +// SequenceT7 converts 7 [IOEither[E, T]] into a [IOEither[E, tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]]] +func SequenceT7[E, T1, T2, T3, T4, T5, T6, T7 any]( + t1 IOEither[E, T1], + t2 IOEither[E, T2], + t3 IOEither[E, T3], + t4 IOEither[E, T4], + t5 IOEither[E, T5], + t6 IOEither[E, T6], + t7 IOEither[E, T7], +) IOEither[E, tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return apply.SequenceT7( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T2], + Ap[func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T3], + Ap[func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T4], + Ap[func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T5], + Ap[func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T6], + Ap[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T7], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + ) +} + +// SequenceSeqT7 converts 7 [IOEither[E, T]] into a [IOEither[E, tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]]] +func SequenceSeqT7[E, T1, T2, T3, T4, T5, T6, T7 any]( + t1 IOEither[E, T1], + t2 IOEither[E, T2], + t3 IOEither[E, T3], + t4 IOEither[E, T4], + t5 IOEither[E, T5], + t6 IOEither[E, T6], + t7 IOEither[E, T7], +) IOEither[E, tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return apply.SequenceT7( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]], + ApSeq[func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T2], + ApSeq[func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T3], + ApSeq[func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T4], + ApSeq[func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T5], + ApSeq[func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T6], + ApSeq[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T7], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + ) +} + +// SequenceParT7 converts 7 [IOEither[E, T]] into a [IOEither[E, tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]]] +func SequenceParT7[E, T1, T2, T3, T4, T5, T6, T7 any]( + t1 IOEither[E, T1], + t2 IOEither[E, T2], + t3 IOEither[E, T3], + t4 IOEither[E, T4], + t5 IOEither[E, T5], + t6 IOEither[E, T6], + t7 IOEither[E, T7], +) IOEither[E, tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return apply.SequenceT7( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]], + ApPar[func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T2], + ApPar[func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T3], + ApPar[func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T4], + ApPar[func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T5], + ApPar[func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T6], + ApPar[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T7], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + ) +} + +// SequenceTuple7 converts a [tuple.Tuple7[IOEither[E, T]]] into a [IOEither[E, tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]]] +func SequenceTuple7[E, T1, T2, T3, T4, T5, T6, T7 any](t tuple.Tuple7[IOEither[E, T1], IOEither[E, T2], IOEither[E, T3], IOEither[E, T4], IOEither[E, T5], IOEither[E, T6], IOEither[E, T7]]) IOEither[E, tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return apply.SequenceTuple7( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T2], + Ap[func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T3], + Ap[func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T4], + Ap[func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T5], + Ap[func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T6], + Ap[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T7], + t, + ) +} + +// SequenceSeqTuple7 converts a [tuple.Tuple7[IOEither[E, T]]] into a [IOEither[E, tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]]] +func SequenceSeqTuple7[E, T1, T2, T3, T4, T5, T6, T7 any](t tuple.Tuple7[IOEither[E, T1], IOEither[E, T2], IOEither[E, T3], IOEither[E, T4], IOEither[E, T5], IOEither[E, T6], IOEither[E, T7]]) IOEither[E, tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return apply.SequenceTuple7( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]], + ApSeq[func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T2], + ApSeq[func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T3], + ApSeq[func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T4], + ApSeq[func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T5], + ApSeq[func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T6], + ApSeq[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T7], + t, + ) +} + +// SequenceParTuple7 converts a [tuple.Tuple7[IOEither[E, T]]] into a [IOEither[E, tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]]] +func SequenceParTuple7[E, T1, T2, T3, T4, T5, T6, T7 any](t tuple.Tuple7[IOEither[E, T1], IOEither[E, T2], IOEither[E, T3], IOEither[E, T4], IOEither[E, T5], IOEither[E, T6], IOEither[E, T7]]) IOEither[E, tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return apply.SequenceTuple7( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]], + ApPar[func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T2], + ApPar[func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T3], + ApPar[func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T4], + ApPar[func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T5], + ApPar[func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T6], + ApPar[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T7], + t, + ) +} + +// TraverseTuple7 converts a [tuple.Tuple7[A1, A2, A3, A4, A5, A6, A7]] into a [IOEither[E, tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]]] +func TraverseTuple7[E, F1 ~func(A1) IOEither[E, T1], F2 ~func(A2) IOEither[E, T2], F3 ~func(A3) IOEither[E, T3], F4 ~func(A4) IOEither[E, T4], F5 ~func(A5) IOEither[E, T5], F6 ~func(A6) IOEither[E, T6], F7 ~func(A7) IOEither[E, T7], T1, T2, T3, T4, T5, T6, T7, A1, A2, A3, A4, A5, A6, A7 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7) func(tuple.Tuple7[A1, A2, A3, A4, A5, A6, A7]) IOEither[E, tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return func(t tuple.Tuple7[A1, A2, A3, A4, A5, A6, A7]) IOEither[E, tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return apply.TraverseTuple7( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T2], + Ap[func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T3], + Ap[func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T4], + Ap[func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T5], + Ap[func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T6], + Ap[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T7], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + t, + ) + } +} + +// TraverseSeqTuple7 converts a [tuple.Tuple7[A1, A2, A3, A4, A5, A6, A7]] into a [IOEither[E, tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]]] +func TraverseSeqTuple7[E, F1 ~func(A1) IOEither[E, T1], F2 ~func(A2) IOEither[E, T2], F3 ~func(A3) IOEither[E, T3], F4 ~func(A4) IOEither[E, T4], F5 ~func(A5) IOEither[E, T5], F6 ~func(A6) IOEither[E, T6], F7 ~func(A7) IOEither[E, T7], T1, T2, T3, T4, T5, T6, T7, A1, A2, A3, A4, A5, A6, A7 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7) func(tuple.Tuple7[A1, A2, A3, A4, A5, A6, A7]) IOEither[E, tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return func(t tuple.Tuple7[A1, A2, A3, A4, A5, A6, A7]) IOEither[E, tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return apply.TraverseTuple7( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]], + ApSeq[func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T2], + ApSeq[func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T3], + ApSeq[func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T4], + ApSeq[func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T5], + ApSeq[func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T6], + ApSeq[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T7], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + t, + ) + } +} + +// TraverseParTuple7 converts a [tuple.Tuple7[A1, A2, A3, A4, A5, A6, A7]] into a [IOEither[E, tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]]] +func TraverseParTuple7[E, F1 ~func(A1) IOEither[E, T1], F2 ~func(A2) IOEither[E, T2], F3 ~func(A3) IOEither[E, T3], F4 ~func(A4) IOEither[E, T4], F5 ~func(A5) IOEither[E, T5], F6 ~func(A6) IOEither[E, T6], F7 ~func(A7) IOEither[E, T7], T1, T2, T3, T4, T5, T6, T7, A1, A2, A3, A4, A5, A6, A7 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7) func(tuple.Tuple7[A1, A2, A3, A4, A5, A6, A7]) IOEither[E, tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return func(t tuple.Tuple7[A1, A2, A3, A4, A5, A6, A7]) IOEither[E, tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return apply.TraverseTuple7( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]], + ApPar[func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T2], + ApPar[func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T3], + ApPar[func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T4], + ApPar[func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T5], + ApPar[func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T6], + ApPar[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], E, T7], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + t, + ) + } +} + +// Eitherize8 converts a function with 9 parameters returning a tuple into a function with 8 parameters returning a [IOEither[error, R]] +func Eitherize8[F ~func(T1, T2, T3, T4, T5, T6, T7, T8) (R, error), T1, T2, T3, T4, T5, T6, T7, T8, R any](f F) func(T1, T2, T3, T4, T5, T6, T7, T8) IOEither[error, R] { + return G.Eitherize8[IOEither[error, R]](f) +} + +// Uneitherize8 converts a function with 9 parameters returning a tuple into a function with 8 parameters returning a [IOEither[error, R]] +func Uneitherize8[F ~func(T1, T2, T3, T4, T5, T6, T7, T8) IOEither[error, R], T1, T2, T3, T4, T5, T6, T7, T8, R any](f F) func(T1, T2, T3, T4, T5, T6, T7, T8) (R, error) { + return G.Uneitherize8[IOEither[error, R]](f) +} + +// SequenceT8 converts 8 [IOEither[E, T]] into a [IOEither[E, tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]]] +func SequenceT8[E, T1, T2, T3, T4, T5, T6, T7, T8 any]( + t1 IOEither[E, T1], + t2 IOEither[E, T2], + t3 IOEither[E, T3], + t4 IOEither[E, T4], + t5 IOEither[E, T5], + t6 IOEither[E, T6], + t7 IOEither[E, T7], + t8 IOEither[E, T8], +) IOEither[E, tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return apply.SequenceT8( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T3], + Ap[func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T4], + Ap[func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T5], + Ap[func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T6], + Ap[func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T7], + Ap[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T8], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + t8, + ) +} + +// SequenceSeqT8 converts 8 [IOEither[E, T]] into a [IOEither[E, tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]]] +func SequenceSeqT8[E, T1, T2, T3, T4, T5, T6, T7, T8 any]( + t1 IOEither[E, T1], + t2 IOEither[E, T2], + t3 IOEither[E, T3], + t4 IOEither[E, T4], + t5 IOEither[E, T5], + t6 IOEither[E, T6], + t7 IOEither[E, T7], + t8 IOEither[E, T8], +) IOEither[E, tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return apply.SequenceT8( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]], + ApSeq[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T2], + ApSeq[func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T3], + ApSeq[func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T4], + ApSeq[func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T5], + ApSeq[func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T6], + ApSeq[func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T7], + ApSeq[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T8], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + t8, + ) +} + +// SequenceParT8 converts 8 [IOEither[E, T]] into a [IOEither[E, tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]]] +func SequenceParT8[E, T1, T2, T3, T4, T5, T6, T7, T8 any]( + t1 IOEither[E, T1], + t2 IOEither[E, T2], + t3 IOEither[E, T3], + t4 IOEither[E, T4], + t5 IOEither[E, T5], + t6 IOEither[E, T6], + t7 IOEither[E, T7], + t8 IOEither[E, T8], +) IOEither[E, tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return apply.SequenceT8( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]], + ApPar[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T2], + ApPar[func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T3], + ApPar[func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T4], + ApPar[func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T5], + ApPar[func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T6], + ApPar[func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T7], + ApPar[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T8], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + t8, + ) +} + +// SequenceTuple8 converts a [tuple.Tuple8[IOEither[E, T]]] into a [IOEither[E, tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]]] +func SequenceTuple8[E, T1, T2, T3, T4, T5, T6, T7, T8 any](t tuple.Tuple8[IOEither[E, T1], IOEither[E, T2], IOEither[E, T3], IOEither[E, T4], IOEither[E, T5], IOEither[E, T6], IOEither[E, T7], IOEither[E, T8]]) IOEither[E, tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return apply.SequenceTuple8( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T3], + Ap[func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T4], + Ap[func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T5], + Ap[func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T6], + Ap[func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T7], + Ap[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T8], + t, + ) +} + +// SequenceSeqTuple8 converts a [tuple.Tuple8[IOEither[E, T]]] into a [IOEither[E, tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]]] +func SequenceSeqTuple8[E, T1, T2, T3, T4, T5, T6, T7, T8 any](t tuple.Tuple8[IOEither[E, T1], IOEither[E, T2], IOEither[E, T3], IOEither[E, T4], IOEither[E, T5], IOEither[E, T6], IOEither[E, T7], IOEither[E, T8]]) IOEither[E, tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return apply.SequenceTuple8( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]], + ApSeq[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T2], + ApSeq[func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T3], + ApSeq[func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T4], + ApSeq[func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T5], + ApSeq[func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T6], + ApSeq[func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T7], + ApSeq[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T8], + t, + ) +} + +// SequenceParTuple8 converts a [tuple.Tuple8[IOEither[E, T]]] into a [IOEither[E, tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]]] +func SequenceParTuple8[E, T1, T2, T3, T4, T5, T6, T7, T8 any](t tuple.Tuple8[IOEither[E, T1], IOEither[E, T2], IOEither[E, T3], IOEither[E, T4], IOEither[E, T5], IOEither[E, T6], IOEither[E, T7], IOEither[E, T8]]) IOEither[E, tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return apply.SequenceTuple8( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]], + ApPar[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T2], + ApPar[func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T3], + ApPar[func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T4], + ApPar[func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T5], + ApPar[func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T6], + ApPar[func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T7], + ApPar[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T8], + t, + ) +} + +// TraverseTuple8 converts a [tuple.Tuple8[A1, A2, A3, A4, A5, A6, A7, A8]] into a [IOEither[E, tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]]] +func TraverseTuple8[E, F1 ~func(A1) IOEither[E, T1], F2 ~func(A2) IOEither[E, T2], F3 ~func(A3) IOEither[E, T3], F4 ~func(A4) IOEither[E, T4], F5 ~func(A5) IOEither[E, T5], F6 ~func(A6) IOEither[E, T6], F7 ~func(A7) IOEither[E, T7], F8 ~func(A8) IOEither[E, T8], T1, T2, T3, T4, T5, T6, T7, T8, A1, A2, A3, A4, A5, A6, A7, A8 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8) func(tuple.Tuple8[A1, A2, A3, A4, A5, A6, A7, A8]) IOEither[E, tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return func(t tuple.Tuple8[A1, A2, A3, A4, A5, A6, A7, A8]) IOEither[E, tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return apply.TraverseTuple8( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T3], + Ap[func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T4], + Ap[func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T5], + Ap[func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T6], + Ap[func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T7], + Ap[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T8], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + f8, + t, + ) + } +} + +// TraverseSeqTuple8 converts a [tuple.Tuple8[A1, A2, A3, A4, A5, A6, A7, A8]] into a [IOEither[E, tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]]] +func TraverseSeqTuple8[E, F1 ~func(A1) IOEither[E, T1], F2 ~func(A2) IOEither[E, T2], F3 ~func(A3) IOEither[E, T3], F4 ~func(A4) IOEither[E, T4], F5 ~func(A5) IOEither[E, T5], F6 ~func(A6) IOEither[E, T6], F7 ~func(A7) IOEither[E, T7], F8 ~func(A8) IOEither[E, T8], T1, T2, T3, T4, T5, T6, T7, T8, A1, A2, A3, A4, A5, A6, A7, A8 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8) func(tuple.Tuple8[A1, A2, A3, A4, A5, A6, A7, A8]) IOEither[E, tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return func(t tuple.Tuple8[A1, A2, A3, A4, A5, A6, A7, A8]) IOEither[E, tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return apply.TraverseTuple8( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]], + ApSeq[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T2], + ApSeq[func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T3], + ApSeq[func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T4], + ApSeq[func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T5], + ApSeq[func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T6], + ApSeq[func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T7], + ApSeq[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T8], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + f8, + t, + ) + } +} + +// TraverseParTuple8 converts a [tuple.Tuple8[A1, A2, A3, A4, A5, A6, A7, A8]] into a [IOEither[E, tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]]] +func TraverseParTuple8[E, F1 ~func(A1) IOEither[E, T1], F2 ~func(A2) IOEither[E, T2], F3 ~func(A3) IOEither[E, T3], F4 ~func(A4) IOEither[E, T4], F5 ~func(A5) IOEither[E, T5], F6 ~func(A6) IOEither[E, T6], F7 ~func(A7) IOEither[E, T7], F8 ~func(A8) IOEither[E, T8], T1, T2, T3, T4, T5, T6, T7, T8, A1, A2, A3, A4, A5, A6, A7, A8 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8) func(tuple.Tuple8[A1, A2, A3, A4, A5, A6, A7, A8]) IOEither[E, tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return func(t tuple.Tuple8[A1, A2, A3, A4, A5, A6, A7, A8]) IOEither[E, tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return apply.TraverseTuple8( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]], + ApPar[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T2], + ApPar[func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T3], + ApPar[func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T4], + ApPar[func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T5], + ApPar[func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T6], + ApPar[func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T7], + ApPar[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], E, T8], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + f8, + t, + ) + } +} + +// Eitherize9 converts a function with 10 parameters returning a tuple into a function with 9 parameters returning a [IOEither[error, R]] +func Eitherize9[F ~func(T1, T2, T3, T4, T5, T6, T7, T8, T9) (R, error), T1, T2, T3, T4, T5, T6, T7, T8, T9, R any](f F) func(T1, T2, T3, T4, T5, T6, T7, T8, T9) IOEither[error, R] { + return G.Eitherize9[IOEither[error, R]](f) +} + +// Uneitherize9 converts a function with 10 parameters returning a tuple into a function with 9 parameters returning a [IOEither[error, R]] +func Uneitherize9[F ~func(T1, T2, T3, T4, T5, T6, T7, T8, T9) IOEither[error, R], T1, T2, T3, T4, T5, T6, T7, T8, T9, R any](f F) func(T1, T2, T3, T4, T5, T6, T7, T8, T9) (R, error) { + return G.Uneitherize9[IOEither[error, R]](f) +} + +// SequenceT9 converts 9 [IOEither[E, T]] into a [IOEither[E, tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]]] +func SequenceT9[E, T1, T2, T3, T4, T5, T6, T7, T8, T9 any]( + t1 IOEither[E, T1], + t2 IOEither[E, T2], + t3 IOEither[E, T3], + t4 IOEither[E, T4], + t5 IOEither[E, T5], + t6 IOEither[E, T6], + t7 IOEither[E, T7], + t8 IOEither[E, T8], + t9 IOEither[E, T9], +) IOEither[E, tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return apply.SequenceT9( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T3], + Ap[func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T4], + Ap[func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T5], + Ap[func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T6], + Ap[func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T7], + Ap[func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T8], + Ap[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T9], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + t8, + t9, + ) +} + +// SequenceSeqT9 converts 9 [IOEither[E, T]] into a [IOEither[E, tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]]] +func SequenceSeqT9[E, T1, T2, T3, T4, T5, T6, T7, T8, T9 any]( + t1 IOEither[E, T1], + t2 IOEither[E, T2], + t3 IOEither[E, T3], + t4 IOEither[E, T4], + t5 IOEither[E, T5], + t6 IOEither[E, T6], + t7 IOEither[E, T7], + t8 IOEither[E, T8], + t9 IOEither[E, T9], +) IOEither[E, tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return apply.SequenceT9( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]], + ApSeq[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T2], + ApSeq[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T3], + ApSeq[func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T4], + ApSeq[func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T5], + ApSeq[func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T6], + ApSeq[func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T7], + ApSeq[func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T8], + ApSeq[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T9], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + t8, + t9, + ) +} + +// SequenceParT9 converts 9 [IOEither[E, T]] into a [IOEither[E, tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]]] +func SequenceParT9[E, T1, T2, T3, T4, T5, T6, T7, T8, T9 any]( + t1 IOEither[E, T1], + t2 IOEither[E, T2], + t3 IOEither[E, T3], + t4 IOEither[E, T4], + t5 IOEither[E, T5], + t6 IOEither[E, T6], + t7 IOEither[E, T7], + t8 IOEither[E, T8], + t9 IOEither[E, T9], +) IOEither[E, tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return apply.SequenceT9( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]], + ApPar[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T2], + ApPar[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T3], + ApPar[func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T4], + ApPar[func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T5], + ApPar[func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T6], + ApPar[func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T7], + ApPar[func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T8], + ApPar[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T9], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + t8, + t9, + ) +} + +// SequenceTuple9 converts a [tuple.Tuple9[IOEither[E, T]]] into a [IOEither[E, tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]]] +func SequenceTuple9[E, T1, T2, T3, T4, T5, T6, T7, T8, T9 any](t tuple.Tuple9[IOEither[E, T1], IOEither[E, T2], IOEither[E, T3], IOEither[E, T4], IOEither[E, T5], IOEither[E, T6], IOEither[E, T7], IOEither[E, T8], IOEither[E, T9]]) IOEither[E, tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return apply.SequenceTuple9( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T3], + Ap[func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T4], + Ap[func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T5], + Ap[func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T6], + Ap[func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T7], + Ap[func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T8], + Ap[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T9], + t, + ) +} + +// SequenceSeqTuple9 converts a [tuple.Tuple9[IOEither[E, T]]] into a [IOEither[E, tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]]] +func SequenceSeqTuple9[E, T1, T2, T3, T4, T5, T6, T7, T8, T9 any](t tuple.Tuple9[IOEither[E, T1], IOEither[E, T2], IOEither[E, T3], IOEither[E, T4], IOEither[E, T5], IOEither[E, T6], IOEither[E, T7], IOEither[E, T8], IOEither[E, T9]]) IOEither[E, tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return apply.SequenceTuple9( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]], + ApSeq[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T2], + ApSeq[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T3], + ApSeq[func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T4], + ApSeq[func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T5], + ApSeq[func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T6], + ApSeq[func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T7], + ApSeq[func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T8], + ApSeq[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T9], + t, + ) +} + +// SequenceParTuple9 converts a [tuple.Tuple9[IOEither[E, T]]] into a [IOEither[E, tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]]] +func SequenceParTuple9[E, T1, T2, T3, T4, T5, T6, T7, T8, T9 any](t tuple.Tuple9[IOEither[E, T1], IOEither[E, T2], IOEither[E, T3], IOEither[E, T4], IOEither[E, T5], IOEither[E, T6], IOEither[E, T7], IOEither[E, T8], IOEither[E, T9]]) IOEither[E, tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return apply.SequenceTuple9( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]], + ApPar[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T2], + ApPar[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T3], + ApPar[func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T4], + ApPar[func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T5], + ApPar[func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T6], + ApPar[func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T7], + ApPar[func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T8], + ApPar[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T9], + t, + ) +} + +// TraverseTuple9 converts a [tuple.Tuple9[A1, A2, A3, A4, A5, A6, A7, A8, A9]] into a [IOEither[E, tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]]] +func TraverseTuple9[E, F1 ~func(A1) IOEither[E, T1], F2 ~func(A2) IOEither[E, T2], F3 ~func(A3) IOEither[E, T3], F4 ~func(A4) IOEither[E, T4], F5 ~func(A5) IOEither[E, T5], F6 ~func(A6) IOEither[E, T6], F7 ~func(A7) IOEither[E, T7], F8 ~func(A8) IOEither[E, T8], F9 ~func(A9) IOEither[E, T9], T1, T2, T3, T4, T5, T6, T7, T8, T9, A1, A2, A3, A4, A5, A6, A7, A8, A9 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9) func(tuple.Tuple9[A1, A2, A3, A4, A5, A6, A7, A8, A9]) IOEither[E, tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return func(t tuple.Tuple9[A1, A2, A3, A4, A5, A6, A7, A8, A9]) IOEither[E, tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return apply.TraverseTuple9( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T3], + Ap[func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T4], + Ap[func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T5], + Ap[func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T6], + Ap[func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T7], + Ap[func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T8], + Ap[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T9], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + f8, + f9, + t, + ) + } +} + +// TraverseSeqTuple9 converts a [tuple.Tuple9[A1, A2, A3, A4, A5, A6, A7, A8, A9]] into a [IOEither[E, tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]]] +func TraverseSeqTuple9[E, F1 ~func(A1) IOEither[E, T1], F2 ~func(A2) IOEither[E, T2], F3 ~func(A3) IOEither[E, T3], F4 ~func(A4) IOEither[E, T4], F5 ~func(A5) IOEither[E, T5], F6 ~func(A6) IOEither[E, T6], F7 ~func(A7) IOEither[E, T7], F8 ~func(A8) IOEither[E, T8], F9 ~func(A9) IOEither[E, T9], T1, T2, T3, T4, T5, T6, T7, T8, T9, A1, A2, A3, A4, A5, A6, A7, A8, A9 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9) func(tuple.Tuple9[A1, A2, A3, A4, A5, A6, A7, A8, A9]) IOEither[E, tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return func(t tuple.Tuple9[A1, A2, A3, A4, A5, A6, A7, A8, A9]) IOEither[E, tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return apply.TraverseTuple9( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]], + ApSeq[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T2], + ApSeq[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T3], + ApSeq[func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T4], + ApSeq[func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T5], + ApSeq[func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T6], + ApSeq[func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T7], + ApSeq[func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T8], + ApSeq[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T9], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + f8, + f9, + t, + ) + } +} + +// TraverseParTuple9 converts a [tuple.Tuple9[A1, A2, A3, A4, A5, A6, A7, A8, A9]] into a [IOEither[E, tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]]] +func TraverseParTuple9[E, F1 ~func(A1) IOEither[E, T1], F2 ~func(A2) IOEither[E, T2], F3 ~func(A3) IOEither[E, T3], F4 ~func(A4) IOEither[E, T4], F5 ~func(A5) IOEither[E, T5], F6 ~func(A6) IOEither[E, T6], F7 ~func(A7) IOEither[E, T7], F8 ~func(A8) IOEither[E, T8], F9 ~func(A9) IOEither[E, T9], T1, T2, T3, T4, T5, T6, T7, T8, T9, A1, A2, A3, A4, A5, A6, A7, A8, A9 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9) func(tuple.Tuple9[A1, A2, A3, A4, A5, A6, A7, A8, A9]) IOEither[E, tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return func(t tuple.Tuple9[A1, A2, A3, A4, A5, A6, A7, A8, A9]) IOEither[E, tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return apply.TraverseTuple9( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]], + ApPar[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T2], + ApPar[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T3], + ApPar[func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T4], + ApPar[func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T5], + ApPar[func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T6], + ApPar[func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T7], + ApPar[func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T8], + ApPar[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], E, T9], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + f8, + f9, + t, + ) + } +} + +// Eitherize10 converts a function with 11 parameters returning a tuple into a function with 10 parameters returning a [IOEither[error, R]] +func Eitherize10[F ~func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) (R, error), T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, R any](f F) func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) IOEither[error, R] { + return G.Eitherize10[IOEither[error, R]](f) +} + +// Uneitherize10 converts a function with 11 parameters returning a tuple into a function with 10 parameters returning a [IOEither[error, R]] +func Uneitherize10[F ~func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) IOEither[error, R], T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, R any](f F) func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) (R, error) { + return G.Uneitherize10[IOEither[error, R]](f) +} + +// SequenceT10 converts 10 [IOEither[E, T]] into a [IOEither[E, tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]]] +func SequenceT10[E, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any]( + t1 IOEither[E, T1], + t2 IOEither[E, T2], + t3 IOEither[E, T3], + t4 IOEither[E, T4], + t5 IOEither[E, T5], + t6 IOEither[E, T6], + t7 IOEither[E, T7], + t8 IOEither[E, T8], + t9 IOEither[E, T9], + t10 IOEither[E, T10], +) IOEither[E, tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return apply.SequenceT10( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T3], + Ap[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T4], + Ap[func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T5], + Ap[func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T6], + Ap[func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T7], + Ap[func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T8], + Ap[func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T9], + Ap[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T10], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + t8, + t9, + t10, + ) +} + +// SequenceSeqT10 converts 10 [IOEither[E, T]] into a [IOEither[E, tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]]] +func SequenceSeqT10[E, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any]( + t1 IOEither[E, T1], + t2 IOEither[E, T2], + t3 IOEither[E, T3], + t4 IOEither[E, T4], + t5 IOEither[E, T5], + t6 IOEither[E, T6], + t7 IOEither[E, T7], + t8 IOEither[E, T8], + t9 IOEither[E, T9], + t10 IOEither[E, T10], +) IOEither[E, tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return apply.SequenceT10( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]], + ApSeq[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T2], + ApSeq[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T3], + ApSeq[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T4], + ApSeq[func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T5], + ApSeq[func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T6], + ApSeq[func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T7], + ApSeq[func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T8], + ApSeq[func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T9], + ApSeq[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T10], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + t8, + t9, + t10, + ) +} + +// SequenceParT10 converts 10 [IOEither[E, T]] into a [IOEither[E, tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]]] +func SequenceParT10[E, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any]( + t1 IOEither[E, T1], + t2 IOEither[E, T2], + t3 IOEither[E, T3], + t4 IOEither[E, T4], + t5 IOEither[E, T5], + t6 IOEither[E, T6], + t7 IOEither[E, T7], + t8 IOEither[E, T8], + t9 IOEither[E, T9], + t10 IOEither[E, T10], +) IOEither[E, tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return apply.SequenceT10( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]], + ApPar[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T2], + ApPar[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T3], + ApPar[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T4], + ApPar[func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T5], + ApPar[func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T6], + ApPar[func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T7], + ApPar[func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T8], + ApPar[func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T9], + ApPar[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T10], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + t8, + t9, + t10, + ) +} + +// SequenceTuple10 converts a [tuple.Tuple10[IOEither[E, T]]] into a [IOEither[E, tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]]] +func SequenceTuple10[E, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any](t tuple.Tuple10[IOEither[E, T1], IOEither[E, T2], IOEither[E, T3], IOEither[E, T4], IOEither[E, T5], IOEither[E, T6], IOEither[E, T7], IOEither[E, T8], IOEither[E, T9], IOEither[E, T10]]) IOEither[E, tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return apply.SequenceTuple10( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T3], + Ap[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T4], + Ap[func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T5], + Ap[func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T6], + Ap[func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T7], + Ap[func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T8], + Ap[func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T9], + Ap[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T10], + t, + ) +} + +// SequenceSeqTuple10 converts a [tuple.Tuple10[IOEither[E, T]]] into a [IOEither[E, tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]]] +func SequenceSeqTuple10[E, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any](t tuple.Tuple10[IOEither[E, T1], IOEither[E, T2], IOEither[E, T3], IOEither[E, T4], IOEither[E, T5], IOEither[E, T6], IOEither[E, T7], IOEither[E, T8], IOEither[E, T9], IOEither[E, T10]]) IOEither[E, tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return apply.SequenceTuple10( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]], + ApSeq[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T2], + ApSeq[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T3], + ApSeq[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T4], + ApSeq[func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T5], + ApSeq[func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T6], + ApSeq[func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T7], + ApSeq[func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T8], + ApSeq[func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T9], + ApSeq[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T10], + t, + ) +} + +// SequenceParTuple10 converts a [tuple.Tuple10[IOEither[E, T]]] into a [IOEither[E, tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]]] +func SequenceParTuple10[E, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any](t tuple.Tuple10[IOEither[E, T1], IOEither[E, T2], IOEither[E, T3], IOEither[E, T4], IOEither[E, T5], IOEither[E, T6], IOEither[E, T7], IOEither[E, T8], IOEither[E, T9], IOEither[E, T10]]) IOEither[E, tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return apply.SequenceTuple10( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]], + ApPar[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T2], + ApPar[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T3], + ApPar[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T4], + ApPar[func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T5], + ApPar[func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T6], + ApPar[func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T7], + ApPar[func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T8], + ApPar[func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T9], + ApPar[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T10], + t, + ) +} + +// TraverseTuple10 converts a [tuple.Tuple10[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10]] into a [IOEither[E, tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]]] +func TraverseTuple10[E, F1 ~func(A1) IOEither[E, T1], F2 ~func(A2) IOEither[E, T2], F3 ~func(A3) IOEither[E, T3], F4 ~func(A4) IOEither[E, T4], F5 ~func(A5) IOEither[E, T5], F6 ~func(A6) IOEither[E, T6], F7 ~func(A7) IOEither[E, T7], F8 ~func(A8) IOEither[E, T8], F9 ~func(A9) IOEither[E, T9], F10 ~func(A10) IOEither[E, T10], T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10) func(tuple.Tuple10[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10]) IOEither[E, tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return func(t tuple.Tuple10[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10]) IOEither[E, tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return apply.TraverseTuple10( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T3], + Ap[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T4], + Ap[func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T5], + Ap[func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T6], + Ap[func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T7], + Ap[func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T8], + Ap[func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T9], + Ap[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T10], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + f8, + f9, + f10, + t, + ) + } +} + +// TraverseSeqTuple10 converts a [tuple.Tuple10[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10]] into a [IOEither[E, tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]]] +func TraverseSeqTuple10[E, F1 ~func(A1) IOEither[E, T1], F2 ~func(A2) IOEither[E, T2], F3 ~func(A3) IOEither[E, T3], F4 ~func(A4) IOEither[E, T4], F5 ~func(A5) IOEither[E, T5], F6 ~func(A6) IOEither[E, T6], F7 ~func(A7) IOEither[E, T7], F8 ~func(A8) IOEither[E, T8], F9 ~func(A9) IOEither[E, T9], F10 ~func(A10) IOEither[E, T10], T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10) func(tuple.Tuple10[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10]) IOEither[E, tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return func(t tuple.Tuple10[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10]) IOEither[E, tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return apply.TraverseTuple10( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]], + ApSeq[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T2], + ApSeq[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T3], + ApSeq[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T4], + ApSeq[func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T5], + ApSeq[func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T6], + ApSeq[func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T7], + ApSeq[func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T8], + ApSeq[func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T9], + ApSeq[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T10], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + f8, + f9, + f10, + t, + ) + } +} + +// TraverseParTuple10 converts a [tuple.Tuple10[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10]] into a [IOEither[E, tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]]] +func TraverseParTuple10[E, F1 ~func(A1) IOEither[E, T1], F2 ~func(A2) IOEither[E, T2], F3 ~func(A3) IOEither[E, T3], F4 ~func(A4) IOEither[E, T4], F5 ~func(A5) IOEither[E, T5], F6 ~func(A6) IOEither[E, T6], F7 ~func(A7) IOEither[E, T7], F8 ~func(A8) IOEither[E, T8], F9 ~func(A9) IOEither[E, T9], F10 ~func(A10) IOEither[E, T10], T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10) func(tuple.Tuple10[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10]) IOEither[E, tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return func(t tuple.Tuple10[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10]) IOEither[E, tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return apply.TraverseTuple10( + Map[E, T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]], + ApPar[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T2], + ApPar[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T3], + ApPar[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T4], + ApPar[func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T5], + ApPar[func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T6], + ApPar[func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T7], + ApPar[func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T8], + ApPar[func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T9], + ApPar[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], E, T10], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + f8, + f9, + f10, + t, + ) + } +} diff --git a/v2/ioeither/generic/gen.go b/v2/ioeither/generic/gen.go new file mode 100644 index 0000000..c26d347 --- /dev/null +++ b/v2/ioeither/generic/gen.go @@ -0,0 +1,185 @@ +// Code generated by go generate; DO NOT EDIT. +// This file was generated by robots at +// 2025-03-09 23:53:07.5979849 +0100 CET m=+0.009458901 +package generic + + +import ( + ET "github.com/IBM/fp-go/v2/either" +) + +// Eitherize0 converts a function with 0 parameters returning a tuple into a function with 0 parameters returning a [GIOA] +func Eitherize0[GIOA ~func() ET.Either[error, R], F ~func() (R, error), R any](f F) func() GIOA { + e := ET.Eitherize0(f) + return func() GIOA { + return func() ET.Either[error, R] { + return e() + }} +} + +// Uneitherize0 converts a function with 0 parameters returning a tuple into a function with 0 parameters returning a [GIOA] +func Uneitherize0[GIOA ~func() ET.Either[error, R], GTA ~func() GIOA, R any](f GTA) func() (R, error) { + return func() (R, error) { + return ET.Unwrap(f()()) + } +} + +// Eitherize1 converts a function with 1 parameters returning a tuple into a function with 1 parameters returning a [GIOA] +func Eitherize1[GIOA ~func() ET.Either[error, R], F ~func(T1) (R, error), T1, R any](f F) func(T1) GIOA { + e := ET.Eitherize1(f) + return func(t1 T1) GIOA { + return func() ET.Either[error, R] { + return e(t1) + }} +} + +// Uneitherize1 converts a function with 1 parameters returning a tuple into a function with 1 parameters returning a [GIOA] +func Uneitherize1[GIOA ~func() ET.Either[error, R], GTA ~func(T1) GIOA, T1, R any](f GTA) func(T1) (R, error) { + return func(t1 T1) (R, error) { + return ET.Unwrap(f(t1)()) + } +} + +// Eitherize2 converts a function with 2 parameters returning a tuple into a function with 2 parameters returning a [GIOA] +func Eitherize2[GIOA ~func() ET.Either[error, R], F ~func(T1, T2) (R, error), T1, T2, R any](f F) func(T1, T2) GIOA { + e := ET.Eitherize2(f) + return func(t1 T1, t2 T2) GIOA { + return func() ET.Either[error, R] { + return e(t1, t2) + }} +} + +// Uneitherize2 converts a function with 2 parameters returning a tuple into a function with 2 parameters returning a [GIOA] +func Uneitherize2[GIOA ~func() ET.Either[error, R], GTA ~func(T1, T2) GIOA, T1, T2, R any](f GTA) func(T1, T2) (R, error) { + return func(t1 T1, t2 T2) (R, error) { + return ET.Unwrap(f(t1, t2)()) + } +} + +// Eitherize3 converts a function with 3 parameters returning a tuple into a function with 3 parameters returning a [GIOA] +func Eitherize3[GIOA ~func() ET.Either[error, R], F ~func(T1, T2, T3) (R, error), T1, T2, T3, R any](f F) func(T1, T2, T3) GIOA { + e := ET.Eitherize3(f) + return func(t1 T1, t2 T2, t3 T3) GIOA { + return func() ET.Either[error, R] { + return e(t1, t2, t3) + }} +} + +// Uneitherize3 converts a function with 3 parameters returning a tuple into a function with 3 parameters returning a [GIOA] +func Uneitherize3[GIOA ~func() ET.Either[error, R], GTA ~func(T1, T2, T3) GIOA, T1, T2, T3, R any](f GTA) func(T1, T2, T3) (R, error) { + return func(t1 T1, t2 T2, t3 T3) (R, error) { + return ET.Unwrap(f(t1, t2, t3)()) + } +} + +// Eitherize4 converts a function with 4 parameters returning a tuple into a function with 4 parameters returning a [GIOA] +func Eitherize4[GIOA ~func() ET.Either[error, R], F ~func(T1, T2, T3, T4) (R, error), T1, T2, T3, T4, R any](f F) func(T1, T2, T3, T4) GIOA { + e := ET.Eitherize4(f) + return func(t1 T1, t2 T2, t3 T3, t4 T4) GIOA { + return func() ET.Either[error, R] { + return e(t1, t2, t3, t4) + }} +} + +// Uneitherize4 converts a function with 4 parameters returning a tuple into a function with 4 parameters returning a [GIOA] +func Uneitherize4[GIOA ~func() ET.Either[error, R], GTA ~func(T1, T2, T3, T4) GIOA, T1, T2, T3, T4, R any](f GTA) func(T1, T2, T3, T4) (R, error) { + return func(t1 T1, t2 T2, t3 T3, t4 T4) (R, error) { + return ET.Unwrap(f(t1, t2, t3, t4)()) + } +} + +// Eitherize5 converts a function with 5 parameters returning a tuple into a function with 5 parameters returning a [GIOA] +func Eitherize5[GIOA ~func() ET.Either[error, R], F ~func(T1, T2, T3, T4, T5) (R, error), T1, T2, T3, T4, T5, R any](f F) func(T1, T2, T3, T4, T5) GIOA { + e := ET.Eitherize5(f) + return func(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5) GIOA { + return func() ET.Either[error, R] { + return e(t1, t2, t3, t4, t5) + }} +} + +// Uneitherize5 converts a function with 5 parameters returning a tuple into a function with 5 parameters returning a [GIOA] +func Uneitherize5[GIOA ~func() ET.Either[error, R], GTA ~func(T1, T2, T3, T4, T5) GIOA, T1, T2, T3, T4, T5, R any](f GTA) func(T1, T2, T3, T4, T5) (R, error) { + return func(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5) (R, error) { + return ET.Unwrap(f(t1, t2, t3, t4, t5)()) + } +} + +// Eitherize6 converts a function with 6 parameters returning a tuple into a function with 6 parameters returning a [GIOA] +func Eitherize6[GIOA ~func() ET.Either[error, R], F ~func(T1, T2, T3, T4, T5, T6) (R, error), T1, T2, T3, T4, T5, T6, R any](f F) func(T1, T2, T3, T4, T5, T6) GIOA { + e := ET.Eitherize6(f) + return func(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6) GIOA { + return func() ET.Either[error, R] { + return e(t1, t2, t3, t4, t5, t6) + }} +} + +// Uneitherize6 converts a function with 6 parameters returning a tuple into a function with 6 parameters returning a [GIOA] +func Uneitherize6[GIOA ~func() ET.Either[error, R], GTA ~func(T1, T2, T3, T4, T5, T6) GIOA, T1, T2, T3, T4, T5, T6, R any](f GTA) func(T1, T2, T3, T4, T5, T6) (R, error) { + return func(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6) (R, error) { + return ET.Unwrap(f(t1, t2, t3, t4, t5, t6)()) + } +} + +// Eitherize7 converts a function with 7 parameters returning a tuple into a function with 7 parameters returning a [GIOA] +func Eitherize7[GIOA ~func() ET.Either[error, R], F ~func(T1, T2, T3, T4, T5, T6, T7) (R, error), T1, T2, T3, T4, T5, T6, T7, R any](f F) func(T1, T2, T3, T4, T5, T6, T7) GIOA { + e := ET.Eitherize7(f) + return func(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7) GIOA { + return func() ET.Either[error, R] { + return e(t1, t2, t3, t4, t5, t6, t7) + }} +} + +// Uneitherize7 converts a function with 7 parameters returning a tuple into a function with 7 parameters returning a [GIOA] +func Uneitherize7[GIOA ~func() ET.Either[error, R], GTA ~func(T1, T2, T3, T4, T5, T6, T7) GIOA, T1, T2, T3, T4, T5, T6, T7, R any](f GTA) func(T1, T2, T3, T4, T5, T6, T7) (R, error) { + return func(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7) (R, error) { + return ET.Unwrap(f(t1, t2, t3, t4, t5, t6, t7)()) + } +} + +// Eitherize8 converts a function with 8 parameters returning a tuple into a function with 8 parameters returning a [GIOA] +func Eitherize8[GIOA ~func() ET.Either[error, R], F ~func(T1, T2, T3, T4, T5, T6, T7, T8) (R, error), T1, T2, T3, T4, T5, T6, T7, T8, R any](f F) func(T1, T2, T3, T4, T5, T6, T7, T8) GIOA { + e := ET.Eitherize8(f) + return func(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8) GIOA { + return func() ET.Either[error, R] { + return e(t1, t2, t3, t4, t5, t6, t7, t8) + }} +} + +// Uneitherize8 converts a function with 8 parameters returning a tuple into a function with 8 parameters returning a [GIOA] +func Uneitherize8[GIOA ~func() ET.Either[error, R], GTA ~func(T1, T2, T3, T4, T5, T6, T7, T8) GIOA, T1, T2, T3, T4, T5, T6, T7, T8, R any](f GTA) func(T1, T2, T3, T4, T5, T6, T7, T8) (R, error) { + return func(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8) (R, error) { + return ET.Unwrap(f(t1, t2, t3, t4, t5, t6, t7, t8)()) + } +} + +// Eitherize9 converts a function with 9 parameters returning a tuple into a function with 9 parameters returning a [GIOA] +func Eitherize9[GIOA ~func() ET.Either[error, R], F ~func(T1, T2, T3, T4, T5, T6, T7, T8, T9) (R, error), T1, T2, T3, T4, T5, T6, T7, T8, T9, R any](f F) func(T1, T2, T3, T4, T5, T6, T7, T8, T9) GIOA { + e := ET.Eitherize9(f) + return func(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9) GIOA { + return func() ET.Either[error, R] { + return e(t1, t2, t3, t4, t5, t6, t7, t8, t9) + }} +} + +// Uneitherize9 converts a function with 9 parameters returning a tuple into a function with 9 parameters returning a [GIOA] +func Uneitherize9[GIOA ~func() ET.Either[error, R], GTA ~func(T1, T2, T3, T4, T5, T6, T7, T8, T9) GIOA, T1, T2, T3, T4, T5, T6, T7, T8, T9, R any](f GTA) func(T1, T2, T3, T4, T5, T6, T7, T8, T9) (R, error) { + return func(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9) (R, error) { + return ET.Unwrap(f(t1, t2, t3, t4, t5, t6, t7, t8, t9)()) + } +} + +// Eitherize10 converts a function with 10 parameters returning a tuple into a function with 10 parameters returning a [GIOA] +func Eitherize10[GIOA ~func() ET.Either[error, R], F ~func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) (R, error), T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, R any](f F) func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) GIOA { + e := ET.Eitherize10(f) + return func(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10) GIOA { + return func() ET.Either[error, R] { + return e(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10) + }} +} + +// Uneitherize10 converts a function with 10 parameters returning a tuple into a function with 10 parameters returning a [GIOA] +func Uneitherize10[GIOA ~func() ET.Either[error, R], GTA ~func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) GIOA, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, R any](f GTA) func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) (R, error) { + return func(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10) (R, error) { + return ET.Unwrap(f(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10)()) + } +} diff --git a/v2/ioeither/generic/ioeither.go b/v2/ioeither/generic/ioeither.go new file mode 100644 index 0000000..7ccecf7 --- /dev/null +++ b/v2/ioeither/generic/ioeither.go @@ -0,0 +1,437 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + "time" + + "github.com/IBM/fp-go/v2/either" + F "github.com/IBM/fp-go/v2/function" + C "github.com/IBM/fp-go/v2/internal/chain" + "github.com/IBM/fp-go/v2/internal/eithert" + FE "github.com/IBM/fp-go/v2/internal/fromeither" + FI "github.com/IBM/fp-go/v2/internal/fromio" + FC "github.com/IBM/fp-go/v2/internal/functor" + IO "github.com/IBM/fp-go/v2/io/generic" + O "github.com/IBM/fp-go/v2/option" +) + +// type IOEither[E, A any] = func() Either[E, A] + +// Deprecated: +func MakeIO[GA ~func() either.Either[E, A], E, A any](f GA) GA { + return f +} + +// Deprecated: +func Left[GA ~func() either.Either[E, A], E, A any](l E) GA { + return MakeIO(eithert.Left(IO.MonadOf[GA, either.Either[E, A]], l)) +} + +// Deprecated: +func Right[GA ~func() either.Either[E, A], E, A any](r A) GA { + return MakeIO(eithert.Right(IO.MonadOf[GA, either.Either[E, A]], r)) +} + +// Deprecated: +func Of[GA ~func() either.Either[E, A], E, A any](r A) GA { + return Right[GA](r) +} + +// Deprecated: +func MonadOf[GA ~func() either.Either[E, A], E, A any](r A) GA { + return Of[GA](r) +} + +// Deprecated: +func LeftIO[GA ~func() either.Either[E, A], GE ~func() E, E, A any](ml GE) GA { + return MakeIO(eithert.LeftF(IO.MonadMap[GE, GA, E, either.Either[E, A]], ml)) +} + +// Deprecated: +func RightIO[GA ~func() either.Either[E, A], GR ~func() A, E, A any](mr GR) GA { + return MakeIO(eithert.RightF(IO.MonadMap[GR, GA, A, either.Either[E, A]], mr)) +} + +func FromEither[GA ~func() either.Either[E, A], E, A any](e either.Either[E, A]) GA { + return IO.Of[GA](e) +} + +func FromOption[GA ~func() either.Either[E, A], E, A any](onNone func() E) func(o O.Option[A]) GA { + return FE.FromOption( + FromEither[GA, E, A], + onNone, + ) +} + +func ChainOptionK[GA ~func() either.Either[E, A], GB ~func() either.Either[E, B], E, A, B any](onNone func() E) func(func(A) O.Option[B]) func(GA) GB { + return FE.ChainOptionK( + MonadChain[GA, GB, E, A, B], + FromEither[GB, E, B], + onNone, + ) +} + +// Deprecated: +func FromIO[GA ~func() either.Either[E, A], GR ~func() A, E, A any](mr GR) GA { + return RightIO[GA](mr) +} + +// Deprecated: +func MonadMap[GA ~func() either.Either[E, A], GB ~func() either.Either[E, B], E, A, B any](fa GA, f func(A) B) GB { + return eithert.MonadMap(IO.MonadMap[GA, GB, either.Either[E, A], either.Either[E, B]], fa, f) +} + +// Deprecated: +func Map[GA ~func() either.Either[E, A], GB ~func() either.Either[E, B], E, A, B any](f func(A) B) func(GA) GB { + return eithert.Map(IO.Map[GA, GB, either.Either[E, A], either.Either[E, B]], f) +} + +// Deprecated: +func MonadMapTo[GA ~func() either.Either[E, A], GB ~func() either.Either[E, B], E, A, B any](fa GA, b B) GB { + return MonadMap[GA, GB](fa, F.Constant1[A](b)) +} + +// Deprecated: +func MapTo[GA ~func() either.Either[E, A], GB ~func() either.Either[E, B], E, A, B any](b B) func(GA) GB { + return Map[GA, GB](F.Constant1[A](b)) +} + +// Deprecated: +func MonadChain[GA ~func() either.Either[E, A], GB ~func() either.Either[E, B], E, A, B any](fa GA, f func(A) GB) GB { + return eithert.MonadChain(IO.MonadChain[GA, GB, either.Either[E, A], either.Either[E, B]], IO.MonadOf[GB, either.Either[E, B]], fa, f) +} + +// Deprecated: +func Chain[GA ~func() either.Either[E, A], GB ~func() either.Either[E, B], E, A, B any](f func(A) GB) func(GA) GB { + return eithert.Chain(IO.Chain[GA, GB, either.Either[E, A], either.Either[E, B]], IO.Of[GB, either.Either[E, B]], f) +} + +func MonadChainTo[GA ~func() either.Either[E, A], GB ~func() either.Either[E, B], E, A, B any](fa GA, fb GB) GB { + return MonadChain(fa, F.Constant1[A](fb)) +} + +func ChainTo[GA ~func() either.Either[E, A], GB ~func() either.Either[E, B], E, A, B any](fb GB) func(GA) GB { + return Chain[GA, GB, E, A, B](F.Constant1[A](fb)) +} + +// Deprecated: +func MonadChainEitherK[GA ~func() either.Either[E, A], GB ~func() either.Either[E, B], E, A, B any](ma GA, f func(A) either.Either[E, B]) GB { + return FE.MonadChainEitherK( + MonadChain[GA, GB, E, A, B], + FromEither[GB, E, B], + ma, + f, + ) +} + +// Deprecated: +func MonadChainIOK[GA ~func() either.Either[E, A], GB ~func() either.Either[E, B], GR ~func() B, E, A, B any](ma GA, f func(A) GR) GB { + return FI.MonadChainIOK( + MonadChain[GA, GB, E, A, B], + FromIO[GB, GR, E, B], + ma, + f, + ) +} + +func ChainIOK[GA ~func() either.Either[E, A], GB ~func() either.Either[E, B], GR ~func() B, E, A, B any](f func(A) GR) func(GA) GB { + return FI.ChainIOK( + Chain[GA, GB, E, A, B], + FromIO[GB, GR, E, B], + f, + ) +} + +// Deprecated: +func ChainEitherK[GA ~func() either.Either[E, A], GB ~func() either.Either[E, B], E, A, B any](f func(A) either.Either[E, B]) func(GA) GB { + return FE.ChainEitherK( + Chain[GA, GB, E, A, B], + FromEither[GB, E, B], + f, + ) +} + +// Deprecated: +func MonadAp[GB ~func() either.Either[E, B], GAB ~func() either.Either[E, func(A) B], GA ~func() either.Either[E, A], E, A, B any](mab GAB, ma GA) GB { + return eithert.MonadAp( + IO.MonadAp[GA, GB, func() func(either.Either[E, A]) either.Either[E, B], either.Either[E, A], either.Either[E, B]], + IO.MonadMap[GAB, func() func(either.Either[E, A]) either.Either[E, B], either.Either[E, func(A) B], func(either.Either[E, A]) either.Either[E, B]], + mab, ma) +} + +// Deprecated: +func Ap[GB ~func() either.Either[E, B], GAB ~func() either.Either[E, func(A) B], GA ~func() either.Either[E, A], E, A, B any](ma GA) func(GAB) GB { + return eithert.Ap( + IO.Ap[GB, func() func(either.Either[E, A]) either.Either[E, B], GA, either.Either[E, B], either.Either[E, A]], + IO.Map[GAB, func() func(either.Either[E, A]) either.Either[E, B], either.Either[E, func(A) B], func(either.Either[E, A]) either.Either[E, B]], + ma) +} + +// Deprecated: +func MonadApSeq[GB ~func() either.Either[E, B], GAB ~func() either.Either[E, func(A) B], GA ~func() either.Either[E, A], E, A, B any](mab GAB, ma GA) GB { + return eithert.MonadAp( + IO.MonadApSeq[GA, GB, func() func(either.Either[E, A]) either.Either[E, B], either.Either[E, A], either.Either[E, B]], + IO.MonadMap[GAB, func() func(either.Either[E, A]) either.Either[E, B], either.Either[E, func(A) B], func(either.Either[E, A]) either.Either[E, B]], + mab, ma) +} + +// Deprecated: +func ApSeq[GB ~func() either.Either[E, B], GAB ~func() either.Either[E, func(A) B], GA ~func() either.Either[E, A], E, A, B any](ma GA) func(GAB) GB { + return eithert.Ap( + IO.ApSeq[GB, func() func(either.Either[E, A]) either.Either[E, B], GA, either.Either[E, B], either.Either[E, A]], + IO.Map[GAB, func() func(either.Either[E, A]) either.Either[E, B], either.Either[E, func(A) B], func(either.Either[E, A]) either.Either[E, B]], + ma) +} + +// Deprecated: +func MonadApPar[GB ~func() either.Either[E, B], GAB ~func() either.Either[E, func(A) B], GA ~func() either.Either[E, A], E, A, B any](mab GAB, ma GA) GB { + return eithert.MonadAp( + IO.MonadApPar[GA, GB, func() func(either.Either[E, A]) either.Either[E, B], either.Either[E, A], either.Either[E, B]], + IO.MonadMap[GAB, func() func(either.Either[E, A]) either.Either[E, B], either.Either[E, func(A) B], func(either.Either[E, A]) either.Either[E, B]], + mab, ma) +} + +// Deprecated: +func ApPar[GB ~func() either.Either[E, B], GAB ~func() either.Either[E, func(A) B], GA ~func() either.Either[E, A], E, A, B any](ma GA) func(GAB) GB { + return eithert.Ap( + IO.ApPar[GB, func() func(either.Either[E, A]) either.Either[E, B], GA, either.Either[E, B], either.Either[E, A]], + IO.Map[GAB, func() func(either.Either[E, A]) either.Either[E, B], either.Either[E, func(A) B], func(either.Either[E, A]) either.Either[E, B]], + ma) +} + +// Deprecated: +func Flatten[GA ~func() either.Either[E, A], GAA ~func() either.Either[E, GA], E, A any](mma GAA) GA { + return MonadChain(mma, F.Identity[GA]) +} + +// Deprecated: +func TryCatch[GA ~func() either.Either[E, A], E, A any](f func() (A, error), onThrow func(error) E) GA { + return MakeIO(func() either.Either[E, A] { + a, err := f() + return either.TryCatch(a, err, onThrow) + }) +} + +// Deprecated: +func TryCatchError[GA ~func() either.Either[error, A], A any](f func() (A, error)) GA { + return MakeIO(func() either.Either[error, A] { + return either.TryCatchError(f()) + }) +} + +// Memoize computes the value of the provided IO monad lazily but exactly once +// +// Deprecated: +func Memoize[GA ~func() either.Either[E, A], E, A any](ma GA) GA { + return IO.Memoize(ma) +} + +// Deprecated: +func MonadMapLeft[GA1 ~func() either.Either[E1, A], GA2 ~func() either.Either[E2, A], E1, E2, A any](fa GA1, f func(E1) E2) GA2 { + return eithert.MonadMapLeft( + IO.MonadMap[GA1, GA2, either.Either[E1, A], either.Either[E2, A]], + fa, + f, + ) +} + +// Deprecated: +func MapLeft[GA1 ~func() either.Either[E1, A], GA2 ~func() either.Either[E2, A], E1, E2, A any](f func(E1) E2) func(GA1) GA2 { + return eithert.MapLeft( + IO.Map[GA1, GA2, either.Either[E1, A], either.Either[E2, A]], + f, + ) +} + +// Delay creates an operation that passes in the value after some [time.Duration] +// +// Deprecated: +func Delay[GA ~func() either.Either[E, A], E, A any](delay time.Duration) func(GA) GA { + return IO.Delay[GA](delay) +} + +// After creates an operation that passes after the given [time.Time] +// +// Deprecated: +func After[GA ~func() either.Either[E, A], E, A any](timestamp time.Time) func(GA) GA { + return IO.After[GA](timestamp) +} + +// Deprecated: +func MonadBiMap[GA ~func() either.Either[E1, A], GB ~func() either.Either[E2, B], E1, E2, A, B any](fa GA, f func(E1) E2, g func(A) B) GB { + return eithert.MonadBiMap(IO.MonadMap[GA, GB, either.Either[E1, A], either.Either[E2, B]], fa, f, g) +} + +// BiMap maps a pair of functions over the two type arguments of the bifunctor. +// +// Deprecated: +func BiMap[GA ~func() either.Either[E1, A], GB ~func() either.Either[E2, B], E1, E2, A, B any](f func(E1) E2, g func(A) B) func(GA) GB { + return eithert.BiMap(IO.Map[GA, GB, either.Either[E1, A], either.Either[E2, B]], f, g) +} + +// Fold convers an IOEither into an IO +// +// Deprecated: +func Fold[GA ~func() either.Either[E, A], GB ~func() B, E, A, B any](onLeft func(E) GB, onRight func(A) GB) func(GA) GB { + return eithert.MatchE(IO.MonadChain[GA, GB, either.Either[E, A], B], onLeft, onRight) +} + +func MonadFold[GA ~func() either.Either[E, A], GB ~func() B, E, A, B any](ma GA, onLeft func(E) GB, onRight func(A) GB) GB { + return eithert.FoldE(IO.MonadChain[GA, GB, either.Either[E, A], B], ma, onLeft, onRight) +} + +// GetOrElse extracts the value or maps the error +func GetOrElse[GA ~func() either.Either[E, A], GB ~func() A, E, A any](onLeft func(E) GB) func(GA) GB { + return eithert.GetOrElse(IO.MonadChain[GA, GB, either.Either[E, A], A], IO.MonadOf[GB, A], onLeft) +} + +// MonadChainFirst runs the monad returned by the function but returns the result of the original monad +// +// Deprecated: +func MonadChainFirst[GA ~func() either.Either[E, A], GB ~func() either.Either[E, B], E, A, B any](ma GA, f func(A) GB) GA { + return C.MonadChainFirst( + MonadChain[GA, GA, E, A, A], + MonadMap[GB, GA, E, B, A], + ma, + f, + ) +} + +// ChainFirst runs the monad returned by the function but returns the result of the original monad +// +// Deprecated: +func ChainFirst[GA ~func() either.Either[E, A], GB ~func() either.Either[E, B], E, A, B any](f func(A) GB) func(GA) GA { + return C.ChainFirst( + Chain[GA, GA, E, A, A], + Map[GB, GA, E, B, A], + f, + ) +} + +// MonadChainFirstIOK runs the monad returned by the function but returns the result of the original monad +// +// Deprecated: +func MonadChainFirstIOK[GA ~func() either.Either[E, A], GIOB ~func() B, E, A, B any](first GA, f func(A) GIOB) GA { + return FI.MonadChainFirstIOK( + MonadChain[GA, GA, E, A, A], + MonadMap[func() either.Either[E, B], GA, E, B, A], + FromIO[func() either.Either[E, B], GIOB, E, B], + first, + f, + ) +} + +// ChainFirstIOK runs the monad returned by the function but returns the result of the original monad +// +// Deprecated: +func ChainFirstIOK[GA ~func() either.Either[E, A], GIOB ~func() B, E, A, B any](f func(A) GIOB) func(GA) GA { + return FI.ChainFirstIOK( + Chain[GA, GA, E, A, A], + Map[func() either.Either[E, B], GA, E, B, A], + FromIO[func() either.Either[E, B], GIOB, E, B], + f, + ) +} + +// MonadChainFirstEitherK runs the monad returned by the function but returns the result of the original monad +// +// Deprecated: +func MonadChainFirstEitherK[GA ~func() either.Either[E, A], E, A, B any](first GA, f func(A) either.Either[E, B]) GA { + return FE.MonadChainFirstEitherK( + MonadChain[GA, GA, E, A, A], + MonadMap[func() either.Either[E, B], GA, E, B, A], + FromEither[func() either.Either[E, B], E, B], + first, + f, + ) +} + +// ChainFirstEitherK runs the monad returned by the function but returns the result of the original monad +// +// Deprecated: +func ChainFirstEitherK[GA ~func() either.Either[E, A], E, A, B any](f func(A) either.Either[E, B]) func(GA) GA { + return FE.ChainFirstEitherK( + Chain[GA, GA, E, A, A], + Map[func() either.Either[E, B], GA, E, B, A], + FromEither[func() either.Either[E, B], E, B], + f, + ) +} + +// Swap changes the order of type parameters +// +// Deprecated: +func Swap[GEA ~func() either.Either[E, A], GAE ~func() either.Either[A, E], E, A any](val GEA) GAE { + return MonadFold(val, Right[GAE], Left[GAE]) +} + +// FromImpure converts a side effect without a return value into a side effect that returns any +// +// Deprecated: +func FromImpure[GA ~func() either.Either[E, any], IMP ~func(), E any](f IMP) GA { + return F.Pipe2( + f, + IO.FromImpure[func() any, IMP], + FromIO[GA, func() any], + ) +} + +// Defer creates an IO by creating a brand new IO via a generator function, each time +// +// Deprecated: +func Defer[GEA ~func() either.Either[E, A], E, A any](gen func() GEA) GEA { + return IO.Defer[GEA](gen) +} + +// Deprecated: +func MonadAlt[LAZY ~func() GIOA, GIOA ~func() either.Either[E, A], E, A any](first GIOA, second LAZY) GIOA { + return eithert.MonadAlt( + IO.Of[GIOA], + IO.MonadChain[GIOA, GIOA], + + first, + second, + ) +} + +// Deprecated: +func Alt[LAZY ~func() GIOA, GIOA ~func() either.Either[E, A], E, A any](second LAZY) func(GIOA) GIOA { + return F.Bind2nd(MonadAlt[LAZY], second) +} + +// Deprecated: +func MonadFlap[GEAB ~func() either.Either[E, func(A) B], GEB ~func() either.Either[E, B], E, B, A any](fab GEAB, a A) GEB { + return FC.MonadFlap(MonadMap[GEAB, GEB], fab, a) +} + +// Deprecated: +func Flap[GEAB ~func() either.Either[E, func(A) B], GEB ~func() either.Either[E, B], E, B, A any](a A) func(GEAB) GEB { + return FC.Flap(Map[GEAB, GEB], a) +} + +// Deprecated: +func ToIOOption[GA ~func() O.Option[A], GEA ~func() either.Either[E, A], E, A any](ioe GEA) GA { + return F.Pipe1( + ioe, + IO.Map[GEA, GA](either.ToOption[E, A]), + ) +} + +// Deprecated: +func FromIOOption[GEA ~func() either.Either[E, A], GA ~func() O.Option[A], E, A any](onNone func() E) func(ioo GA) GEA { + return IO.Map[GA, GEA](either.FromOption[A](onNone)) +} diff --git a/v2/ioeither/generic/types.go b/v2/ioeither/generic/types.go new file mode 100644 index 0000000..8a229ab --- /dev/null +++ b/v2/ioeither/generic/types.go @@ -0,0 +1,22 @@ +// Copyright (c) 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import "github.com/IBM/fp-go/v2/either" + +type ( + Either[E, A any] = either.Either[E, A] +) diff --git a/v2/ioeither/http/builder/builder.go b/v2/ioeither/http/builder/builder.go new file mode 100644 index 0000000..b7c784f --- /dev/null +++ b/v2/ioeither/http/builder/builder.go @@ -0,0 +1,67 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package builder + +import ( + "bytes" + "net/http" + "strconv" + + E "github.com/IBM/fp-go/v2/either" + F "github.com/IBM/fp-go/v2/function" + R "github.com/IBM/fp-go/v2/http/builder" + H "github.com/IBM/fp-go/v2/http/headers" + "github.com/IBM/fp-go/v2/ioeither" + IOEH "github.com/IBM/fp-go/v2/ioeither/http" + LZ "github.com/IBM/fp-go/v2/lazy" + O "github.com/IBM/fp-go/v2/option" +) + +func Requester(builder *R.Builder) IOEH.Requester { + + withBody := F.Curry3(func(data []byte, url string, method string) IOEither[*http.Request] { + return ioeither.TryCatchError(func() (*http.Request, error) { + req, err := http.NewRequest(method, url, bytes.NewReader(data)) + if err == nil { + req.Header.Set(H.ContentLength, strconv.Itoa(len(data))) + H.Monoid.Concat(req.Header, builder.GetHeaders()) + } + return req, err + }) + }) + + withoutBody := F.Curry2(func(url string, method string) IOEither[*http.Request] { + return ioeither.TryCatchError(func() (*http.Request, error) { + req, err := http.NewRequest(method, url, nil) + if err == nil { + H.Monoid.Concat(req.Header, builder.GetHeaders()) + } + return req, err + }) + }) + + return F.Pipe5( + builder.GetBody(), + O.Fold(LZ.Of(E.Of[error](withoutBody)), E.Map[error](withBody)), + E.Ap[func(string) IOEither[*http.Request]](builder.GetTargetURL()), + E.Flap[error, IOEither[*http.Request]](builder.GetMethod()), + E.GetOrElse(ioeither.Left[*http.Request, error]), + ioeither.Map[error](func(req *http.Request) *http.Request { + req.Header = H.Monoid.Concat(req.Header, builder.GetHeaders()) + return req + }), + ) +} diff --git a/v2/ioeither/http/builder/builder_test.go b/v2/ioeither/http/builder/builder_test.go new file mode 100644 index 0000000..1babbe9 --- /dev/null +++ b/v2/ioeither/http/builder/builder_test.go @@ -0,0 +1,58 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package builder + +import ( + "net/http" + "net/url" + "testing" + + E "github.com/IBM/fp-go/v2/either" + F "github.com/IBM/fp-go/v2/function" + R "github.com/IBM/fp-go/v2/http/builder" + "github.com/IBM/fp-go/v2/io" + "github.com/IBM/fp-go/v2/ioeither" + "github.com/stretchr/testify/assert" +) + +func TestBuilderWithQuery(t *testing.T) { + // add some query + withLimit := R.WithQueryArg("limit")("10") + withURL := R.WithUrl("http://www.example.org?a=b") + + b := F.Pipe2( + R.Default, + withLimit, + withURL, + ) + + req := F.Pipe3( + b, + Requester, + ioeither.Map[error](func(r *http.Request) *url.URL { + return r.URL + }), + ioeither.ChainFirstIOK[error](func(u *url.URL) io.IO[any] { + return io.FromImpure(func() { + q := u.Query() + assert.Equal(t, "10", q.Get("limit")) + assert.Equal(t, "b", q.Get("a")) + }) + }), + ) + + assert.True(t, E.IsRight(req())) +} diff --git a/v2/ioeither/http/builder/types.go b/v2/ioeither/http/builder/types.go new file mode 100644 index 0000000..695d00a --- /dev/null +++ b/v2/ioeither/http/builder/types.go @@ -0,0 +1,22 @@ +// Copyright (c) 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package builder + +import "github.com/IBM/fp-go/v2/ioeither" + +type ( + IOEither[A any] = ioeither.IOEither[error, A] +) diff --git a/v2/ioeither/http/di/di.go b/v2/ioeither/http/di/di.go new file mode 100644 index 0000000..8973096 --- /dev/null +++ b/v2/ioeither/http/di/di.go @@ -0,0 +1,32 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package di + +import ( + "net/http" + + DI "github.com/IBM/fp-go/v2/di" + "github.com/IBM/fp-go/v2/ioeither" + IOEH "github.com/IBM/fp-go/v2/ioeither/http" +) + +var ( + // InjHttpClient is the [DI.InjectionToken] for the [http.DefaultClient] + InjHttpClient = DI.MakeTokenWithDefault0("HTTP_CLIENT", ioeither.Of[error](http.DefaultClient)) + + // InjClient is the [DI.InjectionToken] for the default [IOEH.Client] + InjClient = DI.MakeTokenWithDefault1("CLIENT", InjHttpClient.IOEither(), ioeither.Map[error](IOEH.MakeClient)) +) diff --git a/v2/ioeither/http/request.go b/v2/ioeither/http/request.go new file mode 100644 index 0000000..94514a7 --- /dev/null +++ b/v2/ioeither/http/request.go @@ -0,0 +1,145 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package http + +import ( + "bytes" + "io" + "net/http" + + B "github.com/IBM/fp-go/v2/bytes" + FL "github.com/IBM/fp-go/v2/file" + F "github.com/IBM/fp-go/v2/function" + H "github.com/IBM/fp-go/v2/http" + "github.com/IBM/fp-go/v2/ioeither" + IOEF "github.com/IBM/fp-go/v2/ioeither/file" + J "github.com/IBM/fp-go/v2/json" + P "github.com/IBM/fp-go/v2/pair" +) + +type ( + // Requester is a reader that constructs a request + Requester = ioeither.IOEither[error, *http.Request] + + Client interface { + Do(Requester) ioeither.IOEither[error, *http.Response] + } + + client struct { + delegate *http.Client + doIOE func(*http.Request) ioeither.IOEither[error, *http.Response] + } +) + +var ( + // MakeRequest is an eitherized version of [http.NewRequest] + MakeRequest = ioeither.Eitherize3(http.NewRequest) + makeRequest = F.Bind13of3(MakeRequest) + + // specialize + MakeGetRequest = makeRequest("GET", nil) +) + +// MakeBodyRequest creates a request that carries a body +func MakeBodyRequest(method string, body ioeither.IOEither[error, []byte]) func(url string) ioeither.IOEither[error, *http.Request] { + onBody := F.Pipe1( + body, + ioeither.Map[error](F.Flow2( + bytes.NewReader, + FL.ToReader[*bytes.Reader], + )), + ) + onRelease := ioeither.Of[error, io.Reader] + withMethod := F.Bind1of3(MakeRequest)(method) + + return F.Flow2( + F.Bind1of2(withMethod), + ioeither.WithResource[*http.Request](onBody, onRelease), + ) +} + +func (client client) Do(req Requester) ioeither.IOEither[error, *http.Response] { + return F.Pipe1( + req, + ioeither.Chain(client.doIOE), + ) +} + +func MakeClient(httpClient *http.Client) Client { + return client{delegate: httpClient, doIOE: ioeither.Eitherize1(httpClient.Do)} +} + +// ReadFullResponse sends a request, reads the response as a byte array and represents the result as a tuple +func ReadFullResponse(client Client) func(Requester) ioeither.IOEither[error, H.FullResponse] { + return F.Flow3( + client.Do, + ioeither.ChainEitherK(H.ValidateResponse), + ioeither.Chain(func(resp *http.Response) ioeither.IOEither[error, H.FullResponse] { + return F.Pipe1( + F.Pipe3( + resp, + H.GetBody, + ioeither.Of[error, io.ReadCloser], + IOEF.ReadAll[io.ReadCloser], + ), + ioeither.Map[error](F.Bind1st(P.MakePair[*http.Response, []byte], resp)), + ) + }), + ) +} + +// ReadAll sends a request and reads the response as bytes +func ReadAll(client Client) func(Requester) ioeither.IOEither[error, []byte] { + return F.Flow2( + ReadFullResponse(client), + ioeither.Map[error](H.Body), + ) +} + +// ReadText sends a request, reads the response and represents the response as a text string +func ReadText(client Client) func(Requester) ioeither.IOEither[error, string] { + return F.Flow2( + ReadAll(client), + ioeither.Map[error](B.ToString), + ) +} + +// ReadJson sends a request, reads the response and parses the response as JSON +// +// Deprecated: use [ReadJSON] instead +func ReadJson[A any](client Client) func(Requester) ioeither.IOEither[error, A] { + return ReadJSON[A](client) +} + +// readJSON sends a request, reads the response and parses the response as a []byte +func readJSON(client Client) func(Requester) ioeither.IOEither[error, []byte] { + return F.Flow3( + ReadFullResponse(client), + ioeither.ChainFirstEitherK(F.Flow2( + H.Response, + H.ValidateJSONResponse, + )), + ioeither.Map[error](H.Body), + ) +} + +// ReadJSON sends a request, reads the response and parses the response as JSON +func ReadJSON[A any](client Client) func(Requester) ioeither.IOEither[error, A] { + return F.Flow2( + readJSON(client), + ioeither.ChainEitherK[error](J.Unmarshal[A]), + ) +} diff --git a/v2/ioeither/http/retry_test.go b/v2/ioeither/http/retry_test.go new file mode 100644 index 0000000..797d1aa --- /dev/null +++ b/v2/ioeither/http/retry_test.go @@ -0,0 +1,71 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package http + +import ( + "net" + "net/http" + "testing" + "time" + + AR "github.com/IBM/fp-go/v2/array" + E "github.com/IBM/fp-go/v2/either" + "github.com/IBM/fp-go/v2/errors" + F "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/ioeither" + O "github.com/IBM/fp-go/v2/option" + R "github.com/IBM/fp-go/v2/retry" + "github.com/stretchr/testify/assert" +) + +var expLogBackoff = R.ExponentialBackoff(250 * time.Millisecond) + +// our retry policy with a 1s cap +var testLogPolicy = R.CapDelay( + 2*time.Second, + R.Monoid.Concat(expLogBackoff, R.LimitRetries(20)), +) + +type PostItem struct { + UserID uint `json:"userId"` + Id uint `json:"id"` + Title string `json:"title"` + Body string `json:"body"` +} + +func TestRetryHttp(t *testing.T) { + // URLs to try, the first URLs have an invalid hostname + urls := AR.From("https://jsonplaceholder1.typicode.com/posts/1", "https://jsonplaceholder2.typicode.com/posts/1", "https://jsonplaceholder3.typicode.com/posts/1", "https://jsonplaceholder4.typicode.com/posts/1", "https://jsonplaceholder.typicode.com/posts/1") + client := MakeClient(&http.Client{}) + + action := func(status R.RetryStatus) ioeither.IOEither[error, *PostItem] { + return F.Pipe1( + MakeGetRequest(urls[status.IterNumber]), + ReadJSON[*PostItem](client), + ) + } + + check := E.Fold( + F.Flow2( + errors.As[*net.DNSError](), + O.IsSome[*net.DNSError], + ), + F.Constant1[*PostItem](false), + ) + + item := ioeither.Retrying(testLogPolicy, action, check)() + assert.True(t, E.IsRight(item)) +} diff --git a/v2/ioeither/ioeither.go b/v2/ioeither/ioeither.go new file mode 100644 index 0000000..a9401e7 --- /dev/null +++ b/v2/ioeither/ioeither.go @@ -0,0 +1,409 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package ioeither + +import ( + "time" + + "github.com/IBM/fp-go/v2/either" + "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/internal/chain" + "github.com/IBM/fp-go/v2/internal/eithert" + "github.com/IBM/fp-go/v2/internal/file" + "github.com/IBM/fp-go/v2/internal/fromeither" + "github.com/IBM/fp-go/v2/internal/fromio" + "github.com/IBM/fp-go/v2/internal/functor" + "github.com/IBM/fp-go/v2/io" + IOO "github.com/IBM/fp-go/v2/iooption" + "github.com/IBM/fp-go/v2/lazy" + O "github.com/IBM/fp-go/v2/option" + R "github.com/IBM/fp-go/v2/reader" +) + +type ( + IO[A any] = io.IO[A] + Either[E, A any] = either.Either[E, A] + + // IOEither represents a synchronous computation that may fail + // refer to [https://andywhite.xyz/posts/2021-01-27-rte-foundations/#ioeitherlte-agt] for more details + IOEither[E, A any] = IO[Either[E, A]] + + Operator[E, A, B any] = R.Reader[IOEither[E, A], IOEither[E, B]] +) + +func Left[A, E any](l E) IOEither[E, A] { + return eithert.Left(io.MonadOf[Either[E, A]], l) +} + +func Right[E, A any](r A) IOEither[E, A] { + return eithert.Right(io.MonadOf[Either[E, A]], r) +} + +func Of[E, A any](r A) IOEither[E, A] { + return Right[E](r) +} + +func MonadOf[E, A any](r A) IOEither[E, A] { + return Of[E](r) +} + +func LeftIO[A, E any](ml IO[E]) IOEither[E, A] { + return eithert.LeftF(io.MonadMap[E, Either[E, A]], ml) +} + +func RightIO[E, A any](mr IO[A]) IOEither[E, A] { + return eithert.RightF(io.MonadMap[A, Either[E, A]], mr) +} + +func FromEither[E, A any](e Either[E, A]) IOEither[E, A] { + return io.Of(e) +} + +func FromOption[A, E any](onNone func() E) func(o O.Option[A]) IOEither[E, A] { + return fromeither.FromOption( + FromEither[E, A], + onNone, + ) +} + +func FromIOOption[A, E any](onNone func() E) func(o IOO.IOOption[A]) IOEither[E, A] { + return io.Map(either.FromOption[A](onNone)) +} + +func ChainOptionK[A, B, E any](onNone func() E) func(func(A) O.Option[B]) func(IOEither[E, A]) IOEither[E, B] { + return fromeither.ChainOptionK( + MonadChain[E, A, B], + FromEither[E, B], + onNone, + ) +} + +func MonadChainIOK[E, A, B any](ma IOEither[E, A], f func(A) IO[B]) IOEither[E, B] { + return fromio.MonadChainIOK( + MonadChain[E, A, B], + FromIO[E, B], + ma, + f, + ) +} + +func ChainIOK[E, A, B any](f func(A) IO[B]) func(IOEither[E, A]) IOEither[E, B] { + return fromio.ChainIOK( + Chain[E, A, B], + FromIO[E, B], + f, + ) +} + +func ChainLazyK[E, A, B any](f func(A) lazy.Lazy[B]) func(IOEither[E, A]) IOEither[E, B] { + return ChainIOK[E](f) +} + +// FromIO creates an [IOEither] from an [IO] instance, invoking [IO] for each invocation of [IOEither] +func FromIO[E, A any](mr IO[A]) IOEither[E, A] { + return RightIO[E](mr) +} + +// FromLazy creates an [IOEither] from a [Lazy] instance, invoking [Lazy] for each invocation of [IOEither] +func FromLazy[E, A any](mr lazy.Lazy[A]) IOEither[E, A] { + return FromIO[E](mr) +} + +func MonadMap[E, A, B any](fa IOEither[E, A], f func(A) B) IOEither[E, B] { + return eithert.MonadMap(io.MonadMap[Either[E, A], Either[E, B]], fa, f) +} + +func Map[E, A, B any](f func(A) B) Operator[E, A, B] { + return eithert.Map(io.Map[Either[E, A], Either[E, B]], f) +} + +func MonadMapTo[E, A, B any](fa IOEither[E, A], b B) IOEither[E, B] { + return MonadMap(fa, function.Constant1[A](b)) +} + +func MapTo[E, A, B any](b B) Operator[E, A, B] { + return Map[E](function.Constant1[A](b)) +} + +func MonadChain[E, A, B any](fa IOEither[E, A], f func(A) IOEither[E, B]) IOEither[E, B] { + return eithert.MonadChain(io.MonadChain[Either[E, A], Either[E, B]], io.MonadOf[Either[E, B]], fa, f) +} + +func Chain[E, A, B any](f func(A) IOEither[E, B]) Operator[E, A, B] { + return eithert.Chain(io.Chain[Either[E, A], Either[E, B]], io.Of[Either[E, B]], f) +} + +func MonadChainEitherK[E, A, B any](ma IOEither[E, A], f func(A) Either[E, B]) IOEither[E, B] { + return fromeither.MonadChainEitherK( + MonadChain[E, A, B], + FromEither[E, B], + ma, + f, + ) +} + +func ChainEitherK[E, A, B any](f func(A) Either[E, B]) func(IOEither[E, A]) IOEither[E, B] { + return fromeither.ChainEitherK( + Chain[E, A, B], + FromEither[E, B], + f, + ) +} + +func MonadAp[B, E, A any](mab IOEither[E, func(A) B], ma IOEither[E, A]) IOEither[E, B] { + return eithert.MonadAp( + io.MonadAp[Either[E, A], Either[E, B]], + io.MonadMap[Either[E, func(A) B], func(Either[E, A]) Either[E, B]], + mab, ma) +} + +// Ap is an alias of [ApPar] +func Ap[B, E, A any](ma IOEither[E, A]) Operator[E, func(A) B, B] { + return eithert.Ap( + io.Ap[Either[E, B], Either[E, A]], + io.Map[Either[E, func(A) B], func(Either[E, A]) Either[E, B]], + ma) +} + +func MonadApPar[B, E, A any](mab IOEither[E, func(A) B], ma IOEither[E, A]) IOEither[E, B] { + return eithert.MonadAp( + io.MonadApPar[Either[E, A], Either[E, B]], + io.MonadMap[Either[E, func(A) B], func(Either[E, A]) Either[E, B]], + mab, ma) +} + +// ApPar applies function and value in parallel +func ApPar[B, E, A any](ma IOEither[E, A]) Operator[E, func(A) B, B] { + return eithert.Ap( + io.ApPar[Either[E, B], Either[E, A]], + io.Map[Either[E, func(A) B], func(Either[E, A]) Either[E, B]], + ma) +} + +func MonadApSeq[B, E, A any](mab IOEither[E, func(A) B], ma IOEither[E, A]) IOEither[E, B] { + return eithert.MonadAp( + io.MonadApSeq[Either[E, A], Either[E, B]], + io.MonadMap[Either[E, func(A) B], func(Either[E, A]) Either[E, B]], + mab, ma) +} + +// ApSeq applies function and value sequentially +func ApSeq[B, E, A any](ma IOEither[E, A]) func(IOEither[E, func(A) B]) IOEither[E, B] { + return eithert.Ap( + io.ApSeq[Either[E, B], Either[E, A]], + io.Map[Either[E, func(A) B], func(Either[E, A]) Either[E, B]], + ma) +} + +func Flatten[E, A any](mma IOEither[E, IOEither[E, A]]) IOEither[E, A] { + return MonadChain(mma, function.Identity[IOEither[E, A]]) +} + +func TryCatch[E, A any](f func() (A, error), onThrow func(error) E) IOEither[E, A] { + return func() Either[E, A] { + a, err := f() + return either.TryCatch(a, err, onThrow) + } +} + +func TryCatchError[A any](f func() (A, error)) IOEither[error, A] { + return func() Either[error, A] { + return either.TryCatchError(f()) + } +} + +func Memoize[E, A any](ma IOEither[E, A]) IOEither[E, A] { + return io.Memoize(ma) +} + +func MonadMapLeft[E1, E2, A any](fa IOEither[E1, A], f func(E1) E2) IOEither[E2, A] { + return eithert.MonadMapLeft( + io.MonadMap[Either[E1, A], Either[E2, A]], + fa, + f, + ) +} + +func MapLeft[A, E1, E2 any](f func(E1) E2) func(IOEither[E1, A]) IOEither[E2, A] { + return eithert.MapLeft( + io.Map[Either[E1, A], Either[E2, A]], + f, + ) +} + +func MonadBiMap[E1, E2, A, B any](fa IOEither[E1, A], f func(E1) E2, g func(A) B) IOEither[E2, B] { + return eithert.MonadBiMap(io.MonadMap[Either[E1, A], Either[E2, B]], fa, f, g) +} + +// BiMap maps a pair of functions over the two type arguments of the bifunctor. +func BiMap[E1, E2, A, B any](f func(E1) E2, g func(A) B) func(IOEither[E1, A]) IOEither[E2, B] { + return eithert.BiMap(io.Map[Either[E1, A], Either[E2, B]], f, g) +} + +// Fold converts an IOEither into an IO +func Fold[E, A, B any](onLeft func(E) IO[B], onRight func(A) IO[B]) func(IOEither[E, A]) IO[B] { + return eithert.MatchE(io.MonadChain[Either[E, A], B], onLeft, onRight) +} + +// GetOrElse extracts the value or maps the error +func GetOrElse[E, A any](onLeft func(E) IO[A]) func(IOEither[E, A]) IO[A] { + return eithert.GetOrElse(io.MonadChain[Either[E, A], A], io.MonadOf[A], onLeft) +} + +// MonadChainTo composes to the second monad ignoring the return value of the first +func MonadChainTo[A, E, B any](fa IOEither[E, A], fb IOEither[E, B]) IOEither[E, B] { + return MonadChain(fa, function.Constant1[A](fb)) +} + +// ChainTo composes to the second [IOEither] monad ignoring the return value of the first +func ChainTo[A, E, B any](fb IOEither[E, B]) Operator[E, A, B] { + return Chain(function.Constant1[A](fb)) +} + +// MonadChainFirst runs the [IOEither] monad returned by the function but returns the result of the original monad +func MonadChainFirst[E, A, B any](ma IOEither[E, A], f func(A) IOEither[E, B]) IOEither[E, A] { + return chain.MonadChainFirst( + MonadChain[E, A, A], + MonadMap[E, B, A], + ma, + f, + ) +} + +// ChainFirst runs the [IOEither] monad returned by the function but returns the result of the original monad +func ChainFirst[E, A, B any](f func(A) IOEither[E, B]) Operator[E, A, A] { + return chain.ChainFirst( + Chain[E, A, A], + Map[E, B, A], + f, + ) +} + +func MonadChainFirstEitherK[A, E, B any](ma IOEither[E, A], f func(A) Either[E, B]) IOEither[E, A] { + return fromeither.MonadChainFirstEitherK( + MonadChain[E, A, A], + MonadMap[E, B, A], + FromEither[E, B], + ma, + f, + ) +} + +func ChainFirstEitherK[A, E, B any](f func(A) Either[E, B]) Operator[E, A, A] { + return fromeither.ChainFirstEitherK( + Chain[E, A, A], + Map[E, B, A], + FromEither[E, B], + f, + ) +} + +// MonadChainFirstIOK runs [IO] the monad returned by the function but returns the result of the original monad +func MonadChainFirstIOK[E, A, B any](ma IOEither[E, A], f func(A) IO[B]) IOEither[E, A] { + return fromio.MonadChainFirstIOK( + MonadChain[E, A, A], + MonadMap[E, B, A], + FromIO[E, B], + ma, + f, + ) +} + +// ChainFirstIOK runs the [IO] monad returned by the function but returns the result of the original monad +func ChainFirstIOK[E, A, B any](f func(A) IO[B]) func(IOEither[E, A]) IOEither[E, A] { + return fromio.ChainFirstIOK( + Chain[E, A, A], + Map[E, B, A], + FromIO[E, B], + f, + ) +} + +func MonadFold[E, A, B any](ma IOEither[E, A], onLeft func(E) IO[B], onRight func(A) IO[B]) IO[B] { + return eithert.FoldE(io.MonadChain[Either[E, A], B], ma, onLeft, onRight) +} + +// WithResource constructs a function that creates a resource, then operates on it and then releases the resource +func WithResource[A, E, R, ANY any](onCreate IOEither[E, R], onRelease func(R) IOEither[E, ANY]) func(func(R) IOEither[E, A]) IOEither[E, A] { + return file.WithResource( + MonadChain[E, R, A], + MonadFold[E, A, Either[E, A]], + MonadFold[E, ANY, Either[E, A]], + MonadMap[E, ANY, A], + Left[A, E], + )(function.Constant(onCreate), onRelease) +} + +// Swap changes the order of type parameters +func Swap[E, A any](val IOEither[E, A]) IOEither[A, E] { + return MonadFold(val, Right[A, E], Left[E, A]) +} + +// FromImpure converts a side effect without a return value into a side effect that returns any +func FromImpure[E any](f func()) IOEither[E, any] { + return function.Pipe2( + f, + io.FromImpure, + FromIO[E, any], + ) +} + +// Defer creates an IO by creating a brand new IO via a generator function, each time +func Defer[E, A any](gen lazy.Lazy[IOEither[E, A]]) IOEither[E, A] { + return io.Defer(gen) +} + +// MonadAlt identifies an associative operation on a type constructor +func MonadAlt[E, A any](first IOEither[E, A], second lazy.Lazy[IOEither[E, A]]) IOEither[E, A] { + return eithert.MonadAlt( + io.Of[Either[E, A]], + io.MonadChain[Either[E, A], Either[E, A]], + + first, + second, + ) +} + +// Alt identifies an associative operation on a type constructor +func Alt[E, A any](second lazy.Lazy[IOEither[E, A]]) Operator[E, A, A] { + return function.Bind2nd(MonadAlt[E, A], second) +} + +func MonadFlap[E, B, A any](fab IOEither[E, func(A) B], a A) IOEither[E, B] { + return functor.MonadFlap(MonadMap[E, func(A) B, B], fab, a) +} + +func Flap[E, B, A any](a A) Operator[E, func(A) B, B] { + return functor.Flap(Map[E, func(A) B, B], a) +} + +// ToIOOption converts an [IOEither] to an [IOO.IOOption] +func ToIOOption[E, A any](ioe IOEither[E, A]) IOO.IOOption[A] { + return function.Pipe1( + ioe, + io.Map(either.ToOption[E, A]), + ) +} + +// Delay creates an operation that passes in the value after some delay +func Delay[E, A any](delay time.Duration) Operator[E, A, A] { + return io.Delay[Either[E, A]](delay) +} + +// After creates an operation that passes after the given [time.Time] +func After[E, A any](timestamp time.Time) Operator[E, A, A] { + return io.After[Either[E, A]](timestamp) +} diff --git a/v2/ioeither/ioeither_test.go b/v2/ioeither/ioeither_test.go new file mode 100644 index 0000000..ae1f813 --- /dev/null +++ b/v2/ioeither/ioeither_test.go @@ -0,0 +1,136 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package ioeither + +import ( + "fmt" + "testing" + + E "github.com/IBM/fp-go/v2/either" + F "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/internal/utils" + "github.com/IBM/fp-go/v2/io" + I "github.com/IBM/fp-go/v2/io" + O "github.com/IBM/fp-go/v2/option" + "github.com/stretchr/testify/assert" +) + +func TestMap(t *testing.T) { + assert.Equal(t, E.Of[error](2), F.Pipe1( + Of[error](1), + Map[error](utils.Double), + )()) + +} + +func TestChainEitherK(t *testing.T) { + f := ChainEitherK(func(n int) E.Either[string, int] { + if n > 0 { + return E.Of[string](n) + } + return E.Left[int]("a") + + }) + assert.Equal(t, E.Right[string](1), f(Right[string](1))()) + assert.Equal(t, E.Left[int]("a"), f(Right[string](-1))()) + assert.Equal(t, E.Left[int]("b"), f(Left[int]("b"))()) +} + +func TestChainOptionK(t *testing.T) { + f := ChainOptionK[int, int](F.Constant("a"))(func(n int) O.Option[int] { + if n > 0 { + return O.Some(n) + } + return O.None[int]() + }) + + assert.Equal(t, E.Right[string](1), f(Right[string](1))()) + assert.Equal(t, E.Left[int]("a"), f(Right[string](-1))()) + assert.Equal(t, E.Left[int]("b"), f(Left[int]("b"))()) +} + +func TestFromOption(t *testing.T) { + f := FromOption[int](F.Constant("a")) + assert.Equal(t, E.Right[string](1), f(O.Some(1))()) + assert.Equal(t, E.Left[int]("a"), f(O.None[int]())()) +} + +func TestChainIOK(t *testing.T) { + f := ChainIOK[string](func(n int) I.IO[string] { + return func() string { + return fmt.Sprintf("%d", n) + } + }) + + assert.Equal(t, E.Right[string]("1"), f(Right[string](1))()) + assert.Equal(t, E.Left[string, string]("b"), f(Left[int]("b"))()) +} + +func TestChainWithIO(t *testing.T) { + + r := F.Pipe1( + Of[error]("test"), + // sad, we need the generics version ... + io.Map(E.IsRight[error, string]), + ) + + assert.True(t, r()) +} + +func TestChainFirst(t *testing.T) { + f := func(a string) IOEither[string, int] { + if len(a) > 2 { + return Of[string](len(a)) + } + return Left[int]("foo") + } + good := Of[string]("foo") + bad := Of[string]("a") + ch := ChainFirst(f) + + assert.Equal(t, E.Of[string]("foo"), F.Pipe1(good, ch)()) + assert.Equal(t, E.Left[string, string]("foo"), F.Pipe1(bad, ch)()) +} + +func TestChainFirstIOK(t *testing.T) { + f := func(a string) I.IO[int] { + return I.Of(len(a)) + } + good := Of[string]("foo") + ch := ChainFirstIOK[string](f) + + assert.Equal(t, E.Of[string]("foo"), F.Pipe1(good, ch)()) +} + +func TestApFirst(t *testing.T) { + + x := F.Pipe1( + Of[error]("a"), + ApFirst[string](Of[error]("b")), + ) + + assert.Equal(t, E.Of[error]("a"), x()) +} + +func TestApSecond(t *testing.T) { + + x := F.Pipe1( + Of[error]("a"), + ApSecond[string](Of[error]("b")), + ) + + assert.Equal(t, E.Of[error]("b"), x()) +} diff --git a/v2/ioeither/logging.go b/v2/ioeither/logging.go new file mode 100644 index 0000000..680d67f --- /dev/null +++ b/v2/ioeither/logging.go @@ -0,0 +1,43 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package ioeither + +import ( + "encoding/json" + "log" + + "github.com/IBM/fp-go/v2/bytes" + "github.com/IBM/fp-go/v2/either" + "github.com/IBM/fp-go/v2/function" +) + +// LogJSON converts the argument to pretty printed JSON and then logs it via the format string +// Can be used with [ChainFirst] +func LogJSON[A any](prefix string) func(A) IOEither[error, any] { + return func(a A) IOEither[error, any] { + // log this + return function.Pipe3( + either.TryCatchError(json.MarshalIndent(a, "", " ")), + either.Map[error](bytes.ToString), + FromEither[error, string], + Chain(func(data string) IOEither[error, any] { + return FromImpure[error](func() { + log.Printf(prefix, data) + }) + }), + ) + } +} diff --git a/v2/ioeither/logging_test.go b/v2/ioeither/logging_test.go new file mode 100644 index 0000000..bdf3563 --- /dev/null +++ b/v2/ioeither/logging_test.go @@ -0,0 +1,42 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package ioeither + +import ( + "testing" + + E "github.com/IBM/fp-go/v2/either" + F "github.com/IBM/fp-go/v2/function" + "github.com/stretchr/testify/assert" +) + +func TestLogging(t *testing.T) { + + type SomeData struct { + Key string `json:"key"` + Value string `json:"value"` + } + + src := &SomeData{Key: "key", Value: "value"} + + res := F.Pipe1( + Of[error](src), + ChainFirst(LogJSON[*SomeData]("Data: \n%s")), + ) + + dst := res() + assert.Equal(t, E.Of[error](src), dst) +} diff --git a/v2/ioeither/monad.go b/v2/ioeither/monad.go new file mode 100644 index 0000000..cf0726e --- /dev/null +++ b/v2/ioeither/monad.go @@ -0,0 +1,69 @@ +// Copyright (c) 2024 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package ioeither + +import ( + "github.com/IBM/fp-go/v2/internal/functor" + "github.com/IBM/fp-go/v2/internal/monad" + "github.com/IBM/fp-go/v2/internal/pointed" +) + +type ( + ioEitherPointed[E, A any] struct{} + + ioEitherMonad[E, A, B any] struct{} + + ioEitherFunctor[E, A, B any] struct{} +) + +func (o *ioEitherPointed[E, A]) Of(a A) IOEither[E, A] { + return Of[E, A](a) +} + +func (o *ioEitherMonad[E, A, B]) Of(a A) IOEither[E, A] { + return Of[E, A](a) +} + +func (o *ioEitherMonad[E, A, B]) Map(f func(A) B) Operator[E, A, B] { + return Map[E, A, B](f) +} + +func (o *ioEitherMonad[E, A, B]) Chain(f func(A) IOEither[E, B]) Operator[E, A, B] { + return Chain[E, A, B](f) +} + +func (o *ioEitherMonad[E, A, B]) Ap(fa IOEither[E, A]) Operator[E, func(A) B, B] { + return Ap[B, E, A](fa) +} + +func (o *ioEitherFunctor[E, A, B]) Map(f func(A) B) Operator[E, A, B] { + return Map[E, A, B](f) +} + +// Pointed implements the pointed operations for [IOEither] +func Pointed[E, A any]() pointed.Pointed[A, IOEither[E, A]] { + return &ioEitherPointed[E, A]{} +} + +// Functor implements the monadic operations for [IOEither] +func Functor[E, A, B any]() functor.Functor[A, B, IOEither[E, A], IOEither[E, B]] { + return &ioEitherFunctor[E, A, B]{} +} + +// Monad implements the monadic operations for [IOEither] +func Monad[E, A, B any]() monad.Monad[A, B, IOEither[E, A], IOEither[E, B], IOEither[E, func(A) B]] { + return &ioEitherMonad[E, A, B]{} +} diff --git a/v2/ioeither/monoid.go b/v2/ioeither/monoid.go new file mode 100644 index 0000000..fbfce45 --- /dev/null +++ b/v2/ioeither/monoid.go @@ -0,0 +1,60 @@ +// Copyright (c) 2023 IBM Corp. +// All rights reserved. +// +// 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. + +package ioeither + +import ( + "github.com/IBM/fp-go/v2/monoid" +) + +type ( + Monoid[E, A any] = monoid.Monoid[IOEither[E, A]] +) + +// ApplicativeMonoid returns a [Monoid] that concatenates [IOEither] instances via their applicative +func ApplicativeMonoid[E, A any]( + m monoid.Monoid[A], +) Monoid[E, A] { + return monoid.ApplicativeMonoid( + MonadOf[E, A], + MonadMap[E, A, func(A) A], + MonadAp[A, E, A], + m, + ) +} + +// ApplicativeMonoid returns a [Monoid] that concatenates [IOEither] instances via their applicative +func ApplicativeMonoidSeq[E, A any]( + m monoid.Monoid[A], +) Monoid[E, A] { + return monoid.ApplicativeMonoid( + MonadOf[E, A], + MonadMap[E, A, func(A) A], + MonadApSeq[A, E, A], + m, + ) +} + +// ApplicativeMonoid returns a [Monoid] that concatenates [IOEither] instances via their applicative +func ApplicativeMonoidPar[E, A any]( + m monoid.Monoid[A], +) Monoid[E, A] { + return monoid.ApplicativeMonoid( + MonadOf[E, A], + MonadMap[E, A, func(A) A], + MonadApPar[A, E, A], + m, + ) +} diff --git a/v2/ioeither/monoid_test.go b/v2/ioeither/monoid_test.go new file mode 100644 index 0000000..3c74974 --- /dev/null +++ b/v2/ioeither/monoid_test.go @@ -0,0 +1,42 @@ +// Copyright (c) 2023 IBM Corp. +// All rights reserved. +// +// 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. + +package ioeither + +import ( + "fmt" + "testing" + + E "github.com/IBM/fp-go/v2/either" + S "github.com/IBM/fp-go/v2/string" + "github.com/stretchr/testify/assert" +) + +func TestApplicativeMonoid(t *testing.T) { + m := ApplicativeMonoid[error](S.Monoid) + + // good cases + assert.Equal(t, E.Of[error]("ab"), m.Concat(Of[error]("a"), Of[error]("b"))()) + assert.Equal(t, E.Of[error]("a"), m.Concat(Of[error]("a"), m.Empty())()) + assert.Equal(t, E.Of[error]("b"), m.Concat(m.Empty(), Of[error]("b"))()) + + // bad cases + e1 := fmt.Errorf("e1") + e2 := fmt.Errorf("e1") + + assert.Equal(t, E.Left[string](e1), m.Concat(Left[string](e1), Of[error]("b"))()) + assert.Equal(t, E.Left[string](e1), m.Concat(Left[string](e1), Left[string](e2))()) + assert.Equal(t, E.Left[string](e2), m.Concat(Of[error]("a"), Left[string](e2))()) +} diff --git a/v2/ioeither/retry.go b/v2/ioeither/retry.go new file mode 100644 index 0000000..6e55e9f --- /dev/null +++ b/v2/ioeither/retry.go @@ -0,0 +1,34 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package ioeither + +import ( + "github.com/IBM/fp-go/v2/io" + R "github.com/IBM/fp-go/v2/retry" +) + +// Retrying will retry the actions according to the check policy +// +// policy - refers to the retry policy +// action - converts a status into an operation to be executed +// check - checks if the result of the action needs to be retried +func Retrying[E, A any]( + policy R.RetryPolicy, + action func(R.RetryStatus) IOEither[E, A], + check func(Either[E, A]) bool, +) IOEither[E, A] { + return io.Retrying(policy, action, check) +} diff --git a/v2/ioeither/retry_test.go b/v2/ioeither/retry_test.go new file mode 100644 index 0000000..934c609 --- /dev/null +++ b/v2/ioeither/retry_test.go @@ -0,0 +1,48 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package ioeither + +import ( + "fmt" + "testing" + "time" + + E "github.com/IBM/fp-go/v2/either" + R "github.com/IBM/fp-go/v2/retry" + "github.com/stretchr/testify/assert" +) + +var expLogBackoff = R.ExponentialBackoff(10 * time.Millisecond) + +// our retry policy with a 1s cap +var testLogPolicy = R.CapDelay( + 2*time.Second, + R.Monoid.Concat(expLogBackoff, R.LimitRetries(20)), +) + +func TestRetry(t *testing.T) { + action := func(status R.RetryStatus) IOEither[error, string] { + if status.IterNumber < 5 { + return Left[string](fmt.Errorf("retrying %d", status.IterNumber)) + } + return Of[error](fmt.Sprintf("Retrying %d", status.IterNumber)) + } + check := E.IsLeft[error, string] + + r := Retrying(testLogPolicy, action, check) + + assert.Equal(t, E.Of[error]("Retrying 5"), r()) +} diff --git a/v2/ioeither/semigroup.go b/v2/ioeither/semigroup.go new file mode 100644 index 0000000..d5e927d --- /dev/null +++ b/v2/ioeither/semigroup.go @@ -0,0 +1,31 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package ioeither + +import ( + "github.com/IBM/fp-go/v2/semigroup" +) + +type ( + Semigroup[E, A any] = semigroup.Semigroup[IOEither[E, A]] +) + +// AltSemigroup is a [Semigroup] that tries the first item and then the second one using an alternative +func AltSemigroup[E, A any]() semigroup.Semigroup[IOEither[E, A]] { + return semigroup.AltSemigroup( + MonadAlt[E, A], + ) +} diff --git a/v2/ioeither/sequence_test.go b/v2/ioeither/sequence_test.go new file mode 100644 index 0000000..9526032 --- /dev/null +++ b/v2/ioeither/sequence_test.go @@ -0,0 +1,82 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package ioeither + +import ( + "fmt" + + A "github.com/IBM/fp-go/v2/array" + E "github.com/IBM/fp-go/v2/either" + F "github.com/IBM/fp-go/v2/function" + "github.com/stretchr/testify/assert" + + TST "github.com/IBM/fp-go/v2/internal/testing" + + "testing" +) + +func TestMapSeq(t *testing.T) { + var results []string + + handler := func(value string) IOEither[error, string] { + return func() E.Either[error, string] { + results = append(results, value) + return E.Of[error](value) + } + } + + src := A.From("a", "b", "c") + + res := F.Pipe2( + src, + TraverseArraySeq(handler), + Map[error](func(data []string) bool { + return assert.Equal(t, data, results) + }), + ) + + assert.Equal(t, E.Of[error](true), res()) +} + +func TestSequenceArray(t *testing.T) { + + s := TST.SequenceArrayTest( + FromStrictEquals[error, bool](), + Pointed[error, string](), + Pointed[error, bool](), + Functor[error, []string, bool](), + SequenceArray[error, string], + ) + + for i := 0; i < 10; i++ { + t.Run(fmt.Sprintf("TestSequenceArray %d", i), s(i)) + } +} + +func TestSequenceArrayError(t *testing.T) { + + s := TST.SequenceArrayErrorTest( + FromStrictEquals[error, bool](), + Left[string, error], + Left[bool, error], + Pointed[error, string](), + Pointed[error, bool](), + Functor[error, []string, bool](), + SequenceArray[error, string], + ) + // run across four bits + s(4)(t) +} diff --git a/v2/ioeither/sync.go b/v2/ioeither/sync.go new file mode 100644 index 0000000..52a3b2a --- /dev/null +++ b/v2/ioeither/sync.go @@ -0,0 +1,27 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package ioeither + +import ( + "context" + + "github.com/IBM/fp-go/v2/io" +) + +// WithLock executes the provided IO operation in the scope of a lock +func WithLock[E, A any](lock IO[context.CancelFunc]) func(fa IOEither[E, A]) IOEither[E, A] { + return io.WithLock[Either[E, A]](lock) +} diff --git a/v2/ioeither/testing/laws.go b/v2/ioeither/testing/laws.go new file mode 100644 index 0000000..f04b64d --- /dev/null +++ b/v2/ioeither/testing/laws.go @@ -0,0 +1,76 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package testing + +import ( + "testing" + + "github.com/IBM/fp-go/v2/either" + EQ "github.com/IBM/fp-go/v2/eq" + L "github.com/IBM/fp-go/v2/internal/monad/testing" + "github.com/IBM/fp-go/v2/ioeither" +) + +// AssertLaws asserts the apply monad laws for the `IOEither` monad +func AssertLaws[E, A, B, C any](t *testing.T, + eqe EQ.Eq[E], + eqa EQ.Eq[A], + eqb EQ.Eq[B], + eqc EQ.Eq[C], + + ab func(A) B, + bc func(B) C, +) func(a A) bool { + + return L.AssertLaws(t, + ioeither.Eq(either.Eq(eqe, eqa)), + ioeither.Eq(either.Eq(eqe, eqb)), + ioeither.Eq(either.Eq(eqe, eqc)), + + ioeither.Of[E, A], + ioeither.Of[E, B], + ioeither.Of[E, C], + + ioeither.Of[E, func(A) A], + ioeither.Of[E, func(A) B], + ioeither.Of[E, func(B) C], + ioeither.Of[E, func(func(A) B) B], + + ioeither.MonadMap[E, A, A], + ioeither.MonadMap[E, A, B], + ioeither.MonadMap[E, A, C], + ioeither.MonadMap[E, B, C], + + ioeither.MonadMap[E, func(B) C, func(func(A) B) func(A) C], + + ioeither.MonadChain[E, A, A], + ioeither.MonadChain[E, A, B], + ioeither.MonadChain[E, A, C], + ioeither.MonadChain[E, B, C], + + ioeither.MonadAp[A, E, A], + ioeither.MonadAp[B, E, A], + ioeither.MonadAp[C, E, B], + ioeither.MonadAp[C, E, A], + + ioeither.MonadAp[B, E, func(A) B], + ioeither.MonadAp[func(A) C, E, func(A) B], + + ab, + bc, + ) + +} diff --git a/v2/ioeither/testing/laws_test.go b/v2/ioeither/testing/laws_test.go new file mode 100644 index 0000000..d820332 --- /dev/null +++ b/v2/ioeither/testing/laws_test.go @@ -0,0 +1,48 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package testing + +import ( + "fmt" + "testing" + + EQ "github.com/IBM/fp-go/v2/eq" + "github.com/stretchr/testify/assert" +) + +func TestMonadLaws(t *testing.T) { + // some comparison + eqe := EQ.FromStrictEquals[string]() + eqa := EQ.FromStrictEquals[bool]() + eqb := EQ.FromStrictEquals[int]() + eqc := EQ.FromStrictEquals[string]() + + ab := func(a bool) int { + if a { + return 1 + } + return 0 + } + + bc := func(b int) string { + return fmt.Sprintf("value %d", b) + } + + laws := AssertLaws(t, eqe, eqa, eqb, eqc, ab, bc) + + assert.True(t, laws(true)) + assert.True(t, laws(false)) +} diff --git a/v2/ioeither/traverse.go b/v2/ioeither/traverse.go new file mode 100644 index 0000000..ab94b17 --- /dev/null +++ b/v2/ioeither/traverse.go @@ -0,0 +1,184 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package ioeither + +import ( + "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/internal/array" + "github.com/IBM/fp-go/v2/internal/record" +) + +// TraverseArray transforms an array +func TraverseArray[E, A, B any](f func(A) IOEither[E, B]) func([]A) IOEither[E, []B] { + return array.Traverse[[]A]( + Of[E, []B], + Map[E, []B, func(B) []B], + Ap[[]B, E, B], + + f, + ) +} + +// TraverseArrayWithIndex transforms an array +func TraverseArrayWithIndex[E, A, B any](f func(int, A) IOEither[E, B]) func([]A) IOEither[E, []B] { + return array.TraverseWithIndex[[]A]( + Of[E, []B], + Map[E, []B, func(B) []B], + Ap[[]B, E, B], + + f, + ) +} + +// SequenceArray converts a homogeneous sequence of either into an either of sequence +func SequenceArray[E, A any](ma []IOEither[E, A]) IOEither[E, []A] { + return TraverseArray(function.Identity[IOEither[E, A]])(ma) +} + +// TraverseRecord transforms a record +func TraverseRecord[K comparable, E, A, B any](f func(A) IOEither[E, B]) func(map[K]A) IOEither[E, map[K]B] { + return record.Traverse[map[K]A]( + Of[E, map[K]B], + Map[E, map[K]B, func(B) map[K]B], + Ap[map[K]B, E, B], + + f, + ) +} + +// TraverseRecordWithIndex transforms a record +func TraverseRecordWithIndex[K comparable, E, A, B any](f func(K, A) IOEither[E, B]) func(map[K]A) IOEither[E, map[K]B] { + return record.TraverseWithIndex[map[K]A]( + Of[E, map[K]B], + Map[E, map[K]B, func(B) map[K]B], + Ap[map[K]B, E, B], + + f, + ) +} + +// SequenceRecord converts a homogeneous sequence of either into an either of sequence +func SequenceRecord[K comparable, E, A any](ma map[K]IOEither[E, A]) IOEither[E, map[K]A] { + return TraverseRecord[K](function.Identity[IOEither[E, A]])(ma) +} + +// TraverseArraySeq transforms an array +func TraverseArraySeq[E, A, B any](f func(A) IOEither[E, B]) func([]A) IOEither[E, []B] { + return array.Traverse[[]A]( + Of[E, []B], + Map[E, []B, func(B) []B], + ApSeq[[]B, E, B], + + f, + ) +} + +// TraverseArrayWithIndexSeq transforms an array +func TraverseArrayWithIndexSeq[E, A, B any](f func(int, A) IOEither[E, B]) func([]A) IOEither[E, []B] { + return array.TraverseWithIndex[[]A]( + Of[E, []B], + Map[E, []B, func(B) []B], + ApSeq[[]B, E, B], + + f, + ) +} + +// SequenceArraySeq converts a homogeneous sequence of either into an either of sequence +func SequenceArraySeq[E, A any](ma []IOEither[E, A]) IOEither[E, []A] { + return TraverseArraySeq(function.Identity[IOEither[E, A]])(ma) +} + +// TraverseRecordSeq transforms a record +func TraverseRecordSeq[K comparable, E, A, B any](f func(A) IOEither[E, B]) func(map[K]A) IOEither[E, map[K]B] { + return record.Traverse[map[K]A]( + Of[E, map[K]B], + Map[E, map[K]B, func(B) map[K]B], + ApSeq[map[K]B, E, B], + + f, + ) +} + +// TraverseRecordWithIndexSeq transforms a record +func TraverseRecordWithIndexSeq[K comparable, E, A, B any](f func(K, A) IOEither[E, B]) func(map[K]A) IOEither[E, map[K]B] { + return record.TraverseWithIndex[map[K]A]( + Of[E, map[K]B], + Map[E, map[K]B, func(B) map[K]B], + ApSeq[map[K]B, E, B], + + f, + ) +} + +// SequenceRecordSeq converts a homogeneous sequence of either into an either of sequence +func SequenceRecordSeq[K comparable, E, A any](ma map[K]IOEither[E, A]) IOEither[E, map[K]A] { + return TraverseRecordSeq[K](function.Identity[IOEither[E, A]])(ma) +} + +// TraverseArrayPar transforms an array +func TraverseArrayPar[E, A, B any](f func(A) IOEither[E, B]) func([]A) IOEither[E, []B] { + return array.Traverse[[]A]( + Of[E, []B], + Map[E, []B, func(B) []B], + ApPar[[]B, E, B], + + f, + ) +} + +// TraverseArrayWithIndexPar transforms an array +func TraverseArrayWithIndexPar[E, A, B any](f func(int, A) IOEither[E, B]) func([]A) IOEither[E, []B] { + return array.TraverseWithIndex[[]A]( + Of[E, []B], + Map[E, []B, func(B) []B], + ApPar[[]B, E, B], + + f, + ) +} + +// SequenceArrayPar converts a homogeneous Paruence of either into an either of Paruence +func SequenceArrayPar[E, A any](ma []IOEither[E, A]) IOEither[E, []A] { + return TraverseArrayPar(function.Identity[IOEither[E, A]])(ma) +} + +// TraverseRecordPar transforms a record +func TraverseRecordPar[K comparable, E, A, B any](f func(A) IOEither[E, B]) func(map[K]A) IOEither[E, map[K]B] { + return record.Traverse[map[K]A]( + Of[E, map[K]B], + Map[E, map[K]B, func(B) map[K]B], + ApPar[map[K]B, E, B], + + f, + ) +} + +// TraverseRecordWithIndexPar transforms a record +func TraverseRecordWithIndexPar[K comparable, E, A, B any](f func(K, A) IOEither[E, B]) func(map[K]A) IOEither[E, map[K]B] { + return record.TraverseWithIndex[map[K]A]( + Of[E, map[K]B], + Map[E, map[K]B, func(B) map[K]B], + ApSeq[map[K]B, E, B], + + f, + ) +} + +// SequenceRecordPar converts a homogeneous Paruence of either into an either of Paruence +func SequenceRecordPar[K comparable, E, A any](ma map[K]IOEither[E, A]) IOEither[E, map[K]A] { + return TraverseRecordPar[K](function.Identity[IOEither[E, A]])(ma) +} diff --git a/v2/ioeither/traverse_test.go b/v2/ioeither/traverse_test.go new file mode 100644 index 0000000..1423bb8 --- /dev/null +++ b/v2/ioeither/traverse_test.go @@ -0,0 +1,37 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package ioeither + +import ( + "fmt" + "testing" + + A "github.com/IBM/fp-go/v2/array" + E "github.com/IBM/fp-go/v2/either" + "github.com/stretchr/testify/assert" +) + +func TestTraverseArray(t *testing.T) { + + src := A.From("A", "B") + + trfrm := TraverseArrayWithIndex(func(idx int, data string) IOEither[error, string] { + return Of[error](fmt.Sprintf("idx: %d, data: %s", idx, data)) + }) + + assert.Equal(t, E.Of[error](A.From("idx: 0, data: A", "idx: 1, data: B")), trfrm(src)()) + +} diff --git a/v2/iooption/array.go b/v2/iooption/array.go new file mode 100644 index 0000000..17d6712 --- /dev/null +++ b/v2/iooption/array.go @@ -0,0 +1,43 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package iooption + +import ( + "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/io" + "github.com/IBM/fp-go/v2/option" +) + +// TraverseArray transforms an array +func TraverseArray[A, B any](f func(A) IOOption[B]) func([]A) IOOption[[]B] { + return function.Flow2( + io.TraverseArray(f), + io.Map(option.SequenceArray[B]), + ) +} + +// TraverseArrayWithIndex transforms an array +func TraverseArrayWithIndex[A, B any](f func(int, A) IOOption[B]) func([]A) IOOption[[]B] { + return function.Flow2( + io.TraverseArrayWithIndex(f), + io.Map(option.SequenceArray[B]), + ) +} + +// SequenceArray converts a homogeneous sequence of either into an either of sequence +func SequenceArray[A any](ma []IOOption[A]) IOOption[[]A] { + return TraverseArray(function.Identity[IOOption[A]])(ma) +} diff --git a/v2/iooption/bind.go b/v2/iooption/bind.go new file mode 100644 index 0000000..557b77c --- /dev/null +++ b/v2/iooption/bind.go @@ -0,0 +1,89 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package iooption + +import ( + "github.com/IBM/fp-go/v2/internal/apply" + "github.com/IBM/fp-go/v2/internal/chain" + "github.com/IBM/fp-go/v2/internal/functor" +) + +// Bind creates an empty context of type [S] to be used with the [Bind] operation +func Do[S any]( + empty S, +) IOOption[S] { + return Of(empty) +} + +// Bind attaches the result of a computation to a context [S1] to produce a context [S2] +func Bind[S1, S2, T any]( + setter func(T) func(S1) S2, + f func(S1) IOOption[T], +) func(IOOption[S1]) IOOption[S2] { + return chain.Bind( + Chain[S1, S2], + Map[T, S2], + setter, + f, + ) +} + +// Let attaches the result of a computation to a context [S1] to produce a context [S2] +func Let[S1, S2, T any]( + setter func(T) func(S1) S2, + f func(S1) T, +) func(IOOption[S1]) IOOption[S2] { + return functor.Let( + Map[S1, S2], + setter, + f, + ) +} + +// LetTo attaches the a value to a context [S1] to produce a context [S2] +func LetTo[S1, S2, T any]( + setter func(T) func(S1) S2, + b T, +) func(IOOption[S1]) IOOption[S2] { + return functor.LetTo( + Map[S1, S2], + setter, + b, + ) +} + +// BindTo initializes a new state [S1] from a value [T] +func BindTo[S1, T any]( + setter func(T) S1, +) func(IOOption[T]) IOOption[S1] { + return chain.BindTo( + Map[T, S1], + setter, + ) +} + +// ApS attaches a value to a context [S1] to produce a context [S2] by considering the context and the value concurrently +func ApS[S1, S2, T any]( + setter func(T) func(S1) S2, + fa IOOption[T], +) func(IOOption[S1]) IOOption[S2] { + return apply.ApS( + Ap[S2, T], + Map[S1, func(T) S2], + setter, + fa, + ) +} diff --git a/v2/iooption/bind_test.go b/v2/iooption/bind_test.go new file mode 100644 index 0000000..82713d9 --- /dev/null +++ b/v2/iooption/bind_test.go @@ -0,0 +1,57 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package iooption + +import ( + "testing" + + F "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/internal/utils" + O "github.com/IBM/fp-go/v2/option" + "github.com/stretchr/testify/assert" +) + +func getLastName(s utils.Initial) IOOption[string] { + return Of("Doe") +} + +func getGivenName(s utils.WithLastName) IOOption[string] { + return Of("John") +} + +func TestBind(t *testing.T) { + + res := F.Pipe3( + Do(utils.Empty), + Bind(utils.SetLastName, getLastName), + Bind(utils.SetGivenName, getGivenName), + Map(utils.GetFullName), + ) + + assert.Equal(t, res(), O.Of("John Doe")) +} + +func TestApS(t *testing.T) { + + res := F.Pipe3( + Do(utils.Empty), + ApS(utils.SetLastName, Of("Doe")), + ApS(utils.SetGivenName, Of("John")), + Map(utils.GetFullName), + ) + + assert.Equal(t, res(), O.Of("John Doe")) +} diff --git a/v2/iooption/bracket.go b/v2/iooption/bracket.go new file mode 100644 index 0000000..e0292b9 --- /dev/null +++ b/v2/iooption/bracket.go @@ -0,0 +1,40 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package iooption + +import ( + G "github.com/IBM/fp-go/v2/internal/bracket" + "github.com/IBM/fp-go/v2/io" +) + +// Bracket makes sure that a resource is cleaned up in the event of an error. The release action is called regardless of +// whether the body action returns and error or not. +func Bracket[A, B, ANY any]( + acquire IOOption[A], + use func(A) IOOption[B], + release func(A, Option[B]) IOOption[ANY], +) IOOption[B] { + return G.Bracket[IOOption[A], IOOption[B], IOOption[ANY], Option[B], A, B]( + io.Of[Option[B]], + MonadChain[A, B], + io.MonadChain[Option[B], Option[B]], + MonadChain[ANY, B], + + acquire, + use, + release, + ) +} diff --git a/v2/iooption/doc.go b/v2/iooption/doc.go new file mode 100644 index 0000000..fc4e657 --- /dev/null +++ b/v2/iooption/doc.go @@ -0,0 +1,18 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package iooption + +//go:generate go run .. iooption --count 10 --filename gen.go diff --git a/v2/iooption/eq.go b/v2/iooption/eq.go new file mode 100644 index 0000000..8f87039 --- /dev/null +++ b/v2/iooption/eq.go @@ -0,0 +1,32 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package iooption + +import ( + EQ "github.com/IBM/fp-go/v2/eq" + "github.com/IBM/fp-go/v2/io" + O "github.com/IBM/fp-go/v2/option" +) + +// Eq implements the equals predicate for values contained in the IO monad +func Eq[A any](eq EQ.Eq[A]) EQ.Eq[IOOption[A]] { + return io.Eq(O.Eq(eq)) +} + +// FromStrictEquals constructs an [EQ.Eq] from the canonical comparison function +func FromStrictEquals[A comparable]() EQ.Eq[IOOption[A]] { + return Eq(EQ.FromStrictEquals[A]()) +} diff --git a/v2/iooption/gen.go b/v2/iooption/gen.go new file mode 100644 index 0000000..a37a745 --- /dev/null +++ b/v2/iooption/gen.go @@ -0,0 +1,1691 @@ +// Code generated by go generate; DO NOT EDIT. +// This file was generated by robots at +// 2025-03-09 23:53:07.9869507 +0100 CET m=+0.002080401 + +package iooption + + +import ( + "github.com/IBM/fp-go/v2/tuple" + "github.com/IBM/fp-go/v2/internal/apply" +) + +// SequenceT1 converts 1 [IOOption[T]] into a [IOOption[tuple.Tuple1[T1]]] +func SequenceT1[T1 any]( + t1 IOOption[T1], +) IOOption[tuple.Tuple1[T1]] { + return apply.SequenceT1( + Map[T1, tuple.Tuple1[T1]], + t1, + ) +} + +// SequenceSeqT1 converts 1 [IOOption[T]] into a [IOOption[tuple.Tuple1[T1]]] +func SequenceSeqT1[T1 any]( + t1 IOOption[T1], +) IOOption[tuple.Tuple1[T1]] { + return apply.SequenceT1( + Map[T1, tuple.Tuple1[T1]], + t1, + ) +} + +// SequenceParT1 converts 1 [IOOption[T]] into a [IOOption[tuple.Tuple1[T1]]] +func SequenceParT1[T1 any]( + t1 IOOption[T1], +) IOOption[tuple.Tuple1[T1]] { + return apply.SequenceT1( + Map[T1, tuple.Tuple1[T1]], + t1, + ) +} + +// SequenceTuple1 converts a [tuple.Tuple1[IOOption[T]]] into a [IOOption[tuple.Tuple1[T1]]] +func SequenceTuple1[T1 any](t tuple.Tuple1[IOOption[T1]]) IOOption[tuple.Tuple1[T1]] { + return apply.SequenceTuple1( + Map[T1, tuple.Tuple1[T1]], + t, + ) +} + +// SequenceSeqTuple1 converts a [tuple.Tuple1[IOOption[T]]] into a [IOOption[tuple.Tuple1[T1]]] +func SequenceSeqTuple1[T1 any](t tuple.Tuple1[IOOption[T1]]) IOOption[tuple.Tuple1[T1]] { + return apply.SequenceTuple1( + Map[T1, tuple.Tuple1[T1]], + t, + ) +} + +// SequenceParTuple1 converts a [tuple.Tuple1[IOOption[T]]] into a [IOOption[tuple.Tuple1[T1]]] +func SequenceParTuple1[T1 any](t tuple.Tuple1[IOOption[T1]]) IOOption[tuple.Tuple1[T1]] { + return apply.SequenceTuple1( + Map[T1, tuple.Tuple1[T1]], + t, + ) +} + +// TraverseTuple1 converts a [tuple.Tuple1[A1]] into a [IOOption[tuple.Tuple1[T1]]] +func TraverseTuple1[F1 ~func(A1) IOOption[T1], T1, A1 any](f1 F1) func(tuple.Tuple1[A1]) IOOption[tuple.Tuple1[T1]] { + return func(t tuple.Tuple1[A1]) IOOption[tuple.Tuple1[T1]] { + return apply.TraverseTuple1( + Map[T1, tuple.Tuple1[T1]], + f1, + t, + ) + } +} + +// TraverseSeqTuple1 converts a [tuple.Tuple1[A1]] into a [IOOption[tuple.Tuple1[T1]]] +func TraverseSeqTuple1[F1 ~func(A1) IOOption[T1], T1, A1 any](f1 F1) func(tuple.Tuple1[A1]) IOOption[tuple.Tuple1[T1]] { + return func(t tuple.Tuple1[A1]) IOOption[tuple.Tuple1[T1]] { + return apply.TraverseTuple1( + Map[T1, tuple.Tuple1[T1]], + f1, + t, + ) + } +} + +// TraverseParTuple1 converts a [tuple.Tuple1[A1]] into a [IOOption[tuple.Tuple1[T1]]] +func TraverseParTuple1[F1 ~func(A1) IOOption[T1], T1, A1 any](f1 F1) func(tuple.Tuple1[A1]) IOOption[tuple.Tuple1[T1]] { + return func(t tuple.Tuple1[A1]) IOOption[tuple.Tuple1[T1]] { + return apply.TraverseTuple1( + Map[T1, tuple.Tuple1[T1]], + f1, + t, + ) + } +} + +// SequenceT2 converts 2 [IOOption[T]] into a [IOOption[tuple.Tuple2[T1, T2]]] +func SequenceT2[T1, T2 any]( + t1 IOOption[T1], + t2 IOOption[T2], +) IOOption[tuple.Tuple2[T1, T2]] { + return apply.SequenceT2( + Map[T1, func(T2) tuple.Tuple2[T1, T2]], + Ap[tuple.Tuple2[T1, T2], T2], + t1, + t2, + ) +} + +// SequenceSeqT2 converts 2 [IOOption[T]] into a [IOOption[tuple.Tuple2[T1, T2]]] +func SequenceSeqT2[T1, T2 any]( + t1 IOOption[T1], + t2 IOOption[T2], +) IOOption[tuple.Tuple2[T1, T2]] { + return apply.SequenceT2( + Map[T1, func(T2) tuple.Tuple2[T1, T2]], + ApSeq[tuple.Tuple2[T1, T2], T2], + t1, + t2, + ) +} + +// SequenceParT2 converts 2 [IOOption[T]] into a [IOOption[tuple.Tuple2[T1, T2]]] +func SequenceParT2[T1, T2 any]( + t1 IOOption[T1], + t2 IOOption[T2], +) IOOption[tuple.Tuple2[T1, T2]] { + return apply.SequenceT2( + Map[T1, func(T2) tuple.Tuple2[T1, T2]], + ApPar[tuple.Tuple2[T1, T2], T2], + t1, + t2, + ) +} + +// SequenceTuple2 converts a [tuple.Tuple2[IOOption[T]]] into a [IOOption[tuple.Tuple2[T1, T2]]] +func SequenceTuple2[T1, T2 any](t tuple.Tuple2[IOOption[T1], IOOption[T2]]) IOOption[tuple.Tuple2[T1, T2]] { + return apply.SequenceTuple2( + Map[T1, func(T2) tuple.Tuple2[T1, T2]], + Ap[tuple.Tuple2[T1, T2], T2], + t, + ) +} + +// SequenceSeqTuple2 converts a [tuple.Tuple2[IOOption[T]]] into a [IOOption[tuple.Tuple2[T1, T2]]] +func SequenceSeqTuple2[T1, T2 any](t tuple.Tuple2[IOOption[T1], IOOption[T2]]) IOOption[tuple.Tuple2[T1, T2]] { + return apply.SequenceTuple2( + Map[T1, func(T2) tuple.Tuple2[T1, T2]], + ApSeq[tuple.Tuple2[T1, T2], T2], + t, + ) +} + +// SequenceParTuple2 converts a [tuple.Tuple2[IOOption[T]]] into a [IOOption[tuple.Tuple2[T1, T2]]] +func SequenceParTuple2[T1, T2 any](t tuple.Tuple2[IOOption[T1], IOOption[T2]]) IOOption[tuple.Tuple2[T1, T2]] { + return apply.SequenceTuple2( + Map[T1, func(T2) tuple.Tuple2[T1, T2]], + ApPar[tuple.Tuple2[T1, T2], T2], + t, + ) +} + +// TraverseTuple2 converts a [tuple.Tuple2[A1, A2]] into a [IOOption[tuple.Tuple2[T1, T2]]] +func TraverseTuple2[F1 ~func(A1) IOOption[T1], F2 ~func(A2) IOOption[T2], T1, T2, A1, A2 any](f1 F1, f2 F2) func(tuple.Tuple2[A1, A2]) IOOption[tuple.Tuple2[T1, T2]] { + return func(t tuple.Tuple2[A1, A2]) IOOption[tuple.Tuple2[T1, T2]] { + return apply.TraverseTuple2( + Map[T1, func(T2) tuple.Tuple2[T1, T2]], + Ap[tuple.Tuple2[T1, T2], T2], + f1, + f2, + t, + ) + } +} + +// TraverseSeqTuple2 converts a [tuple.Tuple2[A1, A2]] into a [IOOption[tuple.Tuple2[T1, T2]]] +func TraverseSeqTuple2[F1 ~func(A1) IOOption[T1], F2 ~func(A2) IOOption[T2], T1, T2, A1, A2 any](f1 F1, f2 F2) func(tuple.Tuple2[A1, A2]) IOOption[tuple.Tuple2[T1, T2]] { + return func(t tuple.Tuple2[A1, A2]) IOOption[tuple.Tuple2[T1, T2]] { + return apply.TraverseTuple2( + Map[T1, func(T2) tuple.Tuple2[T1, T2]], + ApSeq[tuple.Tuple2[T1, T2], T2], + f1, + f2, + t, + ) + } +} + +// TraverseParTuple2 converts a [tuple.Tuple2[A1, A2]] into a [IOOption[tuple.Tuple2[T1, T2]]] +func TraverseParTuple2[F1 ~func(A1) IOOption[T1], F2 ~func(A2) IOOption[T2], T1, T2, A1, A2 any](f1 F1, f2 F2) func(tuple.Tuple2[A1, A2]) IOOption[tuple.Tuple2[T1, T2]] { + return func(t tuple.Tuple2[A1, A2]) IOOption[tuple.Tuple2[T1, T2]] { + return apply.TraverseTuple2( + Map[T1, func(T2) tuple.Tuple2[T1, T2]], + ApPar[tuple.Tuple2[T1, T2], T2], + f1, + f2, + t, + ) + } +} + +// SequenceT3 converts 3 [IOOption[T]] into a [IOOption[tuple.Tuple3[T1, T2, T3]]] +func SequenceT3[T1, T2, T3 any]( + t1 IOOption[T1], + t2 IOOption[T2], + t3 IOOption[T3], +) IOOption[tuple.Tuple3[T1, T2, T3]] { + return apply.SequenceT3( + Map[T1, func(T2) func(T3) tuple.Tuple3[T1, T2, T3]], + Ap[func(T3) tuple.Tuple3[T1, T2, T3], T2], + Ap[tuple.Tuple3[T1, T2, T3], T3], + t1, + t2, + t3, + ) +} + +// SequenceSeqT3 converts 3 [IOOption[T]] into a [IOOption[tuple.Tuple3[T1, T2, T3]]] +func SequenceSeqT3[T1, T2, T3 any]( + t1 IOOption[T1], + t2 IOOption[T2], + t3 IOOption[T3], +) IOOption[tuple.Tuple3[T1, T2, T3]] { + return apply.SequenceT3( + Map[T1, func(T2) func(T3) tuple.Tuple3[T1, T2, T3]], + ApSeq[func(T3) tuple.Tuple3[T1, T2, T3], T2], + ApSeq[tuple.Tuple3[T1, T2, T3], T3], + t1, + t2, + t3, + ) +} + +// SequenceParT3 converts 3 [IOOption[T]] into a [IOOption[tuple.Tuple3[T1, T2, T3]]] +func SequenceParT3[T1, T2, T3 any]( + t1 IOOption[T1], + t2 IOOption[T2], + t3 IOOption[T3], +) IOOption[tuple.Tuple3[T1, T2, T3]] { + return apply.SequenceT3( + Map[T1, func(T2) func(T3) tuple.Tuple3[T1, T2, T3]], + ApPar[func(T3) tuple.Tuple3[T1, T2, T3], T2], + ApPar[tuple.Tuple3[T1, T2, T3], T3], + t1, + t2, + t3, + ) +} + +// SequenceTuple3 converts a [tuple.Tuple3[IOOption[T]]] into a [IOOption[tuple.Tuple3[T1, T2, T3]]] +func SequenceTuple3[T1, T2, T3 any](t tuple.Tuple3[IOOption[T1], IOOption[T2], IOOption[T3]]) IOOption[tuple.Tuple3[T1, T2, T3]] { + return apply.SequenceTuple3( + Map[T1, func(T2) func(T3) tuple.Tuple3[T1, T2, T3]], + Ap[func(T3) tuple.Tuple3[T1, T2, T3], T2], + Ap[tuple.Tuple3[T1, T2, T3], T3], + t, + ) +} + +// SequenceSeqTuple3 converts a [tuple.Tuple3[IOOption[T]]] into a [IOOption[tuple.Tuple3[T1, T2, T3]]] +func SequenceSeqTuple3[T1, T2, T3 any](t tuple.Tuple3[IOOption[T1], IOOption[T2], IOOption[T3]]) IOOption[tuple.Tuple3[T1, T2, T3]] { + return apply.SequenceTuple3( + Map[T1, func(T2) func(T3) tuple.Tuple3[T1, T2, T3]], + ApSeq[func(T3) tuple.Tuple3[T1, T2, T3], T2], + ApSeq[tuple.Tuple3[T1, T2, T3], T3], + t, + ) +} + +// SequenceParTuple3 converts a [tuple.Tuple3[IOOption[T]]] into a [IOOption[tuple.Tuple3[T1, T2, T3]]] +func SequenceParTuple3[T1, T2, T3 any](t tuple.Tuple3[IOOption[T1], IOOption[T2], IOOption[T3]]) IOOption[tuple.Tuple3[T1, T2, T3]] { + return apply.SequenceTuple3( + Map[T1, func(T2) func(T3) tuple.Tuple3[T1, T2, T3]], + ApPar[func(T3) tuple.Tuple3[T1, T2, T3], T2], + ApPar[tuple.Tuple3[T1, T2, T3], T3], + t, + ) +} + +// TraverseTuple3 converts a [tuple.Tuple3[A1, A2, A3]] into a [IOOption[tuple.Tuple3[T1, T2, T3]]] +func TraverseTuple3[F1 ~func(A1) IOOption[T1], F2 ~func(A2) IOOption[T2], F3 ~func(A3) IOOption[T3], T1, T2, T3, A1, A2, A3 any](f1 F1, f2 F2, f3 F3) func(tuple.Tuple3[A1, A2, A3]) IOOption[tuple.Tuple3[T1, T2, T3]] { + return func(t tuple.Tuple3[A1, A2, A3]) IOOption[tuple.Tuple3[T1, T2, T3]] { + return apply.TraverseTuple3( + Map[T1, func(T2) func(T3) tuple.Tuple3[T1, T2, T3]], + Ap[func(T3) tuple.Tuple3[T1, T2, T3], T2], + Ap[tuple.Tuple3[T1, T2, T3], T3], + f1, + f2, + f3, + t, + ) + } +} + +// TraverseSeqTuple3 converts a [tuple.Tuple3[A1, A2, A3]] into a [IOOption[tuple.Tuple3[T1, T2, T3]]] +func TraverseSeqTuple3[F1 ~func(A1) IOOption[T1], F2 ~func(A2) IOOption[T2], F3 ~func(A3) IOOption[T3], T1, T2, T3, A1, A2, A3 any](f1 F1, f2 F2, f3 F3) func(tuple.Tuple3[A1, A2, A3]) IOOption[tuple.Tuple3[T1, T2, T3]] { + return func(t tuple.Tuple3[A1, A2, A3]) IOOption[tuple.Tuple3[T1, T2, T3]] { + return apply.TraverseTuple3( + Map[T1, func(T2) func(T3) tuple.Tuple3[T1, T2, T3]], + ApSeq[func(T3) tuple.Tuple3[T1, T2, T3], T2], + ApSeq[tuple.Tuple3[T1, T2, T3], T3], + f1, + f2, + f3, + t, + ) + } +} + +// TraverseParTuple3 converts a [tuple.Tuple3[A1, A2, A3]] into a [IOOption[tuple.Tuple3[T1, T2, T3]]] +func TraverseParTuple3[F1 ~func(A1) IOOption[T1], F2 ~func(A2) IOOption[T2], F3 ~func(A3) IOOption[T3], T1, T2, T3, A1, A2, A3 any](f1 F1, f2 F2, f3 F3) func(tuple.Tuple3[A1, A2, A3]) IOOption[tuple.Tuple3[T1, T2, T3]] { + return func(t tuple.Tuple3[A1, A2, A3]) IOOption[tuple.Tuple3[T1, T2, T3]] { + return apply.TraverseTuple3( + Map[T1, func(T2) func(T3) tuple.Tuple3[T1, T2, T3]], + ApPar[func(T3) tuple.Tuple3[T1, T2, T3], T2], + ApPar[tuple.Tuple3[T1, T2, T3], T3], + f1, + f2, + f3, + t, + ) + } +} + +// SequenceT4 converts 4 [IOOption[T]] into a [IOOption[tuple.Tuple4[T1, T2, T3, T4]]] +func SequenceT4[T1, T2, T3, T4 any]( + t1 IOOption[T1], + t2 IOOption[T2], + t3 IOOption[T3], + t4 IOOption[T4], +) IOOption[tuple.Tuple4[T1, T2, T3, T4]] { + return apply.SequenceT4( + Map[T1, func(T2) func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4]], + Ap[func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4], T2], + Ap[func(T4) tuple.Tuple4[T1, T2, T3, T4], T3], + Ap[tuple.Tuple4[T1, T2, T3, T4], T4], + t1, + t2, + t3, + t4, + ) +} + +// SequenceSeqT4 converts 4 [IOOption[T]] into a [IOOption[tuple.Tuple4[T1, T2, T3, T4]]] +func SequenceSeqT4[T1, T2, T3, T4 any]( + t1 IOOption[T1], + t2 IOOption[T2], + t3 IOOption[T3], + t4 IOOption[T4], +) IOOption[tuple.Tuple4[T1, T2, T3, T4]] { + return apply.SequenceT4( + Map[T1, func(T2) func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4]], + ApSeq[func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4], T2], + ApSeq[func(T4) tuple.Tuple4[T1, T2, T3, T4], T3], + ApSeq[tuple.Tuple4[T1, T2, T3, T4], T4], + t1, + t2, + t3, + t4, + ) +} + +// SequenceParT4 converts 4 [IOOption[T]] into a [IOOption[tuple.Tuple4[T1, T2, T3, T4]]] +func SequenceParT4[T1, T2, T3, T4 any]( + t1 IOOption[T1], + t2 IOOption[T2], + t3 IOOption[T3], + t4 IOOption[T4], +) IOOption[tuple.Tuple4[T1, T2, T3, T4]] { + return apply.SequenceT4( + Map[T1, func(T2) func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4]], + ApPar[func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4], T2], + ApPar[func(T4) tuple.Tuple4[T1, T2, T3, T4], T3], + ApPar[tuple.Tuple4[T1, T2, T3, T4], T4], + t1, + t2, + t3, + t4, + ) +} + +// SequenceTuple4 converts a [tuple.Tuple4[IOOption[T]]] into a [IOOption[tuple.Tuple4[T1, T2, T3, T4]]] +func SequenceTuple4[T1, T2, T3, T4 any](t tuple.Tuple4[IOOption[T1], IOOption[T2], IOOption[T3], IOOption[T4]]) IOOption[tuple.Tuple4[T1, T2, T3, T4]] { + return apply.SequenceTuple4( + Map[T1, func(T2) func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4]], + Ap[func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4], T2], + Ap[func(T4) tuple.Tuple4[T1, T2, T3, T4], T3], + Ap[tuple.Tuple4[T1, T2, T3, T4], T4], + t, + ) +} + +// SequenceSeqTuple4 converts a [tuple.Tuple4[IOOption[T]]] into a [IOOption[tuple.Tuple4[T1, T2, T3, T4]]] +func SequenceSeqTuple4[T1, T2, T3, T4 any](t tuple.Tuple4[IOOption[T1], IOOption[T2], IOOption[T3], IOOption[T4]]) IOOption[tuple.Tuple4[T1, T2, T3, T4]] { + return apply.SequenceTuple4( + Map[T1, func(T2) func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4]], + ApSeq[func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4], T2], + ApSeq[func(T4) tuple.Tuple4[T1, T2, T3, T4], T3], + ApSeq[tuple.Tuple4[T1, T2, T3, T4], T4], + t, + ) +} + +// SequenceParTuple4 converts a [tuple.Tuple4[IOOption[T]]] into a [IOOption[tuple.Tuple4[T1, T2, T3, T4]]] +func SequenceParTuple4[T1, T2, T3, T4 any](t tuple.Tuple4[IOOption[T1], IOOption[T2], IOOption[T3], IOOption[T4]]) IOOption[tuple.Tuple4[T1, T2, T3, T4]] { + return apply.SequenceTuple4( + Map[T1, func(T2) func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4]], + ApPar[func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4], T2], + ApPar[func(T4) tuple.Tuple4[T1, T2, T3, T4], T3], + ApPar[tuple.Tuple4[T1, T2, T3, T4], T4], + t, + ) +} + +// TraverseTuple4 converts a [tuple.Tuple4[A1, A2, A3, A4]] into a [IOOption[tuple.Tuple4[T1, T2, T3, T4]]] +func TraverseTuple4[F1 ~func(A1) IOOption[T1], F2 ~func(A2) IOOption[T2], F3 ~func(A3) IOOption[T3], F4 ~func(A4) IOOption[T4], T1, T2, T3, T4, A1, A2, A3, A4 any](f1 F1, f2 F2, f3 F3, f4 F4) func(tuple.Tuple4[A1, A2, A3, A4]) IOOption[tuple.Tuple4[T1, T2, T3, T4]] { + return func(t tuple.Tuple4[A1, A2, A3, A4]) IOOption[tuple.Tuple4[T1, T2, T3, T4]] { + return apply.TraverseTuple4( + Map[T1, func(T2) func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4]], + Ap[func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4], T2], + Ap[func(T4) tuple.Tuple4[T1, T2, T3, T4], T3], + Ap[tuple.Tuple4[T1, T2, T3, T4], T4], + f1, + f2, + f3, + f4, + t, + ) + } +} + +// TraverseSeqTuple4 converts a [tuple.Tuple4[A1, A2, A3, A4]] into a [IOOption[tuple.Tuple4[T1, T2, T3, T4]]] +func TraverseSeqTuple4[F1 ~func(A1) IOOption[T1], F2 ~func(A2) IOOption[T2], F3 ~func(A3) IOOption[T3], F4 ~func(A4) IOOption[T4], T1, T2, T3, T4, A1, A2, A3, A4 any](f1 F1, f2 F2, f3 F3, f4 F4) func(tuple.Tuple4[A1, A2, A3, A4]) IOOption[tuple.Tuple4[T1, T2, T3, T4]] { + return func(t tuple.Tuple4[A1, A2, A3, A4]) IOOption[tuple.Tuple4[T1, T2, T3, T4]] { + return apply.TraverseTuple4( + Map[T1, func(T2) func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4]], + ApSeq[func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4], T2], + ApSeq[func(T4) tuple.Tuple4[T1, T2, T3, T4], T3], + ApSeq[tuple.Tuple4[T1, T2, T3, T4], T4], + f1, + f2, + f3, + f4, + t, + ) + } +} + +// TraverseParTuple4 converts a [tuple.Tuple4[A1, A2, A3, A4]] into a [IOOption[tuple.Tuple4[T1, T2, T3, T4]]] +func TraverseParTuple4[F1 ~func(A1) IOOption[T1], F2 ~func(A2) IOOption[T2], F3 ~func(A3) IOOption[T3], F4 ~func(A4) IOOption[T4], T1, T2, T3, T4, A1, A2, A3, A4 any](f1 F1, f2 F2, f3 F3, f4 F4) func(tuple.Tuple4[A1, A2, A3, A4]) IOOption[tuple.Tuple4[T1, T2, T3, T4]] { + return func(t tuple.Tuple4[A1, A2, A3, A4]) IOOption[tuple.Tuple4[T1, T2, T3, T4]] { + return apply.TraverseTuple4( + Map[T1, func(T2) func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4]], + ApPar[func(T3) func(T4) tuple.Tuple4[T1, T2, T3, T4], T2], + ApPar[func(T4) tuple.Tuple4[T1, T2, T3, T4], T3], + ApPar[tuple.Tuple4[T1, T2, T3, T4], T4], + f1, + f2, + f3, + f4, + t, + ) + } +} + +// SequenceT5 converts 5 [IOOption[T]] into a [IOOption[tuple.Tuple5[T1, T2, T3, T4, T5]]] +func SequenceT5[T1, T2, T3, T4, T5 any]( + t1 IOOption[T1], + t2 IOOption[T2], + t3 IOOption[T3], + t4 IOOption[T4], + t5 IOOption[T5], +) IOOption[tuple.Tuple5[T1, T2, T3, T4, T5]] { + return apply.SequenceT5( + Map[T1, func(T2) func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5]], + Ap[func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T2], + Ap[func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T3], + Ap[func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T4], + Ap[tuple.Tuple5[T1, T2, T3, T4, T5], T5], + t1, + t2, + t3, + t4, + t5, + ) +} + +// SequenceSeqT5 converts 5 [IOOption[T]] into a [IOOption[tuple.Tuple5[T1, T2, T3, T4, T5]]] +func SequenceSeqT5[T1, T2, T3, T4, T5 any]( + t1 IOOption[T1], + t2 IOOption[T2], + t3 IOOption[T3], + t4 IOOption[T4], + t5 IOOption[T5], +) IOOption[tuple.Tuple5[T1, T2, T3, T4, T5]] { + return apply.SequenceT5( + Map[T1, func(T2) func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5]], + ApSeq[func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T2], + ApSeq[func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T3], + ApSeq[func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T4], + ApSeq[tuple.Tuple5[T1, T2, T3, T4, T5], T5], + t1, + t2, + t3, + t4, + t5, + ) +} + +// SequenceParT5 converts 5 [IOOption[T]] into a [IOOption[tuple.Tuple5[T1, T2, T3, T4, T5]]] +func SequenceParT5[T1, T2, T3, T4, T5 any]( + t1 IOOption[T1], + t2 IOOption[T2], + t3 IOOption[T3], + t4 IOOption[T4], + t5 IOOption[T5], +) IOOption[tuple.Tuple5[T1, T2, T3, T4, T5]] { + return apply.SequenceT5( + Map[T1, func(T2) func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5]], + ApPar[func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T2], + ApPar[func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T3], + ApPar[func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T4], + ApPar[tuple.Tuple5[T1, T2, T3, T4, T5], T5], + t1, + t2, + t3, + t4, + t5, + ) +} + +// SequenceTuple5 converts a [tuple.Tuple5[IOOption[T]]] into a [IOOption[tuple.Tuple5[T1, T2, T3, T4, T5]]] +func SequenceTuple5[T1, T2, T3, T4, T5 any](t tuple.Tuple5[IOOption[T1], IOOption[T2], IOOption[T3], IOOption[T4], IOOption[T5]]) IOOption[tuple.Tuple5[T1, T2, T3, T4, T5]] { + return apply.SequenceTuple5( + Map[T1, func(T2) func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5]], + Ap[func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T2], + Ap[func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T3], + Ap[func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T4], + Ap[tuple.Tuple5[T1, T2, T3, T4, T5], T5], + t, + ) +} + +// SequenceSeqTuple5 converts a [tuple.Tuple5[IOOption[T]]] into a [IOOption[tuple.Tuple5[T1, T2, T3, T4, T5]]] +func SequenceSeqTuple5[T1, T2, T3, T4, T5 any](t tuple.Tuple5[IOOption[T1], IOOption[T2], IOOption[T3], IOOption[T4], IOOption[T5]]) IOOption[tuple.Tuple5[T1, T2, T3, T4, T5]] { + return apply.SequenceTuple5( + Map[T1, func(T2) func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5]], + ApSeq[func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T2], + ApSeq[func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T3], + ApSeq[func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T4], + ApSeq[tuple.Tuple5[T1, T2, T3, T4, T5], T5], + t, + ) +} + +// SequenceParTuple5 converts a [tuple.Tuple5[IOOption[T]]] into a [IOOption[tuple.Tuple5[T1, T2, T3, T4, T5]]] +func SequenceParTuple5[T1, T2, T3, T4, T5 any](t tuple.Tuple5[IOOption[T1], IOOption[T2], IOOption[T3], IOOption[T4], IOOption[T5]]) IOOption[tuple.Tuple5[T1, T2, T3, T4, T5]] { + return apply.SequenceTuple5( + Map[T1, func(T2) func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5]], + ApPar[func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T2], + ApPar[func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T3], + ApPar[func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T4], + ApPar[tuple.Tuple5[T1, T2, T3, T4, T5], T5], + t, + ) +} + +// TraverseTuple5 converts a [tuple.Tuple5[A1, A2, A3, A4, A5]] into a [IOOption[tuple.Tuple5[T1, T2, T3, T4, T5]]] +func TraverseTuple5[F1 ~func(A1) IOOption[T1], F2 ~func(A2) IOOption[T2], F3 ~func(A3) IOOption[T3], F4 ~func(A4) IOOption[T4], F5 ~func(A5) IOOption[T5], T1, T2, T3, T4, T5, A1, A2, A3, A4, A5 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5) func(tuple.Tuple5[A1, A2, A3, A4, A5]) IOOption[tuple.Tuple5[T1, T2, T3, T4, T5]] { + return func(t tuple.Tuple5[A1, A2, A3, A4, A5]) IOOption[tuple.Tuple5[T1, T2, T3, T4, T5]] { + return apply.TraverseTuple5( + Map[T1, func(T2) func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5]], + Ap[func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T2], + Ap[func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T3], + Ap[func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T4], + Ap[tuple.Tuple5[T1, T2, T3, T4, T5], T5], + f1, + f2, + f3, + f4, + f5, + t, + ) + } +} + +// TraverseSeqTuple5 converts a [tuple.Tuple5[A1, A2, A3, A4, A5]] into a [IOOption[tuple.Tuple5[T1, T2, T3, T4, T5]]] +func TraverseSeqTuple5[F1 ~func(A1) IOOption[T1], F2 ~func(A2) IOOption[T2], F3 ~func(A3) IOOption[T3], F4 ~func(A4) IOOption[T4], F5 ~func(A5) IOOption[T5], T1, T2, T3, T4, T5, A1, A2, A3, A4, A5 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5) func(tuple.Tuple5[A1, A2, A3, A4, A5]) IOOption[tuple.Tuple5[T1, T2, T3, T4, T5]] { + return func(t tuple.Tuple5[A1, A2, A3, A4, A5]) IOOption[tuple.Tuple5[T1, T2, T3, T4, T5]] { + return apply.TraverseTuple5( + Map[T1, func(T2) func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5]], + ApSeq[func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T2], + ApSeq[func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T3], + ApSeq[func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T4], + ApSeq[tuple.Tuple5[T1, T2, T3, T4, T5], T5], + f1, + f2, + f3, + f4, + f5, + t, + ) + } +} + +// TraverseParTuple5 converts a [tuple.Tuple5[A1, A2, A3, A4, A5]] into a [IOOption[tuple.Tuple5[T1, T2, T3, T4, T5]]] +func TraverseParTuple5[F1 ~func(A1) IOOption[T1], F2 ~func(A2) IOOption[T2], F3 ~func(A3) IOOption[T3], F4 ~func(A4) IOOption[T4], F5 ~func(A5) IOOption[T5], T1, T2, T3, T4, T5, A1, A2, A3, A4, A5 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5) func(tuple.Tuple5[A1, A2, A3, A4, A5]) IOOption[tuple.Tuple5[T1, T2, T3, T4, T5]] { + return func(t tuple.Tuple5[A1, A2, A3, A4, A5]) IOOption[tuple.Tuple5[T1, T2, T3, T4, T5]] { + return apply.TraverseTuple5( + Map[T1, func(T2) func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5]], + ApPar[func(T3) func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T2], + ApPar[func(T4) func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T3], + ApPar[func(T5) tuple.Tuple5[T1, T2, T3, T4, T5], T4], + ApPar[tuple.Tuple5[T1, T2, T3, T4, T5], T5], + f1, + f2, + f3, + f4, + f5, + t, + ) + } +} + +// SequenceT6 converts 6 [IOOption[T]] into a [IOOption[tuple.Tuple6[T1, T2, T3, T4, T5, T6]]] +func SequenceT6[T1, T2, T3, T4, T5, T6 any]( + t1 IOOption[T1], + t2 IOOption[T2], + t3 IOOption[T3], + t4 IOOption[T4], + t5 IOOption[T5], + t6 IOOption[T6], +) IOOption[tuple.Tuple6[T1, T2, T3, T4, T5, T6]] { + return apply.SequenceT6( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6]], + Ap[func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T2], + Ap[func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T3], + Ap[func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T4], + Ap[func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T5], + Ap[tuple.Tuple6[T1, T2, T3, T4, T5, T6], T6], + t1, + t2, + t3, + t4, + t5, + t6, + ) +} + +// SequenceSeqT6 converts 6 [IOOption[T]] into a [IOOption[tuple.Tuple6[T1, T2, T3, T4, T5, T6]]] +func SequenceSeqT6[T1, T2, T3, T4, T5, T6 any]( + t1 IOOption[T1], + t2 IOOption[T2], + t3 IOOption[T3], + t4 IOOption[T4], + t5 IOOption[T5], + t6 IOOption[T6], +) IOOption[tuple.Tuple6[T1, T2, T3, T4, T5, T6]] { + return apply.SequenceT6( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6]], + ApSeq[func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T2], + ApSeq[func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T3], + ApSeq[func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T4], + ApSeq[func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T5], + ApSeq[tuple.Tuple6[T1, T2, T3, T4, T5, T6], T6], + t1, + t2, + t3, + t4, + t5, + t6, + ) +} + +// SequenceParT6 converts 6 [IOOption[T]] into a [IOOption[tuple.Tuple6[T1, T2, T3, T4, T5, T6]]] +func SequenceParT6[T1, T2, T3, T4, T5, T6 any]( + t1 IOOption[T1], + t2 IOOption[T2], + t3 IOOption[T3], + t4 IOOption[T4], + t5 IOOption[T5], + t6 IOOption[T6], +) IOOption[tuple.Tuple6[T1, T2, T3, T4, T5, T6]] { + return apply.SequenceT6( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6]], + ApPar[func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T2], + ApPar[func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T3], + ApPar[func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T4], + ApPar[func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T5], + ApPar[tuple.Tuple6[T1, T2, T3, T4, T5, T6], T6], + t1, + t2, + t3, + t4, + t5, + t6, + ) +} + +// SequenceTuple6 converts a [tuple.Tuple6[IOOption[T]]] into a [IOOption[tuple.Tuple6[T1, T2, T3, T4, T5, T6]]] +func SequenceTuple6[T1, T2, T3, T4, T5, T6 any](t tuple.Tuple6[IOOption[T1], IOOption[T2], IOOption[T3], IOOption[T4], IOOption[T5], IOOption[T6]]) IOOption[tuple.Tuple6[T1, T2, T3, T4, T5, T6]] { + return apply.SequenceTuple6( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6]], + Ap[func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T2], + Ap[func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T3], + Ap[func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T4], + Ap[func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T5], + Ap[tuple.Tuple6[T1, T2, T3, T4, T5, T6], T6], + t, + ) +} + +// SequenceSeqTuple6 converts a [tuple.Tuple6[IOOption[T]]] into a [IOOption[tuple.Tuple6[T1, T2, T3, T4, T5, T6]]] +func SequenceSeqTuple6[T1, T2, T3, T4, T5, T6 any](t tuple.Tuple6[IOOption[T1], IOOption[T2], IOOption[T3], IOOption[T4], IOOption[T5], IOOption[T6]]) IOOption[tuple.Tuple6[T1, T2, T3, T4, T5, T6]] { + return apply.SequenceTuple6( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6]], + ApSeq[func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T2], + ApSeq[func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T3], + ApSeq[func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T4], + ApSeq[func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T5], + ApSeq[tuple.Tuple6[T1, T2, T3, T4, T5, T6], T6], + t, + ) +} + +// SequenceParTuple6 converts a [tuple.Tuple6[IOOption[T]]] into a [IOOption[tuple.Tuple6[T1, T2, T3, T4, T5, T6]]] +func SequenceParTuple6[T1, T2, T3, T4, T5, T6 any](t tuple.Tuple6[IOOption[T1], IOOption[T2], IOOption[T3], IOOption[T4], IOOption[T5], IOOption[T6]]) IOOption[tuple.Tuple6[T1, T2, T3, T4, T5, T6]] { + return apply.SequenceTuple6( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6]], + ApPar[func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T2], + ApPar[func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T3], + ApPar[func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T4], + ApPar[func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T5], + ApPar[tuple.Tuple6[T1, T2, T3, T4, T5, T6], T6], + t, + ) +} + +// TraverseTuple6 converts a [tuple.Tuple6[A1, A2, A3, A4, A5, A6]] into a [IOOption[tuple.Tuple6[T1, T2, T3, T4, T5, T6]]] +func TraverseTuple6[F1 ~func(A1) IOOption[T1], F2 ~func(A2) IOOption[T2], F3 ~func(A3) IOOption[T3], F4 ~func(A4) IOOption[T4], F5 ~func(A5) IOOption[T5], F6 ~func(A6) IOOption[T6], T1, T2, T3, T4, T5, T6, A1, A2, A3, A4, A5, A6 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6) func(tuple.Tuple6[A1, A2, A3, A4, A5, A6]) IOOption[tuple.Tuple6[T1, T2, T3, T4, T5, T6]] { + return func(t tuple.Tuple6[A1, A2, A3, A4, A5, A6]) IOOption[tuple.Tuple6[T1, T2, T3, T4, T5, T6]] { + return apply.TraverseTuple6( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6]], + Ap[func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T2], + Ap[func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T3], + Ap[func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T4], + Ap[func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T5], + Ap[tuple.Tuple6[T1, T2, T3, T4, T5, T6], T6], + f1, + f2, + f3, + f4, + f5, + f6, + t, + ) + } +} + +// TraverseSeqTuple6 converts a [tuple.Tuple6[A1, A2, A3, A4, A5, A6]] into a [IOOption[tuple.Tuple6[T1, T2, T3, T4, T5, T6]]] +func TraverseSeqTuple6[F1 ~func(A1) IOOption[T1], F2 ~func(A2) IOOption[T2], F3 ~func(A3) IOOption[T3], F4 ~func(A4) IOOption[T4], F5 ~func(A5) IOOption[T5], F6 ~func(A6) IOOption[T6], T1, T2, T3, T4, T5, T6, A1, A2, A3, A4, A5, A6 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6) func(tuple.Tuple6[A1, A2, A3, A4, A5, A6]) IOOption[tuple.Tuple6[T1, T2, T3, T4, T5, T6]] { + return func(t tuple.Tuple6[A1, A2, A3, A4, A5, A6]) IOOption[tuple.Tuple6[T1, T2, T3, T4, T5, T6]] { + return apply.TraverseTuple6( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6]], + ApSeq[func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T2], + ApSeq[func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T3], + ApSeq[func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T4], + ApSeq[func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T5], + ApSeq[tuple.Tuple6[T1, T2, T3, T4, T5, T6], T6], + f1, + f2, + f3, + f4, + f5, + f6, + t, + ) + } +} + +// TraverseParTuple6 converts a [tuple.Tuple6[A1, A2, A3, A4, A5, A6]] into a [IOOption[tuple.Tuple6[T1, T2, T3, T4, T5, T6]]] +func TraverseParTuple6[F1 ~func(A1) IOOption[T1], F2 ~func(A2) IOOption[T2], F3 ~func(A3) IOOption[T3], F4 ~func(A4) IOOption[T4], F5 ~func(A5) IOOption[T5], F6 ~func(A6) IOOption[T6], T1, T2, T3, T4, T5, T6, A1, A2, A3, A4, A5, A6 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6) func(tuple.Tuple6[A1, A2, A3, A4, A5, A6]) IOOption[tuple.Tuple6[T1, T2, T3, T4, T5, T6]] { + return func(t tuple.Tuple6[A1, A2, A3, A4, A5, A6]) IOOption[tuple.Tuple6[T1, T2, T3, T4, T5, T6]] { + return apply.TraverseTuple6( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6]], + ApPar[func(T3) func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T2], + ApPar[func(T4) func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T3], + ApPar[func(T5) func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T4], + ApPar[func(T6) tuple.Tuple6[T1, T2, T3, T4, T5, T6], T5], + ApPar[tuple.Tuple6[T1, T2, T3, T4, T5, T6], T6], + f1, + f2, + f3, + f4, + f5, + f6, + t, + ) + } +} + +// SequenceT7 converts 7 [IOOption[T]] into a [IOOption[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]]] +func SequenceT7[T1, T2, T3, T4, T5, T6, T7 any]( + t1 IOOption[T1], + t2 IOOption[T2], + t3 IOOption[T3], + t4 IOOption[T4], + t5 IOOption[T5], + t6 IOOption[T6], + t7 IOOption[T7], +) IOOption[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return apply.SequenceT7( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T2], + Ap[func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T3], + Ap[func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T4], + Ap[func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T5], + Ap[func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T6], + Ap[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T7], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + ) +} + +// SequenceSeqT7 converts 7 [IOOption[T]] into a [IOOption[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]]] +func SequenceSeqT7[T1, T2, T3, T4, T5, T6, T7 any]( + t1 IOOption[T1], + t2 IOOption[T2], + t3 IOOption[T3], + t4 IOOption[T4], + t5 IOOption[T5], + t6 IOOption[T6], + t7 IOOption[T7], +) IOOption[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return apply.SequenceT7( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]], + ApSeq[func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T2], + ApSeq[func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T3], + ApSeq[func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T4], + ApSeq[func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T5], + ApSeq[func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T6], + ApSeq[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T7], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + ) +} + +// SequenceParT7 converts 7 [IOOption[T]] into a [IOOption[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]]] +func SequenceParT7[T1, T2, T3, T4, T5, T6, T7 any]( + t1 IOOption[T1], + t2 IOOption[T2], + t3 IOOption[T3], + t4 IOOption[T4], + t5 IOOption[T5], + t6 IOOption[T6], + t7 IOOption[T7], +) IOOption[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return apply.SequenceT7( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]], + ApPar[func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T2], + ApPar[func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T3], + ApPar[func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T4], + ApPar[func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T5], + ApPar[func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T6], + ApPar[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T7], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + ) +} + +// SequenceTuple7 converts a [tuple.Tuple7[IOOption[T]]] into a [IOOption[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]]] +func SequenceTuple7[T1, T2, T3, T4, T5, T6, T7 any](t tuple.Tuple7[IOOption[T1], IOOption[T2], IOOption[T3], IOOption[T4], IOOption[T5], IOOption[T6], IOOption[T7]]) IOOption[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return apply.SequenceTuple7( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T2], + Ap[func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T3], + Ap[func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T4], + Ap[func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T5], + Ap[func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T6], + Ap[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T7], + t, + ) +} + +// SequenceSeqTuple7 converts a [tuple.Tuple7[IOOption[T]]] into a [IOOption[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]]] +func SequenceSeqTuple7[T1, T2, T3, T4, T5, T6, T7 any](t tuple.Tuple7[IOOption[T1], IOOption[T2], IOOption[T3], IOOption[T4], IOOption[T5], IOOption[T6], IOOption[T7]]) IOOption[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return apply.SequenceTuple7( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]], + ApSeq[func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T2], + ApSeq[func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T3], + ApSeq[func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T4], + ApSeq[func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T5], + ApSeq[func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T6], + ApSeq[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T7], + t, + ) +} + +// SequenceParTuple7 converts a [tuple.Tuple7[IOOption[T]]] into a [IOOption[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]]] +func SequenceParTuple7[T1, T2, T3, T4, T5, T6, T7 any](t tuple.Tuple7[IOOption[T1], IOOption[T2], IOOption[T3], IOOption[T4], IOOption[T5], IOOption[T6], IOOption[T7]]) IOOption[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return apply.SequenceTuple7( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]], + ApPar[func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T2], + ApPar[func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T3], + ApPar[func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T4], + ApPar[func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T5], + ApPar[func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T6], + ApPar[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T7], + t, + ) +} + +// TraverseTuple7 converts a [tuple.Tuple7[A1, A2, A3, A4, A5, A6, A7]] into a [IOOption[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]]] +func TraverseTuple7[F1 ~func(A1) IOOption[T1], F2 ~func(A2) IOOption[T2], F3 ~func(A3) IOOption[T3], F4 ~func(A4) IOOption[T4], F5 ~func(A5) IOOption[T5], F6 ~func(A6) IOOption[T6], F7 ~func(A7) IOOption[T7], T1, T2, T3, T4, T5, T6, T7, A1, A2, A3, A4, A5, A6, A7 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7) func(tuple.Tuple7[A1, A2, A3, A4, A5, A6, A7]) IOOption[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return func(t tuple.Tuple7[A1, A2, A3, A4, A5, A6, A7]) IOOption[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return apply.TraverseTuple7( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T2], + Ap[func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T3], + Ap[func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T4], + Ap[func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T5], + Ap[func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T6], + Ap[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T7], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + t, + ) + } +} + +// TraverseSeqTuple7 converts a [tuple.Tuple7[A1, A2, A3, A4, A5, A6, A7]] into a [IOOption[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]]] +func TraverseSeqTuple7[F1 ~func(A1) IOOption[T1], F2 ~func(A2) IOOption[T2], F3 ~func(A3) IOOption[T3], F4 ~func(A4) IOOption[T4], F5 ~func(A5) IOOption[T5], F6 ~func(A6) IOOption[T6], F7 ~func(A7) IOOption[T7], T1, T2, T3, T4, T5, T6, T7, A1, A2, A3, A4, A5, A6, A7 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7) func(tuple.Tuple7[A1, A2, A3, A4, A5, A6, A7]) IOOption[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return func(t tuple.Tuple7[A1, A2, A3, A4, A5, A6, A7]) IOOption[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return apply.TraverseTuple7( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]], + ApSeq[func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T2], + ApSeq[func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T3], + ApSeq[func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T4], + ApSeq[func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T5], + ApSeq[func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T6], + ApSeq[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T7], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + t, + ) + } +} + +// TraverseParTuple7 converts a [tuple.Tuple7[A1, A2, A3, A4, A5, A6, A7]] into a [IOOption[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]]] +func TraverseParTuple7[F1 ~func(A1) IOOption[T1], F2 ~func(A2) IOOption[T2], F3 ~func(A3) IOOption[T3], F4 ~func(A4) IOOption[T4], F5 ~func(A5) IOOption[T5], F6 ~func(A6) IOOption[T6], F7 ~func(A7) IOOption[T7], T1, T2, T3, T4, T5, T6, T7, A1, A2, A3, A4, A5, A6, A7 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7) func(tuple.Tuple7[A1, A2, A3, A4, A5, A6, A7]) IOOption[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return func(t tuple.Tuple7[A1, A2, A3, A4, A5, A6, A7]) IOOption[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return apply.TraverseTuple7( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7]], + ApPar[func(T3) func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T2], + ApPar[func(T4) func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T3], + ApPar[func(T5) func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T4], + ApPar[func(T6) func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T5], + ApPar[func(T7) tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T6], + ApPar[tuple.Tuple7[T1, T2, T3, T4, T5, T6, T7], T7], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + t, + ) + } +} + +// SequenceT8 converts 8 [IOOption[T]] into a [IOOption[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]]] +func SequenceT8[T1, T2, T3, T4, T5, T6, T7, T8 any]( + t1 IOOption[T1], + t2 IOOption[T2], + t3 IOOption[T3], + t4 IOOption[T4], + t5 IOOption[T5], + t6 IOOption[T6], + t7 IOOption[T7], + t8 IOOption[T8], +) IOOption[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return apply.SequenceT8( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T3], + Ap[func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T4], + Ap[func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T5], + Ap[func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T6], + Ap[func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T7], + Ap[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T8], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + t8, + ) +} + +// SequenceSeqT8 converts 8 [IOOption[T]] into a [IOOption[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]]] +func SequenceSeqT8[T1, T2, T3, T4, T5, T6, T7, T8 any]( + t1 IOOption[T1], + t2 IOOption[T2], + t3 IOOption[T3], + t4 IOOption[T4], + t5 IOOption[T5], + t6 IOOption[T6], + t7 IOOption[T7], + t8 IOOption[T8], +) IOOption[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return apply.SequenceT8( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]], + ApSeq[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T2], + ApSeq[func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T3], + ApSeq[func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T4], + ApSeq[func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T5], + ApSeq[func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T6], + ApSeq[func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T7], + ApSeq[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T8], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + t8, + ) +} + +// SequenceParT8 converts 8 [IOOption[T]] into a [IOOption[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]]] +func SequenceParT8[T1, T2, T3, T4, T5, T6, T7, T8 any]( + t1 IOOption[T1], + t2 IOOption[T2], + t3 IOOption[T3], + t4 IOOption[T4], + t5 IOOption[T5], + t6 IOOption[T6], + t7 IOOption[T7], + t8 IOOption[T8], +) IOOption[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return apply.SequenceT8( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]], + ApPar[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T2], + ApPar[func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T3], + ApPar[func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T4], + ApPar[func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T5], + ApPar[func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T6], + ApPar[func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T7], + ApPar[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T8], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + t8, + ) +} + +// SequenceTuple8 converts a [tuple.Tuple8[IOOption[T]]] into a [IOOption[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]]] +func SequenceTuple8[T1, T2, T3, T4, T5, T6, T7, T8 any](t tuple.Tuple8[IOOption[T1], IOOption[T2], IOOption[T3], IOOption[T4], IOOption[T5], IOOption[T6], IOOption[T7], IOOption[T8]]) IOOption[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return apply.SequenceTuple8( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T3], + Ap[func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T4], + Ap[func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T5], + Ap[func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T6], + Ap[func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T7], + Ap[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T8], + t, + ) +} + +// SequenceSeqTuple8 converts a [tuple.Tuple8[IOOption[T]]] into a [IOOption[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]]] +func SequenceSeqTuple8[T1, T2, T3, T4, T5, T6, T7, T8 any](t tuple.Tuple8[IOOption[T1], IOOption[T2], IOOption[T3], IOOption[T4], IOOption[T5], IOOption[T6], IOOption[T7], IOOption[T8]]) IOOption[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return apply.SequenceTuple8( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]], + ApSeq[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T2], + ApSeq[func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T3], + ApSeq[func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T4], + ApSeq[func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T5], + ApSeq[func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T6], + ApSeq[func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T7], + ApSeq[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T8], + t, + ) +} + +// SequenceParTuple8 converts a [tuple.Tuple8[IOOption[T]]] into a [IOOption[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]]] +func SequenceParTuple8[T1, T2, T3, T4, T5, T6, T7, T8 any](t tuple.Tuple8[IOOption[T1], IOOption[T2], IOOption[T3], IOOption[T4], IOOption[T5], IOOption[T6], IOOption[T7], IOOption[T8]]) IOOption[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return apply.SequenceTuple8( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]], + ApPar[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T2], + ApPar[func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T3], + ApPar[func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T4], + ApPar[func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T5], + ApPar[func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T6], + ApPar[func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T7], + ApPar[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T8], + t, + ) +} + +// TraverseTuple8 converts a [tuple.Tuple8[A1, A2, A3, A4, A5, A6, A7, A8]] into a [IOOption[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]]] +func TraverseTuple8[F1 ~func(A1) IOOption[T1], F2 ~func(A2) IOOption[T2], F3 ~func(A3) IOOption[T3], F4 ~func(A4) IOOption[T4], F5 ~func(A5) IOOption[T5], F6 ~func(A6) IOOption[T6], F7 ~func(A7) IOOption[T7], F8 ~func(A8) IOOption[T8], T1, T2, T3, T4, T5, T6, T7, T8, A1, A2, A3, A4, A5, A6, A7, A8 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8) func(tuple.Tuple8[A1, A2, A3, A4, A5, A6, A7, A8]) IOOption[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return func(t tuple.Tuple8[A1, A2, A3, A4, A5, A6, A7, A8]) IOOption[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return apply.TraverseTuple8( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T3], + Ap[func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T4], + Ap[func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T5], + Ap[func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T6], + Ap[func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T7], + Ap[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T8], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + f8, + t, + ) + } +} + +// TraverseSeqTuple8 converts a [tuple.Tuple8[A1, A2, A3, A4, A5, A6, A7, A8]] into a [IOOption[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]]] +func TraverseSeqTuple8[F1 ~func(A1) IOOption[T1], F2 ~func(A2) IOOption[T2], F3 ~func(A3) IOOption[T3], F4 ~func(A4) IOOption[T4], F5 ~func(A5) IOOption[T5], F6 ~func(A6) IOOption[T6], F7 ~func(A7) IOOption[T7], F8 ~func(A8) IOOption[T8], T1, T2, T3, T4, T5, T6, T7, T8, A1, A2, A3, A4, A5, A6, A7, A8 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8) func(tuple.Tuple8[A1, A2, A3, A4, A5, A6, A7, A8]) IOOption[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return func(t tuple.Tuple8[A1, A2, A3, A4, A5, A6, A7, A8]) IOOption[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return apply.TraverseTuple8( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]], + ApSeq[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T2], + ApSeq[func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T3], + ApSeq[func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T4], + ApSeq[func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T5], + ApSeq[func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T6], + ApSeq[func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T7], + ApSeq[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T8], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + f8, + t, + ) + } +} + +// TraverseParTuple8 converts a [tuple.Tuple8[A1, A2, A3, A4, A5, A6, A7, A8]] into a [IOOption[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]]] +func TraverseParTuple8[F1 ~func(A1) IOOption[T1], F2 ~func(A2) IOOption[T2], F3 ~func(A3) IOOption[T3], F4 ~func(A4) IOOption[T4], F5 ~func(A5) IOOption[T5], F6 ~func(A6) IOOption[T6], F7 ~func(A7) IOOption[T7], F8 ~func(A8) IOOption[T8], T1, T2, T3, T4, T5, T6, T7, T8, A1, A2, A3, A4, A5, A6, A7, A8 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8) func(tuple.Tuple8[A1, A2, A3, A4, A5, A6, A7, A8]) IOOption[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return func(t tuple.Tuple8[A1, A2, A3, A4, A5, A6, A7, A8]) IOOption[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return apply.TraverseTuple8( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]], + ApPar[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T2], + ApPar[func(T4) func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T3], + ApPar[func(T5) func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T4], + ApPar[func(T6) func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T5], + ApPar[func(T7) func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T6], + ApPar[func(T8) tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T7], + ApPar[tuple.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T8], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + f8, + t, + ) + } +} + +// SequenceT9 converts 9 [IOOption[T]] into a [IOOption[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]]] +func SequenceT9[T1, T2, T3, T4, T5, T6, T7, T8, T9 any]( + t1 IOOption[T1], + t2 IOOption[T2], + t3 IOOption[T3], + t4 IOOption[T4], + t5 IOOption[T5], + t6 IOOption[T6], + t7 IOOption[T7], + t8 IOOption[T8], + t9 IOOption[T9], +) IOOption[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return apply.SequenceT9( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T3], + Ap[func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T4], + Ap[func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T5], + Ap[func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T6], + Ap[func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T7], + Ap[func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T8], + Ap[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T9], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + t8, + t9, + ) +} + +// SequenceSeqT9 converts 9 [IOOption[T]] into a [IOOption[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]]] +func SequenceSeqT9[T1, T2, T3, T4, T5, T6, T7, T8, T9 any]( + t1 IOOption[T1], + t2 IOOption[T2], + t3 IOOption[T3], + t4 IOOption[T4], + t5 IOOption[T5], + t6 IOOption[T6], + t7 IOOption[T7], + t8 IOOption[T8], + t9 IOOption[T9], +) IOOption[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return apply.SequenceT9( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]], + ApSeq[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T2], + ApSeq[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T3], + ApSeq[func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T4], + ApSeq[func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T5], + ApSeq[func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T6], + ApSeq[func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T7], + ApSeq[func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T8], + ApSeq[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T9], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + t8, + t9, + ) +} + +// SequenceParT9 converts 9 [IOOption[T]] into a [IOOption[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]]] +func SequenceParT9[T1, T2, T3, T4, T5, T6, T7, T8, T9 any]( + t1 IOOption[T1], + t2 IOOption[T2], + t3 IOOption[T3], + t4 IOOption[T4], + t5 IOOption[T5], + t6 IOOption[T6], + t7 IOOption[T7], + t8 IOOption[T8], + t9 IOOption[T9], +) IOOption[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return apply.SequenceT9( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]], + ApPar[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T2], + ApPar[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T3], + ApPar[func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T4], + ApPar[func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T5], + ApPar[func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T6], + ApPar[func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T7], + ApPar[func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T8], + ApPar[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T9], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + t8, + t9, + ) +} + +// SequenceTuple9 converts a [tuple.Tuple9[IOOption[T]]] into a [IOOption[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]]] +func SequenceTuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9 any](t tuple.Tuple9[IOOption[T1], IOOption[T2], IOOption[T3], IOOption[T4], IOOption[T5], IOOption[T6], IOOption[T7], IOOption[T8], IOOption[T9]]) IOOption[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return apply.SequenceTuple9( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T3], + Ap[func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T4], + Ap[func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T5], + Ap[func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T6], + Ap[func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T7], + Ap[func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T8], + Ap[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T9], + t, + ) +} + +// SequenceSeqTuple9 converts a [tuple.Tuple9[IOOption[T]]] into a [IOOption[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]]] +func SequenceSeqTuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9 any](t tuple.Tuple9[IOOption[T1], IOOption[T2], IOOption[T3], IOOption[T4], IOOption[T5], IOOption[T6], IOOption[T7], IOOption[T8], IOOption[T9]]) IOOption[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return apply.SequenceTuple9( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]], + ApSeq[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T2], + ApSeq[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T3], + ApSeq[func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T4], + ApSeq[func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T5], + ApSeq[func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T6], + ApSeq[func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T7], + ApSeq[func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T8], + ApSeq[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T9], + t, + ) +} + +// SequenceParTuple9 converts a [tuple.Tuple9[IOOption[T]]] into a [IOOption[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]]] +func SequenceParTuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9 any](t tuple.Tuple9[IOOption[T1], IOOption[T2], IOOption[T3], IOOption[T4], IOOption[T5], IOOption[T6], IOOption[T7], IOOption[T8], IOOption[T9]]) IOOption[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return apply.SequenceTuple9( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]], + ApPar[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T2], + ApPar[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T3], + ApPar[func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T4], + ApPar[func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T5], + ApPar[func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T6], + ApPar[func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T7], + ApPar[func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T8], + ApPar[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T9], + t, + ) +} + +// TraverseTuple9 converts a [tuple.Tuple9[A1, A2, A3, A4, A5, A6, A7, A8, A9]] into a [IOOption[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]]] +func TraverseTuple9[F1 ~func(A1) IOOption[T1], F2 ~func(A2) IOOption[T2], F3 ~func(A3) IOOption[T3], F4 ~func(A4) IOOption[T4], F5 ~func(A5) IOOption[T5], F6 ~func(A6) IOOption[T6], F7 ~func(A7) IOOption[T7], F8 ~func(A8) IOOption[T8], F9 ~func(A9) IOOption[T9], T1, T2, T3, T4, T5, T6, T7, T8, T9, A1, A2, A3, A4, A5, A6, A7, A8, A9 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9) func(tuple.Tuple9[A1, A2, A3, A4, A5, A6, A7, A8, A9]) IOOption[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return func(t tuple.Tuple9[A1, A2, A3, A4, A5, A6, A7, A8, A9]) IOOption[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return apply.TraverseTuple9( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T3], + Ap[func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T4], + Ap[func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T5], + Ap[func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T6], + Ap[func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T7], + Ap[func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T8], + Ap[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T9], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + f8, + f9, + t, + ) + } +} + +// TraverseSeqTuple9 converts a [tuple.Tuple9[A1, A2, A3, A4, A5, A6, A7, A8, A9]] into a [IOOption[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]]] +func TraverseSeqTuple9[F1 ~func(A1) IOOption[T1], F2 ~func(A2) IOOption[T2], F3 ~func(A3) IOOption[T3], F4 ~func(A4) IOOption[T4], F5 ~func(A5) IOOption[T5], F6 ~func(A6) IOOption[T6], F7 ~func(A7) IOOption[T7], F8 ~func(A8) IOOption[T8], F9 ~func(A9) IOOption[T9], T1, T2, T3, T4, T5, T6, T7, T8, T9, A1, A2, A3, A4, A5, A6, A7, A8, A9 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9) func(tuple.Tuple9[A1, A2, A3, A4, A5, A6, A7, A8, A9]) IOOption[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return func(t tuple.Tuple9[A1, A2, A3, A4, A5, A6, A7, A8, A9]) IOOption[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return apply.TraverseTuple9( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]], + ApSeq[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T2], + ApSeq[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T3], + ApSeq[func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T4], + ApSeq[func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T5], + ApSeq[func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T6], + ApSeq[func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T7], + ApSeq[func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T8], + ApSeq[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T9], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + f8, + f9, + t, + ) + } +} + +// TraverseParTuple9 converts a [tuple.Tuple9[A1, A2, A3, A4, A5, A6, A7, A8, A9]] into a [IOOption[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]]] +func TraverseParTuple9[F1 ~func(A1) IOOption[T1], F2 ~func(A2) IOOption[T2], F3 ~func(A3) IOOption[T3], F4 ~func(A4) IOOption[T4], F5 ~func(A5) IOOption[T5], F6 ~func(A6) IOOption[T6], F7 ~func(A7) IOOption[T7], F8 ~func(A8) IOOption[T8], F9 ~func(A9) IOOption[T9], T1, T2, T3, T4, T5, T6, T7, T8, T9, A1, A2, A3, A4, A5, A6, A7, A8, A9 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9) func(tuple.Tuple9[A1, A2, A3, A4, A5, A6, A7, A8, A9]) IOOption[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return func(t tuple.Tuple9[A1, A2, A3, A4, A5, A6, A7, A8, A9]) IOOption[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return apply.TraverseTuple9( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]], + ApPar[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T2], + ApPar[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T3], + ApPar[func(T5) func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T4], + ApPar[func(T6) func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T5], + ApPar[func(T7) func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T6], + ApPar[func(T8) func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T7], + ApPar[func(T9) tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T8], + ApPar[tuple.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T9], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + f8, + f9, + t, + ) + } +} + +// SequenceT10 converts 10 [IOOption[T]] into a [IOOption[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]]] +func SequenceT10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any]( + t1 IOOption[T1], + t2 IOOption[T2], + t3 IOOption[T3], + t4 IOOption[T4], + t5 IOOption[T5], + t6 IOOption[T6], + t7 IOOption[T7], + t8 IOOption[T8], + t9 IOOption[T9], + t10 IOOption[T10], +) IOOption[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return apply.SequenceT10( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T3], + Ap[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T4], + Ap[func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T5], + Ap[func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T6], + Ap[func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T7], + Ap[func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T8], + Ap[func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T9], + Ap[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T10], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + t8, + t9, + t10, + ) +} + +// SequenceSeqT10 converts 10 [IOOption[T]] into a [IOOption[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]]] +func SequenceSeqT10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any]( + t1 IOOption[T1], + t2 IOOption[T2], + t3 IOOption[T3], + t4 IOOption[T4], + t5 IOOption[T5], + t6 IOOption[T6], + t7 IOOption[T7], + t8 IOOption[T8], + t9 IOOption[T9], + t10 IOOption[T10], +) IOOption[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return apply.SequenceT10( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]], + ApSeq[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T2], + ApSeq[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T3], + ApSeq[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T4], + ApSeq[func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T5], + ApSeq[func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T6], + ApSeq[func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T7], + ApSeq[func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T8], + ApSeq[func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T9], + ApSeq[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T10], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + t8, + t9, + t10, + ) +} + +// SequenceParT10 converts 10 [IOOption[T]] into a [IOOption[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]]] +func SequenceParT10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any]( + t1 IOOption[T1], + t2 IOOption[T2], + t3 IOOption[T3], + t4 IOOption[T4], + t5 IOOption[T5], + t6 IOOption[T6], + t7 IOOption[T7], + t8 IOOption[T8], + t9 IOOption[T9], + t10 IOOption[T10], +) IOOption[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return apply.SequenceT10( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]], + ApPar[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T2], + ApPar[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T3], + ApPar[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T4], + ApPar[func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T5], + ApPar[func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T6], + ApPar[func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T7], + ApPar[func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T8], + ApPar[func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T9], + ApPar[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T10], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + t8, + t9, + t10, + ) +} + +// SequenceTuple10 converts a [tuple.Tuple10[IOOption[T]]] into a [IOOption[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]]] +func SequenceTuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any](t tuple.Tuple10[IOOption[T1], IOOption[T2], IOOption[T3], IOOption[T4], IOOption[T5], IOOption[T6], IOOption[T7], IOOption[T8], IOOption[T9], IOOption[T10]]) IOOption[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return apply.SequenceTuple10( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T3], + Ap[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T4], + Ap[func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T5], + Ap[func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T6], + Ap[func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T7], + Ap[func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T8], + Ap[func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T9], + Ap[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T10], + t, + ) +} + +// SequenceSeqTuple10 converts a [tuple.Tuple10[IOOption[T]]] into a [IOOption[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]]] +func SequenceSeqTuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any](t tuple.Tuple10[IOOption[T1], IOOption[T2], IOOption[T3], IOOption[T4], IOOption[T5], IOOption[T6], IOOption[T7], IOOption[T8], IOOption[T9], IOOption[T10]]) IOOption[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return apply.SequenceTuple10( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]], + ApSeq[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T2], + ApSeq[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T3], + ApSeq[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T4], + ApSeq[func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T5], + ApSeq[func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T6], + ApSeq[func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T7], + ApSeq[func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T8], + ApSeq[func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T9], + ApSeq[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T10], + t, + ) +} + +// SequenceParTuple10 converts a [tuple.Tuple10[IOOption[T]]] into a [IOOption[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]]] +func SequenceParTuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any](t tuple.Tuple10[IOOption[T1], IOOption[T2], IOOption[T3], IOOption[T4], IOOption[T5], IOOption[T6], IOOption[T7], IOOption[T8], IOOption[T9], IOOption[T10]]) IOOption[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return apply.SequenceTuple10( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]], + ApPar[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T2], + ApPar[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T3], + ApPar[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T4], + ApPar[func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T5], + ApPar[func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T6], + ApPar[func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T7], + ApPar[func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T8], + ApPar[func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T9], + ApPar[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T10], + t, + ) +} + +// TraverseTuple10 converts a [tuple.Tuple10[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10]] into a [IOOption[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]]] +func TraverseTuple10[F1 ~func(A1) IOOption[T1], F2 ~func(A2) IOOption[T2], F3 ~func(A3) IOOption[T3], F4 ~func(A4) IOOption[T4], F5 ~func(A5) IOOption[T5], F6 ~func(A6) IOOption[T6], F7 ~func(A7) IOOption[T7], F8 ~func(A8) IOOption[T8], F9 ~func(A9) IOOption[T9], F10 ~func(A10) IOOption[T10], T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10) func(tuple.Tuple10[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10]) IOOption[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return func(t tuple.Tuple10[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10]) IOOption[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return apply.TraverseTuple10( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T3], + Ap[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T4], + Ap[func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T5], + Ap[func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T6], + Ap[func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T7], + Ap[func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T8], + Ap[func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T9], + Ap[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T10], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + f8, + f9, + f10, + t, + ) + } +} + +// TraverseSeqTuple10 converts a [tuple.Tuple10[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10]] into a [IOOption[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]]] +func TraverseSeqTuple10[F1 ~func(A1) IOOption[T1], F2 ~func(A2) IOOption[T2], F3 ~func(A3) IOOption[T3], F4 ~func(A4) IOOption[T4], F5 ~func(A5) IOOption[T5], F6 ~func(A6) IOOption[T6], F7 ~func(A7) IOOption[T7], F8 ~func(A8) IOOption[T8], F9 ~func(A9) IOOption[T9], F10 ~func(A10) IOOption[T10], T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10) func(tuple.Tuple10[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10]) IOOption[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return func(t tuple.Tuple10[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10]) IOOption[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return apply.TraverseTuple10( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]], + ApSeq[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T2], + ApSeq[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T3], + ApSeq[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T4], + ApSeq[func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T5], + ApSeq[func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T6], + ApSeq[func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T7], + ApSeq[func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T8], + ApSeq[func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T9], + ApSeq[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T10], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + f8, + f9, + f10, + t, + ) + } +} + +// TraverseParTuple10 converts a [tuple.Tuple10[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10]] into a [IOOption[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]]] +func TraverseParTuple10[F1 ~func(A1) IOOption[T1], F2 ~func(A2) IOOption[T2], F3 ~func(A3) IOOption[T3], F4 ~func(A4) IOOption[T4], F5 ~func(A5) IOOption[T5], F6 ~func(A6) IOOption[T6], F7 ~func(A7) IOOption[T7], F8 ~func(A8) IOOption[T8], F9 ~func(A9) IOOption[T9], F10 ~func(A10) IOOption[T10], T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10) func(tuple.Tuple10[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10]) IOOption[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return func(t tuple.Tuple10[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10]) IOOption[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return apply.TraverseTuple10( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]], + ApPar[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T2], + ApPar[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T3], + ApPar[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T4], + ApPar[func(T6) func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T5], + ApPar[func(T7) func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T6], + ApPar[func(T8) func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T7], + ApPar[func(T9) func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T8], + ApPar[func(T10) tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T9], + ApPar[tuple.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T10], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + f8, + f9, + f10, + t, + ) + } +} diff --git a/v2/iooption/generic/iooption.go b/v2/iooption/generic/iooption.go new file mode 100644 index 0000000..e128cdd --- /dev/null +++ b/v2/iooption/generic/iooption.go @@ -0,0 +1,261 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + "time" + + ET "github.com/IBM/fp-go/v2/either" + F "github.com/IBM/fp-go/v2/function" + C "github.com/IBM/fp-go/v2/internal/chain" + FI "github.com/IBM/fp-go/v2/internal/fromio" + "github.com/IBM/fp-go/v2/internal/optiont" + IO "github.com/IBM/fp-go/v2/io/generic" + O "github.com/IBM/fp-go/v2/option" +) + +// type IOOption[A any] = func() Option[A] + +func MakeIO[GA ~func() O.Option[A], A any](f GA) GA { + return f +} + +func Of[GA ~func() O.Option[A], A any](r A) GA { + return MakeIO(optiont.Of(IO.MonadOf[GA, O.Option[A]], r)) +} + +func Some[GA ~func() O.Option[A], A any](r A) GA { + return Of[GA](r) +} + +func None[GA ~func() O.Option[A], A any]() GA { + return MakeIO(optiont.None(IO.MonadOf[GA, O.Option[A]])) +} + +func MonadOf[GA ~func() O.Option[A], A any](r A) GA { + return Of[GA](r) +} + +func FromIO[GA ~func() O.Option[A], GR ~func() A, A any](mr GR) GA { + return MakeIO(optiont.OfF(IO.MonadMap[GR, GA, A, O.Option[A]], mr)) +} + +func FromOption[GA ~func() O.Option[A], A any](o O.Option[A]) GA { + return IO.Of[GA](o) +} + +func FromEither[GA ~func() O.Option[A], E, A any](e ET.Either[E, A]) GA { + return F.Pipe2( + e, + ET.ToOption[E, A], + FromOption[GA], + ) +} + +func MonadMap[GA ~func() O.Option[A], GB ~func() O.Option[B], A, B any](fa GA, f func(A) B) GB { + return optiont.MonadMap(IO.MonadMap[GA, GB, O.Option[A], O.Option[B]], fa, f) +} + +func Map[GA ~func() O.Option[A], GB ~func() O.Option[B], A, B any](f func(A) B) func(GA) GB { + return optiont.Map(IO.Map[GA, GB, O.Option[A], O.Option[B]], f) +} + +func MonadChain[GA ~func() O.Option[A], GB ~func() O.Option[B], A, B any](fa GA, f func(A) GB) GB { + return optiont.MonadChain(IO.MonadChain[GA, GB, O.Option[A], O.Option[B]], IO.MonadOf[GB, O.Option[B]], fa, f) +} + +// MonadChainFirst runs the monad returned by the function but returns the result of the original monad +func MonadChainFirst[GA ~func() O.Option[A], GB ~func() O.Option[B], A, B any](ma GA, f func(A) GB) GA { + return C.MonadChainFirst( + MonadChain[GA, GA, A, A], + MonadMap[GB, GA, B, A], + ma, + f, + ) +} + +// ChainFirst runs the monad returned by the function but returns the result of the original monad +func ChainFirst[GA ~func() O.Option[A], GB ~func() O.Option[B], A, B any](f func(A) GB) func(GA) GA { + return C.ChainFirst( + Chain[GA, GA, A, A], + Map[GB, GA, B, A], + f, + ) +} + +// MonadChainFirstIOK runs the monad returned by the function but returns the result of the original monad +func MonadChainFirstIOK[GA ~func() O.Option[A], GIOB ~func() B, A, B any](first GA, f func(A) GIOB) GA { + return FI.MonadChainFirstIOK( + MonadChain[GA, GA, A, A], + MonadMap[func() O.Option[B], GA, B, A], + FromIO[func() O.Option[B], GIOB, B], + first, + f, + ) +} + +// ChainFirstIOK runs the monad returned by the function but returns the result of the original monad +func ChainFirstIOK[GA ~func() O.Option[A], GIOB ~func() B, A, B any](f func(A) GIOB) func(GA) GA { + return FI.ChainFirstIOK( + Chain[GA, GA, A, A], + Map[func() O.Option[B], GA, B, A], + FromIO[func() O.Option[B], GIOB, B], + f, + ) +} + +func Chain[GA ~func() O.Option[A], GB ~func() O.Option[B], A, B any](f func(A) GB) func(GA) GB { + return optiont.Chain(IO.Chain[GA, GB, O.Option[A], O.Option[B]], IO.Of[GB, O.Option[B]], f) +} + +func MonadChainOptionK[GA ~func() O.Option[A], GB ~func() O.Option[B], A, B any](ma GA, f func(A) O.Option[B]) GB { + return optiont.MonadChainOptionK( + IO.MonadChain[GA, GB, O.Option[A], O.Option[B]], + FromOption[GB, B], + ma, + f, + ) +} + +func ChainOptionK[GA ~func() O.Option[A], GB ~func() O.Option[B], A, B any](f func(A) O.Option[B]) func(GA) GB { + return optiont.ChainOptionK( + IO.Chain[GA, GB, O.Option[A], O.Option[B]], + FromOption[GB, B], + f, + ) +} + +func MonadChainIOK[GA ~func() O.Option[A], GB ~func() O.Option[B], GR ~func() B, A, B any](ma GA, f func(A) GR) GB { + return FI.MonadChainIOK( + MonadChain[GA, GB, A, B], + FromIO[GB, GR, B], + ma, + f, + ) +} + +func ChainIOK[GA ~func() O.Option[A], GB ~func() O.Option[B], GR ~func() B, A, B any](f func(A) GR) func(GA) GB { + return FI.ChainIOK( + Chain[GA, GB, A, B], + FromIO[GB, GR, B], + f, + ) +} + +func MonadAp[GB ~func() O.Option[B], GAB ~func() O.Option[func(A) B], GA ~func() O.Option[A], A, B any](mab GAB, ma GA) GB { + return optiont.MonadAp( + IO.MonadAp[GA, GB, func() func(O.Option[A]) O.Option[B], O.Option[A], O.Option[B]], + IO.MonadMap[GAB, func() func(O.Option[A]) O.Option[B], O.Option[func(A) B], func(O.Option[A]) O.Option[B]], + mab, ma) +} + +func Ap[GB ~func() O.Option[B], GAB ~func() O.Option[func(A) B], GA ~func() O.Option[A], A, B any](ma GA) func(GAB) GB { + return optiont.Ap( + IO.Ap[GB, func() func(O.Option[A]) O.Option[B], GA, O.Option[B], O.Option[A]], + IO.Map[GAB, func() func(O.Option[A]) O.Option[B], O.Option[func(A) B], func(O.Option[A]) O.Option[B]], + ma) +} + +func Flatten[GA ~func() O.Option[A], GAA ~func() O.Option[GA], A any](mma GAA) GA { + return MonadChain(mma, F.Identity[GA]) +} + +func Optionize0[GA ~func() O.Option[A], A any](f func() (A, bool)) func() GA { + ef := O.Optionize0(f) + return func() GA { + return MakeIO[GA](ef) + } +} + +func Optionize1[GA ~func() O.Option[A], T1, A any](f func(t1 T1) (A, bool)) func(T1) GA { + ef := O.Optionize1(f) + return func(t1 T1) GA { + return MakeIO[GA](func() O.Option[A] { + return ef(t1) + }) + } +} + +func Optionize2[GA ~func() O.Option[A], T1, T2, A any](f func(t1 T1, t2 T2) (A, bool)) func(T1, T2) GA { + ef := O.Optionize2(f) + return func(t1 T1, t2 T2) GA { + return MakeIO[GA](func() O.Option[A] { + return ef(t1, t2) + }) + } +} + +func Optionize3[GA ~func() O.Option[A], T1, T2, T3, A any](f func(t1 T1, t2 T2, t3 T3) (A, bool)) func(T1, T2, T3) GA { + ef := O.Optionize3(f) + return func(t1 T1, t2 T2, t3 T3) GA { + return MakeIO[GA](func() O.Option[A] { + return ef(t1, t2, t3) + }) + } +} + +func Optionize4[GA ~func() O.Option[A], T1, T2, T3, T4, A any](f func(t1 T1, t2 T2, t3 T3, t4 T4) (A, bool)) func(T1, T2, T3, T4) GA { + ef := O.Optionize4(f) + return func(t1 T1, t2 T2, t3 T3, t4 T4) GA { + return MakeIO[GA](func() O.Option[A] { + return ef(t1, t2, t3, t4) + }) + } +} + +// Memoize computes the value of the provided IO monad lazily but exactly once +func Memoize[GA ~func() O.Option[A], A any](ma GA) GA { + return IO.Memoize(ma) +} + +// Delay creates an operation that passes in the value after some delay +func Delay[GA ~func() O.Option[A], A any](delay time.Duration) func(GA) GA { + return IO.Delay[GA](delay) +} + +// After creates an operation that passes after the given [time.Time] +func After[GA ~func() O.Option[A], A any](timestamp time.Time) func(GA) GA { + return IO.After[GA](timestamp) +} + +// Fold convers an IOOption into an IO +func Fold[GA ~func() O.Option[A], GB ~func() B, A, B any](onNone func() GB, onSome func(A) GB) func(GA) GB { + return optiont.MatchE(IO.MonadChain[GA, GB, O.Option[A], B], onNone, onSome) +} + +// Defer creates an IO by creating a brand new IO via a generator function, each time +func Defer[GA ~func() O.Option[A], A any](gen func() GA) GA { + return IO.Defer[GA](gen) +} + +func MonadAlt[LAZY ~func() GIOA, GIOA ~func() O.Option[A], A any](first GIOA, second LAZY) GIOA { + return optiont.MonadAlt( + IO.MonadOf[GIOA], + IO.MonadChain[GIOA, GIOA], + + first, + second, + ) +} + +func Alt[LAZY ~func() GIOA, GIOA ~func() O.Option[A], A any](second LAZY) func(GIOA) GIOA { + return optiont.Alt( + IO.Of[GIOA], + IO.Chain[GIOA, GIOA], + + second, + ) +} diff --git a/v2/iooption/iooption.go b/v2/iooption/iooption.go new file mode 100644 index 0000000..6f6cb82 --- /dev/null +++ b/v2/iooption/iooption.go @@ -0,0 +1,262 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package iooption + +import ( + "time" + + ET "github.com/IBM/fp-go/v2/either" + "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/internal/chain" + "github.com/IBM/fp-go/v2/internal/fromio" + "github.com/IBM/fp-go/v2/internal/optiont" + "github.com/IBM/fp-go/v2/io" + "github.com/IBM/fp-go/v2/option" +) + +func Of[A any](r A) IOOption[A] { + return optiont.Of(io.Of[Option[A]], r) +} + +func Some[A any](r A) IOOption[A] { + return Of(r) +} + +func None[A any]() IOOption[A] { + return optiont.None(io.Of[Option[A]]) +} + +func MonadOf[A any](r A) IOOption[A] { + return Of(r) +} + +func FromOption[A any](o Option[A]) IOOption[A] { + return io.Of(o) +} + +func ChainOptionK[A, B any](f func(A) Option[B]) func(IOOption[A]) IOOption[B] { + return optiont.ChainOptionK( + io.Chain[Option[A], Option[B]], + FromOption[B], + f, + ) +} + +func MonadChainIOK[A, B any](ma IOOption[A], f func(A) IO[B]) IOOption[B] { + return fromio.MonadChainIOK( + MonadChain[A, B], + FromIO[B], + ma, + f, + ) +} + +func ChainIOK[A, B any](f func(A) IO[B]) func(IOOption[A]) IOOption[B] { + return fromio.ChainIOK( + Chain[A, B], + FromIO[B], + f, + ) +} + +func FromIO[A any](mr IO[A]) IOOption[A] { + return optiont.OfF(io.MonadMap[A, Option[A]], mr) +} + +func MonadMap[A, B any](fa IOOption[A], f func(A) B) IOOption[B] { + return optiont.MonadMap(io.MonadMap[Option[A], Option[B]], fa, f) +} + +func Map[A, B any](f func(A) B) func(IOOption[A]) IOOption[B] { + return optiont.Map(io.Map[Option[A], Option[B]], f) +} + +func MonadChain[A, B any](fa IOOption[A], f func(A) IOOption[B]) IOOption[B] { + return optiont.MonadChain(io.MonadChain[Option[A], Option[B]], io.MonadOf[Option[B]], fa, f) +} + +func Chain[A, B any](f func(A) IOOption[B]) func(IOOption[A]) IOOption[B] { + return optiont.Chain(io.Chain[Option[A], Option[B]], io.Of[Option[B]], f) +} + +func MonadAp[B, A any](mab IOOption[func(A) B], ma IOOption[A]) IOOption[B] { + return optiont.MonadAp( + io.MonadAp[Option[A], Option[B]], + io.MonadMap[Option[func(A) B], func(Option[A]) Option[B]], + mab, ma) +} + +func Ap[B, A any](ma IOOption[A]) func(IOOption[func(A) B]) IOOption[B] { + return optiont.Ap( + io.Ap[Option[B], Option[A]], + io.Map[Option[func(A) B], func(Option[A]) Option[B]], + ma) +} + +func ApSeq[B, A any](ma IOOption[A]) func(IOOption[func(A) B]) IOOption[B] { + return optiont.Ap( + io.ApSeq[Option[B], Option[A]], + io.Map[Option[func(A) B], func(Option[A]) Option[B]], + ma) +} + +func ApPar[B, A any](ma IOOption[A]) func(IOOption[func(A) B]) IOOption[B] { + return optiont.Ap( + io.ApPar[Option[B], Option[A]], + io.Map[Option[func(A) B], func(Option[A]) Option[B]], + ma) +} + +func Flatten[A any](mma IOOption[IOOption[A]]) IOOption[A] { + return MonadChain(mma, function.Identity[IOOption[A]]) +} + +func Optionize0[A any](f func() (A, bool)) func() IOOption[A] { + ef := option.Optionize0(f) + return func() IOOption[A] { + return ef + } +} + +func Optionize1[T1, A any](f func(t1 T1) (A, bool)) func(T1) IOOption[A] { + ef := option.Optionize1(f) + return func(t1 T1) IOOption[A] { + return func() Option[A] { + return ef(t1) + } + } +} + +func Optionize2[T1, T2, A any](f func(t1 T1, t2 T2) (A, bool)) func(T1, T2) IOOption[A] { + ef := option.Optionize2(f) + return func(t1 T1, t2 T2) IOOption[A] { + return func() Option[A] { + return ef(t1, t2) + } + } +} + +func Optionize3[T1, T2, T3, A any](f func(t1 T1, t2 T2, t3 T3) (A, bool)) func(T1, T2, T3) IOOption[A] { + ef := option.Optionize3(f) + return func(t1 T1, t2 T2, t3 T3) IOOption[A] { + return func() Option[A] { + return ef(t1, t2, t3) + } + } +} + +func Optionize4[T1, T2, T3, T4, A any](f func(t1 T1, t2 T2, t3 T3, t4 T4) (A, bool)) func(T1, T2, T3, T4) IOOption[A] { + ef := option.Optionize4(f) + return func(t1 T1, t2 T2, t3 T3, t4 T4) IOOption[A] { + return func() Option[A] { + return ef(t1, t2, t3, t4) + } + } +} + +func Memoize[A any](ma IOOption[A]) IOOption[A] { + return io.Memoize(ma) +} + +// Fold convers an [IOOption] into an [IO] +func Fold[A, B any](onNone func() IO[B], onSome func(A) IO[B]) func(IOOption[A]) IO[B] { + return optiont.MatchE(io.MonadChain[Option[A], B], onNone, onSome) +} + +// Defer creates an IO by creating a brand new IO via a generator function, each time +func Defer[A any](gen func() IOOption[A]) IOOption[A] { + return io.Defer(gen) +} + +// FromEither converts an [Either] into an [IOOption] +func FromEither[E, A any](e Either[E, A]) IOOption[A] { + return function.Pipe2( + e, + ET.ToOption[E, A], + FromOption[A], + ) +} + +// MonadAlt identifies an associative operation on a type constructor +func MonadAlt[A any](first IOOption[A], second Lazy[IOOption[A]]) IOOption[A] { + return optiont.MonadAlt( + io.MonadOf[Option[A]], + io.MonadChain[Option[A], Option[A]], + + first, + second, + ) +} + +// Alt identifies an associative operation on a type constructor +func Alt[A any](second Lazy[IOOption[A]]) func(IOOption[A]) IOOption[A] { + return optiont.Alt( + io.Of[Option[A]], + io.Chain[Option[A], Option[A]], + + second, + ) +} + +// MonadChainFirst runs the monad returned by the function but returns the result of the original monad +func MonadChainFirst[A, B any](ma IOOption[A], f func(A) IOOption[B]) IOOption[A] { + return chain.MonadChainFirst( + MonadChain[A, A], + MonadMap[B, A], + ma, + f, + ) +} + +// ChainFirst runs the monad returned by the function but returns the result of the original monad +func ChainFirst[A, B any](f func(A) IOOption[B]) func(IOOption[A]) IOOption[A] { + return chain.ChainFirst( + Chain[A, A], + Map[B, A], + f, + ) +} + +// MonadChainFirstIOK runs the monad returned by the function but returns the result of the original monad +func MonadChainFirstIOK[A, B any](first IOOption[A], f func(A) IO[B]) IOOption[A] { + return fromio.MonadChainFirstIOK( + MonadChain[A, A], + MonadMap[B, A], + FromIO[B], + first, + f, + ) +} + +// ChainFirstIOK runs the monad returned by the function but returns the result of the original monad +func ChainFirstIOK[A, B any](f func(A) IO[B]) func(IOOption[A]) IOOption[A] { + return fromio.ChainFirstIOK( + Chain[A, A], + Map[B, A], + FromIO[B], + f, + ) +} + +// Delay creates an operation that passes in the value after some delay +func Delay[A any](delay time.Duration) func(IOOption[A]) IOOption[A] { + return io.Delay[Option[A]](delay) +} + +// After creates an operation that passes after the given [time.Time] +func After[A any](timestamp time.Time) func(IOOption[A]) IOOption[A] { + return io.After[Option[A]](timestamp) +} diff --git a/v2/iooption/iooption_test.go b/v2/iooption/iooption_test.go new file mode 100644 index 0000000..084d348 --- /dev/null +++ b/v2/iooption/iooption_test.go @@ -0,0 +1,73 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package iooption + +import ( + "fmt" + "os" + "testing" + + F "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/internal/utils" + I "github.com/IBM/fp-go/v2/io" + O "github.com/IBM/fp-go/v2/option" + "github.com/stretchr/testify/assert" +) + +func TestMap(t *testing.T) { + assert.Equal(t, O.Of(2), F.Pipe1( + Of(1), + Map(utils.Double), + )()) + +} + +func TestChainOptionK(t *testing.T) { + f := ChainOptionK(func(n int) Option[int] { + if n > 0 { + return O.Of(n) + } + return O.None[int]() + + }) + assert.Equal(t, O.Of(1), f(Of(1))()) + assert.Equal(t, O.None[int](), f(Of(-1))()) + assert.Equal(t, O.None[int](), f(None[int]())()) +} + +func TestFromOption(t *testing.T) { + f := FromOption[int] + assert.Equal(t, O.Of(1), f(O.Some(1))()) + assert.Equal(t, O.None[int](), f(O.None[int]())()) +} + +func TestChainIOK(t *testing.T) { + f := ChainIOK(func(n int) I.IO[string] { + return func() string { + return fmt.Sprintf("%d", n) + } + }) + + assert.Equal(t, O.Of("1"), f(Of(1))()) + assert.Equal(t, O.None[string](), f(None[int]())()) +} + +func TestEnv(t *testing.T) { + env := Optionize1(os.LookupEnv) + + assert.True(t, O.IsSome(env("PATH")())) + assert.False(t, O.IsSome(env("PATHxyz")())) +} diff --git a/v2/iooption/resource.go b/v2/iooption/resource.go new file mode 100644 index 0000000..1e95e78 --- /dev/null +++ b/v2/iooption/resource.go @@ -0,0 +1,25 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package iooption + +import "github.com/IBM/fp-go/v2/function" + +// WithResource constructs a function that creates a resource, then operates on it and then releases the resource +func WithResource[ + R, A, ANY any](onCreate IOOption[R], onRelease func(R) IOOption[ANY]) func(func(R) IOOption[A]) IOOption[A] { + // simply map to implementation of bracket + return function.Bind13of3(Bracket[R, A, ANY])(onCreate, function.Ignore2of2[Option[A]](onRelease)) +} diff --git a/v2/iooption/retry.go b/v2/iooption/retry.go new file mode 100644 index 0000000..0794af7 --- /dev/null +++ b/v2/iooption/retry.go @@ -0,0 +1,38 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package iooption + +import ( + R "github.com/IBM/fp-go/v2/retry" + G "github.com/IBM/fp-go/v2/retry/generic" +) + +// Retrying will retry the actions according to the check policy +func Retrying[A any]( + policy R.RetryPolicy, + action func(R.RetryStatus) IOOption[A], + check func(A) bool, +) IOOption[A] { + // get an implementation for the types + return G.Retrying( + Chain[A, A], + Chain[R.RetryStatus, A], + Of[A], + Of[R.RetryStatus], + Delay[R.RetryStatus], + + policy, action, check) +} diff --git a/v2/iooption/sync.go b/v2/iooption/sync.go new file mode 100644 index 0000000..46637ab --- /dev/null +++ b/v2/iooption/sync.go @@ -0,0 +1,28 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package iooption + +import ( + "context" + + "github.com/IBM/fp-go/v2/io" + "github.com/IBM/fp-go/v2/option" +) + +// WithLock executes the provided IO operation in the scope of a lock +func WithLock[E, A any](lock IO[context.CancelFunc]) func(fa IOOption[A]) IOOption[A] { + return io.WithLock[option.Option[A]](lock) +} diff --git a/v2/iooption/testing/laws.go b/v2/iooption/testing/laws.go new file mode 100644 index 0000000..b4e999b --- /dev/null +++ b/v2/iooption/testing/laws.go @@ -0,0 +1,74 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package testing + +import ( + "testing" + + EQ "github.com/IBM/fp-go/v2/eq" + L "github.com/IBM/fp-go/v2/internal/monad/testing" + IOO "github.com/IBM/fp-go/v2/iooption" +) + +// AssertLaws asserts the apply monad laws for the `Either` monad +func AssertLaws[A, B, C any](t *testing.T, + eqa EQ.Eq[A], + eqb EQ.Eq[B], + eqc EQ.Eq[C], + + ab func(A) B, + bc func(B) C, +) func(a A) bool { + + return L.AssertLaws(t, + IOO.Eq(eqa), + IOO.Eq(eqb), + IOO.Eq(eqc), + + IOO.Of[A], + IOO.Of[B], + IOO.Of[C], + + IOO.Of[func(A) A], + IOO.Of[func(A) B], + IOO.Of[func(B) C], + IOO.Of[func(func(A) B) B], + + IOO.MonadMap[A, A], + IOO.MonadMap[A, B], + IOO.MonadMap[A, C], + IOO.MonadMap[B, C], + + IOO.MonadMap[func(B) C, func(func(A) B) func(A) C], + + IOO.MonadChain[A, A], + IOO.MonadChain[A, B], + IOO.MonadChain[A, C], + IOO.MonadChain[B, C], + + IOO.MonadAp[A, A], + IOO.MonadAp[B, A], + IOO.MonadAp[C, B], + IOO.MonadAp[C, A], + + IOO.MonadAp[B, func(A) B], + IOO.MonadAp[func(A) C, func(A) B], + + ab, + bc, + ) + +} diff --git a/v2/iooption/testing/laws_test.go b/v2/iooption/testing/laws_test.go new file mode 100644 index 0000000..faff6d2 --- /dev/null +++ b/v2/iooption/testing/laws_test.go @@ -0,0 +1,47 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package testing + +import ( + "fmt" + "testing" + + EQ "github.com/IBM/fp-go/v2/eq" + "github.com/stretchr/testify/assert" +) + +func TestMonadLaws(t *testing.T) { + // some comparison + eqa := EQ.FromStrictEquals[bool]() + eqb := EQ.FromStrictEquals[int]() + eqc := EQ.FromStrictEquals[string]() + + ab := func(a bool) int { + if a { + return 1 + } + return 0 + } + + bc := func(b int) string { + return fmt.Sprintf("value %d", b) + } + + laws := AssertLaws(t, eqa, eqb, eqc, ab, bc) + + assert.True(t, laws(true)) + assert.True(t, laws(false)) +} diff --git a/v2/iooption/types.go b/v2/iooption/types.go new file mode 100644 index 0000000..36060e2 --- /dev/null +++ b/v2/iooption/types.go @@ -0,0 +1,34 @@ +// Copyright (c) 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package iooption + +import ( + "github.com/IBM/fp-go/v2/either" + "github.com/IBM/fp-go/v2/io" + "github.com/IBM/fp-go/v2/lazy" + "github.com/IBM/fp-go/v2/option" +) + +type ( + Either[E, A any] = either.Either[E, A] + Option[A any] = option.Option[A] + IO[A any] = io.IO[A] + Lazy[A any] = lazy.Lazy[A] + + // IOOption represents a synchronous computation that may fail + // refer to [https://andywhite.xyz/posts/2021-01-27-rte-foundations/#ioeitherlte-agt] for more details + IOOption[A any] = io.IO[Option[A]] +) diff --git a/v2/iterator/stateless/any.go b/v2/iterator/stateless/any.go new file mode 100644 index 0000000..ff3da5b --- /dev/null +++ b/v2/iterator/stateless/any.go @@ -0,0 +1,26 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package stateless + +import ( + G "github.com/IBM/fp-go/v2/iterator/stateless/generic" +) + +// Any returns `true` if any element of the iterable is `true`. If the iterable is empty, return `false` +// Similar to the [https://docs.python.org/3/library/functions.html#any] function +func Any[U any](pred func(U) bool) func(ma Iterator[U]) bool { + return G.Any[Iterator[U]](pred) +} diff --git a/v2/iterator/stateless/any_test.go b/v2/iterator/stateless/any_test.go new file mode 100644 index 0000000..f2d0400 --- /dev/null +++ b/v2/iterator/stateless/any_test.go @@ -0,0 +1,38 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package stateless + +import ( + "testing" + + A "github.com/IBM/fp-go/v2/array" + F "github.com/IBM/fp-go/v2/function" + "github.com/stretchr/testify/assert" +) + +func TestAny(t *testing.T) { + + anyBool := Any(F.Identity[bool]) + + i1 := FromArray(A.From(false, true, false)) + assert.True(t, anyBool(i1)) + + i2 := FromArray(A.From(false, false, false)) + assert.False(t, anyBool(i2)) + + i3 := Empty[bool]() + assert.False(t, anyBool(i3)) +} diff --git a/v2/iterator/stateless/benchmark_test.go b/v2/iterator/stateless/benchmark_test.go new file mode 100644 index 0000000..b6dedd6 --- /dev/null +++ b/v2/iterator/stateless/benchmark_test.go @@ -0,0 +1,65 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package stateless + +import ( + "testing" + + F "github.com/IBM/fp-go/v2/function" +) + +func BenchmarkMulti(b *testing.B) { + // run the Fib function b.N times + for n := 0; n < b.N; n++ { + single() + } +} + +func single() int64 { + + length := 10000 + nums := make([]int, 0, length) + for i := 0; i < length; i++ { + nums = append(nums, i+1) + } + + return F.Pipe6( + nums, + FromArray[int], + Filter(func(n int) bool { + return n%2 == 0 + }), + Map(func(t int) int64 { + return int64(t) + }), + Filter(func(t int64) bool { + n := t + for n/10 != 0 { + if n%10 == 4 { + return false + } + n = n / 10 + } + return true + }), + Map(func(t int64) int { + return int(t) + }), + Reduce(func(n int64, r int) int64 { + return n + int64(r) + }, int64(0)), + ) +} diff --git a/v2/iterator/stateless/bind.go b/v2/iterator/stateless/bind.go new file mode 100644 index 0000000..5774bca --- /dev/null +++ b/v2/iterator/stateless/bind.go @@ -0,0 +1,66 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package stateless + +import ( + G "github.com/IBM/fp-go/v2/iterator/stateless/generic" +) + +// Bind creates an empty context of type [S] to be used with the [Bind] operation +func Do[S any]( + empty S, +) Iterator[S] { + return G.Do[Iterator[S]](empty) +} + +// Bind attaches the result of a computation to a context [S1] to produce a context [S2] +func Bind[S1, S2, T any]( + setter func(T) func(S1) S2, + f func(S1) Iterator[T], +) func(Iterator[S1]) Iterator[S2] { + return G.Bind[Iterator[S1], Iterator[S2], Iterator[T], S1, S2, T](setter, f) +} + +// Let attaches the result of a computation to a context [S1] to produce a context [S2] +func Let[S1, S2, T any]( + setter func(T) func(S1) S2, + f func(S1) T, +) func(Iterator[S1]) Iterator[S2] { + return G.Let[Iterator[S1], Iterator[S2], S1, S2, T](setter, f) +} + +// LetTo attaches the a value to a context [S1] to produce a context [S2] +func LetTo[S1, S2, T any]( + setter func(T) func(S1) S2, + b T, +) func(Iterator[S1]) Iterator[S2] { + return G.LetTo[Iterator[S1], Iterator[S2], S1, S2, T](setter, b) +} + +// BindTo initializes a new state [S1] from a value [T] +func BindTo[S1, T any]( + setter func(T) S1, +) func(Iterator[T]) Iterator[S1] { + return G.BindTo[Iterator[S1], Iterator[T], S1, T](setter) +} + +// ApS attaches a value to a context [S1] to produce a context [S2] by considering the context and the value concurrently +func ApS[S1, S2, T any]( + setter func(T) func(S1) S2, + fa Iterator[T], +) func(Iterator[S1]) Iterator[S2] { + return G.ApS[Iterator[func(T) S2], Iterator[S1], Iterator[S2], Iterator[T], S1, S2, T](setter, fa) +} diff --git a/v2/iterator/stateless/bind_test.go b/v2/iterator/stateless/bind_test.go new file mode 100644 index 0000000..3645439 --- /dev/null +++ b/v2/iterator/stateless/bind_test.go @@ -0,0 +1,57 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package stateless + +import ( + "testing" + + A "github.com/IBM/fp-go/v2/array" + F "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/internal/utils" + "github.com/stretchr/testify/assert" +) + +func getLastName(s utils.Initial) Iterator[string] { + return Of("Doe") +} + +func getGivenName(s utils.WithLastName) Iterator[string] { + return Of("John") +} + +func TestBind(t *testing.T) { + + res := F.Pipe3( + Do(utils.Empty), + Bind(utils.SetLastName, getLastName), + Bind(utils.SetGivenName, getGivenName), + Map(utils.GetFullName), + ) + + assert.Equal(t, ToArray(res), A.Of("John Doe")) +} + +func TestApS(t *testing.T) { + + res := F.Pipe3( + Do(utils.Empty), + ApS(utils.SetLastName, Of("Doe")), + ApS(utils.SetGivenName, Of("John")), + Map(utils.GetFullName), + ) + + assert.Equal(t, ToArray(res), A.Of("John Doe")) +} diff --git a/v2/iterator/stateless/compress.go b/v2/iterator/stateless/compress.go new file mode 100644 index 0000000..dbdd0ce --- /dev/null +++ b/v2/iterator/stateless/compress.go @@ -0,0 +1,27 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package stateless + +import ( + G "github.com/IBM/fp-go/v2/iterator/stateless/generic" + P "github.com/IBM/fp-go/v2/pair" +) + +// Compress returns an [Iterator] that filters elements from a data [Iterator] returning only those that have a corresponding element in selector [Iterator] that evaluates to `true`. +// Stops when either the data or selectors iterator has been exhausted. +func Compress[U any](sel Iterator[bool]) func(Iterator[U]) Iterator[U] { + return G.Compress[Iterator[U], Iterator[bool], Iterator[P.Pair[U, bool]]](sel) +} diff --git a/v2/iterator/stateless/compress_test.go b/v2/iterator/stateless/compress_test.go new file mode 100644 index 0000000..525bef2 --- /dev/null +++ b/v2/iterator/stateless/compress_test.go @@ -0,0 +1,35 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package stateless + +import ( + "testing" + + A "github.com/IBM/fp-go/v2/array" + "github.com/stretchr/testify/assert" +) + +func TestCompress(t *testing.T) { + // sequence of 5 items + data := From(0, 1, 2, 3) + // select some of these items + selector := From(true, false, false, true) + // compressed result + compressed := Compress[int](selector)(data) + + assert.Equal(t, A.From(0, 3), ToArray(compressed)) + +} diff --git a/v2/iterator/stateless/cycle.go b/v2/iterator/stateless/cycle.go new file mode 100644 index 0000000..ffcaa2b --- /dev/null +++ b/v2/iterator/stateless/cycle.go @@ -0,0 +1,26 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package stateless + +import ( + G "github.com/IBM/fp-go/v2/iterator/stateless/generic" +) + +// DropWhile creates an [Iterator] that drops elements from the [Iterator] as long as the predicate is true; afterwards, returns every element. +// Note, the [Iterator] does not produce any output until the predicate first becomes false +func Cycle[U any](ma Iterator[U]) Iterator[U] { + return G.Cycle[Iterator[U]](ma) +} diff --git a/v2/iterator/stateless/cycle_test.go b/v2/iterator/stateless/cycle_test.go new file mode 100644 index 0000000..f241053 --- /dev/null +++ b/v2/iterator/stateless/cycle_test.go @@ -0,0 +1,33 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package stateless + +import ( + "testing" + + A "github.com/IBM/fp-go/v2/array" + "github.com/stretchr/testify/assert" +) + +func TestCycle(t *testing.T) { + // sequence of 5 items + items := Take[int](5)(Count(0)) + // repeat + repeated := Take[int](17)(Cycle(items)) + + assert.Equal(t, A.From(0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1), ToArray(repeated)) + +} diff --git a/v2/iterator/stateless/doc.go b/v2/iterator/stateless/doc.go new file mode 100644 index 0000000..af32e66 --- /dev/null +++ b/v2/iterator/stateless/doc.go @@ -0,0 +1,18 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +// Package stateless defines a stateless (pure) iterator, i.e. one that can be iterated over multiple times without +// side effects, it is threadsafe +package stateless diff --git a/v2/iterator/stateless/dropwhile.go b/v2/iterator/stateless/dropwhile.go new file mode 100644 index 0000000..8ac4330 --- /dev/null +++ b/v2/iterator/stateless/dropwhile.go @@ -0,0 +1,26 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package stateless + +import ( + G "github.com/IBM/fp-go/v2/iterator/stateless/generic" +) + +// DropWhile creates an [Iterator] that drops elements from the [Iterator] as long as the predicate is true; afterwards, returns every element. +// Note, the [Iterator] does not produce any output until the predicate first becomes false +func DropWhile[U any](pred func(U) bool) func(Iterator[U]) Iterator[U] { + return G.DropWhile[Iterator[U]](pred) +} diff --git a/v2/iterator/stateless/dropwhile_test.go b/v2/iterator/stateless/dropwhile_test.go new file mode 100644 index 0000000..d93372e --- /dev/null +++ b/v2/iterator/stateless/dropwhile_test.go @@ -0,0 +1,35 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package stateless + +import ( + "testing" + + A "github.com/IBM/fp-go/v2/array" + "github.com/stretchr/testify/assert" +) + +func TestDropWhile(t *testing.T) { + // sequence of 5 items + data := Take[int](10)(Cycle(From(0, 1, 2, 3))) + + total := DropWhile(func(data int) bool { + return data <= 2 + })(data) + + assert.Equal(t, A.From(3, 0, 1, 2, 3, 0, 1), ToArray(total)) + +} diff --git a/v2/iterator/stateless/example_test.go b/v2/iterator/stateless/example_test.go new file mode 100644 index 0000000..d37743d --- /dev/null +++ b/v2/iterator/stateless/example_test.go @@ -0,0 +1,55 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package stateless + +import ( + "fmt" + + A "github.com/IBM/fp-go/v2/array" + F "github.com/IBM/fp-go/v2/function" + O "github.com/IBM/fp-go/v2/option" +) + +func Example_any() { + // `Any` function that simply returns the boolean identity + anyBool := Any(F.Identity[bool]) + + fmt.Println(anyBool(FromArray(A.From(true, false, false)))) + fmt.Println(anyBool(FromArray(A.From(false, false, false)))) + fmt.Println(anyBool(Empty[bool]())) + + // Output: + // true + // false + // false +} + +func Example_next() { + + seq := MakeBy(F.Identity[int]) + + first := seq() + + value := F.Pipe1( + first, + O.Map(Current[int]), + ) + + fmt.Println(value) + + // Output: + // Some[int](0) +} diff --git a/v2/iterator/stateless/first.go b/v2/iterator/stateless/first.go new file mode 100644 index 0000000..9d47ad9 --- /dev/null +++ b/v2/iterator/stateless/first.go @@ -0,0 +1,26 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package stateless + +import ( + G "github.com/IBM/fp-go/v2/iterator/stateless/generic" + O "github.com/IBM/fp-go/v2/option" +) + +// First returns the first item in an iterator if such an item exists +func First[U any](mu Iterator[U]) O.Option[U] { + return G.First[Iterator[U]](mu) +} diff --git a/v2/iterator/stateless/first_test.go b/v2/iterator/stateless/first_test.go new file mode 100644 index 0000000..06f5dd7 --- /dev/null +++ b/v2/iterator/stateless/first_test.go @@ -0,0 +1,41 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package stateless + +import ( + "testing" + + O "github.com/IBM/fp-go/v2/option" + "github.com/stretchr/testify/assert" +) + +func TestFirst(t *testing.T) { + + seq := From(1, 2, 3) + + fst := First(seq) + + assert.Equal(t, O.Of(1), fst) +} + +func TestNoFirst(t *testing.T) { + + seq := Empty[int]() + + fst := First(seq) + + assert.Equal(t, O.None[int](), fst) +} diff --git a/v2/iterator/stateless/generic/any.go b/v2/iterator/stateless/generic/any.go new file mode 100644 index 0000000..7f68db3 --- /dev/null +++ b/v2/iterator/stateless/generic/any.go @@ -0,0 +1,31 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + F "github.com/IBM/fp-go/v2/function" + O "github.com/IBM/fp-go/v2/option" + P "github.com/IBM/fp-go/v2/pair" +) + +// Any returns `true` if any element of the iterable is `true`. If the iterable is empty, return `false` +func Any[GU ~func() O.Option[P.Pair[GU, U]], FCT ~func(U) bool, U any](pred FCT) func(ma GU) bool { + return F.Flow3( + Filter[GU](pred), + First[GU], + O.IsSome[U], + ) +} diff --git a/v2/iterator/stateless/generic/bind.go b/v2/iterator/stateless/generic/bind.go new file mode 100644 index 0000000..bce0b47 --- /dev/null +++ b/v2/iterator/stateless/generic/bind.go @@ -0,0 +1,92 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + "github.com/IBM/fp-go/v2/internal/apply" + C "github.com/IBM/fp-go/v2/internal/chain" + F "github.com/IBM/fp-go/v2/internal/functor" + O "github.com/IBM/fp-go/v2/option" + P "github.com/IBM/fp-go/v2/pair" +) + +// Bind creates an empty context of type [S] to be used with the [Bind] operation +func Do[GS ~func() O.Option[P.Pair[GS, S]], S any]( + empty S, +) GS { + return Of[GS](empty) +} + +// Bind attaches the result of a computation to a context [S1] to produce a context [S2] +func Bind[GS1 ~func() O.Option[P.Pair[GS1, S1]], GS2 ~func() O.Option[P.Pair[GS2, S2]], GA ~func() O.Option[P.Pair[GA, A]], S1, S2, A any]( + setter func(A) func(S1) S2, + f func(S1) GA, +) func(GS1) GS2 { + + return C.Bind( + Chain[GS2, GS1, S1, S2], + Map[GS2, GA, func(A) S2, A, S2], + setter, + f, + ) +} + +// Let attaches the result of a computation to a context [S1] to produce a context [S2] +func Let[GS1 ~func() O.Option[P.Pair[GS1, S1]], GS2 ~func() O.Option[P.Pair[GS2, S2]], S1, S2, A any]( + key func(A) func(S1) S2, + f func(S1) A, +) func(GS1) GS2 { + return F.Let( + Map[GS2, GS1, func(S1) S2, S1, S2], + key, + f, + ) +} + +// LetTo attaches the a value to a context [S1] to produce a context [S2] +func LetTo[GS1 ~func() O.Option[P.Pair[GS1, S1]], GS2 ~func() O.Option[P.Pair[GS2, S2]], S1, S2, B any]( + key func(B) func(S1) S2, + b B, +) func(GS1) GS2 { + return F.LetTo( + Map[GS2, GS1, func(S1) S2, S1, S2], + key, + b, + ) +} + +// BindTo initializes a new state [S1] from a value [T] +func BindTo[GS1 ~func() O.Option[P.Pair[GS1, S1]], GA ~func() O.Option[P.Pair[GA, A]], S1, A any]( + setter func(A) S1, +) func(GA) GS1 { + return C.BindTo( + Map[GS1, GA, func(A) S1, A, S1], + setter, + ) +} + +// ApS attaches a value to a context [S1] to produce a context [S2] by considering the context and the value concurrently +func ApS[GAS2 ~func() O.Option[P.Pair[GAS2, func(A) S2]], GS1 ~func() O.Option[P.Pair[GS1, S1]], GS2 ~func() O.Option[P.Pair[GS2, S2]], GA ~func() O.Option[P.Pair[GA, A]], S1, S2, A any]( + setter func(A) func(S1) S2, + fa GA, +) func(GS1) GS2 { + return apply.ApS( + Ap[GAS2, GS2, GA, A, S2], + Map[GAS2, GS1, func(S1) func(A) S2, S1, func(A) S2], + setter, + fa, + ) +} diff --git a/v2/iterator/stateless/generic/compress.go b/v2/iterator/stateless/generic/compress.go new file mode 100644 index 0000000..43cc050 --- /dev/null +++ b/v2/iterator/stateless/generic/compress.go @@ -0,0 +1,34 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + F "github.com/IBM/fp-go/v2/function" + O "github.com/IBM/fp-go/v2/option" + P "github.com/IBM/fp-go/v2/pair" +) + +// Compress returns an [Iterator] that filters elements from a data [Iterator] returning only those that have a corresponding element in selector [Iterator] that evaluates to `true`. +// Stops when either the data or selectors iterator has been exhausted. +func Compress[GU ~func() O.Option[P.Pair[GU, U]], GB ~func() O.Option[P.Pair[GB, bool]], CS ~func() O.Option[P.Pair[CS, P.Pair[U, bool]]], U any](sel GB) func(GU) GU { + return F.Flow2( + Zip[GU, GB, CS](sel), + FilterMap[GU, CS](F.Flow2( + O.FromPredicate(P.Tail[U, bool]), + O.Map(P.Head[U, bool]), + )), + ) +} diff --git a/v2/iterator/stateless/generic/cycle.go b/v2/iterator/stateless/generic/cycle.go new file mode 100644 index 0000000..6d5be09 --- /dev/null +++ b/v2/iterator/stateless/generic/cycle.go @@ -0,0 +1,43 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + F "github.com/IBM/fp-go/v2/function" + O "github.com/IBM/fp-go/v2/option" + P "github.com/IBM/fp-go/v2/pair" +) + +func Cycle[GU ~func() O.Option[P.Pair[GU, U]], U any](ma GU) GU { + // avoid cyclic references + var m func(O.Option[P.Pair[GU, U]]) O.Option[P.Pair[GU, U]] + + recurse := func(mu GU) GU { + return F.Nullary2( + mu, + m, + ) + } + + m = O.Fold(func() O.Option[P.Pair[GU, U]] { + return recurse(ma)() + }, F.Flow2( + P.BiMap(recurse, F.Identity[U]), + O.Of[P.Pair[GU, U]], + )) + + return recurse(ma) +} diff --git a/v2/iterator/stateless/generic/dropwhile.go b/v2/iterator/stateless/generic/dropwhile.go new file mode 100644 index 0000000..6f7a1e3 --- /dev/null +++ b/v2/iterator/stateless/generic/dropwhile.go @@ -0,0 +1,49 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + F "github.com/IBM/fp-go/v2/function" + O "github.com/IBM/fp-go/v2/option" + P "github.com/IBM/fp-go/v2/pair" + PR "github.com/IBM/fp-go/v2/predicate" +) + +// DropWhile creates an [Iterator] that drops elements from the [Iterator] as long as the predicate is true; afterwards, returns every element. +// Note, the [Iterator] does not produce any output until the predicate first becomes false +func DropWhile[GU ~func() O.Option[P.Pair[GU, U]], U any](pred func(U) bool) func(GU) GU { + // avoid cyclic references + var m func(O.Option[P.Pair[GU, U]]) O.Option[P.Pair[GU, U]] + + fromPred := O.FromPredicate(PR.Not(PR.ContraMap(P.Tail[GU, U])(pred))) + + recurse := func(mu GU) GU { + return F.Nullary2( + mu, + m, + ) + } + + m = O.Chain(func(t P.Pair[GU, U]) O.Option[P.Pair[GU, U]] { + return F.Pipe2( + t, + fromPred, + O.Fold(recurse(Next(t)), O.Of[P.Pair[GU, U]]), + ) + }) + + return recurse +} diff --git a/v2/iterator/stateless/generic/first.go b/v2/iterator/stateless/generic/first.go new file mode 100644 index 0000000..304f64f --- /dev/null +++ b/v2/iterator/stateless/generic/first.go @@ -0,0 +1,30 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + F "github.com/IBM/fp-go/v2/function" + O "github.com/IBM/fp-go/v2/option" + P "github.com/IBM/fp-go/v2/pair" +) + +// First returns the first item in an iterator if such an item exists +func First[GU ~func() O.Option[P.Pair[GU, U]], U any](mu GU) O.Option[U] { + return F.Pipe1( + mu(), + O.Map(P.Tail[GU, U]), + ) +} diff --git a/v2/iterator/stateless/generic/io.go b/v2/iterator/stateless/generic/io.go new file mode 100644 index 0000000..7037621 --- /dev/null +++ b/v2/iterator/stateless/generic/io.go @@ -0,0 +1,34 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + F "github.com/IBM/fp-go/v2/function" + L "github.com/IBM/fp-go/v2/io/generic" + O "github.com/IBM/fp-go/v2/option" + P "github.com/IBM/fp-go/v2/pair" +) + +// FromLazy returns an iterator on top of a lazy function +func FromLazy[GU ~func() O.Option[P.Pair[GU, U]], LZ ~func() U, U any](l LZ) GU { + return F.Pipe1( + l, + L.Map[LZ, GU](F.Flow2( + F.Bind1st(P.MakePair[GU, U], Empty[GU]()), + O.Of[P.Pair[GU, U]], + )), + ) +} diff --git a/v2/iterator/stateless/generic/iterator.go b/v2/iterator/stateless/generic/iterator.go new file mode 100644 index 0000000..f7a2b4b --- /dev/null +++ b/v2/iterator/stateless/generic/iterator.go @@ -0,0 +1,270 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + A "github.com/IBM/fp-go/v2/array/generic" + F "github.com/IBM/fp-go/v2/function" + C "github.com/IBM/fp-go/v2/internal/chain" + "github.com/IBM/fp-go/v2/internal/utils" + IO "github.com/IBM/fp-go/v2/iooption/generic" + M "github.com/IBM/fp-go/v2/monoid" + N "github.com/IBM/fp-go/v2/number" + O "github.com/IBM/fp-go/v2/option" + P "github.com/IBM/fp-go/v2/pair" +) + +// Next returns the iterator for the next element in an iterator `P.Pair` +func Next[GU ~func() O.Option[P.Pair[GU, U]], U any](m P.Pair[GU, U]) GU { + return P.Head(m) +} + +// Current returns the current element in an iterator `P.Pair` +func Current[GU ~func() O.Option[P.Pair[GU, U]], U any](m P.Pair[GU, U]) U { + return P.Tail(m) +} + +// From constructs an array from a set of variadic arguments +func From[GU ~func() O.Option[P.Pair[GU, U]], U any](data ...U) GU { + return FromArray[GU](data) +} + +// Empty returns the empty iterator +func Empty[GU ~func() O.Option[P.Pair[GU, U]], U any]() GU { + return IO.None[GU]() +} + +// Of returns an iterator with one single element +func Of[GU ~func() O.Option[P.Pair[GU, U]], U any](a U) GU { + return IO.Of[GU](P.MakePair(Empty[GU](), a)) +} + +// FromArray returns an iterator from multiple elements +func FromArray[GU ~func() O.Option[P.Pair[GU, U]], US ~[]U, U any](as US) GU { + return A.MatchLeft(Empty[GU], func(head U, tail US) GU { + return func() O.Option[P.Pair[GU, U]] { + return O.Of(P.MakePair(FromArray[GU](tail), head)) + } + })(as) +} + +// reduce applies a function for each value of the iterator with a floating result +func reduce[GU ~func() O.Option[P.Pair[GU, U]], U, V any](as GU, f func(V, U) V, initial V) V { + next, ok := O.Unwrap(as()) + current := initial + for ok { + // next (with bad side effect) + current = f(current, Current(next)) + next, ok = O.Unwrap(Next(next)()) + } + return current +} + +// Reduce applies a function for each value of the iterator with a floating result +func Reduce[GU ~func() O.Option[P.Pair[GU, U]], U, V any](f func(V, U) V, initial V) func(GU) V { + return F.Bind23of3(reduce[GU, U, V])(f, initial) +} + +// ToArray converts the iterator to an array +func ToArray[GU ~func() O.Option[P.Pair[GU, U]], US ~[]U, U any](u GU) US { + return Reduce[GU](A.Append[US], A.Empty[US]())(u) +} + +func Map[GV ~func() O.Option[P.Pair[GV, V]], GU ~func() O.Option[P.Pair[GU, U]], FCT ~func(U) V, U, V any](f FCT) func(ma GU) GV { + // pre-declare to avoid cyclic reference + var m func(O.Option[P.Pair[GU, U]]) O.Option[P.Pair[GV, V]] + + recurse := func(ma GU) GV { + return F.Nullary2( + ma, + m, + ) + } + + m = O.Map(P.BiMap(recurse, f)) + + return recurse +} + +func MonadMap[GV ~func() O.Option[P.Pair[GV, V]], GU ~func() O.Option[P.Pair[GU, U]], U, V any](ma GU, f func(U) V) GV { + return Map[GV, GU](f)(ma) +} + +func concat[GU ~func() O.Option[P.Pair[GU, U]], U any](right, left GU) GU { + var m func(ma O.Option[P.Pair[GU, U]]) O.Option[P.Pair[GU, U]] + + recurse := func(left GU) GU { + return F.Nullary2(left, m) + } + + m = O.Fold( + right, + F.Flow2( + P.BiMap(recurse, F.Identity[U]), + O.Some[P.Pair[GU, U]], + )) + + return recurse(left) +} + +func Chain[GV ~func() O.Option[P.Pair[GV, V]], GU ~func() O.Option[P.Pair[GU, U]], U, V any](f func(U) GV) func(GU) GV { + // pre-declare to avoid cyclic reference + var m func(O.Option[P.Pair[GU, U]]) O.Option[P.Pair[GV, V]] + + recurse := func(ma GU) GV { + return F.Nullary2( + ma, + m, + ) + } + m = O.Chain( + F.Flow3( + P.BiMap(recurse, f), + P.Paired(concat[GV]), + func(v GV) O.Option[P.Pair[GV, V]] { + return v() + }, + ), + ) + + return recurse +} + +func MonadChain[GV ~func() O.Option[P.Pair[GV, V]], GU ~func() O.Option[P.Pair[GU, U]], U, V any](ma GU, f func(U) GV) GV { + return Chain[GV, GU](f)(ma) +} + +func MonadChainFirst[GV ~func() O.Option[P.Pair[GV, V]], GU ~func() O.Option[P.Pair[GU, U]], U, V any](ma GU, f func(U) GV) GU { + return C.MonadChainFirst( + MonadChain[GU, GU, U, U], + MonadMap[GU, GV, V, U], + ma, + f, + ) +} + +func ChainFirst[GV ~func() O.Option[P.Pair[GV, V]], GU ~func() O.Option[P.Pair[GU, U]], U, V any](f func(U) GV) func(GU) GU { + return C.ChainFirst( + Chain[GU, GU, U, U], + Map[GU, GV, func(V) U, V, U], + f, + ) +} + +func Flatten[GV ~func() O.Option[P.Pair[GV, GU]], GU ~func() O.Option[P.Pair[GU, U]], U any](ma GV) GU { + return MonadChain(ma, F.Identity[GU]) +} + +// MakeBy returns an [Iterator] with an infinite number of elements initialized with `f(i)` +func MakeBy[GU ~func() O.Option[P.Pair[GU, U]], FCT ~func(int) U, U any](f FCT) GU { + + var m func(int) O.Option[P.Pair[GU, U]] + + recurse := func(i int) GU { + return F.Nullary2( + F.Constant(i), + m, + ) + } + + m = F.Flow3( + P.Of[int], + P.BiMap(F.Flow2( + utils.Inc, + recurse), + f), + O.Of[P.Pair[GU, U]], + ) + + // bootstrap + return recurse(0) +} + +// Replicate creates an infinite [Iterator] containing a value. +func Replicate[GU ~func() O.Option[P.Pair[GU, U]], U any](a U) GU { + return MakeBy[GU](F.Constant1[int](a)) +} + +// Repeat creates an [Iterator] containing a value repeated the specified number of times. +// Alias of [Replicate] combined with [Take] +func Repeat[GU ~func() O.Option[P.Pair[GU, U]], U any](n int, a U) GU { + return F.Pipe2( + a, + Replicate[GU], + Take[GU](n), + ) +} + +// Count creates an [Iterator] containing a consecutive sequence of integers starting with the provided start value +func Count[GU ~func() O.Option[P.Pair[GU, int]]](start int) GU { + return MakeBy[GU](N.Add(start)) +} + +func FilterMap[GV ~func() O.Option[P.Pair[GV, V]], GU ~func() O.Option[P.Pair[GU, U]], FCT ~func(U) O.Option[V], U, V any](f FCT) func(ma GU) GV { + // pre-declare to avoid cyclic reference + var m func(O.Option[P.Pair[GU, U]]) O.Option[P.Pair[GV, V]] + + recurse := func(ma GU) GV { + return F.Nullary2( + ma, + m, + ) + } + + m = O.Fold( + Empty[GV](), + func(t P.Pair[GU, U]) O.Option[P.Pair[GV, V]] { + r := recurse(Next(t)) + return O.MonadFold(f(Current(t)), r, F.Flow2( + F.Bind1st(P.MakePair[GV, V], r), + O.Some[P.Pair[GV, V]], + )) + }, + ) + + return recurse +} + +func Filter[GU ~func() O.Option[P.Pair[GU, U]], FCT ~func(U) bool, U any](f FCT) func(ma GU) GU { + return FilterMap[GU, GU](O.FromPredicate(f)) +} + +func Ap[GUV ~func() O.Option[P.Pair[GUV, func(U) V]], GV ~func() O.Option[P.Pair[GV, V]], GU ~func() O.Option[P.Pair[GU, U]], U, V any](ma GU) func(fab GUV) GV { + return Chain[GV, GUV](F.Bind1st(MonadMap[GV, GU], ma)) +} + +func MonadAp[GUV ~func() O.Option[P.Pair[GUV, func(U) V]], GV ~func() O.Option[P.Pair[GV, V]], GU ~func() O.Option[P.Pair[GU, U]], U, V any](fab GUV, ma GU) GV { + return Ap[GUV, GV, GU](ma)(fab) +} + +func FilterChain[GVV ~func() O.Option[P.Pair[GVV, GV]], GV ~func() O.Option[P.Pair[GV, V]], GU ~func() O.Option[P.Pair[GU, U]], FCT ~func(U) O.Option[GV], U, V any](f FCT) func(ma GU) GV { + return F.Flow2( + FilterMap[GVV, GU](f), + Flatten[GVV], + ) +} + +func FoldMap[GU ~func() O.Option[P.Pair[GU, U]], FCT ~func(U) V, U, V any](m M.Monoid[V]) func(FCT) func(ma GU) V { + return func(f FCT) func(ma GU) V { + return Reduce[GU](func(cur V, a U) V { + return m.Concat(cur, f(a)) + }, m.Empty()) + } +} + +func Fold[GU ~func() O.Option[P.Pair[GU, U]], U any](m M.Monoid[U]) func(ma GU) U { + return Reduce[GU](m.Concat, m.Empty()) +} diff --git a/v2/iterator/stateless/generic/last.go b/v2/iterator/stateless/generic/last.go new file mode 100644 index 0000000..81c6fd5 --- /dev/null +++ b/v2/iterator/stateless/generic/last.go @@ -0,0 +1,27 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + F "github.com/IBM/fp-go/v2/function" + O "github.com/IBM/fp-go/v2/option" + P "github.com/IBM/fp-go/v2/pair" +) + +// Last returns the last item in an iterator if such an item exists +func Last[GU ~func() O.Option[P.Pair[GU, U]], U any](mu GU) O.Option[U] { + return reduce(mu, F.Ignore1of2[O.Option[U]](O.Of[U]), O.None[U]()) +} diff --git a/v2/iterator/stateless/generic/monad.go b/v2/iterator/stateless/generic/monad.go new file mode 100644 index 0000000..64ef765 --- /dev/null +++ b/v2/iterator/stateless/generic/monad.go @@ -0,0 +1,45 @@ +// Copyright (c) 2024 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + "github.com/IBM/fp-go/v2/internal/monad" + O "github.com/IBM/fp-go/v2/option" + P "github.com/IBM/fp-go/v2/pair" +) + +type iteratorMonad[A, B any, GA ~func() O.Option[P.Pair[GA, A]], GB ~func() O.Option[P.Pair[GB, B]], GAB ~func() O.Option[P.Pair[GAB, func(A) B]]] struct{} + +func (o *iteratorMonad[A, B, GA, GB, GAB]) Of(a A) GA { + return Of[GA, A](a) +} + +func (o *iteratorMonad[A, B, GA, GB, GAB]) Map(f func(A) B) func(GA) GB { + return Map[GB, GA, func(A) B, A, B](f) +} + +func (o *iteratorMonad[A, B, GA, GB, GAB]) Chain(f func(A) GB) func(GA) GB { + return Chain[GB, GA, A, B](f) +} + +func (o *iteratorMonad[A, B, GA, GB, GAB]) Ap(fa GA) func(GAB) GB { + return Ap[GAB, GB, GA, A, B](fa) +} + +// Monad implements the monadic operations for iterators +func Monad[A, B any, GA ~func() O.Option[P.Pair[GA, A]], GB ~func() O.Option[P.Pair[GB, B]], GAB ~func() O.Option[P.Pair[GAB, func(A) B]]]() monad.Monad[A, B, GA, GB, GAB] { + return &iteratorMonad[A, B, GA, GB, GAB]{} +} diff --git a/v2/iterator/stateless/generic/monoid.go b/v2/iterator/stateless/generic/monoid.go new file mode 100644 index 0000000..f8217f1 --- /dev/null +++ b/v2/iterator/stateless/generic/monoid.go @@ -0,0 +1,30 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + F "github.com/IBM/fp-go/v2/function" + M "github.com/IBM/fp-go/v2/monoid" + O "github.com/IBM/fp-go/v2/option" + P "github.com/IBM/fp-go/v2/pair" +) + +func Monoid[GU ~func() O.Option[P.Pair[GU, U]], U any]() M.Monoid[GU] { + return M.MakeMonoid( + F.Swap(concat[GU]), + Empty[GU](), + ) +} diff --git a/v2/iterator/stateless/generic/reflect.go b/v2/iterator/stateless/generic/reflect.go new file mode 100644 index 0000000..3db3a64 --- /dev/null +++ b/v2/iterator/stateless/generic/reflect.go @@ -0,0 +1,53 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + R "reflect" + + F "github.com/IBM/fp-go/v2/function" + LG "github.com/IBM/fp-go/v2/io/generic" + L "github.com/IBM/fp-go/v2/lazy" + N "github.com/IBM/fp-go/v2/number" + I "github.com/IBM/fp-go/v2/number/integer" + O "github.com/IBM/fp-go/v2/option" + P "github.com/IBM/fp-go/v2/pair" +) + +func FromReflect[GR ~func() O.Option[P.Pair[GR, R.Value]]](val R.Value) GR { + // recursive callback + var recurse func(idx int) GR + + // limits the index + fromPred := O.FromPredicate(I.Between(0, val.Len())) + + recurse = func(idx int) GR { + return F.Pipe3( + idx, + L.Of[int], + L.Map(fromPred), + LG.Map[L.Lazy[O.Option[int]], GR](O.Map( + F.Flow2( + P.Of[int], + P.BiMap(F.Flow2(N.Add(1), recurse), val.Index), + ), + )), + ) + } + + // start the recursion + return recurse(0) +} diff --git a/v2/iterator/stateless/generic/scan.go b/v2/iterator/stateless/generic/scan.go new file mode 100644 index 0000000..712ccf2 --- /dev/null +++ b/v2/iterator/stateless/generic/scan.go @@ -0,0 +1,45 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + F "github.com/IBM/fp-go/v2/function" + O "github.com/IBM/fp-go/v2/option" + P "github.com/IBM/fp-go/v2/pair" +) + +func apTuple[A, B any](t P.Pair[func(A) B, A]) P.Pair[B, A] { + return P.MakePair(P.Head(t)(P.Tail(t)), P.Tail(t)) +} + +func Scan[GV ~func() O.Option[P.Pair[GV, V]], GU ~func() O.Option[P.Pair[GU, U]], FCT ~func(V, U) V, U, V any](f FCT, initial V) func(ma GU) GV { + // pre-declare to avoid cyclic reference + var m func(GU) func(V) GV + + recurse := func(ma GU, current V) GV { + return F.Nullary2( + ma, + O.Map(F.Flow2( + P.BiMap(m, F.Bind1st(f, current)), + apTuple[V, GV], + )), + ) + } + + m = F.Curry2(recurse) + + return F.Bind2nd(recurse, initial) +} diff --git a/v2/iterator/stateless/generic/take.go b/v2/iterator/stateless/generic/take.go new file mode 100644 index 0000000..2f23650 --- /dev/null +++ b/v2/iterator/stateless/generic/take.go @@ -0,0 +1,43 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + F "github.com/IBM/fp-go/v2/function" + N "github.com/IBM/fp-go/v2/number/integer" + O "github.com/IBM/fp-go/v2/option" + P "github.com/IBM/fp-go/v2/pair" +) + +func Take[GU ~func() O.Option[P.Pair[GU, U]], U any](n int) func(ma GU) GU { + // pre-declare to avoid cyclic reference + var recurse func(ma GU, idx int) GU + + fromPred := O.FromPredicate(N.Between(0, n)) + + recurse = func(ma GU, idx int) GU { + return F.Nullary3( + F.Constant(idx), + fromPred, + O.Chain(F.Ignore1of1[int](F.Nullary2( + ma, + O.Map(P.BiMap(F.Bind2nd(recurse, idx+1), F.Identity[U])), + ))), + ) + } + + return F.Bind2nd(recurse, 0) +} diff --git a/v2/iterator/stateless/generic/uniq.go b/v2/iterator/stateless/generic/uniq.go new file mode 100644 index 0000000..8556f19 --- /dev/null +++ b/v2/iterator/stateless/generic/uniq.go @@ -0,0 +1,62 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + F "github.com/IBM/fp-go/v2/function" + O "github.com/IBM/fp-go/v2/option" + P "github.com/IBM/fp-go/v2/pair" +) + +// addToMap makes a deep copy of a map and adds a value +func addToMap[A comparable](a A, m map[A]bool) map[A]bool { + cpy := make(map[A]bool, len(m)+1) + for k, v := range m { + cpy[k] = v + } + cpy[a] = true + return cpy +} + +func Uniq[AS ~func() O.Option[P.Pair[AS, A]], K comparable, A any](f func(A) K) func(as AS) AS { + + var recurse func(as AS, mp map[K]bool) AS + + recurse = func(as AS, mp map[K]bool) AS { + return F.Nullary2( + as, + O.Chain(func(a P.Pair[AS, A]) O.Option[P.Pair[AS, A]] { + return F.Pipe3( + P.Tail(a), + f, + O.FromPredicate(func(k K) bool { + _, ok := mp[k] + return !ok + }), + O.Fold(recurse(P.Head(a), mp), func(k K) O.Option[P.Pair[AS, A]] { + return O.Of(P.MakePair(recurse(P.Head(a), addToMap(k, mp)), P.Tail(a))) + }), + ) + }), + ) + } + + return F.Bind2nd(recurse, make(map[K]bool, 0)) +} + +func StrictUniq[AS ~func() O.Option[P.Pair[AS, A]], A comparable](as AS) AS { + return Uniq[AS](F.Identity[A])(as) +} diff --git a/v2/iterator/stateless/generic/zip.go b/v2/iterator/stateless/generic/zip.go new file mode 100644 index 0000000..13f90f3 --- /dev/null +++ b/v2/iterator/stateless/generic/zip.go @@ -0,0 +1,54 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + F "github.com/IBM/fp-go/v2/function" + O "github.com/IBM/fp-go/v2/option" + P "github.com/IBM/fp-go/v2/pair" +) + +// ZipWith applies a function to pairs of elements at the same index in two iterators, collecting the results in a new iterator. If one +// input iterator is short, excess elements of the longer iterator are discarded. +func ZipWith[AS ~func() O.Option[P.Pair[AS, A]], BS ~func() O.Option[P.Pair[BS, B]], CS ~func() O.Option[P.Pair[CS, C]], FCT ~func(A, B) C, A, B, C any](fa AS, fb BS, f FCT) CS { + // pre-declare to avoid cyclic reference + var m func(P.Pair[O.Option[P.Pair[AS, A]], O.Option[P.Pair[BS, B]]]) O.Option[P.Pair[CS, C]] + + recurse := func(as AS, bs BS) CS { + return func() O.Option[P.Pair[CS, C]] { + // combine + return F.Pipe1( + P.MakePair(as(), bs()), + m, + ) + } + } + + m = F.Flow2( + O.SequencePair[P.Pair[AS, A], P.Pair[BS, B]], + O.Map(func(t P.Pair[P.Pair[AS, A], P.Pair[BS, B]]) P.Pair[CS, C] { + return P.MakePair(recurse(P.Head(P.Head(t)), P.Head(P.Tail(t))), f(P.Tail(P.Head(t)), P.Tail(P.Tail(t)))) + })) + + // trigger the recursion + return recurse(fa, fb) +} + +// Zip takes two iterators and returns an iterators of corresponding pairs. If one input iterators is short, excess elements of the +// longer iterator are discarded +func Zip[AS ~func() O.Option[P.Pair[AS, A]], BS ~func() O.Option[P.Pair[BS, B]], CS ~func() O.Option[P.Pair[CS, P.Pair[A, B]]], A, B any](fb BS) func(AS) CS { + return F.Bind23of3(ZipWith[AS, BS, CS, func(A, B) P.Pair[A, B]])(fb, P.MakePair[A, B]) +} diff --git a/v2/iterator/stateless/io.go b/v2/iterator/stateless/io.go new file mode 100644 index 0000000..eae3048 --- /dev/null +++ b/v2/iterator/stateless/io.go @@ -0,0 +1,32 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package stateless + +import ( + IO "github.com/IBM/fp-go/v2/io" + G "github.com/IBM/fp-go/v2/iterator/stateless/generic" + L "github.com/IBM/fp-go/v2/lazy" +) + +// FromLazy returns an [Iterator] on top of a lazy function +func FromLazy[U any](l L.Lazy[U]) Iterator[U] { + return G.FromLazy[Iterator[U], L.Lazy[U]](l) +} + +// FromIO returns an [Iterator] on top of an IO function +func FromIO[U any](io IO.IO[U]) Iterator[U] { + return G.FromLazy[Iterator[U], IO.IO[U]](io) +} diff --git a/v2/iterator/stateless/io_test.go b/v2/iterator/stateless/io_test.go new file mode 100644 index 0000000..355c268 --- /dev/null +++ b/v2/iterator/stateless/io_test.go @@ -0,0 +1,38 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package stateless + +import ( + "math/rand" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestIteratorFromLazy(t *testing.T) { + num := rand.Int + + cit := FromLazy(num) + + // create arrays twice + c1 := ToArray(cit) + c2 := ToArray(cit) + + assert.Equal(t, 1, len(c1)) + assert.Equal(t, 1, len(c2)) + + assert.NotEqual(t, c1, c2) +} diff --git a/v2/iterator/stateless/iterator.go b/v2/iterator/stateless/iterator.go new file mode 100644 index 0000000..0ef31f4 --- /dev/null +++ b/v2/iterator/stateless/iterator.go @@ -0,0 +1,155 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package stateless + +import ( + "github.com/IBM/fp-go/v2/iooption" + G "github.com/IBM/fp-go/v2/iterator/stateless/generic" + L "github.com/IBM/fp-go/v2/lazy" + M "github.com/IBM/fp-go/v2/monoid" + O "github.com/IBM/fp-go/v2/option" + "github.com/IBM/fp-go/v2/pair" +) + +// Iterator represents a stateless, pure way to iterate over a sequence +type Iterator[U any] L.Lazy[O.Option[pair.Pair[Iterator[U], U]]] + +// Next returns the [Iterator] for the next element in an iterator [pair.Pair] +func Next[U any](m pair.Pair[Iterator[U], U]) Iterator[U] { + return pair.Head(m) +} + +// Current returns the current element in an [Iterator] [pair.Pair] +func Current[U any](m pair.Pair[Iterator[U], U]) U { + return pair.Tail(m) +} + +// Empty returns the empty iterator +func Empty[U any]() Iterator[U] { + return iooption.None[pair.Pair[Iterator[U], U]]() +} + +// Of returns an iterator with one single element +func Of[U any](a U) Iterator[U] { + return iooption.Of(pair.MakePair(Empty[U](), a)) +} + +// FromArray returns an iterator from multiple elements +func FromArray[U any](as []U) Iterator[U] { + return G.FromArray[Iterator[U]](as) +} + +// ToArray converts the iterator to an array +func ToArray[U any](u Iterator[U]) []U { + return G.ToArray[Iterator[U], []U](u) +} + +// Reduce applies a function for each value of the iterator with a floating result +func Reduce[U, V any](f func(V, U) V, initial V) func(Iterator[U]) V { + return G.Reduce[Iterator[U]](f, initial) +} + +// MonadMap transforms an [Iterator] of type [U] into an [Iterator] of type [V] via a mapping function +func MonadMap[U, V any](ma Iterator[U], f func(U) V) Iterator[V] { + return G.MonadMap[Iterator[V], Iterator[U]](ma, f) +} + +// Map transforms an [Iterator] of type [U] into an [Iterator] of type [V] via a mapping function +func Map[U, V any](f func(U) V) func(ma Iterator[U]) Iterator[V] { + return G.Map[Iterator[V], Iterator[U]](f) +} + +func MonadChain[U, V any](ma Iterator[U], f func(U) Iterator[V]) Iterator[V] { + return G.MonadChain[Iterator[V], Iterator[U]](ma, f) +} + +func Chain[U, V any](f func(U) Iterator[V]) func(Iterator[U]) Iterator[V] { + return G.Chain[Iterator[V], Iterator[U]](f) +} + +// Flatten converts an [Iterator] of [Iterator] into a simple [Iterator] +func Flatten[U any](ma Iterator[Iterator[U]]) Iterator[U] { + return G.Flatten[Iterator[Iterator[U]], Iterator[U]](ma) +} + +// From constructs an [Iterator] from a set of variadic arguments +func From[U any](data ...U) Iterator[U] { + return G.From[Iterator[U]](data...) +} + +// MakeBy returns an [Iterator] with an infinite number of elements initialized with `f(i)` +func MakeBy[FCT ~func(int) U, U any](f FCT) Iterator[U] { + return G.MakeBy[Iterator[U]](f) +} + +// Replicate creates an [Iterator] containing a value repeated an infinite number of times. +func Replicate[U any](a U) Iterator[U] { + return G.Replicate[Iterator[U]](a) +} + +// FilterMap filters and transforms the content of an iterator +func FilterMap[U, V any](f func(U) O.Option[V]) func(ma Iterator[U]) Iterator[V] { + return G.FilterMap[Iterator[V], Iterator[U]](f) +} + +// Filter filters the content of an iterator +func Filter[U any](f func(U) bool) func(ma Iterator[U]) Iterator[U] { + return G.Filter[Iterator[U]](f) +} + +// Ap is the applicative functor for iterators +func Ap[V, U any](ma Iterator[U]) func(Iterator[func(U) V]) Iterator[V] { + return G.Ap[Iterator[func(U) V], Iterator[V]](ma) +} + +// MonadAp is the applicative functor for iterators +func MonadAp[V, U any](fab Iterator[func(U) V], ma Iterator[U]) Iterator[V] { + return G.MonadAp[Iterator[func(U) V], Iterator[V]](fab, ma) +} + +// Repeat creates an [Iterator] containing a value repeated the specified number of times. +// Alias of [Replicate] +func Repeat[U any](n int, a U) Iterator[U] { + return G.Repeat[Iterator[U]](n, a) +} + +// Count creates an [Iterator] containing a consecutive sequence of integers starting with the provided start value +func Count(start int) Iterator[int] { + return G.Count[Iterator[int]](start) +} + +// FilterChain filters and transforms the content of an iterator +func FilterChain[U, V any](f func(U) O.Option[Iterator[V]]) func(ma Iterator[U]) Iterator[V] { + return G.FilterChain[Iterator[Iterator[V]], Iterator[V], Iterator[U]](f) +} + +// FoldMap maps and folds an iterator. Map the iterator passing each value to the iterating function. Then fold the results using the provided Monoid. +func FoldMap[U, V any](m M.Monoid[V]) func(func(U) V) func(ma Iterator[U]) V { + return G.FoldMap[Iterator[U], func(U) V, U, V](m) +} + +// Fold folds the iterator using the provided Monoid. +func Fold[U any](m M.Monoid[U]) func(Iterator[U]) U { + return G.Fold[Iterator[U]](m) +} + +func MonadChainFirst[U, V any](ma Iterator[U], f func(U) Iterator[V]) Iterator[U] { + return G.MonadChainFirst[Iterator[V], Iterator[U], U, V](ma, f) +} + +func ChainFirst[U, V any](f func(U) Iterator[V]) func(Iterator[U]) Iterator[U] { + return G.ChainFirst[Iterator[V], Iterator[U], U, V](f) +} diff --git a/v2/iterator/stateless/iterator_test.go b/v2/iterator/stateless/iterator_test.go new file mode 100644 index 0000000..4299c0e --- /dev/null +++ b/v2/iterator/stateless/iterator_test.go @@ -0,0 +1,117 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package stateless + +import ( + "fmt" + "math" + "strings" + "testing" + + A "github.com/IBM/fp-go/v2/array" + F "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/internal/utils" + O "github.com/IBM/fp-go/v2/option" + S "github.com/IBM/fp-go/v2/string" + "github.com/stretchr/testify/assert" +) + +func TestIterator(t *testing.T) { + + result := F.Pipe2( + A.From(1, 2, 3), + FromArray[int], + Reduce(utils.Sum, 0), + ) + + assert.Equal(t, 6, result) +} + +func TestChain(t *testing.T) { + + outer := From(1, 2, 3) + + inner := func(data int) Iterator[string] { + return F.Pipe2( + A.From(0, 1), + FromArray[int], + Map(func(idx int) string { + return fmt.Sprintf("item[%d][%d]", data, idx) + }), + ) + } + + total := F.Pipe2( + outer, + Chain(inner), + ToArray[string], + ) + + assert.Equal(t, A.From("item[1][0]", "item[1][1]", "item[2][0]", "item[2][1]", "item[3][0]", "item[3][1]"), total) +} + +func isPrimeNumber(num int) bool { + if num <= 2 { + return true + } + sqRoot := int(math.Sqrt(float64(num))) + for i := 2; i <= sqRoot; i++ { + if num%i == 0 { + return false + } + } + return true +} + +func TestFilterMap(t *testing.T) { + + it := F.Pipe3( + MakeBy(utils.Inc), + Take[int](100), + FilterMap(O.FromPredicate(isPrimeNumber)), + ToArray[int], + ) + + assert.Equal(t, A.From(1, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97), it) +} + +func TestAp(t *testing.T) { + + f := F.Curry3(func(s1 string, n int, s2 string) string { + return fmt.Sprintf("%s-%d-%s", s1, n, s2) + }) + + it := F.Pipe4( + Of(f), + Ap[func(int) func(string) string](From("a", "b")), + Ap[func(string) string](From(1, 2)), + Ap[string](From("c", "d")), + ToArray[string], + ) + + assert.Equal(t, A.From("a-1-c", "a-1-d", "a-2-c", "a-2-d", "b-1-c", "b-1-d", "b-2-c", "b-2-d"), it) +} + +func ExampleFoldMap() { + src := From("a", "b", "c") + + fold := FoldMap[string](S.Monoid)(strings.ToUpper) + + fmt.Println(fold(src)) + + // Output: ABC + +} diff --git a/v2/iterator/stateless/last.go b/v2/iterator/stateless/last.go new file mode 100644 index 0000000..0e45cb2 --- /dev/null +++ b/v2/iterator/stateless/last.go @@ -0,0 +1,27 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package stateless + +import ( + G "github.com/IBM/fp-go/v2/iterator/stateless/generic" + O "github.com/IBM/fp-go/v2/option" +) + +// Last returns the last item in an iterator if such an item exists +// Note that the function will consume the [Iterator] in this call completely, to identify the last element. Do not use this for infinite iterators +func Last[U any](mu Iterator[U]) O.Option[U] { + return G.Last[Iterator[U]](mu) +} diff --git a/v2/iterator/stateless/last_test.go b/v2/iterator/stateless/last_test.go new file mode 100644 index 0000000..251f211 --- /dev/null +++ b/v2/iterator/stateless/last_test.go @@ -0,0 +1,41 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package stateless + +import ( + "testing" + + O "github.com/IBM/fp-go/v2/option" + "github.com/stretchr/testify/assert" +) + +func TestLast(t *testing.T) { + + seq := From(1, 2, 3) + + fst := Last(seq) + + assert.Equal(t, O.Of(3), fst) +} + +func TestNoLast(t *testing.T) { + + seq := Empty[int]() + + fst := Last(seq) + + assert.Equal(t, O.None[int](), fst) +} diff --git a/v2/iterator/stateless/monad.go b/v2/iterator/stateless/monad.go new file mode 100644 index 0000000..af80dbd --- /dev/null +++ b/v2/iterator/stateless/monad.go @@ -0,0 +1,26 @@ +// Copyright (c) 2024 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package stateless + +import ( + "github.com/IBM/fp-go/v2/internal/monad" + G "github.com/IBM/fp-go/v2/iterator/stateless/generic" +) + +// Monad returns the monadic operations for an [Iterator] +func Monad[A, B any]() monad.Monad[A, B, Iterator[A], Iterator[B], Iterator[func(A) B]] { + return G.Monad[A, B, Iterator[A], Iterator[B], Iterator[func(A) B]]() +} diff --git a/v2/iterator/stateless/monoid.go b/v2/iterator/stateless/monoid.go new file mode 100644 index 0000000..5e5e848 --- /dev/null +++ b/v2/iterator/stateless/monoid.go @@ -0,0 +1,26 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package stateless + +import ( + G "github.com/IBM/fp-go/v2/iterator/stateless/generic" + M "github.com/IBM/fp-go/v2/monoid" +) + +// Monoid contructs a [M.Monoid] that concatenates two [Iterator]s +func Monoid[U any]() M.Monoid[Iterator[U]] { + return G.Monoid[Iterator[U]]() +} diff --git a/v2/iterator/stateless/reflect.go b/v2/iterator/stateless/reflect.go new file mode 100644 index 0000000..ff48498 --- /dev/null +++ b/v2/iterator/stateless/reflect.go @@ -0,0 +1,27 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package stateless + +import ( + R "reflect" + + G "github.com/IBM/fp-go/v2/iterator/stateless/generic" +) + +// FromReflect creates an iterator that can iterate over types that define [R.Index] and [R.Len] +func FromReflect(val R.Value) Iterator[R.Value] { + return G.FromReflect[Iterator[R.Value]](val) +} diff --git a/v2/iterator/stateless/reflect_test.go b/v2/iterator/stateless/reflect_test.go new file mode 100644 index 0000000..bfa95a1 --- /dev/null +++ b/v2/iterator/stateless/reflect_test.go @@ -0,0 +1,38 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package stateless + +import ( + "reflect" + "testing" + + A "github.com/IBM/fp-go/v2/array" + F "github.com/IBM/fp-go/v2/function" + "github.com/stretchr/testify/assert" +) + +func TestReflect(t *testing.T) { + ar := A.From("a", "b", "c") + + res := F.Pipe3( + reflect.ValueOf(ar), + FromReflect, + ToArray[reflect.Value], + A.Map(reflect.Value.String), + ) + + assert.Equal(t, ar, res) +} diff --git a/v2/iterator/stateless/scan.go b/v2/iterator/stateless/scan.go new file mode 100644 index 0000000..b9219c7 --- /dev/null +++ b/v2/iterator/stateless/scan.go @@ -0,0 +1,27 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package stateless + +import ( + G "github.com/IBM/fp-go/v2/iterator/stateless/generic" +) + +// Scan takes an [Iterator] and returns a new [Iterator] of the same length, where the values +// of the new [Iterator] are the result of the application of `f` to the value of the +// source iterator with the previously accumulated value +func Scan[FCT ~func(V, U) V, U, V any](f FCT, initial V) func(ma Iterator[U]) Iterator[V] { + return G.Scan[Iterator[V], Iterator[U], FCT](f, initial) +} diff --git a/v2/iterator/stateless/scan_test.go b/v2/iterator/stateless/scan_test.go new file mode 100644 index 0000000..fdf1ca0 --- /dev/null +++ b/v2/iterator/stateless/scan_test.go @@ -0,0 +1,42 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package stateless + +import ( + "testing" + + F "github.com/IBM/fp-go/v2/function" + P "github.com/IBM/fp-go/v2/pair" + "github.com/stretchr/testify/assert" +) + +func TestScan(t *testing.T) { + + src := From("a", "b", "c") + + dst := F.Pipe1( + src, + Scan(func(cur P.Pair[int, string], val string) P.Pair[int, string] { + return P.MakePair(P.Head(cur)+1, val) + }, P.MakePair(0, "")), + ) + + assert.Equal(t, ToArray(From( + P.MakePair(1, "a"), + P.MakePair(2, "b"), + P.MakePair(3, "c"), + )), ToArray(dst)) +} diff --git a/v2/iterator/stateless/take.go b/v2/iterator/stateless/take.go new file mode 100644 index 0000000..f9aef51 --- /dev/null +++ b/v2/iterator/stateless/take.go @@ -0,0 +1,25 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package stateless + +import ( + G "github.com/IBM/fp-go/v2/iterator/stateless/generic" +) + +// Take limits the number of values in the [Iterator] to a maximum number +func Take[U any](n int) func(ma Iterator[U]) Iterator[U] { + return G.Take[Iterator[U]](n) +} diff --git a/v2/iterator/stateless/take_test.go b/v2/iterator/stateless/take_test.go new file mode 100644 index 0000000..a8ec4de --- /dev/null +++ b/v2/iterator/stateless/take_test.go @@ -0,0 +1,37 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package stateless + +import ( + "testing" + + A "github.com/IBM/fp-go/v2/array" + F "github.com/IBM/fp-go/v2/function" + "github.com/stretchr/testify/assert" +) + +func TestTake(t *testing.T) { + + total := MakeBy(F.Identity[int]) + + trimmed := F.Pipe1( + total, + Take[int](10), + ) + + assert.Equal(t, A.MakeBy(10, F.Identity[int]), ToArray(trimmed)) + +} diff --git a/v2/iterator/stateless/types.go b/v2/iterator/stateless/types.go new file mode 100644 index 0000000..ad3307b --- /dev/null +++ b/v2/iterator/stateless/types.go @@ -0,0 +1,22 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package stateless + +import "github.com/IBM/fp-go/v2/option" + +type ( + Option[A any] = option.Option[A] +) diff --git a/v2/iterator/stateless/uniq.go b/v2/iterator/stateless/uniq.go new file mode 100644 index 0000000..8ec8e73 --- /dev/null +++ b/v2/iterator/stateless/uniq.go @@ -0,0 +1,32 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package stateless + +import ( + G "github.com/IBM/fp-go/v2/iterator/stateless/generic" +) + +// StrictUniq converts an [Iterator] of arbitrary items into an [Iterator] or unique items +// where uniqueness is determined by the built-in uniqueness constraint +func StrictUniq[A comparable](as Iterator[A]) Iterator[A] { + return G.StrictUniq[Iterator[A]](as) +} + +// Uniq converts an [Iterator] of arbitrary items into an [Iterator] or unique items +// where uniqueness is determined based on a key extractor function +func Uniq[A any, K comparable](f func(A) K) func(as Iterator[A]) Iterator[A] { + return G.Uniq[Iterator[A], K](f) +} diff --git a/v2/iterator/stateless/uniq_test.go b/v2/iterator/stateless/uniq_test.go new file mode 100644 index 0000000..0e20f88 --- /dev/null +++ b/v2/iterator/stateless/uniq_test.go @@ -0,0 +1,31 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package stateless + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestUniq(t *testing.T) { + // iterator with duplicate items + dups := From(1, 2, 3, 1, 4, 5, 2) + + u := StrictUniq(dups) + + assert.Equal(t, ToArray(From(1, 2, 3, 4, 5)), ToArray(u)) +} diff --git a/v2/iterator/stateless/zip.go b/v2/iterator/stateless/zip.go new file mode 100644 index 0000000..4a2649b --- /dev/null +++ b/v2/iterator/stateless/zip.go @@ -0,0 +1,33 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package stateless + +import ( + G "github.com/IBM/fp-go/v2/iterator/stateless/generic" + P "github.com/IBM/fp-go/v2/pair" +) + +// ZipWith applies a function to pairs of elements at the same index in two iterators, collecting the results in a new iterator. If one +// input iterator is short, excess elements of the longer iterator are discarded. +func ZipWith[FCT ~func(A, B) C, A, B, C any](fa Iterator[A], fb Iterator[B], f FCT) Iterator[C] { + return G.ZipWith[Iterator[A], Iterator[B], Iterator[C]](fa, fb, f) +} + +// Zip takes two iterators and returns an iterators of corresponding pairs. If one input iterators is short, excess elements of the +// longer iterator are discarded +func Zip[A, B any](fb Iterator[B]) func(Iterator[A]) Iterator[P.Pair[A, B]] { + return G.Zip[Iterator[A], Iterator[B], Iterator[P.Pair[A, B]]](fb) +} diff --git a/v2/iterator/stateless/zip_test.go b/v2/iterator/stateless/zip_test.go new file mode 100644 index 0000000..9c73777 --- /dev/null +++ b/v2/iterator/stateless/zip_test.go @@ -0,0 +1,44 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package stateless + +import ( + "fmt" + "testing" + + P "github.com/IBM/fp-go/v2/pair" + "github.com/stretchr/testify/assert" +) + +func TestZipWith(t *testing.T) { + left := From(1, 2, 3) + right := From("a", "b", "c", "d") + + res := ZipWith(left, right, func(l int, r string) string { + return fmt.Sprintf("%s%d", r, l) + }) + + assert.Equal(t, ToArray(From("a1", "b2", "c3")), ToArray(res)) +} + +func TestZip(t *testing.T) { + left := From(1, 2, 3) + right := From("a", "b", "c", "d") + + res := Zip[string](left)(right) + + assert.Equal(t, ToArray(From(P.MakePair("a", 1), P.MakePair("b", 2), P.MakePair("c", 3))), ToArray(res)) +} diff --git a/v2/json/doc.go b/v2/json/doc.go new file mode 100644 index 0000000..0a64609 --- /dev/null +++ b/v2/json/doc.go @@ -0,0 +1,89 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +// Package json provides functional wrappers around Go's encoding/json package using Either for error handling. +// +// This package wraps JSON marshaling and unmarshaling operations in Either monads, making it easier +// to compose JSON operations with other functional code and handle errors in a functional style. +// +// # Core Concepts +// +// The json package provides type-safe JSON operations that return Either[error, A] instead of +// the traditional (value, error) tuple pattern. This allows for better composition with other +// functional operations and eliminates the need for explicit error checking at each step. +// +// # Basic Usage +// +// // Unmarshaling JSON data +// type Person struct { +// Name string `json:"name"` +// Age int `json:"age"` +// } +// +// data := []byte(`{"name":"Alice","age":30}`) +// result := json.Unmarshal[Person](data) +// // result is Either[error, Person] +// +// // Marshaling to JSON +// person := Person{Name: "Bob", Age: 25} +// jsonBytes := json.Marshal(person) +// // jsonBytes is Either[error, []byte] +// +// # Chaining Operations +// +// Since Marshal and Unmarshal return Either values, they can be easily composed: +// +// result := function.Pipe2( +// person, +// json.Marshal[Person], +// either.Chain(json.Unmarshal[Person]), +// ) +// +// # Type Conversion +// +// The package provides utilities for converting between types using JSON as an intermediate format: +// +// // Convert from one type to another via JSON (returns Either) +// type Source struct { Value int } +// type Target struct { Value int } +// +// src := Source{Value: 42} +// result := json.ToTypeE[Target](src) +// // result is Either[error, Target] +// +// // Convert with Option (discards error details) +// maybeTarget := json.ToTypeO[Target](src) +// // maybeTarget is Option[Target] +// +// # Error Handling +// +// All operations return Either[error, A], allowing you to handle errors functionally: +// +// result := function.Pipe1( +// json.Unmarshal[Person](data), +// either.Fold( +// func(err error) string { return "Failed: " + err.Error() }, +// func(p Person) string { return "Success: " + p.Name }, +// ), +// ) +// +// # Type Aliases +// +// The package defines convenient type aliases: +// - Either[A] = either.Either[error, A] +// - Option[A] = option.Option[A] +// +// These aliases simplify type signatures and make the code more readable. +package json diff --git a/v2/json/json.go b/v2/json/json.go new file mode 100644 index 0000000..52b7857 --- /dev/null +++ b/v2/json/json.go @@ -0,0 +1,80 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package json + +import ( + "encoding/json" + + E "github.com/IBM/fp-go/v2/either" +) + +// Unmarshal parses JSON-encoded data and returns an Either containing the decoded value or an error. +// +// This function wraps the standard json.Unmarshal in an Either monad, converting the traditional +// (value, error) tuple into a functional Either type. If unmarshaling succeeds, it returns Right[A]. +// If it fails, it returns Left[error]. +// +// Type parameter A specifies the target type for unmarshaling. The type must be compatible with +// the JSON structure in the input data. +// +// Example: +// +// type Person struct { +// Name string `json:"name"` +// Age int `json:"age"` +// } +// +// data := []byte(`{"name":"Alice","age":30}`) +// result := json.Unmarshal[Person](data) +// // result is Either[error, Person] +// +// either.Fold( +// func(err error) { fmt.Println("Error:", err) }, +// func(p Person) { fmt.Printf("Success: %s, %d\n", p.Name, p.Age) }, +// )(result) +func Unmarshal[A any](data []byte) Either[A] { + var result A + err := json.Unmarshal(data, &result) + return E.TryCatchError(result, err) +} + +// Marshal converts a Go value to JSON-encoded bytes and returns an Either containing the result or an error. +// +// This function wraps the standard json.Marshal in an Either monad, converting the traditional +// (value, error) tuple into a functional Either type. If marshaling succeeds, it returns Right[[]byte]. +// If it fails, it returns Left[error]. +// +// The function uses the same encoding rules as the standard library's json.Marshal, including +// support for struct tags, custom MarshalJSON methods, and standard type conversions. +// +// Example: +// +// type Person struct { +// Name string `json:"name"` +// Age int `json:"age"` +// } +// +// person := Person{Name: "Bob", Age: 25} +// result := json.Marshal(person) +// // result is Either[error, []byte] +// +// either.Map(func(data []byte) string { +// return string(data) +// })(result) +// // Returns Either[error, string] with JSON string +func Marshal[A any](a A) Either[[]byte] { + return E.TryCatchError(json.Marshal(a)) +} diff --git a/v2/json/json_test.go b/v2/json/json_test.go new file mode 100644 index 0000000..1c8b515 --- /dev/null +++ b/v2/json/json_test.go @@ -0,0 +1,209 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package json + +import ( + "fmt" + "testing" + + E "github.com/IBM/fp-go/v2/either" + F "github.com/IBM/fp-go/v2/function" + "github.com/stretchr/testify/assert" +) + +type Json map[string]any + +func TestJsonMarshal(t *testing.T) { + + resRight := Unmarshal[Json]([]byte("{\"a\": \"b\"}")) + assert.True(t, E.IsRight(resRight)) + + resLeft := Unmarshal[Json]([]byte("{\"a\"")) + assert.True(t, E.IsLeft(resLeft)) + + res1 := F.Pipe1( + resRight, + E.Chain(Marshal[Json]), + ) + fmt.Println(res1) +} + +func TestUnmarshalSuccess(t *testing.T) { + type Person struct { + Name string `json:"name"` + Age int `json:"age"` + } + + data := []byte(`{"name":"Alice","age":30}`) + result := Unmarshal[Person](data) + + assert.True(t, E.IsRight(result)) + + person := E.GetOrElse(func(error) Person { return Person{} })(result) + assert.Equal(t, "Alice", person.Name) + assert.Equal(t, 30, person.Age) +} + +func TestUnmarshalError(t *testing.T) { + type Person struct { + Name string `json:"name"` + Age int `json:"age"` + } + + // Invalid JSON + data := []byte(`{"name":"Alice","age":`) + result := Unmarshal[Person](data) + + assert.True(t, E.IsLeft(result)) +} + +func TestUnmarshalTypeMismatch(t *testing.T) { + type Person struct { + Name string `json:"name"` + Age int `json:"age"` + } + + // Age is a string instead of int + data := []byte(`{"name":"Alice","age":"thirty"}`) + result := Unmarshal[Person](data) + + assert.True(t, E.IsLeft(result)) +} + +func TestMarshalSuccess(t *testing.T) { + type Person struct { + Name string `json:"name"` + Age int `json:"age"` + } + + person := Person{Name: "Bob", Age: 25} + result := Marshal(person) + + assert.True(t, E.IsRight(result)) + + jsonBytes := E.GetOrElse(func(error) []byte { return []byte{} })(result) + assert.Contains(t, string(jsonBytes), `"name":"Bob"`) + assert.Contains(t, string(jsonBytes), `"age":25`) +} + +func TestMarshalWithMap(t *testing.T) { + data := map[string]any{ + "key1": "value1", + "key2": 42, + "key3": true, + } + + result := Marshal(data) + assert.True(t, E.IsRight(result)) +} + +func TestMarshalWithSlice(t *testing.T) { + data := []int{1, 2, 3, 4, 5} + result := Marshal(data) + + assert.True(t, E.IsRight(result)) + + jsonBytes := E.GetOrElse(func(error) []byte { return []byte{} })(result) + assert.Equal(t, "[1,2,3,4,5]", string(jsonBytes)) +} + +func TestMarshalWithNil(t *testing.T) { + var data *string + result := Marshal(data) + + assert.True(t, E.IsRight(result)) + + jsonBytes := E.GetOrElse(func(error) []byte { return []byte{} })(result) + assert.Equal(t, "null", string(jsonBytes)) +} + +func TestMarshalUnmarshalRoundTrip(t *testing.T) { + type Config struct { + Host string `json:"host"` + Port int `json:"port"` + } + + original := Config{Host: "localhost", Port: 8080} + + result := F.Pipe2( + original, + Marshal[Config], + E.Chain(Unmarshal[Config]), + ) + + assert.True(t, E.IsRight(result)) + + recovered := E.GetOrElse(func(error) Config { return Config{} })(result) + assert.Equal(t, original, recovered) +} + +func TestMarshalWithNestedStructs(t *testing.T) { + type Address struct { + Street string `json:"street"` + City string `json:"city"` + } + + type Person struct { + Name string `json:"name"` + Address Address `json:"address"` + } + + person := Person{ + Name: "Charlie", + Address: Address{ + Street: "123 Main St", + City: "Springfield", + }, + } + + result := Marshal(person) + assert.True(t, E.IsRight(result)) + + jsonBytes := E.GetOrElse(func(error) []byte { return []byte{} })(result) + jsonStr := string(jsonBytes) + assert.Contains(t, jsonStr, `"name":"Charlie"`) + assert.Contains(t, jsonStr, `"street":"123 Main St"`) + assert.Contains(t, jsonStr, `"city":"Springfield"`) +} + +func TestUnmarshalEmptyJSON(t *testing.T) { + type Empty struct{} + + data := []byte(`{}`) + result := Unmarshal[Empty](data) + + assert.True(t, E.IsRight(result)) +} + +func TestUnmarshalArray(t *testing.T) { + data := []byte(`[1, 2, 3, 4, 5]`) + result := Unmarshal[[]int](data) + + assert.True(t, E.IsRight(result)) + + arr := E.GetOrElse(func(error) []int { return []int{} })(result) + assert.Equal(t, []int{1, 2, 3, 4, 5}, arr) +} + +func TestUnmarshalNull(t *testing.T) { + data := []byte(`null`) + result := Unmarshal[*string](data) + + assert.True(t, E.IsRight(result)) + + ptr := E.GetOrElse(func(error) *string { return nil })(result) + assert.Nil(t, ptr) +} diff --git a/v2/json/type.go b/v2/json/type.go new file mode 100644 index 0000000..ddc38e7 --- /dev/null +++ b/v2/json/type.go @@ -0,0 +1,102 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package json + +import ( + E "github.com/IBM/fp-go/v2/either" + "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/option" +) + +type ( + // Either is a type alias for either.Either[error, A], representing a computation that may fail. + // By convention, Left contains an error and Right contains the successful result of type A. + Either[A any] = E.Either[error, A] + + // Option is a type alias for option.Option[A], representing an optional value. + // It can be either Some(value) or None. + Option[A any] = option.Option[A] +) + +// ToTypeE converts a value from one type to another using JSON as an intermediate format, +// returning an Either that contains the converted value or an error. +// +// This function performs a round-trip conversion: src → JSON → target type A. +// It's useful for converting between compatible types (e.g., map[string]any to a struct) +// or for deep copying values. +// +// The conversion will fail if: +// - The source value cannot be marshaled to JSON +// - The JSON cannot be unmarshaled into the target type A +// - The JSON structure doesn't match the target type's structure +// +// Example: +// +// type Source struct { +// Name string +// Value int +// } +// +// type Target struct { +// Name string `json:"name"` +// Value int `json:"value"` +// } +// +// src := Source{Name: "test", Value: 42} +// result := json.ToTypeE[Target](src) +// // result is Either[error, Target] +// +// // Converting from map to struct +// data := map[string]any{"name": "Alice", "value": 100} +// person := json.ToTypeE[Target](data) +func ToTypeE[A any](src any) Either[A] { + return function.Pipe2( + src, + Marshal[any], + E.Chain(Unmarshal[A]), + ) +} + +// ToTypeO converts a value from one type to another using JSON as an intermediate format, +// returning an Option that contains the converted value or None if conversion fails. +// +// This is a convenience wrapper around ToTypeE that discards error details and returns +// an Option instead. Use this when you only care about success/failure and don't need +// the specific error message. +// +// The conversion follows the same rules as ToTypeE, performing a round-trip through JSON. +// +// Example: +// +// type Config struct { +// Host string `json:"host"` +// Port int `json:"port"` +// } +// +// data := map[string]any{"host": "localhost", "port": 8080} +// maybeConfig := json.ToTypeO[Config](data) +// // maybeConfig is Option[Config] +// +// option.Fold( +// func() { fmt.Println("Conversion failed") }, +// func(cfg Config) { fmt.Printf("Config: %s:%d\n", cfg.Host, cfg.Port) }, +// )(maybeConfig) +func ToTypeO[A any](src any) Option[A] { + return function.Pipe1( + ToTypeE[A](src), + E.ToOption[error, A], + ) +} diff --git a/v2/json/type_test.go b/v2/json/type_test.go new file mode 100644 index 0000000..a07c8c4 --- /dev/null +++ b/v2/json/type_test.go @@ -0,0 +1,293 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package json + +import ( + "testing" + + E "github.com/IBM/fp-go/v2/either" + F "github.com/IBM/fp-go/v2/function" + O "github.com/IBM/fp-go/v2/option" + "github.com/stretchr/testify/assert" +) + +type TestType struct { + A string `json:"a"` + B int `json:"b"` +} + +func TestToType(t *testing.T) { + + generic := map[string]any{"a": "value", "b": 1} + + assert.True(t, E.IsRight(ToTypeE[TestType](generic))) + assert.True(t, E.IsRight(ToTypeE[TestType](&generic))) + + assert.Equal(t, E.Right[error](&TestType{A: "value", B: 1}), ToTypeE[*TestType](&generic)) + assert.Equal(t, E.Right[error](TestType{A: "value", B: 1}), F.Pipe1(ToTypeE[*TestType](&generic), E.Map[error](F.Deref[TestType]))) +} + +func TestToTypeESuccess(t *testing.T) { + type Source struct { + Name string + Value int + } + + type Target struct { + Name string `json:"Name"` + Value int `json:"Value"` + } + + src := Source{Name: "test", Value: 42} + result := ToTypeE[Target](src) + + assert.True(t, E.IsRight(result)) + + target := E.GetOrElse(func(error) Target { return Target{} })(result) + assert.Equal(t, "test", target.Name) + assert.Equal(t, 42, target.Value) +} + +func TestToTypeEFromMap(t *testing.T) { + type Config struct { + Host string `json:"host"` + Port int `json:"port"` + } + + data := map[string]any{"host": "localhost", "port": 8080} + result := ToTypeE[Config](data) + + assert.True(t, E.IsRight(result)) + + config := E.GetOrElse(func(error) Config { return Config{} })(result) + assert.Equal(t, "localhost", config.Host) + assert.Equal(t, 8080, config.Port) +} + +func TestToTypeEError(t *testing.T) { + type Target struct { + Value int `json:"value"` + } + + // Invalid source that can't be marshaled + src := make(chan int) + result := ToTypeE[Target](src) + + assert.True(t, E.IsLeft(result)) +} + +func TestToTypeETypeMismatch(t *testing.T) { + type Target struct { + Value int `json:"value"` + } + + // Source has string where int is expected + data := map[string]any{"value": "not a number"} + result := ToTypeE[Target](data) + + assert.True(t, E.IsLeft(result)) +} + +func TestToTypeOSuccess(t *testing.T) { + type Source struct { + Name string + Value int + } + + type Target struct { + Name string `json:"Name"` + Value int `json:"Value"` + } + + src := Source{Name: "test", Value: 42} + result := ToTypeO[Target](src) + + assert.True(t, O.IsSome(result)) + + target := O.GetOrElse(func() Target { return Target{} })(result) + assert.Equal(t, "test", target.Name) + assert.Equal(t, 42, target.Value) +} + +func TestToTypeOFromMap(t *testing.T) { + type Person struct { + Name string `json:"name"` + Age int `json:"age"` + } + + data := map[string]any{"name": "Alice", "age": 30} + result := ToTypeO[Person](data) + + assert.True(t, O.IsSome(result)) + + person := O.GetOrElse(func() Person { return Person{} })(result) + assert.Equal(t, "Alice", person.Name) + assert.Equal(t, 30, person.Age) +} + +func TestToTypeOError(t *testing.T) { + type Target struct { + Value int `json:"value"` + } + + // Invalid source that can't be marshaled + src := make(chan int) + result := ToTypeO[Target](src) + + assert.True(t, O.IsNone(result)) +} + +func TestToTypeOTypeMismatch(t *testing.T) { + type Target struct { + Value int `json:"value"` + } + + // Source has string where int is expected + data := map[string]any{"value": "not a number"} + result := ToTypeO[Target](data) + + assert.True(t, O.IsNone(result)) +} + +func TestToTypeEWithNestedStructs(t *testing.T) { + type Address struct { + Street string `json:"street"` + City string `json:"city"` + } + + type Person struct { + Name string `json:"name"` + Address Address `json:"address"` + } + + data := map[string]any{ + "name": "Bob", + "address": map[string]any{ + "street": "123 Main St", + "city": "Springfield", + }, + } + + result := ToTypeE[Person](data) + assert.True(t, E.IsRight(result)) + + person := E.GetOrElse(func(error) Person { return Person{} })(result) + assert.Equal(t, "Bob", person.Name) + assert.Equal(t, "123 Main St", person.Address.Street) + assert.Equal(t, "Springfield", person.Address.City) +} + +func TestToTypeOWithNestedStructs(t *testing.T) { + type Address struct { + Street string `json:"street"` + City string `json:"city"` + } + + type Person struct { + Name string `json:"name"` + Address Address `json:"address"` + } + + data := map[string]any{ + "name": "Charlie", + "address": map[string]any{ + "street": "456 Oak Ave", + "city": "Shelbyville", + }, + } + + result := ToTypeO[Person](data) + assert.True(t, O.IsSome(result)) + + person := O.GetOrElse(func() Person { return Person{} })(result) + assert.Equal(t, "Charlie", person.Name) + assert.Equal(t, "456 Oak Ave", person.Address.Street) + assert.Equal(t, "Shelbyville", person.Address.City) +} + +func TestToTypeEWithSlice(t *testing.T) { + type Item struct { + ID int `json:"id"` + Name string `json:"name"` + } + + data := []map[string]any{ + {"id": 1, "name": "Item1"}, + {"id": 2, "name": "Item2"}, + } + + result := ToTypeE[[]Item](data) + assert.True(t, E.IsRight(result)) + + items := E.GetOrElse(func(error) []Item { return []Item{} })(result) + assert.Len(t, items, 2) + assert.Equal(t, 1, items[0].ID) + assert.Equal(t, "Item1", items[0].Name) + assert.Equal(t, 2, items[1].ID) + assert.Equal(t, "Item2", items[1].Name) +} + +func TestToTypeOWithSlice(t *testing.T) { + type Item struct { + ID int `json:"id"` + Name string `json:"name"` + } + + data := []map[string]any{ + {"id": 3, "name": "Item3"}, + {"id": 4, "name": "Item4"}, + } + + result := ToTypeO[[]Item](data) + assert.True(t, O.IsSome(result)) + + items := O.GetOrElse(func() []Item { return []Item{} })(result) + assert.Len(t, items, 2) + assert.Equal(t, 3, items[0].ID) + assert.Equal(t, "Item3", items[0].Name) + assert.Equal(t, 4, items[1].ID) + assert.Equal(t, "Item4", items[1].Name) +} + +func TestToTypeEWithPointer(t *testing.T) { + type Data struct { + Value *int `json:"value"` + } + + val := 100 + src := map[string]any{"value": val} + result := ToTypeE[Data](src) + + assert.True(t, E.IsRight(result)) + + data := E.GetOrElse(func(error) Data { return Data{} })(result) + assert.NotNil(t, data.Value) + assert.Equal(t, 100, *data.Value) +} + +func TestToTypeOWithNullValue(t *testing.T) { + type Data struct { + Value *int `json:"value"` + } + + src := map[string]any{"value": nil} + result := ToTypeO[Data](src) + + assert.True(t, O.IsSome(result)) + + data := O.GetOrElse(func() Data { return Data{} })(result) + assert.Nil(t, data.Value) +} diff --git a/v2/lambda/y.nogo b/v2/lambda/y.nogo new file mode 100644 index 0000000..6354de9 --- /dev/null +++ b/v2/lambda/y.nogo @@ -0,0 +1,29 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package lambda + +// Y is the Y-combinator based on https://dreamsongs.com/Files/WhyOfY.pdf +func Y[Endo ~func(RecFct) RecFct, RecFct ~func(T) R, T, R any](f Endo) RecFct { + + type internal[RecFct ~func(T) R, T, R any] func(internal[RecFct, T, R]) RecFct + + g := func(h internal[RecFct, T, R]) RecFct { + return func(t T) R { + return f(h(h))(t) + } + } + return g(g) +} diff --git a/v2/lambda/y_test.nogo b/v2/lambda/y_test.nogo new file mode 100644 index 0000000..e19a7a5 --- /dev/null +++ b/v2/lambda/y_test.nogo @@ -0,0 +1,34 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package lambda + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestFactorial(t *testing.T) { + fct := Y(func(r func(int) int) func(int) int { + return func(n int) int { + if n <= 0 { + return 1 + } + return n * r(n-1) + } + }) + assert.Equal(t, 3628800, fct(10)) +} diff --git a/v2/lazy/apply.go b/v2/lazy/apply.go new file mode 100644 index 0000000..4656660 --- /dev/null +++ b/v2/lazy/apply.go @@ -0,0 +1,30 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package lazy + +import ( + IO "github.com/IBM/fp-go/v2/io" + M "github.com/IBM/fp-go/v2/monoid" + S "github.com/IBM/fp-go/v2/semigroup" +) + +func ApplySemigroup[A any](s S.Semigroup[A]) S.Semigroup[Lazy[A]] { + return IO.ApplySemigroup(s) +} + +func ApplicativeMonoid[A any](m M.Monoid[A]) M.Monoid[Lazy[A]] { + return IO.ApplicativeMonoid(m) +} diff --git a/v2/lazy/bind.go b/v2/lazy/bind.go new file mode 100644 index 0000000..c635c91 --- /dev/null +++ b/v2/lazy/bind.go @@ -0,0 +1,66 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package lazy + +import ( + "github.com/IBM/fp-go/v2/io" +) + +// Bind creates an empty context of type [S] to be used with the [Bind] operation +func Do[S any]( + empty S, +) Lazy[S] { + return io.Do(empty) +} + +// Bind attaches the result of a computation to a context [S1] to produce a context [S2] +func Bind[S1, S2, T any]( + setter func(T) func(S1) S2, + f func(S1) Lazy[T], +) func(Lazy[S1]) Lazy[S2] { + return io.Bind(setter, f) +} + +// Let attaches the result of a computation to a context [S1] to produce a context [S2] +func Let[S1, S2, T any]( + setter func(T) func(S1) S2, + f func(S1) T, +) func(Lazy[S1]) Lazy[S2] { + return io.Let(setter, f) +} + +// LetTo attaches the a value to a context [S1] to produce a context [S2] +func LetTo[S1, S2, T any]( + setter func(T) func(S1) S2, + b T, +) func(Lazy[S1]) Lazy[S2] { + return io.LetTo(setter, b) +} + +// BindTo initializes a new state [S1] from a value [T] +func BindTo[S1, T any]( + setter func(T) S1, +) func(Lazy[T]) Lazy[S1] { + return io.BindTo(setter) +} + +// ApS attaches a value to a context [S1] to produce a context [S2] by considering the context and the value concurrently +func ApS[S1, S2, T any]( + setter func(T) func(S1) S2, + fa Lazy[T], +) func(Lazy[S1]) Lazy[S2] { + return io.ApS(setter, fa) +} diff --git a/v2/lazy/bind_test.go b/v2/lazy/bind_test.go new file mode 100644 index 0000000..e3c011b --- /dev/null +++ b/v2/lazy/bind_test.go @@ -0,0 +1,56 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package lazy + +import ( + "testing" + + F "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/internal/utils" + "github.com/stretchr/testify/assert" +) + +func getLastName(s utils.Initial) Lazy[string] { + return Of("Doe") +} + +func getGivenName(s utils.WithLastName) Lazy[string] { + return Of("John") +} + +func TestBind(t *testing.T) { + + res := F.Pipe3( + Do(utils.Empty), + Bind(utils.SetLastName, getLastName), + Bind(utils.SetGivenName, getGivenName), + Map(utils.GetFullName), + ) + + assert.Equal(t, res(), "John Doe") +} + +func TestApS(t *testing.T) { + + res := F.Pipe3( + Do(utils.Empty), + ApS(utils.SetLastName, Of("Doe")), + ApS(utils.SetGivenName, Of("John")), + Map(utils.GetFullName), + ) + + assert.Equal(t, res(), "John Doe") +} diff --git a/v2/lazy/eq.go b/v2/lazy/eq.go new file mode 100644 index 0000000..8096e6d --- /dev/null +++ b/v2/lazy/eq.go @@ -0,0 +1,26 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package lazy + +import ( + EQ "github.com/IBM/fp-go/v2/eq" + IO "github.com/IBM/fp-go/v2/io" +) + +// Eq implements the equals predicate for values contained in the IO monad +func Eq[A any](e EQ.Eq[A]) EQ.Eq[Lazy[A]] { + return IO.Eq(e) +} diff --git a/v2/lazy/example_lazy_test.go b/v2/lazy/example_lazy_test.go new file mode 100644 index 0000000..f2ab55b --- /dev/null +++ b/v2/lazy/example_lazy_test.go @@ -0,0 +1,38 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package lazy + +import ( + "fmt" + "strconv" + + F "github.com/IBM/fp-go/v2/function" +) + +func ExampleLazy_creation() { + // lazy function of a constant value + val := Of(42) + // create another function to transform this + valS := F.Pipe1( + val, + Map(strconv.Itoa), + ) + + fmt.Println(valS()) + + // Output: + // 42 +} diff --git a/v2/lazy/lazy.go b/v2/lazy/lazy.go new file mode 100644 index 0000000..87efa13 --- /dev/null +++ b/v2/lazy/lazy.go @@ -0,0 +1,135 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package lazy + +import ( + "time" + + "github.com/IBM/fp-go/v2/io" +) + +// Lazy represents a synchronous computation without side effects +type Lazy[A any] = func() A + +func Of[A any](a A) Lazy[A] { + return io.Of(a) +} + +func FromLazy[A any](a Lazy[A]) Lazy[A] { + return io.FromIO(a) +} + +// FromImpure converts a side effect without a return value into a side effect that returns any +func FromImpure(f func()) Lazy[any] { + return io.FromImpure(f) +} + +func MonadOf[A any](a A) Lazy[A] { + return io.MonadOf(a) +} + +func MonadMap[A, B any](fa Lazy[A], f func(A) B) Lazy[B] { + return io.MonadMap(fa, f) +} + +func Map[A, B any](f func(A) B) func(fa Lazy[A]) Lazy[B] { + return io.Map(f) +} + +func MonadMapTo[A, B any](fa Lazy[A], b B) Lazy[B] { + return io.MonadMapTo(fa, b) +} + +func MapTo[A, B any](b B) func(Lazy[A]) Lazy[B] { + return io.MapTo[A](b) +} + +// MonadChain composes computations in sequence, using the return value of one computation to determine the next computation. +func MonadChain[A, B any](fa Lazy[A], f func(A) Lazy[B]) Lazy[B] { + return io.MonadChain(fa, f) +} + +// Chain composes computations in sequence, using the return value of one computation to determine the next computation. +func Chain[A, B any](f func(A) Lazy[B]) func(Lazy[A]) Lazy[B] { + return io.Chain(f) +} + +func MonadAp[B, A any](mab Lazy[func(A) B], ma Lazy[A]) Lazy[B] { + return io.MonadApSeq(mab, ma) +} + +func Ap[B, A any](ma Lazy[A]) func(Lazy[func(A) B]) Lazy[B] { + return io.ApSeq[B](ma) +} + +func Flatten[A any](mma Lazy[Lazy[A]]) Lazy[A] { + return io.Flatten(mma) +} + +// Memoize computes the value of the provided [Lazy] monad lazily but exactly once +func Memoize[A any](ma Lazy[A]) Lazy[A] { + return io.Memoize(ma) +} + +// MonadChainFirst composes computations in sequence, using the return value of one computation to determine the next computation and +// keeping only the result of the first. +func MonadChainFirst[A, B any](fa Lazy[A], f func(A) Lazy[B]) Lazy[A] { + return io.MonadChainFirst(fa, f) +} + +// ChainFirst composes computations in sequence, using the return value of one computation to determine the next computation and +// keeping only the result of the first. +func ChainFirst[A, B any](f func(A) Lazy[B]) func(Lazy[A]) Lazy[A] { + return io.ChainFirst(f) +} + +// MonadApFirst combines two effectful actions, keeping only the result of the first. +func MonadApFirst[A, B any](first Lazy[A], second Lazy[B]) Lazy[A] { + return io.MonadApFirst(first, second) +} + +// ApFirst combines two effectful actions, keeping only the result of the first. +func ApFirst[A, B any](second Lazy[B]) func(Lazy[A]) Lazy[A] { + return io.ApFirst[A](second) +} + +// MonadApSecond combines two effectful actions, keeping only the result of the second. +func MonadApSecond[A, B any](first Lazy[A], second Lazy[B]) Lazy[B] { + return io.MonadApSecond(first, second) +} + +// ApSecond combines two effectful actions, keeping only the result of the second. +func ApSecond[A, B any](second Lazy[B]) func(Lazy[A]) Lazy[B] { + return io.ApSecond[A](second) +} + +// MonadChainTo composes computations in sequence, ignoring the return value of the first computation +func MonadChainTo[A, B any](fa Lazy[A], fb Lazy[B]) Lazy[B] { + return io.MonadChainTo(fa, fb) +} + +// ChainTo composes computations in sequence, ignoring the return value of the first computation +func ChainTo[A, B any](fb Lazy[B]) func(Lazy[A]) Lazy[B] { + return io.ChainTo[A](fb) +} + +// Now returns the current timestamp +var Now Lazy[time.Time] = io.Now + +// Defer creates an IO by creating a brand new IO via a generator function, each time +func Defer[A any](gen func() Lazy[A]) Lazy[A] { + return io.Defer(gen) +} diff --git a/v2/lazy/lazy_test.go b/v2/lazy/lazy_test.go new file mode 100644 index 0000000..9212b5b --- /dev/null +++ b/v2/lazy/lazy_test.go @@ -0,0 +1,73 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package lazy + +import ( + "math/rand" + "testing" + + F "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/internal/utils" + "github.com/stretchr/testify/assert" +) + +func TestMap(t *testing.T) { + assert.Equal(t, 2, F.Pipe1(Of(1), Map(utils.Double))()) +} + +func TestChain(t *testing.T) { + f := func(n int) Lazy[int] { + return Of(n * 2) + } + assert.Equal(t, 2, F.Pipe1(Of(1), Chain(f))()) +} + +func TestAp(t *testing.T) { + assert.Equal(t, 2, F.Pipe1(Of(utils.Double), Ap[int, int](Of(1)))()) +} + +func TestFlatten(t *testing.T) { + assert.Equal(t, 1, F.Pipe1(Of(Of(1)), Flatten[int])()) +} + +func TestMemoize(t *testing.T) { + data := Memoize(rand.Int) + + value1 := data() + value2 := data() + + assert.Equal(t, value1, value2) +} + +func TestApFirst(t *testing.T) { + + x := F.Pipe1( + Of("a"), + ApFirst[string](Of("b")), + ) + + assert.Equal(t, "a", x()) +} + +func TestApSecond(t *testing.T) { + + x := F.Pipe1( + Of("a"), + ApSecond[string](Of("b")), + ) + + assert.Equal(t, "b", x()) +} diff --git a/v2/lazy/retry.go b/v2/lazy/retry.go new file mode 100644 index 0000000..bee6922 --- /dev/null +++ b/v2/lazy/retry.go @@ -0,0 +1,34 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package lazy + +import ( + "github.com/IBM/fp-go/v2/io" + R "github.com/IBM/fp-go/v2/retry" +) + +// Retrying will retry the actions according to the check policy +// +// policy - refers to the retry policy +// action - converts a status into an operation to be executed +// check - checks if the result of the action needs to be retried +func Retrying[A any]( + policy R.RetryPolicy, + action func(R.RetryStatus) Lazy[A], + check func(A) bool, +) Lazy[A] { + return io.Retrying(policy, action, check) +} diff --git a/v2/lazy/retry_test.go b/v2/lazy/retry_test.go new file mode 100644 index 0000000..7278829 --- /dev/null +++ b/v2/lazy/retry_test.go @@ -0,0 +1,47 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package lazy + +import ( + "fmt" + "strings" + "testing" + "time" + + R "github.com/IBM/fp-go/v2/retry" + "github.com/stretchr/testify/assert" +) + +var expLogBackoff = R.ExponentialBackoff(10) + +// our retry policy with a 1s cap +var testLogPolicy = R.CapDelay( + 2*time.Second, + R.Monoid.Concat(expLogBackoff, R.LimitRetries(20)), +) + +func TestRetry(t *testing.T) { + action := func(status R.RetryStatus) Lazy[string] { + return Of(fmt.Sprintf("Retrying %d", status.IterNumber)) + } + check := func(value string) bool { + return !strings.Contains(value, "5") + } + + r := Retrying(testLogPolicy, action, check) + + assert.Equal(t, "Retrying 5", r()) +} diff --git a/v2/lazy/sequence.go b/v2/lazy/sequence.go new file mode 100644 index 0000000..1d06899 --- /dev/null +++ b/v2/lazy/sequence.go @@ -0,0 +1,39 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package lazy + +import ( + "github.com/IBM/fp-go/v2/io" + "github.com/IBM/fp-go/v2/tuple" +) + +// SequenceT converts n inputs of higher kinded types into a higher kinded types of n strongly typed values, represented as a tuple + +func SequenceT1[A any](a Lazy[A]) Lazy[tuple.Tuple1[A]] { + return io.SequenceT1(a) +} + +func SequenceT2[A, B any](a Lazy[A], b Lazy[B]) Lazy[tuple.Tuple2[A, B]] { + return io.SequenceT2(a, b) +} + +func SequenceT3[A, B, C any](a Lazy[A], b Lazy[B], c Lazy[C]) Lazy[tuple.Tuple3[A, B, C]] { + return io.SequenceT3(a, b, c) +} + +func SequenceT4[A, B, C, D any](a Lazy[A], b Lazy[B], c Lazy[C], d Lazy[D]) Lazy[tuple.Tuple4[A, B, C, D]] { + return io.SequenceT4(a, b, c, d) +} diff --git a/v2/lazy/testing/laws.go b/v2/lazy/testing/laws.go new file mode 100644 index 0000000..a3ef4f1 --- /dev/null +++ b/v2/lazy/testing/laws.go @@ -0,0 +1,74 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package testing + +import ( + "testing" + + EQ "github.com/IBM/fp-go/v2/eq" + L "github.com/IBM/fp-go/v2/internal/monad/testing" + "github.com/IBM/fp-go/v2/lazy" +) + +// AssertLaws asserts the apply monad laws for the `Either` monad +func AssertLaws[A, B, C any](t *testing.T, + eqa EQ.Eq[A], + eqb EQ.Eq[B], + eqc EQ.Eq[C], + + ab func(A) B, + bc func(B) C, +) func(a A) bool { + + return L.AssertLaws(t, + lazy.Eq(eqa), + lazy.Eq(eqb), + lazy.Eq(eqc), + + lazy.Of[A], + lazy.Of[B], + lazy.Of[C], + + lazy.Of[func(A) A], + lazy.Of[func(A) B], + lazy.Of[func(B) C], + lazy.Of[func(func(A) B) B], + + lazy.MonadMap[A, A], + lazy.MonadMap[A, B], + lazy.MonadMap[A, C], + lazy.MonadMap[B, C], + + lazy.MonadMap[func(B) C, func(func(A) B) func(A) C], + + lazy.MonadChain[A, A], + lazy.MonadChain[A, B], + lazy.MonadChain[A, C], + lazy.MonadChain[B, C], + + lazy.MonadAp[A, A], + lazy.MonadAp[B, A], + lazy.MonadAp[C, B], + lazy.MonadAp[C, A], + + lazy.MonadAp[B, func(A) B], + lazy.MonadAp[func(A) C, func(A) B], + + ab, + bc, + ) + +} diff --git a/v2/lazy/testing/laws_test.go b/v2/lazy/testing/laws_test.go new file mode 100644 index 0000000..faff6d2 --- /dev/null +++ b/v2/lazy/testing/laws_test.go @@ -0,0 +1,47 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package testing + +import ( + "fmt" + "testing" + + EQ "github.com/IBM/fp-go/v2/eq" + "github.com/stretchr/testify/assert" +) + +func TestMonadLaws(t *testing.T) { + // some comparison + eqa := EQ.FromStrictEquals[bool]() + eqb := EQ.FromStrictEquals[int]() + eqc := EQ.FromStrictEquals[string]() + + ab := func(a bool) int { + if a { + return 1 + } + return 0 + } + + bc := func(b int) string { + return fmt.Sprintf("value %d", b) + } + + laws := AssertLaws(t, eqa, eqb, eqc, ab, bc) + + assert.True(t, laws(true)) + assert.True(t, laws(false)) +} diff --git a/v2/lazy/traverse.go b/v2/lazy/traverse.go new file mode 100644 index 0000000..baa5fad --- /dev/null +++ b/v2/lazy/traverse.go @@ -0,0 +1,60 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package lazy + +import "github.com/IBM/fp-go/v2/io" + +func MonadTraverseArray[A, B any](tas []A, f func(A) Lazy[B]) Lazy[[]B] { + return io.MonadTraverseArray(tas, f) +} + +// TraverseArray applies a function returning an [IO] to all elements in an array and the +// transforms this into an [IO] of that array +func TraverseArray[A, B any](f func(A) Lazy[B]) func([]A) Lazy[[]B] { + return io.TraverseArray(f) +} + +// TraverseArrayWithIndex applies a function returning an [IO] to all elements in an array and the +// transforms this into an [IO] of that array +func TraverseArrayWithIndex[A, B any](f func(int, A) Lazy[B]) func([]A) Lazy[[]B] { + return io.TraverseArrayWithIndex(f) +} + +// SequenceArray converts an array of [IO] to an [IO] of an array +func SequenceArray[A any](tas []Lazy[A]) Lazy[[]A] { + return io.SequenceArray(tas) +} + +func MonadTraverseRecord[K comparable, A, B any](tas map[K]A, f func(A) Lazy[B]) Lazy[map[K]B] { + return io.MonadTraverseRecord(tas, f) +} + +// TraverseRecord applies a function returning an [IO] to all elements in a record and the +// transforms this into an [IO] of that record +func TraverseRecord[K comparable, A, B any](f func(A) Lazy[B]) func(map[K]A) Lazy[map[K]B] { + return io.TraverseRecord[K](f) +} + +// TraverseRecord applies a function returning an [IO] to all elements in a record and the +// transforms this into an [IO] of that record +func TraverseRecordWithIndex[K comparable, A, B any](f func(K, A) Lazy[B]) func(map[K]A) Lazy[map[K]B] { + return io.TraverseRecordWithIndex[K](f) +} + +// SequenceRecord converts a record of [IO] to an [IO] of a record +func SequenceRecord[K comparable, A any](tas map[K]Lazy[A]) Lazy[map[K]A] { + return io.SequenceRecord(tas) +} diff --git a/v2/logging/logger.go b/v2/logging/logger.go new file mode 100644 index 0000000..cc4c76c --- /dev/null +++ b/v2/logging/logger.go @@ -0,0 +1,33 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package logging + +import ( + "log" +) + +func LoggingCallbacks(loggers ...*log.Logger) (func(string, ...any), func(string, ...any)) { + switch len(loggers) { + case 0: + def := log.Default() + return def.Printf, def.Printf + case 1: + log0 := loggers[0] + return log0.Printf, log0.Printf + default: + return loggers[0].Printf, loggers[1].Printf + } +} diff --git a/v2/magma/array.go b/v2/magma/array.go new file mode 100644 index 0000000..9fcca55 --- /dev/null +++ b/v2/magma/array.go @@ -0,0 +1,44 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package magma + +import ( + F "github.com/IBM/fp-go/v2/function" + AR "github.com/IBM/fp-go/v2/internal/array" +) + +func GenericMonadConcatAll[GA ~[]A, A any](m Magma[A]) func(GA, A) A { + return func(as GA, first A) A { + return AR.Reduce(as, m.Concat, first) + } +} + +// GenericConcatAll concats all items using the semigroup and a starting value +func GenericConcatAll[GA ~[]A, A any](m Magma[A]) func(A) func(GA) A { + ca := GenericMonadConcatAll[GA](m) + return func(a A) func(GA) A { + return F.Bind2nd(ca, a) + } +} + +func MonadConcatAll[A any](m Magma[A]) func([]A, A) A { + return GenericMonadConcatAll[[]A](m) +} + +// ConcatAll concats all items using the semigroup and a starting value +func ConcatAll[A any](m Magma[A]) func(A) func([]A) A { + return GenericConcatAll[[]A](m) +} diff --git a/v2/magma/doc.go b/v2/magma/doc.go new file mode 100644 index 0000000..b84c865 --- /dev/null +++ b/v2/magma/doc.go @@ -0,0 +1,246 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +/* +Package magma provides the Magma algebraic structure. + +# Overview + +A Magma is the most basic algebraic structure in abstract algebra. It consists +of a set equipped with a single binary operation (Concat) that combines two +elements to produce another element of the same type. Unlike more constrained +structures like Semigroup or Monoid, a Magma's operation doesn't need to be +associative or have an identity element. + +The Magma interface: + + type Magma[A any] interface { + Concat(x A, y A) A + } + +This simple structure serves as the foundation for more complex algebraic +structures in the type class hierarchy: + - Magma (no laws) + - Semigroup (adds associativity) + - Monoid (adds identity element) + +# Basic Usage + +Creating and using magmas: + + // Create a magma for integer addition + addMagma := magma.MakeMagma(func(a, b int) int { + return a + b + }) + + result := addMagma.Concat(5, 3) + // result is 8 + + // Create a magma for string concatenation + stringMagma := magma.MakeMagma(func(a, b string) string { + return a + b + }) + + result := stringMagma.Concat("Hello", " World") + // result is "Hello World" + +# Built-in Magmas + +The package provides several pre-defined magmas: + +First - Always returns the first argument: + + m := magma.First[int]() + result := m.Concat(1, 2) + // result is 1 + +Second - Always returns the second argument: + + m := magma.Second[int]() + result := m.Concat(1, 2) + // result is 2 + +# Transforming Magmas + +Reverse - Swaps the order of arguments: + + addMagma := magma.MakeMagma(func(a, b int) int { + return a - b + }) + + reversedMagma := magma.Reverse(addMagma) + + result1 := addMagma.Concat(10, 3) // 10 - 3 = 7 + result2 := reversedMagma.Concat(10, 3) // 3 - 10 = -7 + +FilterFirst - Only applies operation if first argument satisfies predicate: + + addMagma := magma.MakeMagma(func(a, b int) int { + return a + b + }) + + // Only add if first number is positive + filteredMagma := magma.FilterFirst(func(n int) bool { + return n > 0 + })(addMagma) + + result1 := filteredMagma.Concat(5, 3) // 5 + 3 = 8 (5 is positive) + result2 := filteredMagma.Concat(-5, 3) // 3 (5 is negative, return second) + +FilterSecond - Only applies operation if second argument satisfies predicate: + + addMagma := magma.MakeMagma(func(a, b int) int { + return a + b + }) + + // Only add if second number is positive + filteredMagma := magma.FilterSecond(func(n int) bool { + return n > 0 + })(addMagma) + + result1 := filteredMagma.Concat(5, 3) // 5 + 3 = 8 (3 is positive) + result2 := filteredMagma.Concat(5, -3) // 5 (-3 is negative, return first) + +Endo - Applies a function to both arguments before combining: + + addMagma := magma.MakeMagma(func(a, b int) int { + return a + b + }) + + // Double both numbers before adding + doubledMagma := magma.Endo(func(n int) int { + return n * 2 + })(addMagma) + + result := doubledMagma.Concat(3, 4) + // (3*2) + (4*2) = 6 + 8 = 14 + +# Array Operations + +ConcatAll - Combines all elements in a slice using a magma: + + addMagma := magma.MakeMagma(func(a, b int) int { + return a + b + }) + + numbers := []int{1, 2, 3, 4, 5} + result := magma.ConcatAll(addMagma)(0)(numbers) + // 0 + 1 + 2 + 3 + 4 + 5 = 15 + +MonadConcatAll - Uncurried version: + + addMagma := magma.MakeMagma(func(a, b int) int { + return a + b + }) + + numbers := []int{1, 2, 3, 4, 5} + result := magma.MonadConcatAll(addMagma)(numbers, 0) + // 0 + 1 + 2 + 3 + 4 + 5 = 15 + +# Generic Array Operations + +The package provides generic versions that work with custom slice types: + + type IntSlice []int + + addMagma := magma.MakeMagma(func(a, b int) int { + return a + b + }) + + numbers := IntSlice{1, 2, 3} + result := magma.GenericConcatAll[IntSlice](addMagma)(0)(numbers) + // result is 6 + +# Practical Examples + +Building a max magma: + + maxMagma := magma.MakeMagma(func(a, b int) int { + if a > b { + return a + } + return b + }) + + numbers := []int{3, 7, 2, 9, 1} + maximum := magma.ConcatAll(maxMagma)(0)(numbers) + // maximum is 9 + +Building a min magma: + + minMagma := magma.MakeMagma(func(a, b int) int { + if a < b { + return a + } + return b + }) + + numbers := []int{3, 7, 2, 9, 1} + minimum := magma.ConcatAll(minMagma)(10)(numbers) + // minimum is 1 + +Combining strings with separator: + + joinMagma := magma.MakeMagma(func(a, b string) string { + if a == "" { + return b + } + if b == "" { + return a + } + return a + ", " + b + }) + + words := []string{"apple", "banana", "cherry"} + result := magma.ConcatAll(joinMagma)("")(words) + // result is "apple, banana, cherry" + +# Relationship to Other Structures + +Magma is the base of the algebraic hierarchy: + + Magma (no laws) + ↓ + Semigroup (associative) + ↓ + Monoid (identity element) + +Any Semigroup or Monoid is also a Magma, but not vice versa. + +# Functions + +Core operations: + - MakeMagma[A any](func(A, A) A) - Create a magma from a binary operation + - First[A any]() - Magma that returns first argument + - Second[A any]() - Magma that returns second argument + +Transformations: + - Reverse[A any](Magma[A]) - Swap argument order + - FilterFirst[A any](func(A) bool) - Filter based on first argument + - FilterSecond[A any](func(A) bool) - Filter based on second argument + - Endo[A any](func(A) A) - Apply function before combining + +Array operations: + - ConcatAll[A any](Magma[A]) - Combine all elements (curried) + - MonadConcatAll[A any](Magma[A]) - Combine all elements (uncurried) + - GenericConcatAll[GA ~[]A, A any](Magma[A]) - Generic version (curried) + - GenericMonadConcatAll[GA ~[]A, A any](Magma[A]) - Generic version (uncurried) + +# Related Packages + + - semigroup: Adds associativity law to Magma + - monoid: Adds identity element to Semigroup +*/ +package magma diff --git a/v2/magma/magma.go b/v2/magma/magma.go new file mode 100644 index 0000000..e0ca4a1 --- /dev/null +++ b/v2/magma/magma.go @@ -0,0 +1,99 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package magma + +type Magma[A any] interface { + Concat(x A, y A) A +} + +type magma[A any] struct { + c func(A, A) A +} + +func (m magma[A]) Concat(x A, y A) A { + return m.c(x, y) +} + +func MakeMagma[A any](c func(A, A) A) Magma[A] { + return magma[A]{c: c} +} + +func Reverse[A any](m Magma[A]) Magma[A] { + return MakeMagma(func(x A, y A) A { + return m.Concat(y, x) + }) +} + +func filterFirst[A any](p func(A) bool, c func(A, A) A, x A, y A) A { + if p(x) { + return c(x, y) + } + return y +} + +func filterSecond[A any](p func(A) bool, c func(A, A) A, x A, y A) A { + if p(y) { + return c(x, y) + } + return x +} + +func FilterFirst[A any](p func(A) bool) func(Magma[A]) Magma[A] { + return func(m Magma[A]) Magma[A] { + c := m.Concat + return MakeMagma(func(x A, y A) A { + return filterFirst(p, c, x, y) + }) + } +} + +func FilterSecond[A any](p func(A) bool) func(Magma[A]) Magma[A] { + return func(m Magma[A]) Magma[A] { + c := m.Concat + return MakeMagma(func(x, y A) A { + return filterSecond(p, c, x, y) + }) + } +} + +func first[A any](x, _ A) A { + return x +} + +func second[A any](_, y A) A { + return y +} + +func First[A any]() Magma[A] { + return MakeMagma(first[A]) +} + +func Second[A any]() Magma[A] { + return MakeMagma(second[A]) +} + +func endo[A any](f func(A) A, c func(A, A) A, x, y A) A { + return c(f(x), f(y)) +} + +func Endo[A any](f func(A) A) func(Magma[A]) Magma[A] { + return func(m Magma[A]) Magma[A] { + c := m.Concat + return MakeMagma(func(x A, y A) A { + return endo(f, c, x, y) + }) + } +} diff --git a/v2/magma/magma_test.go b/v2/magma/magma_test.go new file mode 100644 index 0000000..bb4065e --- /dev/null +++ b/v2/magma/magma_test.go @@ -0,0 +1,423 @@ +// Copyright (c) 2023 IBM Corp. +// All rights reserved. +// +// 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. + +package magma + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestMakeMagma(t *testing.T) { + t.Run("integer addition", func(t *testing.T) { + addMagma := MakeMagma(func(a, b int) int { + return a + b + }) + + assert.Equal(t, 8, addMagma.Concat(5, 3)) + assert.Equal(t, 0, addMagma.Concat(-5, 5)) + assert.Equal(t, 10, addMagma.Concat(7, 3)) + }) + + t.Run("integer multiplication", func(t *testing.T) { + mulMagma := MakeMagma(func(a, b int) int { + return a * b + }) + + assert.Equal(t, 15, mulMagma.Concat(5, 3)) + assert.Equal(t, 0, mulMagma.Concat(0, 5)) + assert.Equal(t, 21, mulMagma.Concat(7, 3)) + }) + + t.Run("string concatenation", func(t *testing.T) { + stringMagma := MakeMagma(func(a, b string) string { + return a + b + }) + + assert.Equal(t, "HelloWorld", stringMagma.Concat("Hello", "World")) + assert.Equal(t, "ab", stringMagma.Concat("a", "b")) + }) + + t.Run("max operation", func(t *testing.T) { + maxMagma := MakeMagma(func(a, b int) int { + if a > b { + return a + } + return b + }) + + assert.Equal(t, 5, maxMagma.Concat(5, 3)) + assert.Equal(t, 7, maxMagma.Concat(2, 7)) + assert.Equal(t, 10, maxMagma.Concat(10, 10)) + }) +} + +func TestFirst(t *testing.T) { + t.Run("string", func(t *testing.T) { + m := First[string]() + + assert.Equal(t, "a", m.Concat("a", "b")) + assert.Equal(t, "first", m.Concat("first", "second")) + assert.Equal(t, "", m.Concat("", "something")) + }) + + t.Run("int", func(t *testing.T) { + m := First[int]() + + assert.Equal(t, 1, m.Concat(1, 2)) + assert.Equal(t, 42, m.Concat(42, 100)) + assert.Equal(t, 0, m.Concat(0, 5)) + }) +} + +func TestSecond(t *testing.T) { + t.Run("string", func(t *testing.T) { + m := Second[string]() + + assert.Equal(t, "b", m.Concat("a", "b")) + assert.Equal(t, "second", m.Concat("first", "second")) + assert.Equal(t, "something", m.Concat("", "something")) + }) + + t.Run("int", func(t *testing.T) { + m := Second[int]() + + assert.Equal(t, 2, m.Concat(1, 2)) + assert.Equal(t, 100, m.Concat(42, 100)) + assert.Equal(t, 5, m.Concat(0, 5)) + }) +} + +func TestReverse(t *testing.T) { + t.Run("subtraction", func(t *testing.T) { + subMagma := MakeMagma(func(a, b int) int { + return a - b + }) + + reversedMagma := Reverse(subMagma) + + assert.Equal(t, 7, subMagma.Concat(10, 3)) // 10 - 3 + assert.Equal(t, -7, reversedMagma.Concat(10, 3)) // 3 - 10 + }) + + t.Run("division", func(t *testing.T) { + divMagma := MakeMagma(func(a, b int) int { + if b == 0 { + return 0 + } + return a / b + }) + + reversedMagma := Reverse(divMagma) + + assert.Equal(t, 5, divMagma.Concat(10, 2)) // 10 / 2 + assert.Equal(t, 0, reversedMagma.Concat(10, 2)) // 2 / 10 + }) + + t.Run("string concatenation", func(t *testing.T) { + stringMagma := MakeMagma(func(a, b string) string { + return a + b + }) + + reversedMagma := Reverse(stringMagma) + + assert.Equal(t, "ab", stringMagma.Concat("a", "b")) + assert.Equal(t, "ba", reversedMagma.Concat("a", "b")) + }) +} + +func TestFilterFirst(t *testing.T) { + addMagma := MakeMagma(func(a, b int) int { + return a + b + }) + + t.Run("positive first", func(t *testing.T) { + filteredMagma := FilterFirst(func(n int) bool { + return n > 0 + })(addMagma) + + assert.Equal(t, 8, filteredMagma.Concat(5, 3)) // 5 is positive: 5 + 3 + assert.Equal(t, 3, filteredMagma.Concat(-5, 3)) // -5 is negative: return 3 + assert.Equal(t, 10, filteredMagma.Concat(7, 3)) // 7 is positive: 7 + 3 + }) + + t.Run("even first", func(t *testing.T) { + filteredMagma := FilterFirst(func(n int) bool { + return n%2 == 0 + })(addMagma) + + assert.Equal(t, 7, filteredMagma.Concat(4, 3)) // 4 is even: 4 + 3 + assert.Equal(t, 3, filteredMagma.Concat(5, 3)) // 5 is odd: return 3 + assert.Equal(t, 5, filteredMagma.Concat(2, 3)) // 2 is even: 2 + 3 + }) +} + +func TestFilterSecond(t *testing.T) { + addMagma := MakeMagma(func(a, b int) int { + return a + b + }) + + t.Run("positive second", func(t *testing.T) { + filteredMagma := FilterSecond(func(n int) bool { + return n > 0 + })(addMagma) + + assert.Equal(t, 8, filteredMagma.Concat(5, 3)) // 3 is positive: 5 + 3 + assert.Equal(t, 5, filteredMagma.Concat(5, -3)) // -3 is negative: return 5 + assert.Equal(t, 10, filteredMagma.Concat(7, 3)) // 3 is positive: 7 + 3 + }) + + t.Run("even second", func(t *testing.T) { + filteredMagma := FilterSecond(func(n int) bool { + return n%2 == 0 + })(addMagma) + + assert.Equal(t, 9, filteredMagma.Concat(5, 4)) // 4 is even: 5 + 4 + assert.Equal(t, 5, filteredMagma.Concat(5, 3)) // 3 is odd: return 5 + assert.Equal(t, 7, filteredMagma.Concat(5, 2)) // 2 is even: 5 + 2 + }) +} + +func TestEndo(t *testing.T) { + addMagma := MakeMagma(func(a, b int) int { + return a + b + }) + + t.Run("double before adding", func(t *testing.T) { + doubledMagma := Endo(func(n int) int { + return n * 2 + })(addMagma) + + assert.Equal(t, 14, doubledMagma.Concat(3, 4)) // (3*2) + (4*2) = 6 + 8 + assert.Equal(t, 10, doubledMagma.Concat(2, 3)) // (2*2) + (3*2) = 4 + 6 + assert.Equal(t, 0, doubledMagma.Concat(0, 0)) // (0*2) + (0*2) = 0 + }) + + t.Run("square before adding", func(t *testing.T) { + squaredMagma := Endo(func(n int) int { + return n * n + })(addMagma) + + assert.Equal(t, 25, squaredMagma.Concat(3, 4)) // (3*3) + (4*4) = 9 + 16 + assert.Equal(t, 13, squaredMagma.Concat(2, 3)) // (2*2) + (3*3) = 4 + 9 + assert.Equal(t, 2, squaredMagma.Concat(1, 1)) // (1*1) + (1*1) = 2 + }) + + t.Run("negate before adding", func(t *testing.T) { + negatedMagma := Endo(func(n int) int { + return -n + })(addMagma) + + assert.Equal(t, -7, negatedMagma.Concat(3, 4)) // (-3) + (-4) = -7 + assert.Equal(t, -5, negatedMagma.Concat(2, 3)) // (-2) + (-3) = -5 + assert.Equal(t, 0, negatedMagma.Concat(0, 0)) // 0 + 0 = 0 + }) +} + +func TestConcatAll(t *testing.T) { + t.Run("sum integers", func(t *testing.T) { + addMagma := MakeMagma(func(a, b int) int { + return a + b + }) + + numbers := []int{1, 2, 3, 4, 5} + result := ConcatAll(addMagma)(0)(numbers) + assert.Equal(t, 15, result) + }) + + t.Run("multiply integers", func(t *testing.T) { + mulMagma := MakeMagma(func(a, b int) int { + return a * b + }) + + numbers := []int{2, 3, 4} + result := ConcatAll(mulMagma)(1)(numbers) + assert.Equal(t, 24, result) + }) + + t.Run("max of integers", func(t *testing.T) { + maxMagma := MakeMagma(func(a, b int) int { + if a > b { + return a + } + return b + }) + + numbers := []int{3, 7, 2, 9, 1} + result := ConcatAll(maxMagma)(0)(numbers) + assert.Equal(t, 9, result) + }) + + t.Run("concatenate strings", func(t *testing.T) { + stringMagma := MakeMagma(func(a, b string) string { + return a + b + }) + + words := []string{"Hello", " ", "World"} + result := ConcatAll(stringMagma)("")(words) + assert.Equal(t, "Hello World", result) + }) + + t.Run("empty slice", func(t *testing.T) { + addMagma := MakeMagma(func(a, b int) int { + return a + b + }) + + numbers := []int{} + result := ConcatAll(addMagma)(42)(numbers) + assert.Equal(t, 42, result) + }) +} + +func TestMonadConcatAll(t *testing.T) { + t.Run("sum integers", func(t *testing.T) { + addMagma := MakeMagma(func(a, b int) int { + return a + b + }) + + numbers := []int{1, 2, 3, 4, 5} + result := MonadConcatAll(addMagma)(numbers, 0) + assert.Equal(t, 15, result) + }) + + t.Run("multiply integers", func(t *testing.T) { + mulMagma := MakeMagma(func(a, b int) int { + return a * b + }) + + numbers := []int{2, 3, 4} + result := MonadConcatAll(mulMagma)(numbers, 1) + assert.Equal(t, 24, result) + }) + + t.Run("empty slice", func(t *testing.T) { + addMagma := MakeMagma(func(a, b int) int { + return a + b + }) + + numbers := []int{} + result := MonadConcatAll(addMagma)(numbers, 100) + assert.Equal(t, 100, result) + }) +} + +func TestGenericConcatAll(t *testing.T) { + type IntSlice []int + + t.Run("custom slice type", func(t *testing.T) { + addMagma := MakeMagma(func(a, b int) int { + return a + b + }) + + numbers := IntSlice{1, 2, 3, 4, 5} + result := GenericConcatAll[IntSlice](addMagma)(0)(numbers) + assert.Equal(t, 15, result) + }) + + t.Run("regular slice", func(t *testing.T) { + mulMagma := MakeMagma(func(a, b int) int { + return a * b + }) + + numbers := []int{2, 3, 4} + result := GenericConcatAll[[]int](mulMagma)(1)(numbers) + assert.Equal(t, 24, result) + }) +} + +func TestGenericMonadConcatAll(t *testing.T) { + type IntSlice []int + + t.Run("custom slice type", func(t *testing.T) { + addMagma := MakeMagma(func(a, b int) int { + return a + b + }) + + numbers := IntSlice{1, 2, 3, 4, 5} + result := GenericMonadConcatAll[IntSlice](addMagma)(numbers, 0) + assert.Equal(t, 15, result) + }) + + t.Run("regular slice", func(t *testing.T) { + mulMagma := MakeMagma(func(a, b int) int { + return a * b + }) + + numbers := []int{2, 3, 4} + result := GenericMonadConcatAll[[]int](mulMagma)(numbers, 1) + assert.Equal(t, 24, result) + }) +} + +// Test practical examples +func TestPracticalExamples(t *testing.T) { + t.Run("min magma", func(t *testing.T) { + minMagma := MakeMagma(func(a, b int) int { + if a < b { + return a + } + return b + }) + + numbers := []int{3, 7, 2, 9, 1} + minimum := ConcatAll(minMagma)(100)(numbers) + assert.Equal(t, 1, minimum) + }) + + t.Run("string join with separator", func(t *testing.T) { + joinMagma := MakeMagma(func(a, b string) string { + if a == "" { + return b + } + if b == "" { + return a + } + return a + ", " + b + }) + + words := []string{"apple", "banana", "cherry"} + result := ConcatAll(joinMagma)("")(words) + assert.Equal(t, "apple, banana, cherry", result) + }) + + t.Run("boolean AND", func(t *testing.T) { + andMagma := MakeMagma(func(a, b bool) bool { + return a && b + }) + + values := []bool{true, true, true} + result := ConcatAll(andMagma)(true)(values) + assert.True(t, result) + + values2 := []bool{true, false, true} + result2 := ConcatAll(andMagma)(true)(values2) + assert.False(t, result2) + }) + + t.Run("boolean OR", func(t *testing.T) { + orMagma := MakeMagma(func(a, b bool) bool { + return a || b + }) + + values := []bool{false, false, false} + result := ConcatAll(orMagma)(false)(values) + assert.False(t, result) + + values2 := []bool{false, true, false} + result2 := ConcatAll(orMagma)(false)(values2) + assert.True(t, result2) + }) +} diff --git a/v2/main.go b/v2/main.go new file mode 100644 index 0000000..22d03a1 --- /dev/null +++ b/v2/main.go @@ -0,0 +1,39 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +// Package main contains the entry point for the code generator +package main + +import ( + "log" + "os" + + "github.com/IBM/fp-go/v2/cli" + + C "github.com/urfave/cli/v2" +) + +func main() { + + app := &C.App{ + Name: "fp-go", + Usage: "Code generation for fp-go", + Commands: cli.Commands(), + } + + if err := app.Run(os.Args); err != nil { + log.Fatal(err) + } +} diff --git a/v2/monoid/alt.go b/v2/monoid/alt.go new file mode 100644 index 0000000..431f0f1 --- /dev/null +++ b/v2/monoid/alt.go @@ -0,0 +1,141 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package monoid + +import ( + S "github.com/IBM/fp-go/v2/semigroup" +) + +// AlternativeMonoid creates a monoid for types that are both Applicative and Alternative. +// +// This combines the behavior of ApplicativeMonoid with Alternative semantics, providing +// both applicative combination and fallback/choice behavior. The resulting monoid tries +// to combine values using the applicative monoid, but falls back to alternative behavior +// when needed. +// +// This is useful for types like Option, Either, or Parser that support both applicative +// composition and alternative/fallback semantics. +// +// Type Parameters: +// - A: The base type with a monoid +// - HKTA: The higher-kinded type representing the functor applied to A +// - HKTFA: The higher-kinded type representing the functor applied to func(A) A +// - LAZYHKTA: A lazy/deferred computation of HKTA (typically func() HKTA) +// +// Parameters: +// - fof: The "pure" operation that lifts a value into the context +// - fmap: The map operation for the functor +// - fap: The apply operation for the applicative +// - falt: The alternative operation providing fallback/choice behavior +// - m: The monoid for the base type A +// +// Returns: +// - A Monoid[HKTA] combining applicative and alternative semantics +// +// Example (conceptual with Option-like type): +// +// intAddMonoid := MakeMonoid( +// func(a, b int) int { return a + b }, +// 0, +// ) +// +// optMonoid := AlternativeMonoid( +// some, // pure +// fmap, // map +// fap, // apply +// falt, // alternative (fallback) +// intAddMonoid, +// ) +// +// // Combines Some values using addition +// result1 := optMonoid.Concat(Some(5), Some(3)) // Some(8) +// // Falls back when first is None +// result2 := optMonoid.Concat(None(), Some(3)) // Some(3) +func AlternativeMonoid[A, HKTA, HKTFA any, LAZYHKTA ~func() HKTA]( + fof func(A) HKTA, + + fmap func(HKTA, func(A) func(A) A) HKTFA, + fap func(HKTFA, HKTA) HKTA, + + falt func(HKTA, LAZYHKTA) HKTA, + + m Monoid[A], + +) Monoid[HKTA] { + + sg := ApplicativeMonoid(fof, fmap, fap, m) + + return MakeMonoid( + func(first, second HKTA) HKTA { + snd := func() HKTA { return second } + + return falt(sg.Concat(first, second), func() HKTA { + return falt(first, snd) + }) + }, + sg.Empty(), + ) +} + +// AltMonoid creates a monoid from an Alt type class (alternative/choice operation). +// +// This creates a monoid for types that support alternative/fallback semantics, +// where the Concat operation tries the first value and falls back to the second +// if the first fails or is empty. The Empty value is provided by fzero. +// +// This is commonly used with Option, Either, Parser, and similar types that +// represent computations that may fail or have multiple alternatives. +// +// Type Parameters: +// - HKTA: The higher-kinded type (e.g., Option[A], Either[E, A]) +// - LAZYHKTA: A lazy/deferred computation of HKTA (typically func() HKTA) +// +// Parameters: +// - fzero: A lazy computation that produces the empty/zero value +// - falt: The alternative operation that provides fallback behavior +// +// Returns: +// - A Monoid[HKTA] with alternative/choice semantics +// +// Example (conceptual with Option-like type): +// +// optMonoid := AltMonoid( +// func() Option[int] { return None() }, // empty +// func(first Option[int], second func() Option[int]) Option[int] { +// if first.IsSome() { +// return first +// } +// return second() +// }, +// ) +// +// // First Some wins +// result1 := optMonoid.Concat(Some(5), Some(3)) // Some(5) +// // Falls back to second when first is None +// result2 := optMonoid.Concat(None(), Some(3)) // Some(3) +// // Both None returns None +// result3 := optMonoid.Concat(None(), None()) // None() +func AltMonoid[HKTA any, LAZYHKTA ~func() HKTA]( + fzero LAZYHKTA, + falt func(HKTA, LAZYHKTA) HKTA, + +) Monoid[HKTA] { + + return MakeMonoid( + S.AltSemigroup(falt).Concat, + fzero(), + ) +} diff --git a/v2/monoid/apply.go b/v2/monoid/apply.go new file mode 100644 index 0000000..c7fb956 --- /dev/null +++ b/v2/monoid/apply.go @@ -0,0 +1,76 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package monoid + +import ( + S "github.com/IBM/fp-go/v2/semigroup" +) + +// ApplicativeMonoid lifts a monoid into an applicative functor context. +// +// This function creates a monoid for applicative functor values (HKTA) given a monoid +// for the base type (A). It uses the applicative functor's operations (of, map, ap) +// to lift the monoid operations into the applicative context. +// +// This is useful for combining values that are wrapped in applicative contexts +// (like Option, Either, IO, etc.) using the underlying monoid's combination logic. +// +// Type Parameters: +// - A: The base type with a monoid +// - HKTA: The higher-kinded type representing the applicative functor applied to A +// - HKTFA: The higher-kinded type representing the applicative functor applied to func(A) A +// +// Parameters: +// - fof: The "pure" or "of" operation that lifts a value into the applicative context +// - fmap: The map operation for the applicative functor +// - fap: The apply operation for the applicative functor +// - m: The monoid for the base type A +// +// Returns: +// - A Monoid[HKTA] that combines applicative values using the base monoid +// +// Example (conceptual with Option-like type): +// +// type Option[A any] struct { value *A } +// +// intAddMonoid := MakeMonoid( +// func(a, b int) int { return a + b }, +// 0, +// ) +// +// optMonoid := ApplicativeMonoid( +// some, // func(int) Option[int] +// fmap, // func(Option[int], func(int) func(int) int) Option[func(int) int] +// fap, // func(Option[func(int) int], Option[int]) Option[int] +// intAddMonoid, +// ) +// +// // Combine Option values using addition +// result := optMonoid.Concat(Some(5), Some(3)) // Some(8) +// empty := optMonoid.Empty() // Some(0) +func ApplicativeMonoid[A, HKTA, HKTFA any]( + fof func(A) HKTA, + fmap func(HKTA, func(A) func(A) A) HKTFA, + fap func(HKTFA, HKTA) HKTA, + + m Monoid[A], +) Monoid[HKTA] { + + return MakeMonoid( + S.ApplySemigroup(fmap, fap, m).Concat, + fof(m.Empty()), + ) +} diff --git a/v2/monoid/array.go b/v2/monoid/array.go new file mode 100644 index 0000000..ed42832 --- /dev/null +++ b/v2/monoid/array.go @@ -0,0 +1,113 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package monoid + +import ( + S "github.com/IBM/fp-go/v2/semigroup" +) + +// GenericConcatAll combines all elements in a generic slice using a monoid. +// +// This function works with custom slice types (types that are defined as ~[]A). +// It uses the monoid's Concat operation to combine elements and returns the +// monoid's Empty value for empty slices. +// +// Type Parameters: +// - GA: A generic slice type constraint (~[]A) +// - A: The element type +// +// Parameters: +// - m: The monoid to use for combining elements +// +// Returns: +// - A function that takes a slice and returns the combined result +// +// Example: +// +// type IntSlice []int +// +// addMonoid := MakeMonoid( +// func(a, b int) int { return a + b }, +// 0, +// ) +// +// concatAll := GenericConcatAll[IntSlice](addMonoid) +// result := concatAll(IntSlice{1, 2, 3, 4, 5}) // 15 +// empty := concatAll(IntSlice{}) // 0 +func GenericConcatAll[GA ~[]A, A any](m Monoid[A]) func(GA) A { + return S.GenericConcatAll[GA](S.MakeSemigroup(m.Concat))(m.Empty()) +} + +// ConcatAll combines all elements in a slice using a monoid. +// +// This function reduces a slice to a single value by repeatedly applying the +// monoid's Concat operation. For an empty slice, it returns the monoid's Empty value. +// +// This is the standard version that works with []A slices. For custom slice types, +// use GenericConcatAll. +// +// Parameters: +// - m: The monoid to use for combining elements +// +// Returns: +// - A function that takes a slice and returns the combined result +// +// Example: +// +// addMonoid := MakeMonoid( +// func(a, b int) int { return a + b }, +// 0, +// ) +// +// concatAll := ConcatAll(addMonoid) +// sum := concatAll([]int{1, 2, 3, 4, 5}) // 15 +// empty := concatAll([]int{}) // 0 +// +// stringMonoid := MakeMonoid( +// func(a, b string) string { return a + b }, +// "", +// ) +// concat := ConcatAll(stringMonoid) +// result := concat([]string{"Hello", " ", "World"}) // "Hello World" +func ConcatAll[A any](m Monoid[A]) func([]A) A { + return GenericConcatAll[[]A](m) +} + +// Fold combines all elements in a slice using a monoid. +// +// This is an alias for ConcatAll, providing a more functional programming-style name. +// It performs a left fold (reduce) operation using the monoid's Concat function, +// starting with the monoid's Empty value. +// +// Parameters: +// - m: The monoid to use for combining elements +// +// Returns: +// - A function that takes a slice and returns the combined result +// +// Example: +// +// mulMonoid := MakeMonoid( +// func(a, b int) int { return a * b }, +// 1, +// ) +// +// fold := Fold(mulMonoid) +// product := fold([]int{2, 3, 4}) // 24 (1 * 2 * 3 * 4) +// empty := fold([]int{}) // 1 +func Fold[A any](m Monoid[A]) func([]A) A { + return GenericConcatAll[[]A](m) +} diff --git a/v2/monoid/coverage.out b/v2/monoid/coverage.out new file mode 100644 index 0000000..1630fd9 --- /dev/null +++ b/v2/monoid/coverage.out @@ -0,0 +1,17 @@ +mode: set +github.com/IBM/fp-go/v2/monoid/alt.go:77.16,82.33 2 1 +github.com/IBM/fp-go/v2/monoid/alt.go:82.33,83.23 1 1 +github.com/IBM/fp-go/v2/monoid/alt.go:83.23,83.40 1 1 +github.com/IBM/fp-go/v2/monoid/alt.go:85.4,85.54 1 1 +github.com/IBM/fp-go/v2/monoid/alt.go:85.54,87.5 1 1 +github.com/IBM/fp-go/v2/monoid/alt.go:135.16,141.2 1 1 +github.com/IBM/fp-go/v2/monoid/apply.go:70.16,76.2 1 1 +github.com/IBM/fp-go/v2/monoid/array.go:50.63,52.2 1 1 +github.com/IBM/fp-go/v2/monoid/array.go:85.48,87.2 1 1 +github.com/IBM/fp-go/v2/monoid/array.go:111.43,113.2 1 1 +github.com/IBM/fp-go/v2/monoid/function.go:66.62,71.2 1 1 +github.com/IBM/fp-go/v2/monoid/monoid.go:48.37,50.2 1 1 +github.com/IBM/fp-go/v2/monoid/monoid.go:52.30,54.2 1 1 +github.com/IBM/fp-go/v2/monoid/monoid.go:84.55,86.2 1 1 +github.com/IBM/fp-go/v2/monoid/monoid.go:119.44,121.2 1 1 +github.com/IBM/fp-go/v2/monoid/monoid.go:142.53,144.2 1 1 diff --git a/v2/monoid/doc.go b/v2/monoid/doc.go new file mode 100644 index 0000000..a815915 --- /dev/null +++ b/v2/monoid/doc.go @@ -0,0 +1,358 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +/* +Package monoid provides the Monoid algebraic structure. + +# Overview + +A Monoid is a Semigroup with an identity element (called Empty). It extends +the Magma → Semigroup hierarchy by adding a neutral element that doesn't +change other elements when combined with them. + +The Monoid interface: + + type Monoid[A any] interface { + Semigroup[A] // Provides Concat(x, y A) A + Empty() A // Identity element + } + +Monoid laws: + - Associativity (from Semigroup): Concat(Concat(x, y), z) = Concat(x, Concat(y, z)) + - Left identity: Concat(Empty(), x) = x + - Right identity: Concat(x, Empty()) = x + +# Algebraic Hierarchy + + Magma (binary operation) + ↓ + Semigroup (associative) + ↓ + Monoid (identity element) + +# Basic Usage + +Creating monoids for common types: + + // Integer addition monoid (identity: 0) + addMonoid := monoid.MakeMonoid( + func(a, b int) int { return a + b }, + 0, + ) + + result := addMonoid.Concat(5, 3) // 8 + empty := addMonoid.Empty() // 0 + + // Verify identity laws + assert.Equal(t, 5, addMonoid.Concat(addMonoid.Empty(), 5)) // Left identity + assert.Equal(t, 5, addMonoid.Concat(5, addMonoid.Empty())) // Right identity + + // Integer multiplication monoid (identity: 1) + mulMonoid := monoid.MakeMonoid( + func(a, b int) int { return a * b }, + 1, + ) + + result := mulMonoid.Concat(5, 3) // 15 + empty := mulMonoid.Empty() // 1 + + // String concatenation monoid (identity: "") + stringMonoid := monoid.MakeMonoid( + func(a, b string) string { return a + b }, + "", + ) + + result := stringMonoid.Concat("Hello", " World") // "Hello World" + empty := stringMonoid.Empty() // "" + +# Array Operations + +ConcatAll - Combines all elements using the monoid's empty as starting value: + + addMonoid := monoid.MakeMonoid( + func(a, b int) int { return a + b }, + 0, + ) + + numbers := []int{1, 2, 3, 4, 5} + sum := monoid.ConcatAll(addMonoid)(numbers) + // sum is 15 (0 + 1 + 2 + 3 + 4 + 5) + + // Empty slice returns the identity + empty := monoid.ConcatAll(addMonoid)([]int{}) + // empty is 0 + +Fold - Alias for ConcatAll: + + mulMonoid := monoid.MakeMonoid( + func(a, b int) int { return a * b }, + 1, + ) + + numbers := []int{2, 3, 4} + product := monoid.Fold(mulMonoid)(numbers) + // product is 24 (1 * 2 * 3 * 4) + +GenericConcatAll - Works with custom slice types: + + type IntSlice []int + + addMonoid := monoid.MakeMonoid( + func(a, b int) int { return a + b }, + 0, + ) + + numbers := IntSlice{1, 2, 3} + sum := monoid.GenericConcatAll[IntSlice](addMonoid)(numbers) + // sum is 6 + +# Transforming Monoids + +Reverse - Swaps the order of arguments: + + subMonoid := monoid.MakeMonoid( + func(a, b int) int { return a - b }, + 0, + ) + + reversedMonoid := monoid.Reverse(subMonoid) + + result1 := subMonoid.Concat(10, 3) // 10 - 3 = 7 + result2 := reversedMonoid.Concat(10, 3) // 3 - 10 = -7 + +ToSemigroup - Converts a Monoid to a Semigroup: + + m := monoid.MakeMonoid( + func(a, b int) int { return a + b }, + 0, + ) + + sg := monoid.ToSemigroup(m) + result := sg.Concat(5, 3) // 8 + +# Function Monoid + +FunctionMonoid - Creates a monoid for functions when the codomain has a monoid: + + // Monoid for functions that return integers + intAddMonoid := monoid.MakeMonoid( + func(a, b int) int { return a + b }, + 0, + ) + + funcMonoid := monoid.FunctionMonoid[string, int](intAddMonoid) + + f1 := func(s string) int { return len(s) } + f2 := func(s string) int { return len(s) * 2 } + + // Combine functions: result(x) = f1(x) + f2(x) + combined := funcMonoid.Concat(f1, f2) + + result := combined("hello") // len("hello") + len("hello")*2 = 5 + 10 = 15 + + // Empty function always returns the monoid's empty + emptyFunc := funcMonoid.Empty() + result := emptyFunc("anything") // 0 + +# Applicative Monoid + +ApplicativeMonoid - Lifts a monoid into an applicative functor: + + // This is used internally for combining applicative effects + // Example with Option-like types: + + type Option[A any] struct { + value *A + } + + func Some[A any](a A) Option[A] { + return Option[A]{value: &a} + } + + func None[A any]() Option[A] { + return Option[A]{value: nil} + } + + // Define applicative operations for Option + // Then use ApplicativeMonoid to combine Option values + +# Alternative Monoid + +AltMonoid - Creates a monoid from an Alt type class: + + // For types with alternative/fallback semantics + // Used with Option, Either, etc. to provide fallback behavior + +AlternativeMonoid - Combines applicative and alternative: + + // Advanced usage for types that are both Applicative and Alternative + // Provides rich composition of effects with fallback + +# Practical Examples + +Boolean AND monoid: + + andMonoid := monoid.MakeMonoid( + func(a, b bool) bool { return a && b }, + true, // Identity: true AND x = x + ) + + values := []bool{true, true, true} + result := monoid.ConcatAll(andMonoid)(values) // true + + values2 := []bool{true, false, true} + result2 := monoid.ConcatAll(andMonoid)(values2) // false + +Boolean OR monoid: + + orMonoid := monoid.MakeMonoid( + func(a, b bool) bool { return a || b }, + false, // Identity: false OR x = x + ) + + values := []bool{false, false, false} + result := monoid.ConcatAll(orMonoid)(values) // false + + values2 := []bool{false, true, false} + result2 := monoid.ConcatAll(orMonoid)(values2) // true + +Max monoid (with bounded integers): + + maxMonoid := monoid.MakeMonoid( + func(a, b int) int { + if a > b { + return a + } + return b + }, + math.MinInt, // Identity: smallest possible int + ) + + numbers := []int{3, 7, 2, 9, 1} + maximum := monoid.ConcatAll(maxMonoid)(numbers) // 9 + +Min monoid (with bounded integers): + + minMonoid := monoid.MakeMonoid( + func(a, b int) int { + if a < b { + return a + } + return b + }, + math.MaxInt, // Identity: largest possible int + ) + + numbers := []int{3, 7, 2, 9, 1} + minimum := monoid.ConcatAll(minMonoid)(numbers) // 1 + +List concatenation monoid: + + listMonoid := monoid.MakeMonoid( + func(a, b []int) []int { + result := make([]int, len(a)+len(b)) + copy(result, a) + copy(result[len(a):], b) + return result + }, + []int{}, // Identity: empty slice + ) + + lists := [][]int{{1, 2}, {3, 4}, {5}} + flattened := monoid.ConcatAll(listMonoid)(lists) + // flattened is []int{1, 2, 3, 4, 5} + +# Monoid Laws + +All monoid instances must satisfy these laws: + + 1. Associativity (from Semigroup): + Concat(Concat(x, y), z) = Concat(x, Concat(y, z)) + + 2. Left Identity: + Concat(Empty(), x) = x + + 3. Right Identity: + Concat(x, Empty()) = x + +Example verification: + + m := monoid.MakeMonoid( + func(a, b int) int { return a + b }, + 0, + ) + + // Associativity + assert.Equal(t, + m.Concat(m.Concat(1, 2), 3), + m.Concat(1, m.Concat(2, 3)), + ) // Both equal 6 + + // Left identity + assert.Equal(t, 5, m.Concat(m.Empty(), 5)) + + // Right identity + assert.Equal(t, 5, m.Concat(5, m.Empty())) + +# Common Monoids + +Additive monoid (integers): + - Concat: addition + - Empty: 0 + +Multiplicative monoid (integers): + - Concat: multiplication + - Empty: 1 + +String monoid: + - Concat: concatenation + - Empty: "" + +List monoid: + - Concat: list concatenation + - Empty: [] + +Boolean AND monoid: + - Concat: logical AND + - Empty: true + +Boolean OR monoid: + - Concat: logical OR + - Empty: false + +# Functions + +Core operations: + - MakeMonoid[A any](func(A, A) A, A) - Create a monoid + - Reverse[A any](Monoid[A]) - Swap argument order + - ToSemigroup[A any](Monoid[A]) - Convert to semigroup + +Array operations: + - ConcatAll[A any](Monoid[A]) - Combine all elements + - Fold[A any](Monoid[A]) - Alias for ConcatAll + - GenericConcatAll[GA ~[]A, A any](Monoid[A]) - Generic version + +Higher-order: + - FunctionMonoid[A, B any](Monoid[B]) - Monoid for functions + - ApplicativeMonoid[A, HKTA, HKTFA any](...) - Lift into applicative + - AltMonoid[HKTA any, LAZYHKTA ~func() HKTA](...) - From Alt type class + - AlternativeMonoid[A, HKTA, HKTFA any, LAZYHKTA ~func() HKTA](...) - Applicative + Alternative + +# Related Packages + + - semigroup: Parent structure (associative binary operation) + - magma: Grandparent structure (binary operation) +*/ +package monoid diff --git a/v2/monoid/function.go b/v2/monoid/function.go new file mode 100644 index 0000000..1ea7695 --- /dev/null +++ b/v2/monoid/function.go @@ -0,0 +1,71 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package monoid + +import ( + F "github.com/IBM/fp-go/v2/function" + S "github.com/IBM/fp-go/v2/semigroup" +) + +// FunctionMonoid creates a monoid for functions when the codomain (return type) has a monoid. +// +// Given a monoid for type B, this creates a monoid for functions of type func(A) B. +// The resulting monoid combines functions by combining their results using the provided +// monoid's Concat operation. The empty function always returns the monoid's Empty value. +// +// This allows you to compose functions that return monoidal values in a point-wise manner. +// +// Type Parameters: +// - A: The domain (input type) of the functions +// - B: The codomain (return type) of the functions, which must have a monoid +// +// Parameters: +// - m: A monoid for the codomain type B +// +// Returns: +// - A Monoid[func(A) B] that combines functions point-wise +// +// Example: +// +// // Monoid for functions returning integers +// intAddMonoid := MakeMonoid( +// func(a, b int) int { return a + b }, +// 0, +// ) +// +// funcMonoid := FunctionMonoid[string, int](intAddMonoid) +// +// // Define some functions +// f1 := func(s string) int { return len(s) } +// f2 := func(s string) int { return len(s) * 2 } +// +// // Combine functions: result(x) = f1(x) + f2(x) +// combined := funcMonoid.Concat(f1, f2) +// result := combined("hello") // len("hello") + len("hello")*2 = 5 + 10 = 15 +// +// // Empty function always returns 0 +// emptyFunc := funcMonoid.Empty() +// result := emptyFunc("anything") // 0 +// +// // Verify identity laws +// assert.Equal(t, f1("test"), funcMonoid.Concat(funcMonoid.Empty(), f1)("test")) +// assert.Equal(t, f1("test"), funcMonoid.Concat(f1, funcMonoid.Empty())("test")) +func FunctionMonoid[A, B any](m Monoid[B]) Monoid[func(A) B] { + return MakeMonoid( + S.FunctionSemigroup[A, B](m).Concat, + F.Constant1[A](m.Empty()), + ) +} diff --git a/v2/monoid/monoid.go b/v2/monoid/monoid.go new file mode 100644 index 0000000..c57623c --- /dev/null +++ b/v2/monoid/monoid.go @@ -0,0 +1,144 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package monoid + +import ( + S "github.com/IBM/fp-go/v2/semigroup" +) + +// Monoid represents an algebraic structure with an associative binary operation and an identity element. +// +// A Monoid extends Semigroup by adding an identity element (Empty) that satisfies: +// - Left identity: Concat(Empty(), x) = x +// - Right identity: Concat(x, Empty()) = x +// +// The Monoid must also satisfy the associativity law from Semigroup: +// - Associativity: Concat(Concat(x, y), z) = Concat(x, Concat(y, z)) +// +// Common examples: +// - Integer addition with 0 as identity +// - Integer multiplication with 1 as identity +// - String concatenation with "" as identity +// - List concatenation with [] as identity +// - Boolean AND with true as identity +// - Boolean OR with false as identity +type Monoid[A any] interface { + S.Semigroup[A] + Empty() A +} + +type monoid[A any] struct { + c func(A, A) A + e A +} + +func (m monoid[A]) Concat(x, y A) A { + return m.c(x, y) +} + +func (m monoid[A]) Empty() A { + return m.e +} + +// MakeMonoid creates a monoid from a binary operation and an identity element. +// +// The provided concat function must be associative, and the empty element must +// satisfy the identity laws (left and right identity). +// +// Parameters: +// - c: An associative binary operation func(A, A) A +// - e: The identity element of type A +// +// Returns: +// - A Monoid[A] instance +// +// Example: +// +// // Integer addition monoid +// addMonoid := MakeMonoid( +// func(a, b int) int { return a + b }, +// 0, // identity element +// ) +// result := addMonoid.Concat(5, 3) // 8 +// empty := addMonoid.Empty() // 0 +// +// // String concatenation monoid +// stringMonoid := MakeMonoid( +// func(a, b string) string { return a + b }, +// "", // identity element +// ) +// result := stringMonoid.Concat("Hello", " World") // "Hello World" +func MakeMonoid[A any](c func(A, A) A, e A) Monoid[A] { + return monoid[A]{c: c, e: e} +} + +// Reverse returns the dual of a Monoid by swapping the arguments of Concat. +// +// The reversed monoid has the same identity element but applies the binary +// operation in the opposite order. This is useful for operations that are +// not commutative. +// +// Parameters: +// - m: The monoid to reverse +// +// Returns: +// - A new Monoid[A] with reversed operation order +// +// Example: +// +// // Subtraction monoid (not commutative) +// subMonoid := MakeMonoid( +// func(a, b int) int { return a - b }, +// 0, +// ) +// reversedMonoid := Reverse(subMonoid) +// +// result1 := subMonoid.Concat(10, 3) // 10 - 3 = 7 +// result2 := reversedMonoid.Concat(10, 3) // 3 - 10 = -7 +// +// // String concatenation +// stringMonoid := MakeMonoid( +// func(a, b string) string { return a + b }, +// "", +// ) +// reversed := Reverse(stringMonoid) +// result := reversed.Concat("Hello", "World") // "WorldHello" +func Reverse[A any](m Monoid[A]) Monoid[A] { + return MakeMonoid(S.Reverse[A](m).Concat, m.Empty()) +} + +// ToSemigroup converts a Monoid to a Semigroup by discarding the identity element. +// +// This is useful when you need to use a monoid in a context that only requires +// a semigroup (associative binary operation without identity). +// +// Parameters: +// - m: The monoid to convert +// +// Returns: +// - A Semigroup[A] that uses the same Concat operation +// +// Example: +// +// addMonoid := MakeMonoid( +// func(a, b int) int { return a + b }, +// 0, +// ) +// sg := ToSemigroup(addMonoid) +// result := sg.Concat(5, 3) // 8 (identity not available) +func ToSemigroup[A any](m Monoid[A]) S.Semigroup[A] { + return S.Semigroup[A](m) +} diff --git a/v2/monoid/monoid_test.go b/v2/monoid/monoid_test.go new file mode 100644 index 0000000..955e6f0 --- /dev/null +++ b/v2/monoid/monoid_test.go @@ -0,0 +1,716 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package monoid + +import ( + "math" + "testing" + + S "github.com/IBM/fp-go/v2/semigroup" + "github.com/stretchr/testify/assert" +) + +// assertMonoidLaws checks monoid laws for a given value +func assertMonoidLaws[A any](t *testing.T, m Monoid[A], a A) bool { + e := m.Empty() + return assert.Equal(t, a, m.Concat(a, e), "Monoid right identity") && + assert.Equal(t, a, m.Concat(e, a), "Monoid left identity") +} + +// assertMonoidLawsForAll checks monoid laws for all values in a slice +func assertMonoidLawsForAll[A any](t *testing.T, m Monoid[A], data []A) bool { + result := true + for _, value := range data { + result = result && assertMonoidLaws(t, m, value) + } + return result +} + +// Test MakeMonoid creates a valid monoid +func TestMakeMonoid(t *testing.T) { + // Integer addition monoid + addMonoid := MakeMonoid( + func(a, b int) int { return a + b }, + 0, + ) + + assert.Equal(t, 8, addMonoid.Concat(5, 3)) + assert.Equal(t, 0, addMonoid.Empty()) + + // Integer multiplication monoid + mulMonoid := MakeMonoid( + func(a, b int) int { return a * b }, + 1, + ) + + assert.Equal(t, 15, mulMonoid.Concat(5, 3)) + assert.Equal(t, 1, mulMonoid.Empty()) + + // String concatenation monoid + stringMonoid := MakeMonoid( + func(a, b string) string { return a + b }, + "", + ) + + assert.Equal(t, "HelloWorld", stringMonoid.Concat("Hello", "World")) + assert.Equal(t, "", stringMonoid.Empty()) +} + +// Test monoid laws for integer addition +func TestMonoidLaws_IntAddition(t *testing.T) { + addMonoid := MakeMonoid( + func(a, b int) int { return a + b }, + 0, + ) + + testData := []int{0, 1, -1, 5, 10, -10, 100} + + // Test using helper function + assert.True(t, assertMonoidLawsForAll(t, addMonoid, testData)) + + // Explicit law tests + for _, x := range testData { + // Left identity: Empty() + x = x + assert.Equal(t, x, addMonoid.Concat(addMonoid.Empty(), x), + "Left identity failed for %d", x) + + // Right identity: x + Empty() = x + assert.Equal(t, x, addMonoid.Concat(x, addMonoid.Empty()), + "Right identity failed for %d", x) + } + + // Associativity (from Semigroup) + assert.Equal(t, + addMonoid.Concat(addMonoid.Concat(1, 2), 3), + addMonoid.Concat(1, addMonoid.Concat(2, 3)), + ) +} + +// Test monoid laws for integer multiplication +func TestMonoidLaws_IntMultiplication(t *testing.T) { + mulMonoid := MakeMonoid( + func(a, b int) int { return a * b }, + 1, + ) + + testData := []int{1, 2, 3, 5, 10} + + assert.True(t, assertMonoidLawsForAll(t, mulMonoid, testData)) + + // Explicit tests + for _, x := range testData { + assert.Equal(t, x, mulMonoid.Concat(mulMonoid.Empty(), x)) + assert.Equal(t, x, mulMonoid.Concat(x, mulMonoid.Empty())) + } +} + +// Test monoid laws for string concatenation +func TestMonoidLaws_String(t *testing.T) { + stringMonoid := MakeMonoid( + func(a, b string) string { return a + b }, + "", + ) + + testData := []string{"", "a", "hello", "world", "test"} + + assert.True(t, assertMonoidLawsForAll(t, stringMonoid, testData)) +} + +// Test monoid laws for boolean AND +func TestMonoidLaws_BooleanAnd(t *testing.T) { + andMonoid := MakeMonoid( + func(a, b bool) bool { return a && b }, + true, + ) + + testData := []bool{true, false} + + assert.True(t, assertMonoidLawsForAll(t, andMonoid, testData)) + + // Specific tests + assert.Equal(t, true, andMonoid.Concat(true, true)) + assert.Equal(t, false, andMonoid.Concat(true, false)) + assert.Equal(t, false, andMonoid.Concat(false, true)) + assert.Equal(t, false, andMonoid.Concat(false, false)) + assert.Equal(t, true, andMonoid.Empty()) +} + +// Test monoid laws for boolean OR +func TestMonoidLaws_BooleanOr(t *testing.T) { + orMonoid := MakeMonoid( + func(a, b bool) bool { return a || b }, + false, + ) + + testData := []bool{true, false} + + assert.True(t, assertMonoidLawsForAll(t, orMonoid, testData)) + + // Specific tests + assert.Equal(t, true, orMonoid.Concat(true, true)) + assert.Equal(t, true, orMonoid.Concat(true, false)) + assert.Equal(t, true, orMonoid.Concat(false, true)) + assert.Equal(t, false, orMonoid.Concat(false, false)) + assert.Equal(t, false, orMonoid.Empty()) +} + +// Test Reverse swaps argument order +func TestReverse(t *testing.T) { + // Subtraction is not commutative, so reverse will give different results + subMonoid := MakeMonoid( + func(a, b int) int { return a - b }, + 0, + ) + + reversedMonoid := Reverse(subMonoid) + + // Original: 10 - 3 = 7 + assert.Equal(t, 7, subMonoid.Concat(10, 3)) + + // Reversed: 3 - 10 = -7 + assert.Equal(t, -7, reversedMonoid.Concat(10, 3)) + + // Empty should be the same + assert.Equal(t, subMonoid.Empty(), reversedMonoid.Empty()) + + // Test with string concatenation + stringMonoid := MakeMonoid( + func(a, b string) string { return a + b }, + "", + ) + + reversedString := Reverse(stringMonoid) + + assert.Equal(t, "HelloWorld", stringMonoid.Concat("Hello", "World")) + assert.Equal(t, "WorldHello", reversedString.Concat("Hello", "World")) +} + +// Test Reverse preserves monoid laws +func TestReverse_PreservesLaws(t *testing.T) { + addMonoid := MakeMonoid( + func(a, b int) int { return a + b }, + 0, + ) + + reversedMonoid := Reverse(addMonoid) + + testData := []int{0, 1, 5, 10} + + // Reversed monoid should still satisfy monoid laws + assert.True(t, assertMonoidLawsForAll(t, reversedMonoid, testData)) +} + +// Test ToSemigroup conversion +func TestToSemigroup(t *testing.T) { + addMonoid := MakeMonoid( + func(a, b int) int { return a + b }, + 0, + ) + + sg := ToSemigroup(addMonoid) + + // Should work as a semigroup + assert.Equal(t, 8, sg.Concat(5, 3)) + assert.Equal(t, 15, sg.Concat(10, 5)) + + // Verify it's the same underlying operation + assert.Equal(t, addMonoid.Concat(5, 3), sg.Concat(5, 3)) +} + +// Test ConcatAll with various inputs +func TestConcatAll(t *testing.T) { + addMonoid := MakeMonoid( + func(a, b int) int { return a + b }, + 0, + ) + + concatAll := ConcatAll(addMonoid) + + tests := []struct { + name string + input []int + expected int + }{ + {"empty slice", []int{}, 0}, + {"single element", []int{5}, 5}, + {"multiple elements", []int{1, 2, 3, 4, 5}, 15}, + {"with zeros", []int{0, 5, 0, 3}, 8}, + {"negative numbers", []int{-1, -2, -3}, -6}, + {"mixed signs", []int{10, -5, 3, -2}, 6}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := concatAll(tt.input) + assert.Equal(t, tt.expected, result) + }) + } +} + +// Test ConcatAll with multiplication +func TestConcatAll_Multiplication(t *testing.T) { + mulMonoid := MakeMonoid( + func(a, b int) int { return a * b }, + 1, + ) + + concatAll := ConcatAll(mulMonoid) + + tests := []struct { + name string + input []int + expected int + }{ + {"empty slice", []int{}, 1}, + {"single element", []int{5}, 5}, + {"multiple elements", []int{2, 3, 4}, 24}, + {"with one", []int{1, 5, 1, 3}, 15}, + {"with zero", []int{2, 0, 3}, 0}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := concatAll(tt.input) + assert.Equal(t, tt.expected, result) + }) + } +} + +// Test ConcatAll with strings +func TestConcatAll_String(t *testing.T) { + stringMonoid := MakeMonoid( + func(a, b string) string { return a + b }, + "", + ) + + concatAll := ConcatAll(stringMonoid) + + tests := []struct { + name string + input []string + expected string + }{ + {"empty slice", []string{}, ""}, + {"single element", []string{"hello"}, "hello"}, + {"multiple elements", []string{"Hello", " ", "World", "!"}, "Hello World!"}, + {"with empty strings", []string{"a", "", "b", "", "c"}, "abc"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := concatAll(tt.input) + assert.Equal(t, tt.expected, result) + }) + } +} + +// Test Fold (alias for ConcatAll) +func TestFold(t *testing.T) { + addMonoid := MakeMonoid( + func(a, b int) int { return a + b }, + 0, + ) + + fold := Fold(addMonoid) + concatAll := ConcatAll(addMonoid) + + input := []int{1, 2, 3, 4, 5} + + // Fold and ConcatAll should produce the same result + assert.Equal(t, concatAll(input), fold(input)) + assert.Equal(t, 15, fold(input)) +} + +// Test GenericConcatAll with custom slice type +func TestGenericConcatAll(t *testing.T) { + type IntSlice []int + + addMonoid := MakeMonoid( + func(a, b int) int { return a + b }, + 0, + ) + + concatAll := GenericConcatAll[IntSlice](addMonoid) + + tests := []struct { + name string + input IntSlice + expected int + }{ + {"empty slice", IntSlice{}, 0}, + {"single element", IntSlice{5}, 5}, + {"multiple elements", IntSlice{1, 2, 3, 4, 5}, 15}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := concatAll(tt.input) + assert.Equal(t, tt.expected, result) + }) + } +} + +// Test FunctionMonoid +func TestFunctionMonoid(t *testing.T) { + // Create a monoid for functions that return integers + intAddMonoid := MakeMonoid( + func(a, b int) int { return a + b }, + 0, + ) + + funcMonoid := FunctionMonoid[string, int](intAddMonoid) + + // Create some functions + f1 := func(s string) int { return len(s) } + f2 := func(s string) int { return len(s) * 2 } + f3 := func(s string) int { return 10 } + + // Combine functions + combined := funcMonoid.Concat(f1, f2) + + // Test combined function + assert.Equal(t, 15, combined("hello")) // len("hello") + len("hello")*2 = 5 + 10 = 15 + assert.Equal(t, 9, combined("abc")) // len("abc") + len("abc")*2 = 3 + 6 = 9 + + // Test with three functions + combined3 := funcMonoid.Concat(funcMonoid.Concat(f1, f2), f3) + assert.Equal(t, 25, combined3("hello")) // 5 + 10 + 10 = 25 + + // Test empty function + emptyFunc := funcMonoid.Empty() + assert.Equal(t, 0, emptyFunc("anything")) + assert.Equal(t, 0, emptyFunc("")) + + // Test identity laws + input := "test" + assert.Equal(t, f1(input), funcMonoid.Concat(funcMonoid.Empty(), f1)(input)) + assert.Equal(t, f1(input), funcMonoid.Concat(f1, funcMonoid.Empty())(input)) +} + +// Test FunctionMonoid with different types +func TestFunctionMonoid_DifferentTypes(t *testing.T) { + // Monoid for functions that return booleans (OR) + boolOrMonoid := MakeMonoid( + func(a, b bool) bool { return a || b }, + false, + ) + + funcMonoid := FunctionMonoid[int, bool](boolOrMonoid) + + isEven := func(n int) bool { return n%2 == 0 } + isPositive := func(n int) bool { return n > 0 } + + // Combine: returns true if number is even OR positive + combined := funcMonoid.Concat(isEven, isPositive) + + assert.True(t, combined(4)) // even and positive + assert.True(t, combined(3)) // odd but positive + assert.True(t, combined(-2)) // even but negative + assert.False(t, combined(-3)) // odd and negative +} + +// Test ApplicativeMonoid with a simple applicative +func TestApplicativeMonoid(t *testing.T) { + // Simple Option-like type for testing + type Option[A any] struct { + value *A + } + + some := func(a int) Option[int] { + return Option[int]{value: &a} + } + + none := func() Option[int] { + return Option[int]{value: nil} + } + + fmap := func(opt Option[int], f func(int) func(int) int) Option[func(int) int] { + if opt.value == nil { + return Option[func(int) int]{value: nil} + } + fn := f(*opt.value) + return Option[func(int) int]{value: &fn} + } + + fap := func(optF Option[func(int) int], opt Option[int]) Option[int] { + if optF.value == nil || opt.value == nil { + return none() + } + result := (*optF.value)(*opt.value) + return some(result) + } + + intAddMonoid := MakeMonoid( + func(a, b int) int { return a + b }, + 0, + ) + + optMonoid := ApplicativeMonoid(some, fmap, fap, intAddMonoid) + + // Test concat of Some values + opt1 := some(5) + opt2 := some(3) + result := optMonoid.Concat(opt1, opt2) + assert.NotNil(t, result.value) + assert.Equal(t, 8, *result.value) + + // Test empty + empty := optMonoid.Empty() + assert.NotNil(t, empty.value) + assert.Equal(t, 0, *empty.value) + + // Test identity laws + assert.Equal(t, opt1, optMonoid.Concat(optMonoid.Empty(), opt1)) + assert.Equal(t, opt1, optMonoid.Concat(opt1, optMonoid.Empty())) +} + +// Test AlternativeMonoid +func TestAlternativeMonoid(t *testing.T) { + // Simple Option-like type for testing + type Option[A any] struct { + value *A + } + + some := func(a int) Option[int] { + return Option[int]{value: &a} + } + + none := func() Option[int] { + return Option[int]{value: nil} + } + + fmap := func(opt Option[int], f func(int) func(int) int) Option[func(int) int] { + if opt.value == nil { + return Option[func(int) int]{value: nil} + } + fn := f(*opt.value) + return Option[func(int) int]{value: &fn} + } + + fap := func(optF Option[func(int) int], opt Option[int]) Option[int] { + if optF.value == nil || opt.value == nil { + return none() + } + result := (*optF.value)(*opt.value) + return some(result) + } + + falt := func(first Option[int], second func() Option[int]) Option[int] { + if first.value != nil { + return first + } + return second() + } + + intAddMonoid := MakeMonoid( + func(a, b int) int { return a + b }, + 0, + ) + + optMonoid := AlternativeMonoid(some, fmap, fap, falt, intAddMonoid) + + // Test concat of Some values - should add them + opt1 := some(5) + opt2 := some(3) + result := optMonoid.Concat(opt1, opt2) + assert.NotNil(t, result.value) + assert.Equal(t, 8, *result.value) + + // Test concat with None - should fall back + result2 := optMonoid.Concat(none(), opt2) + assert.NotNil(t, result2.value) + assert.Equal(t, 3, *result2.value) + + // Test concat: first Some wins in alternative + result3 := optMonoid.Concat(opt1, opt2) + assert.NotNil(t, result3.value) + // The result should be 8 (5 + 3) from applicative behavior + + // Test empty + empty := optMonoid.Empty() + assert.NotNil(t, empty.value) + assert.Equal(t, 0, *empty.value) + + // Test identity laws + assert.Equal(t, opt1, optMonoid.Concat(optMonoid.Empty(), opt1)) + assert.Equal(t, opt1, optMonoid.Concat(opt1, optMonoid.Empty())) +} + +// Test AltMonoid +func TestAltMonoid(t *testing.T) { + // Simple Option-like type + type Option[A any] struct { + value *A + } + + some := func(a int) Option[int] { + return Option[int]{value: &a} + } + + none := func() Option[int] { + return Option[int]{value: nil} + } + + falt := func(first Option[int], second func() Option[int]) Option[int] { + if first.value != nil { + return first + } + return second() + } + + optMonoid := AltMonoid(none, falt) + + // Test concat: first Some wins + opt1 := some(5) + opt2 := some(3) + result := optMonoid.Concat(opt1, opt2) + assert.NotNil(t, result.value) + assert.Equal(t, 5, *result.value) + + // Test concat: None falls back to second + result2 := optMonoid.Concat(none(), opt2) + assert.NotNil(t, result2.value) + assert.Equal(t, 3, *result2.value) + + // Test concat: both None + result3 := optMonoid.Concat(none(), none()) + assert.Nil(t, result3.value) + + // Test empty + empty := optMonoid.Empty() + assert.Nil(t, empty.value) +} + +// Test monoid with max operation +func TestMonoid_Max(t *testing.T) { + maxMonoid := MakeMonoid( + func(a, b int) int { + if a > b { + return a + } + return b + }, + math.MinInt, + ) + + numbers := []int{3, 7, 2, 9, 1, 5} + maximum := ConcatAll(maxMonoid)(numbers) + assert.Equal(t, 9, maximum) + + // Empty slice returns identity (MinInt) + emptyMax := ConcatAll(maxMonoid)([]int{}) + assert.Equal(t, math.MinInt, emptyMax) +} + +// Test monoid with min operation +func TestMonoid_Min(t *testing.T) { + minMonoid := MakeMonoid( + func(a, b int) int { + if a < b { + return a + } + return b + }, + math.MaxInt, + ) + + numbers := []int{3, 7, 2, 9, 1, 5} + minimum := ConcatAll(minMonoid)(numbers) + assert.Equal(t, 1, minimum) + + // Empty slice returns identity (MaxInt) + emptyMin := ConcatAll(minMonoid)([]int{}) + assert.Equal(t, math.MaxInt, emptyMin) +} + +// Test monoid with list concatenation +func TestMonoid_ListConcat(t *testing.T) { + listMonoid := MakeMonoid( + func(a, b []int) []int { + result := make([]int, len(a)+len(b)) + copy(result, a) + copy(result[len(a):], b) + return result + }, + []int{}, + ) + + lists := [][]int{{1, 2}, {3, 4}, {5}} + flattened := ConcatAll(listMonoid)(lists) + assert.Equal(t, []int{1, 2, 3, 4, 5}, flattened) + + // Empty slice returns empty list + emptyList := ConcatAll(listMonoid)([][]int{}) + assert.Equal(t, []int{}, emptyList) + + // Test identity laws + list := []int{1, 2, 3} + assert.Equal(t, list, listMonoid.Concat(listMonoid.Empty(), list)) + assert.Equal(t, list, listMonoid.Concat(list, listMonoid.Empty())) +} + +// Test that monoid interface is properly implemented +func TestMonoidInterface(t *testing.T) { + addMonoid := MakeMonoid( + func(a, b int) int { return a + b }, + 0, + ) + + // Verify it implements Monoid interface + var _ Monoid[int] = addMonoid + + // Verify it also implements Semigroup interface (through embedding) + var _ S.Semigroup[int] = addMonoid +} + +// Benchmark ConcatAll +func BenchmarkConcatAll(b *testing.B) { + addMonoid := MakeMonoid( + func(a, b int) int { return a + b }, + 0, + ) + + concatAll := ConcatAll(addMonoid) + numbers := make([]int, 1000) + for i := range numbers { + numbers[i] = i + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = concatAll(numbers) + } +} + +// Benchmark FunctionMonoid +func BenchmarkFunctionMonoid(b *testing.B) { + intAddMonoid := MakeMonoid( + func(a, b int) int { return a + b }, + 0, + ) + + funcMonoid := FunctionMonoid[string, int](intAddMonoid) + + f1 := func(s string) int { return len(s) } + f2 := func(s string) int { return len(s) * 2 } + + combined := funcMonoid.Concat(f1, f2) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = combined("benchmark") + } +} diff --git a/v2/monoid/testing/rules.go b/v2/monoid/testing/rules.go new file mode 100644 index 0000000..822ee47 --- /dev/null +++ b/v2/monoid/testing/rules.go @@ -0,0 +1,43 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package testing + +import ( + "testing" + + AR "github.com/IBM/fp-go/v2/internal/array" + M "github.com/IBM/fp-go/v2/monoid" + "github.com/stretchr/testify/assert" +) + +func assertLaws[A any](t *testing.T, m M.Monoid[A]) func(a A) bool { + e := m.Empty() + return func(a A) bool { + return assert.Equal(t, a, m.Concat(a, e), "Monoid right identity") && + assert.Equal(t, a, m.Concat(e, a), "Monoid left identity") + } +} + +// AssertLaws asserts the monoid laws for a dataset +func AssertLaws[A any](t *testing.T, m M.Monoid[A]) func(data []A) bool { + law := assertLaws(t, m) + + return func(data []A) bool { + return AR.Reduce(data, func(result bool, value A) bool { + return result && law(value) + }, true) + } +} diff --git a/v2/number/doc.go b/v2/number/doc.go new file mode 100644 index 0000000..b67322a --- /dev/null +++ b/v2/number/doc.go @@ -0,0 +1,275 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +/* +Package number provides algebraic structures and utility functions for numeric types. + +# Overview + +This package defines common algebraic structures (Magma, Semigroup, Monoid) for +numeric types, along with utility functions for arithmetic operations. It works +with any type that satisfies the Number constraint (integers, floats, complex numbers). + +The Number type constraint: + + type Number interface { + constraints.Integer | constraints.Float | constraints.Complex + } + +# Algebraic Structures + +Magma - Binary operations (not necessarily associative): + - MagmaSub[A Number]() - Subtraction magma + - MagmaDiv[A Number]() - Division magma + +Semigroup - Associative binary operations: + - SemigroupSum[A Number]() - Addition semigroup + - SemigroupProduct[A Number]() - Multiplication semigroup + +Monoid - Semigroups with identity elements: + - MonoidSum[A Number]() - Addition monoid (identity: 0) + - MonoidProduct[A Number]() - Multiplication monoid (identity: 1) + +# Basic Usage + +Using monoids for numeric operations: + + // Sum monoid + sumMonoid := number.MonoidSum[int]() + result := sumMonoid.Concat(5, 3) // 8 + empty := sumMonoid.Empty() // 0 + + // Product monoid + prodMonoid := number.MonoidProduct[int]() + result := prodMonoid.Concat(5, 3) // 15 + empty := prodMonoid.Empty() // 1 + +Using semigroups: + + // Addition semigroup + addSemigroup := number.SemigroupSum[int]() + result := addSemigroup.Concat(10, 20) // 30 + + // Multiplication semigroup + mulSemigroup := number.SemigroupProduct[float64]() + result := mulSemigroup.Concat(2.5, 4.0) // 10.0 + +Using magmas (non-associative operations): + + // Subtraction magma + subMagma := number.MagmaSub[int]() + result := subMagma.Concat(10, 3) // 7 + + // Division magma + divMagma := number.MagmaDiv[float64]() + result := divMagma.Concat(10.0, 2.0) // 5.0 + +# Curried Arithmetic Functions + +The package provides curried versions of arithmetic operations for use in +functional composition: + + // Add - curried addition + add5 := number.Add(5) + result := add5(10) // 15 + + // Sub - curried subtraction + sub3 := number.Sub(3) + result := sub3(10) // 7 + + // Mul - curried multiplication + double := number.Mul(2) + result := double(5) // 10 + + // Div - curried division + half := number.Div[float64](2) + result := half(10.0) // 5.0 + +Using with array operations: + + import ( + A "github.com/IBM/fp-go/v2/array" + N "github.com/IBM/fp-go/v2/number" + ) + + numbers := []int{1, 2, 3, 4, 5} + + // Add 10 to each number + result := A.Map(N.Add(10))(numbers) + // result: [11, 12, 13, 14, 15] + + // Double each number + doubled := A.Map(N.Mul(2))(numbers) + // doubled: [2, 4, 6, 8, 10] + +# Utility Functions + +Inc - Increment a number: + + result := number.Inc(5) // 6 + result := number.Inc(2.5) // 3.5 + +Min - Get the minimum of two values: + + result := number.Min(5, 3) // 3 + result := number.Min(2.5, 7.8) // 2.5 + +Max - Get the maximum of two values: + + result := number.Max(5, 3) // 5 + result := number.Max(2.5, 7.8) // 7.8 + +# Working with Different Numeric Types + +Integers: + + sumMonoid := number.MonoidSum[int]() + result := sumMonoid.Concat(100, 200) // 300 + + add10 := number.Add(10) + result := add10(5) // 15 + +Floats: + + sumMonoid := number.MonoidSum[float64]() + result := sumMonoid.Concat(3.14, 2.86) // 6.0 + + half := number.Div[float64](2.0) + result := half(10.5) // 5.25 + +Complex numbers: + + sumMonoid := number.MonoidSum[complex128]() + c1 := complex(1, 2) + c2 := complex(3, 4) + result := sumMonoid.Concat(c1, c2) // (4+6i) + +# Combining with Monoid Operations + +Using with monoid.ConcatAll: + + import ( + M "github.com/IBM/fp-go/v2/monoid" + N "github.com/IBM/fp-go/v2/number" + ) + + // Sum all numbers + sumMonoid := N.MonoidSum[int]() + numbers := []int{1, 2, 3, 4, 5} + total := M.ConcatAll(sumMonoid)(numbers) + // total: 15 + + // Product of all numbers + prodMonoid := N.MonoidProduct[int]() + product := M.ConcatAll(prodMonoid)(numbers) + // product: 120 + +# Practical Examples + +Calculate average: + + import ( + M "github.com/IBM/fp-go/v2/monoid" + N "github.com/IBM/fp-go/v2/number" + ) + + numbers := []float64{10.0, 20.0, 30.0, 40.0, 50.0} + sumMonoid := N.MonoidSum[float64]() + sum := M.ConcatAll(sumMonoid)(numbers) + average := sum / float64(len(numbers)) + // average: 30.0 + +Factorial using product monoid: + + import ( + M "github.com/IBM/fp-go/v2/monoid" + N "github.com/IBM/fp-go/v2/number" + ) + + factorial := func(n int) int { + if n <= 1 { + return 1 + } + numbers := make([]int, n) + for i := range numbers { + numbers[i] = i + 1 + } + prodMonoid := N.MonoidProduct[int]() + return M.ConcatAll(prodMonoid)(numbers) + } + + result := factorial(5) // 120 + +Transform and sum: + + import ( + A "github.com/IBM/fp-go/v2/array" + F "github.com/IBM/fp-go/v2/function" + M "github.com/IBM/fp-go/v2/monoid" + N "github.com/IBM/fp-go/v2/number" + ) + + // Sum of squares + numbers := []int{1, 2, 3, 4, 5} + squares := A.Map(func(x int) int { return x * x })(numbers) + sumMonoid := N.MonoidSum[int]() + sumOfSquares := M.ConcatAll(sumMonoid)(squares) + // sumOfSquares: 55 (1 + 4 + 9 + 16 + 25) + +# Type Safety + +All functions are type-safe and work with any numeric type: + + // Works with int + intSum := number.MonoidSum[int]() + + // Works with float64 + floatSum := number.MonoidSum[float64]() + + // Works with complex128 + complexSum := number.MonoidSum[complex128]() + + // Compile-time error for non-numeric types + // stringSum := number.MonoidSum[string]() // Error! + +# Functions + +Algebraic structures: + - MagmaSub[A Number]() - Subtraction magma + - MagmaDiv[A Number]() - Division magma + - SemigroupSum[A Number]() - Addition semigroup + - SemigroupProduct[A Number]() - Multiplication semigroup + - MonoidSum[A Number]() - Addition monoid (identity: 0) + - MonoidProduct[A Number]() - Multiplication monoid (identity: 1) + +Curried arithmetic: + - Add[T Number](T) func(T) T - Curried addition + - Sub[T Number](T) func(T) T - Curried subtraction + - Mul[T Number](T) func(T) T - Curried multiplication + - Div[T Number](T) func(T) T - Curried division + +Utilities: + - Inc[T Number](T) T - Increment by 1 + - Min[A Ordered](A, A) A - Minimum of two values + - Max[A Ordered](A, A) A - Maximum of two values + +# Related Packages + + - magma: Base algebraic structure (binary operation) + - semigroup: Associative binary operation + - monoid: Semigroup with identity element + - constraints: Type constraints for generics +*/ +package number diff --git a/v2/number/integer/ord.go b/v2/number/integer/ord.go new file mode 100644 index 0000000..3070f13 --- /dev/null +++ b/v2/number/integer/ord.go @@ -0,0 +1,26 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package integer + +import ( + O "github.com/IBM/fp-go/v2/ord" +) + +// Ord is the strict ordering for integers +var Ord = O.FromStrictCompare[int]() + +// Between checks if an integer is between two values +var Between = O.Between[int](Ord) diff --git a/v2/number/integer/string.go b/v2/number/integer/string.go new file mode 100644 index 0000000..83c696c --- /dev/null +++ b/v2/number/integer/string.go @@ -0,0 +1,23 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package integer + +import "strconv" + +var ( + // ToString converts an integer to a string + ToString = strconv.Itoa +) diff --git a/v2/number/magma.go b/v2/number/magma.go new file mode 100644 index 0000000..66891f4 --- /dev/null +++ b/v2/number/magma.go @@ -0,0 +1,32 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package number + +import ( + M "github.com/IBM/fp-go/v2/magma" +) + +func MagmaSub[A Number]() M.Magma[A] { + return M.MakeMagma(func(first A, second A) A { + return first - second + }) +} + +func MagmaDiv[A Number]() M.Magma[A] { + return M.MakeMagma(func(first A, second A) A { + return first / second + }) +} diff --git a/v2/number/magma_test.go b/v2/number/magma_test.go new file mode 100644 index 0000000..6a9e125 --- /dev/null +++ b/v2/number/magma_test.go @@ -0,0 +1,33 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package number + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + M "github.com/IBM/fp-go/v2/magma" +) + +func TestSemigroupIsMagma(t *testing.T) { + sum := SemigroupSum[int]() + + var magma M.Magma[int] = sum + + assert.Equal(t, sum.Concat(1, 2), magma.Concat(1, 2)) + assert.Equal(t, sum.Concat(1, 2), sum.Concat(2, 1)) +} diff --git a/v2/number/monoid.go b/v2/number/monoid.go new file mode 100644 index 0000000..7789fb0 --- /dev/null +++ b/v2/number/monoid.go @@ -0,0 +1,38 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package number + +import ( + M "github.com/IBM/fp-go/v2/monoid" +) + +// MonoidSum is the [Monoid] that adds elements with a zero empty element +func MonoidSum[A Number]() M.Monoid[A] { + s := SemigroupSum[A]() + return M.MakeMonoid( + s.Concat, + 0, + ) +} + +// MonoidProduct is the [Monoid] that multiplies elements with a one empty element +func MonoidProduct[A Number]() M.Monoid[A] { + s := SemigroupProduct[A]() + return M.MakeMonoid( + s.Concat, + 1, + ) +} diff --git a/v2/number/monoid_test.go b/v2/number/monoid_test.go new file mode 100644 index 0000000..bc4802b --- /dev/null +++ b/v2/number/monoid_test.go @@ -0,0 +1,62 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package number + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +// Test monoid laws for MonoidSum +func TestMonoidSumLaws(t *testing.T) { + sumMonoid := MonoidSum[int]() + testData := []int{0, 1, 1000, -1, -100, 42} + + // Test identity laws manually + for _, x := range testData { + assert.Equal(t, x, sumMonoid.Concat(sumMonoid.Empty(), x), + "Left identity failed for %d", x) + assert.Equal(t, x, sumMonoid.Concat(x, sumMonoid.Empty()), + "Right identity failed for %d", x) + } + + // Test associativity + assert.Equal(t, + sumMonoid.Concat(sumMonoid.Concat(1, 2), 3), + sumMonoid.Concat(1, sumMonoid.Concat(2, 3)), + ) +} + +// Test monoid laws for MonoidProduct +func TestMonoidProductLaws(t *testing.T) { + prodMonoid := MonoidProduct[int]() + testData := []int{1, 2, 10, -1, -5, 42} + + // Test identity laws + for _, x := range testData { + assert.Equal(t, x, prodMonoid.Concat(prodMonoid.Empty(), x), + "Left identity failed for %d", x) + assert.Equal(t, x, prodMonoid.Concat(x, prodMonoid.Empty()), + "Right identity failed for %d", x) + } + + // Test associativity + assert.Equal(t, + prodMonoid.Concat(prodMonoid.Concat(2, 3), 4), + prodMonoid.Concat(2, prodMonoid.Concat(3, 4)), + ) +} diff --git a/v2/number/number_test.go b/v2/number/number_test.go new file mode 100644 index 0000000..9ef185a --- /dev/null +++ b/v2/number/number_test.go @@ -0,0 +1,529 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package number + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +// Test MagmaSub +func TestMagmaSub(t *testing.T) { + subMagma := MagmaSub[int]() + + tests := []struct { + name string + first int + second int + expected int + }{ + {"positive numbers", 10, 3, 7}, + {"negative result", 3, 10, -7}, + {"with zero", 5, 0, 5}, + {"zero minus number", 0, 5, -5}, + {"negative numbers", -5, -3, -2}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := subMagma.Concat(tt.first, tt.second) + assert.Equal(t, tt.expected, result) + }) + } +} + +// Test MagmaSub with floats +func TestMagmaSub_Float(t *testing.T) { + subMagma := MagmaSub[float64]() + + result := subMagma.Concat(10.5, 3.2) + assert.InDelta(t, 7.3, result, 0.0001) + + result = subMagma.Concat(3.2, 10.5) + assert.InDelta(t, -7.3, result, 0.0001) +} + +// Test MagmaDiv +func TestMagmaDiv(t *testing.T) { + divMagma := MagmaDiv[int]() + + tests := []struct { + name string + first int + second int + expected int + }{ + {"simple division", 10, 2, 5}, + {"division with remainder", 10, 3, 3}, + {"one divided by itself", 5, 5, 1}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := divMagma.Concat(tt.first, tt.second) + assert.Equal(t, tt.expected, result) + }) + } +} + +// Test MagmaDiv with floats +func TestMagmaDiv_Float(t *testing.T) { + divMagma := MagmaDiv[float64]() + + result := divMagma.Concat(10.0, 2.0) + assert.Equal(t, 5.0, result) + + result = divMagma.Concat(10.0, 3.0) + assert.InDelta(t, 3.333333, result, 0.0001) + + result = divMagma.Concat(1.0, 2.0) + assert.Equal(t, 0.5, result) +} + +// Test SemigroupSum +func TestSemigroupSum(t *testing.T) { + sumSemigroup := SemigroupSum[int]() + + tests := []struct { + name string + first int + second int + expected int + }{ + {"positive numbers", 5, 3, 8}, + {"with zero", 5, 0, 5}, + {"negative numbers", -5, -3, -8}, + {"mixed signs", 10, -3, 7}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := sumSemigroup.Concat(tt.first, tt.second) + assert.Equal(t, tt.expected, result) + }) + } + + // Test associativity + a, b, c := 1, 2, 3 + assert.Equal(t, + sumSemigroup.Concat(sumSemigroup.Concat(a, b), c), + sumSemigroup.Concat(a, sumSemigroup.Concat(b, c)), + ) +} + +// Test SemigroupSum with floats +func TestSemigroupSum_Float(t *testing.T) { + sumSemigroup := SemigroupSum[float64]() + + result := sumSemigroup.Concat(3.14, 2.86) + assert.InDelta(t, 6.0, result, 0.0001) + + result = sumSemigroup.Concat(-1.5, 2.5) + assert.Equal(t, 1.0, result) +} + +// Test SemigroupSum with complex numbers +func TestSemigroupSum_Complex(t *testing.T) { + sumSemigroup := SemigroupSum[complex128]() + + c1 := complex(1, 2) + c2 := complex(3, 4) + result := sumSemigroup.Concat(c1, c2) + expected := complex(4, 6) + assert.Equal(t, expected, result) +} + +// Test SemigroupProduct +func TestSemigroupProduct(t *testing.T) { + prodSemigroup := SemigroupProduct[int]() + + tests := []struct { + name string + first int + second int + expected int + }{ + {"positive numbers", 5, 3, 15}, + {"with one", 5, 1, 5}, + {"with zero", 5, 0, 0}, + {"negative numbers", -5, -3, 15}, + {"mixed signs", 5, -3, -15}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := prodSemigroup.Concat(tt.first, tt.second) + assert.Equal(t, tt.expected, result) + }) + } + + // Test associativity + a, b, c := 2, 3, 4 + assert.Equal(t, + prodSemigroup.Concat(prodSemigroup.Concat(a, b), c), + prodSemigroup.Concat(a, prodSemigroup.Concat(b, c)), + ) +} + +// Test SemigroupProduct with floats +func TestSemigroupProduct_Float(t *testing.T) { + prodSemigroup := SemigroupProduct[float64]() + + result := prodSemigroup.Concat(2.5, 4.0) + assert.Equal(t, 10.0, result) + + result = prodSemigroup.Concat(0.5, 10.0) + assert.Equal(t, 5.0, result) +} + +// Test MonoidSum +func TestMonoidSum(t *testing.T) { + sumMonoid := MonoidSum[int]() + + // Test concat + assert.Equal(t, 8, sumMonoid.Concat(5, 3)) + assert.Equal(t, 0, sumMonoid.Concat(5, -5)) + + // Test empty + assert.Equal(t, 0, sumMonoid.Empty()) + + // Test identity laws + testValues := []int{0, 1, -1, 5, 10, -10, 100} + for _, x := range testValues { + // Left identity: Empty() + x = x + assert.Equal(t, x, sumMonoid.Concat(sumMonoid.Empty(), x), + "Left identity failed for %d", x) + + // Right identity: x + Empty() = x + assert.Equal(t, x, sumMonoid.Concat(x, sumMonoid.Empty()), + "Right identity failed for %d", x) + } + + // Test associativity + assert.Equal(t, + sumMonoid.Concat(sumMonoid.Concat(1, 2), 3), + sumMonoid.Concat(1, sumMonoid.Concat(2, 3)), + ) +} + +// Test MonoidSum with floats +func TestMonoidSum_Float(t *testing.T) { + sumMonoid := MonoidSum[float64]() + + assert.InDelta(t, 6.0, sumMonoid.Concat(3.14, 2.86), 0.0001) + assert.Equal(t, 0.0, sumMonoid.Empty()) + + // Test identity + x := 5.5 + assert.Equal(t, x, sumMonoid.Concat(sumMonoid.Empty(), x)) + assert.Equal(t, x, sumMonoid.Concat(x, sumMonoid.Empty())) +} + +// Test MonoidProduct +func TestMonoidProduct(t *testing.T) { + prodMonoid := MonoidProduct[int]() + + // Test concat + assert.Equal(t, 15, prodMonoid.Concat(5, 3)) + assert.Equal(t, 0, prodMonoid.Concat(5, 0)) + + // Test empty + assert.Equal(t, 1, prodMonoid.Empty()) + + // Test identity laws + testValues := []int{1, 2, 3, 5, 10} + for _, x := range testValues { + // Left identity: Empty() * x = x + assert.Equal(t, x, prodMonoid.Concat(prodMonoid.Empty(), x), + "Left identity failed for %d", x) + + // Right identity: x * Empty() = x + assert.Equal(t, x, prodMonoid.Concat(x, prodMonoid.Empty()), + "Right identity failed for %d", x) + } + + // Test associativity + assert.Equal(t, + prodMonoid.Concat(prodMonoid.Concat(2, 3), 4), + prodMonoid.Concat(2, prodMonoid.Concat(3, 4)), + ) +} + +// Test MonoidProduct with floats +func TestMonoidProduct_Float(t *testing.T) { + prodMonoid := MonoidProduct[float64]() + + assert.Equal(t, 10.0, prodMonoid.Concat(2.5, 4.0)) + assert.Equal(t, 1.0, prodMonoid.Empty()) + + // Test identity + x := 5.5 + assert.Equal(t, x, prodMonoid.Concat(prodMonoid.Empty(), x)) + assert.Equal(t, x, prodMonoid.Concat(x, prodMonoid.Empty())) +} + +// Test Add curried function +func TestAdd(t *testing.T) { + add5 := Add(5) + + tests := []struct { + name string + input int + expected int + }{ + {"positive", 10, 15}, + {"zero", 0, 5}, + {"negative", -3, 2}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := add5(tt.input) + assert.Equal(t, tt.expected, result) + }) + } +} + +// Test Add with floats +func TestAdd_Float(t *testing.T) { + add2_5 := Add(2.5) + + assert.Equal(t, 7.5, add2_5(5.0)) + assert.Equal(t, 2.5, add2_5(0.0)) + assert.InDelta(t, 5.64, add2_5(3.14), 0.0001) +} + +// Test Sub curried function +func TestSub(t *testing.T) { + sub3 := Sub(3) + + tests := []struct { + name string + input int + expected int + }{ + {"positive result", 10, 7}, + {"zero result", 3, 0}, + {"negative result", 1, -2}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := sub3(tt.input) + assert.Equal(t, tt.expected, result) + }) + } +} + +// Test Sub with floats +func TestSub_Float(t *testing.T) { + sub2_5 := Sub(2.5) + + assert.Equal(t, 2.5, sub2_5(5.0)) + assert.Equal(t, -2.5, sub2_5(0.0)) + assert.InDelta(t, 0.64, sub2_5(3.14), 0.0001) +} + +// Test Mul curried function +func TestMul(t *testing.T) { + double := Mul(2) + + tests := []struct { + name string + input int + expected int + }{ + {"positive", 5, 10}, + {"zero", 0, 0}, + {"negative", -3, -6}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := double(tt.input) + assert.Equal(t, tt.expected, result) + }) + } +} + +// Test Mul with floats +func TestMul_Float(t *testing.T) { + triple := Mul(3.0) + + assert.Equal(t, 15.0, triple(5.0)) + assert.Equal(t, 0.0, triple(0.0)) + assert.InDelta(t, 9.42, triple(3.14), 0.0001) +} + +// Test Div curried function +func TestDiv(t *testing.T) { + divBy2 := Div(2) + + tests := []struct { + name string + input int + expected int + }{ + {"even number", 10, 5}, + {"odd number", 9, 4}, + {"zero", 0, 0}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := divBy2(tt.input) + assert.Equal(t, tt.expected, result) + }) + } +} + +// Test Div with floats +func TestDiv_Float(t *testing.T) { + half := Div[float64](2.0) + + assert.Equal(t, 5.0, half(10.0)) + assert.Equal(t, 2.5, half(5.0)) + assert.InDelta(t, 1.57, half(3.14), 0.0001) +} + +// Test Inc function +func TestInc(t *testing.T) { + tests := []struct { + name string + input int + expected int + }{ + {"positive", 5, 6}, + {"zero", 0, 1}, + {"negative", -1, 0}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := Inc(tt.input) + assert.Equal(t, tt.expected, result) + }) + } +} + +// Test Inc with floats +func TestInc_Float(t *testing.T) { + assert.Equal(t, 6.5, Inc(5.5)) + assert.Equal(t, 1.0, Inc(0.0)) + assert.InDelta(t, 4.14, Inc(3.14), 0.0001) +} + +// Test Min function +func TestMin(t *testing.T) { + tests := []struct { + name string + a int + b int + expected int + }{ + {"a smaller", 3, 5, 3}, + {"b smaller", 5, 3, 3}, + {"equal", 5, 5, 5}, + {"negative", -5, -3, -5}, + {"mixed signs", -5, 3, -5}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := Min(tt.a, tt.b) + assert.Equal(t, tt.expected, result) + }) + } +} + +// Test Min with floats +func TestMin_Float(t *testing.T) { + assert.Equal(t, 2.5, Min(2.5, 7.8)) + assert.Equal(t, 2.5, Min(7.8, 2.5)) + assert.Equal(t, 5.5, Min(5.5, 5.5)) + assert.Equal(t, -3.14, Min(-3.14, 2.71)) +} + +// Test Max function +func TestMax(t *testing.T) { + tests := []struct { + name string + a int + b int + expected int + }{ + {"a larger", 5, 3, 5}, + {"b larger", 3, 5, 5}, + {"equal", 5, 5, 5}, + {"negative", -5, -3, -3}, + {"mixed signs", -5, 3, 3}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := Max(tt.a, tt.b) + assert.Equal(t, tt.expected, result) + }) + } +} + +// Test Max with floats +func TestMax_Float(t *testing.T) { + assert.Equal(t, 7.8, Max(2.5, 7.8)) + assert.Equal(t, 7.8, Max(7.8, 2.5)) + assert.Equal(t, 5.5, Max(5.5, 5.5)) + assert.Equal(t, 2.71, Max(-3.14, 2.71)) +} + +// Benchmark tests +func BenchmarkMonoidSum(b *testing.B) { + sumMonoid := MonoidSum[int]() + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = sumMonoid.Concat(i, i+1) + } +} + +func BenchmarkMonoidProduct(b *testing.B) { + prodMonoid := MonoidProduct[int]() + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = prodMonoid.Concat(i+1, i+2) + } +} + +func BenchmarkAdd(b *testing.B) { + add5 := Add(5) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = add5(i) + } +} + +func BenchmarkMin(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = Min(i, i+1) + } +} + +func BenchmarkMax(b *testing.B) { + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = Max(i, i+1) + } +} diff --git a/v2/number/semigroup.go b/v2/number/semigroup.go new file mode 100644 index 0000000..5e73691 --- /dev/null +++ b/v2/number/semigroup.go @@ -0,0 +1,32 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package number + +import ( + S "github.com/IBM/fp-go/v2/semigroup" +) + +func SemigroupSum[A Number]() S.Semigroup[A] { + return S.MakeSemigroup(func(first A, second A) A { + return first + second + }) +} + +func SemigroupProduct[A Number]() S.Semigroup[A] { + return S.MakeSemigroup(func(first A, second A) A { + return first * second + }) +} diff --git a/v2/number/utils.go b/v2/number/utils.go new file mode 100644 index 0000000..e64e027 --- /dev/null +++ b/v2/number/utils.go @@ -0,0 +1,73 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package number + +import ( + C "github.com/IBM/fp-go/v2/constraints" +) + +type Number interface { + C.Integer | C.Float | C.Complex +} + +// Add is a curried function used to add two numbers +func Add[T Number](right T) func(T) T { + return func(left T) T { + return left + right + } +} + +// Sub is a curried function used to subtract two numbers +func Sub[T Number](right T) func(T) T { + return func(left T) T { + return left - right + } +} + +// Mul is a curried function used to multiply two numbers +func Mul[T Number](right T) func(T) T { + return func(left T) T { + return left * right + } +} + +// Div is a curried function used to divide two numbers +func Div[T Number](right T) func(T) T { + return func(left T) T { + return left / right + } +} + +// Inc is a function that increments a number +func Inc[T Number](value T) T { + return value + 1 +} + +// Min takes the minimum of two values. If they are considered equal, the first argument is chosen +func Min[A C.Ordered](a, b A) A { + if a < b { + return a + } + return b +} + +// Max takes the maximum of two values. If they are considered equal, the first argument is chosen +func Max[A C.Ordered](a, b A) A { + if a > b { + return a + } + return b +} diff --git a/v2/optics/README.md b/v2/optics/README.md new file mode 100644 index 0000000..71b4e96 --- /dev/null +++ b/v2/optics/README.md @@ -0,0 +1,4 @@ +# Optics + +Refer to [Introduction to optics: lenses and prisms](https://medium.com/@gcanti/introduction-to-optics-lenses-and-prisms-3230e73bfcfe) for an introduction about functional optics. + diff --git a/v2/optics/doc.go b/v2/optics/doc.go new file mode 100644 index 0000000..6a9c5f4 --- /dev/null +++ b/v2/optics/doc.go @@ -0,0 +1,352 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +/* +Package optics provides functional optics for composable data access and manipulation. + +# Overview + +Optics are first-class, composable references to parts of data structures. They provide +a uniform interface for reading, writing, and transforming nested immutable data without +verbose boilerplate code. + +The optics package family includes several types of optics, each suited for different +data structure patterns: + + - Lens: Focus on a field within a product type (struct) + - Prism: Focus on a variant within a sum type (union/Either) + - Iso: Bidirectional transformation between equivalent types + - Optional: Focus on a value that may not exist + - Traversal: Focus on multiple values simultaneously + +# Why Optics? + +Working with deeply nested immutable data structures in Go can be verbose: + + // Without optics - updating nested data + updated := Person{ + Name: person.Name, + Age: person.Age, + Address: Address{ + Street: person.Address.Street, + City: "New York", // Only this changed! + Zip: person.Address.Zip, + }, + } + +With optics, this becomes: + + // With optics - clean and composable + updated := cityLens.Set("New York")(person) + +# Core Optics Types + +## Lens - Product Types (Structs) + +A Lens focuses on a single field within a struct. It provides get and set operations +that maintain immutability. + + type Person struct { + Name string + Age int + } + + nameLens := lens.MakeLens( + func(p Person) string { return p.Name }, + func(p Person, name string) Person { + p.Name = name + return p + }, + ) + + person := Person{Name: "Alice", Age: 30} + name := nameLens.Get(person) // "Alice" + updated := nameLens.Set("Bob")(person) // Person{Name: "Bob", Age: 30} + +**Use lenses when:** + - Working with struct fields + - The field always exists + - You need both read and write access + +## Prism - Sum Types (Variants) + +A Prism focuses on one variant of a sum type. It provides optional get (the variant +may not match) and definite set operations. + + type Result interface{ isResult() } + type Success struct{ Value int } + type Failure struct{ Error string } + + successPrism := prism.MakePrism( + func(r Result) option.Option[int] { + if s, ok := r.(Success); ok { + return option.Some(s.Value) + } + return option.None[int]() + }, + func(v int) Result { return Success{Value: v} }, + ) + + result := Success{Value: 42} + value := successPrism.GetOption(result) // Some(42) + +**Use prisms when:** + - Working with sum types (Either, Result, etc.) + - The value may not be the expected variant + - You need to match on specific cases + +## Iso - Isomorphisms + +An Iso represents a bidirectional transformation between two equivalent types with +no information loss. + + celsiusToFahrenheit := iso.MakeIso( + func(c float64) float64 { return c*9/5 + 32 }, + func(f float64) float64 { return (f - 32) * 5 / 9 }, + ) + + fahrenheit := celsiusToFahrenheit.Get(20.0) // 68.0 + celsius := celsiusToFahrenheit.ReverseGet(68.0) // 20.0 + +**Use isos when:** + - Converting between equivalent representations + - Wrapping/unwrapping newtypes + - Encoding/decoding data + +## Optional - Maybe Values + +An Optional focuses on a value that may or may not exist, similar to Option[A]. + +**Use optionals when:** + - Working with nullable fields + - The value may be absent + - You need to handle the None case + +## Traversal - Multiple Values + +A Traversal focuses on multiple values simultaneously, allowing batch operations. + +**Use traversals when:** + - Working with collections + - Updating multiple fields at once + - Applying transformations to all matching elements + +# Composition + +The real power of optics comes from composition. Optics of the same or compatible +types can be composed to create more complex accessors: + + type Company struct { + Name string + Address Address + } + + type Address struct { + Street string + City string + } + + // Individual lenses + addressLens := lens.MakeLens( + func(c Company) Address { return c.Address }, + func(c Company, a Address) Company { + c.Address = a + return c + }, + ) + + cityLens := lens.MakeLens( + func(a Address) string { return a.City }, + func(a Address, city string) Address { + a.City = city + return a + }, + ) + + // Compose to access city directly from company + companyCityLens := F.Pipe1( + addressLens, + lens.Compose[Company](cityLens), + ) + + company := Company{ + Name: "Acme Corp", + Address: Address{Street: "Main St", City: "NYC"}, + } + + city := companyCityLens.Get(company) // "NYC" + updated := companyCityLens.Set("Boston")(company) + +# Optics Hierarchy + +Optics form a hierarchy where more specific optics can be converted to more general ones: + + Iso[S, A] + ↓ + Lens[S, A] + ↓ + Optional[S, A] + ↓ + Traversal[S, A] + + Prism[S, A] + ↓ + Optional[S, A] + ↓ + Traversal[S, A] + +This means: + - Every Iso is a Lens + - Every Lens is an Optional + - Every Prism is an Optional + - Every Optional is a Traversal + +# Laws + +Each optic type must satisfy specific laws to ensure correct behavior: + +**Lens Laws:** + 1. GetSet: lens.Set(lens.Get(s))(s) == s + 2. SetGet: lens.Get(lens.Set(a)(s)) == a + 3. SetSet: lens.Set(a2)(lens.Set(a1)(s)) == lens.Set(a2)(s) + +**Prism Laws:** + 1. GetOptionReverseGet: prism.GetOption(prism.ReverseGet(a)) == Some(a) + 2. ReverseGetGetOption: if GetOption(s) == Some(a), then ReverseGet(a) == s + +**Iso Laws:** + 1. RoundTrip1: iso.ReverseGet(iso.Get(s)) == s + 2. RoundTrip2: iso.Get(iso.ReverseGet(a)) == a + +# Real-World Example: Configuration Management + + type DatabaseConfig struct { + Host string + Port int + Username string + Password string + } + + type CacheConfig struct { + TTL int + MaxSize int + } + + type AppConfig struct { + Database *DatabaseConfig + Cache *CacheConfig + Debug bool + } + + // Create lenses for nested access + dbLens := lens.FromNillable(lens.MakeLens( + func(c AppConfig) *DatabaseConfig { return c.Database }, + func(c AppConfig, db *DatabaseConfig) AppConfig { + c.Database = db + return c + }, + )) + + dbHostLens := lens.MakeLensRef( + func(db *DatabaseConfig) string { return db.Host }, + func(db *DatabaseConfig, host string) *DatabaseConfig { + db.Host = host + return db + }, + ) + + defaultDB := &DatabaseConfig{ + Host: "localhost", + Port: 5432, + Username: "admin", + Password: "", + } + + // Compose to access database host from app config + appDbHostLens := F.Pipe1( + dbLens, + lens.ComposeOption[AppConfig, string](defaultDB)(dbHostLens), + ) + + config := AppConfig{Database: nil, Debug: true} + + // Get returns None when database is not configured + host := appDbHostLens.Get(config) // None[string] + + // Set creates database with default values + updated := appDbHostLens.Set(option.Some("prod.example.com"))(config) + // updated.Database.Host == "prod.example.com" + // updated.Database.Port == 5432 (from default) + +# Package Structure + +The optics package is organized into subpackages: + + - optics/lens: Lenses for product types + - optics/prism: Prisms for sum types + - optics/iso: Isomorphisms for equivalent types + - optics/optional: Optional optics for maybe values + - optics/traversal: Traversals for multiple values + +Each subpackage may have additional specialized subpackages for common patterns: + - array: Optics for array/slice operations + - either: Optics for Either types + - option: Optics for Option types + - record: Optics for record/map types + +# Performance Considerations + +Optics are designed to be efficient: + - No reflection - all operations are type-safe at compile time + - Minimal allocations - optics themselves are lightweight + - Composition is efficient - creates function closures + - Immutability ensures thread safety + +For performance-critical code: + - Cache composed optics rather than recomposing + - Use pointer-based lenses (MakeLensRef) for large structs + - Consider batch operations with traversals + +# Type Safety + +All optics are fully type-safe: + - Compile-time type checking + - No runtime type assertions + - Generic type parameters ensure correctness + - Composition maintains type relationships + +# Getting Started + +1. Choose the right optic for your data structure +2. Create basic optics for your types +3. Compose optics for nested access +4. Use Modify for transformations +5. Leverage the optics hierarchy when needed + +# Further Reading + +For detailed documentation on each optic type, see: + - github.com/IBM/fp-go/v2/optics/lens + - github.com/IBM/fp-go/v2/optics/prism + - github.com/IBM/fp-go/v2/optics/iso + - github.com/IBM/fp-go/v2/optics/optional + - github.com/IBM/fp-go/v2/optics/traversal + +For related functional programming concepts: + - github.com/IBM/fp-go/v2/option: Optional values + - github.com/IBM/fp-go/v2/either: Sum types + - github.com/IBM/fp-go/v2/function: Function composition +*/ +package optics diff --git a/v2/optics/iso/doc.go b/v2/optics/iso/doc.go new file mode 100644 index 0000000..abad8ea --- /dev/null +++ b/v2/optics/iso/doc.go @@ -0,0 +1,316 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +/* +Package iso provides isomorphisms - bidirectional transformations between types without loss of information. + +# Overview + +An Isomorphism (Iso) is an optic that converts elements of type S into elements of type A +and back again without any loss of information. Unlike lenses which focus on a part of a +structure, isomorphisms represent a complete, reversible transformation between two types. + +Isomorphisms are useful for: + - Converting between equivalent representations (e.g., Celsius ↔ Fahrenheit) + - Wrapping and unwrapping newtypes + - Encoding and decoding data formats + - Normalizing data structures + +# Mathematical Foundation + +An Iso[S, A] consists of two functions: + - Get: S → A (convert from S to A) + - ReverseGet: A → S (convert from A back to S) + +Isomorphisms must satisfy the round-trip laws: + 1. ReverseGet(Get(s)) == s (for all s: S) + 2. Get(ReverseGet(a)) == a (for all a: A) + +These laws ensure that the transformation is truly reversible with no information loss. + +# Basic Usage + +Creating an isomorphism between Celsius and Fahrenheit: + + celsiusToFahrenheit := iso.MakeIso( + func(c float64) float64 { return c*9/5 + 32 }, + func(f float64) float64 { return (f - 32) * 5 / 9 }, + ) + + // Convert Celsius to Fahrenheit + fahrenheit := celsiusToFahrenheit.Get(20.0) // 68.0 + + // Convert back to Celsius + celsius := celsiusToFahrenheit.ReverseGet(68.0) // 20.0 + +# Identity Isomorphism + +The identity isomorphism represents no transformation: + + idIso := iso.Id[int]() + + value := idIso.Get(42) // 42 + same := idIso.ReverseGet(42) // 42 + +# Composing Isomorphisms + +Isomorphisms can be composed to create more complex transformations: + + metersToKm := iso.MakeIso( + func(m float64) float64 { return m / 1000 }, + func(km float64) float64 { return km * 1000 }, + ) + + kmToMiles := iso.MakeIso( + func(km float64) float64 { return km * 0.621371 }, + func(mi float64) float64 { return mi / 0.621371 }, + ) + + // Compose: meters → kilometers → miles + metersToMiles := F.Pipe1( + metersToKm, + iso.Compose[float64](kmToMiles), + ) + + miles := metersToMiles.Get(5000) // ~3.11 miles + meters := metersToMiles.ReverseGet(3.11) // ~5000 meters + +# Reversing Isomorphisms + +Any isomorphism can be reversed to swap the direction: + + fahrenheitToCelsius := iso.Reverse(celsiusToFahrenheit) + + celsius := fahrenheitToCelsius.Get(68.0) // 20.0 + fahrenheit := fahrenheitToCelsius.ReverseGet(20.0) // 68.0 + +# Modifying Through Isomorphisms + +Apply transformations in the target space and convert back: + + type Meters float64 + type Kilometers float64 + + mToKm := iso.MakeIso( + func(m Meters) Kilometers { return Kilometers(m / 1000) }, + func(km Kilometers) Meters { return Meters(km * 1000) }, + ) + + // Double the distance in kilometers, result in meters + doubled := iso.Modify[Meters](func(km Kilometers) Kilometers { + return km * 2 + })(mToKm)(Meters(5000)) + // Result: 10000 meters + +# Wrapping and Unwrapping + +Convenient functions for working with newtypes: + + type UserId int + type User struct { + id UserId + } + + userIdIso := iso.MakeIso( + func(id UserId) int { return int(id) }, + func(i int) UserId { return UserId(i) }, + ) + + // Unwrap (Get) + rawId := iso.Unwrap[int](UserId(42))(userIdIso) // 42 + // Also available as: iso.To[int](UserId(42))(userIdIso) + + // Wrap (ReverseGet) + userId := iso.Wrap[UserId](42)(userIdIso) // UserId(42) + // Also available as: iso.From[UserId](42)(userIdIso) + +# Bidirectional Mapping + +Transform both directions of an isomorphism: + + type Celsius float64 + type Kelvin float64 + + celsiusIso := iso.Id[Celsius]() + + // Create isomorphism to Kelvin + celsiusToKelvin := F.Pipe1( + celsiusIso, + iso.IMap( + func(c Celsius) Kelvin { return Kelvin(c + 273.15) }, + func(k Kelvin) Celsius { return Celsius(k - 273.15) }, + ), + ) + + kelvin := celsiusToKelvin.Get(Celsius(20)) // 293.15 K + celsius := celsiusToKelvin.ReverseGet(Kelvin(293.15)) // 20°C + +# Real-World Example: Data Encoding + + type JSON string + type User struct { + Name string + Age int + } + + userJsonIso := iso.MakeIso( + func(u User) JSON { + data, _ := json.Marshal(u) + return JSON(data) + }, + func(j JSON) User { + var u User + json.Unmarshal([]byte(j), &u) + return u + }, + ) + + user := User{Name: "Alice", Age: 30} + + // Encode to JSON + jsonData := userJsonIso.Get(user) + // `{"Name":"Alice","Age":30}` + + // Decode from JSON + decoded := userJsonIso.ReverseGet(jsonData) + // User{Name: "Alice", Age: 30} + +# Real-World Example: Unit Conversions + + type Distance float64 + type DistanceUnit int + + const ( + Meters DistanceUnit = iota + Kilometers + Miles + ) + + type MeasuredDistance struct { + Value Distance + Unit DistanceUnit + } + + // Normalize all distances to meters + normalizeIso := iso.MakeIso( + func(md MeasuredDistance) Distance { + switch md.Unit { + case Kilometers: + return md.Value * 1000 + case Miles: + return md.Value * 1609.34 + default: + return md.Value + } + }, + func(d Distance) MeasuredDistance { + return MeasuredDistance{Value: d, Unit: Meters} + }, + ) + + // Convert 5 km to meters + meters := normalizeIso.Get(MeasuredDistance{ + Value: 5, + Unit: Kilometers, + }) // 5000 meters + + // Convert back (always to meters) + measured := normalizeIso.ReverseGet(5000) + // MeasuredDistance{Value: 5000, Unit: Meters} + +# Real-World Example: Newtype Pattern + + type Email string + type ValidatedEmail struct { + value Email + } + + func validateEmail(s string) (ValidatedEmail, error) { + if !strings.Contains(s, "@") { + return ValidatedEmail{}, errors.New("invalid email") + } + return ValidatedEmail{value: Email(s)}, nil + } + + // Note: This iso assumes validation has already occurred + emailIso := iso.MakeIso( + func(ve ValidatedEmail) Email { return ve.value }, + func(e Email) ValidatedEmail { return ValidatedEmail{value: e} }, + ) + + validated := ValidatedEmail{value: "user@example.com"} + + // Extract raw email + raw := emailIso.Get(validated) // "user@example.com" + + // Wrap back (assumes valid) + wrapped := emailIso.ReverseGet(Email("admin@example.com")) + +# Isomorphisms vs Lenses + +While both are optics, they serve different purposes: + +**Isomorphisms:** + - Represent complete, reversible transformations + - No information loss + - Both directions are equally important + - Example: Celsius ↔ Fahrenheit + +**Lenses:** + - Focus on a part of a larger structure + - Information loss when setting (other fields unchanged) + - Asymmetric (get vs set) + - Example: Person → Name + +# Performance Considerations + +Isomorphisms are lightweight and have minimal overhead: + - No allocations for the iso structure itself + - Performance depends on the Get and ReverseGet functions + - Composition creates new function closures but is still efficient + - Consider caching results if transformations are expensive + +# Type Safety + +Isomorphisms are fully type-safe: + - The compiler ensures Get and ReverseGet have compatible types + - Composition maintains type relationships + - No runtime type assertions needed + +# Function Reference + +Core Functions: + - MakeIso: Create an isomorphism from two functions + - Id: Create an identity isomorphism + - Compose: Compose two isomorphisms + - Reverse: Reverse the direction of an isomorphism + +Transformation: + - Modify: Apply a transformation in the target space + - IMap: Bidirectionally map an isomorphism + +Convenience Functions: + - Unwrap/To: Extract the target value (Get) + - Wrap/From: Wrap into the source value (ReverseGet) + +# Related Packages + + - github.com/IBM/fp-go/v2/optics/lens: Lenses for focusing on parts of structures + - github.com/IBM/fp-go/v2/optics/prism: Prisms for sum types + - github.com/IBM/fp-go/v2/optics/optional: Optional optics + - github.com/IBM/fp-go/v2/function: Function composition utilities + - github.com/IBM/fp-go/v2/endomorphism: Endomorphisms (A → A functions) +*/ +package iso diff --git a/v2/optics/iso/iso.go b/v2/optics/iso/iso.go new file mode 100644 index 0000000..5a2b4e5 --- /dev/null +++ b/v2/optics/iso/iso.go @@ -0,0 +1,106 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +// Iso is an optic which converts elements of type `S` into elements of type `A` without loss. +package iso + +import ( + EM "github.com/IBM/fp-go/v2/endomorphism" + F "github.com/IBM/fp-go/v2/function" +) + +type Iso[S, A any] struct { + Get func(s S) A + ReverseGet func(a A) S +} + +func MakeIso[S, A any](get func(S) A, reverse func(A) S) Iso[S, A] { + return Iso[S, A]{Get: get, ReverseGet: reverse} +} + +// Id returns an iso implementing the identity operation +func Id[S any]() Iso[S, S] { + return MakeIso(F.Identity[S], F.Identity[S]) +} + +// Compose combines an ISO with another ISO +func Compose[S, A, B any](ab Iso[A, B]) func(Iso[S, A]) Iso[S, B] { + return func(sa Iso[S, A]) Iso[S, B] { + return MakeIso( + F.Flow2(sa.Get, ab.Get), + F.Flow2(ab.ReverseGet, sa.ReverseGet), + ) + } +} + +// Reverse changes the order of parameters for an iso +func Reverse[S, A any](sa Iso[S, A]) Iso[A, S] { + return MakeIso( + sa.ReverseGet, + sa.Get, + ) +} + +func modify[FCT ~func(A) A, S, A any](f FCT, sa Iso[S, A], s S) S { + return F.Pipe3( + s, + sa.Get, + f, + sa.ReverseGet, + ) +} + +// Modify applies a transformation +func Modify[S any, FCT ~func(A) A, A any](f FCT) func(Iso[S, A]) EM.Endomorphism[S] { + return EM.Curry3(modify[FCT, S, A])(f) +} + +// Wrap wraps the value +func Unwrap[A, S any](s S) func(Iso[S, A]) A { + return func(sa Iso[S, A]) A { + return sa.Get(s) + } +} + +// Unwrap unwraps the value +func Wrap[S, A any](a A) func(Iso[S, A]) S { + return func(sa Iso[S, A]) S { + return sa.ReverseGet(a) + } +} + +// From wraps the value +func To[A, S any](s S) func(Iso[S, A]) A { + return Unwrap[A, S](s) +} + +// To unwraps the value +func From[S, A any](a A) func(Iso[S, A]) S { + return Wrap[S](a) +} + +func imap[S, A, B any](sa Iso[S, A], ab func(A) B, ba func(B) A) Iso[S, B] { + return MakeIso( + F.Flow2(sa.Get, ab), + F.Flow2(ba, sa.ReverseGet), + ) +} + +// IMap implements a bidirectional mapping of the transform +func IMap[S, A, B any](ab func(A) B, ba func(B) A) func(Iso[S, A]) Iso[S, B] { + return func(sa Iso[S, A]) Iso[S, B] { + return imap(sa, ab, ba) + } +} diff --git a/v2/optics/iso/iso_test.go b/v2/optics/iso/iso_test.go new file mode 100644 index 0000000..0e53d9b --- /dev/null +++ b/v2/optics/iso/iso_test.go @@ -0,0 +1,132 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package iso + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +var ( + mToKm = MakeIso( + func(m float32) float32 { + return m / 1000 + }, + func(km float32) float32 { + return km * 1000 + }, + ) + + kmToMile = MakeIso( + func(km float32) float32 { + return km * 0.621371 + }, + func(mile float32) float32 { + return mile / 0.621371 + }, + ) +) + +func TestGet(t *testing.T) { + assert.Equal(t, mToKm.Get(100), float32(0.1)) + assert.Equal(t, Unwrap[float32, float32](float32(100))(mToKm), float32(0.1)) + assert.Equal(t, To[float32, float32](float32(100))(mToKm), float32(0.1)) +} + +func TestReverseGet(t *testing.T) { + assert.Equal(t, mToKm.ReverseGet(1.2), float32(1200)) + assert.Equal(t, Wrap[float32](float32(1.2))(mToKm), float32(1200)) + assert.Equal(t, From[float32](float32(1.2))(mToKm), float32(1200)) +} + +func TestModify(t *testing.T) { + + double := func(x float32) float32 { + return x * 2 + } + + assert.Equal(t, float32(2000), Modify[float32](double)(mToKm)(float32(1000))) +} + +func TestReverse(t *testing.T) { + + double := func(x float32) float32 { + return x * 2 + } + + assert.Equal(t, float32(4000), Modify[float32](double)(Reverse(mToKm))(float32(2000))) +} + +func TestCompose(t *testing.T) { + comp := Compose[float32](mToKm)(kmToMile) + + assert.InDelta(t, 0.93, comp.Get(1500), 0.01) + assert.InDelta(t, 1609.34, comp.ReverseGet(1), 0.01) +} + +func TestId(t *testing.T) { + idIso := Id[int]() + + assert.Equal(t, 42, idIso.Get(42)) + assert.Equal(t, 42, idIso.ReverseGet(42)) +} + +func TestIMap(t *testing.T) { + // Start with meters to kilometers + localMToKm := MakeIso( + func(m float32) float32 { return m / 1000 }, + func(km float32) float32 { return km * 1000 }, + ) + + // Map to a different representation (string) + kmToString := IMap[float32, float32, string]( + func(km float32) string { return fmt.Sprintf("%.2f km", km) }, + func(s string) float32 { + var km float32 + fmt.Sscanf(s, "%f km", &km) + return km + }, + )(localMToKm) + + assert.Equal(t, "1.50 km", kmToString.Get(1500)) + assert.InDelta(t, 2000, kmToString.ReverseGet("2.00 km"), 0.01) +} + +func TestRoundTripLaws(t *testing.T) { + // Test that isomorphisms satisfy round-trip laws + + // Law 1: ReverseGet(Get(s)) == s + meters := float32(1500) + assert.InDelta(t, meters, mToKm.ReverseGet(mToKm.Get(meters)), 0.001) + + // Law 2: Get(ReverseGet(a)) == a + km := float32(1.5) + assert.InDelta(t, km, mToKm.Get(mToKm.ReverseGet(km)), 0.001) +} + +func TestComposeAssociativity(t *testing.T) { + // Test that composition is associative + // Compose left-to-right: (mToKm . kmToMile) + leftCompose := Compose[float32](kmToMile)(mToKm) + + // Compose right-to-left should give same result + rightCompose := Compose[float32](Compose[float32](kmToMile)(mToKm))(Id[float32]()) + + meters := float32(1609.34) + assert.InDelta(t, leftCompose.Get(meters), rightCompose.Get(meters), 0.01) +} diff --git a/v2/optics/iso/lens/lens.go b/v2/optics/iso/lens/lens.go new file mode 100644 index 0000000..745f087 --- /dev/null +++ b/v2/optics/iso/lens/lens.go @@ -0,0 +1,33 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package lens + +import ( + EM "github.com/IBM/fp-go/v2/endomorphism" + F "github.com/IBM/fp-go/v2/function" + I "github.com/IBM/fp-go/v2/optics/iso" + L "github.com/IBM/fp-go/v2/optics/lens" +) + +// IsoAsLens converts an `Iso` to a `Lens` +func IsoAsLens[S, A any](sa I.Iso[S, A]) L.Lens[S, A] { + return L.MakeLensCurried(sa.Get, F.Flow2(sa.ReverseGet, F.Flow2(F.Constant1[S, S], EM.Of[func(S) S]))) +} + +// IsoAsLensRef converts an `Iso` to a `Lens` +func IsoAsLensRef[S, A any](sa I.Iso[*S, A]) L.Lens[*S, A] { + return L.MakeLensRefCurried(sa.Get, F.Flow2(sa.ReverseGet, F.Flow2(F.Constant1[*S, *S], EM.Of[func(*S) *S]))) +} diff --git a/v2/optics/lens/array/generic/head.go b/v2/optics/lens/array/generic/head.go new file mode 100644 index 0000000..4899207 --- /dev/null +++ b/v2/optics/lens/array/generic/head.go @@ -0,0 +1,39 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + AA "github.com/IBM/fp-go/v2/array/generic" + L "github.com/IBM/fp-go/v2/optics/lens" + O "github.com/IBM/fp-go/v2/option" +) + +// AtHead focusses on the head of an array. The setter works as follows +// - if the new value is none, the result will be an empty array +// - if the new value is some and the array is empty, it creates a new array with one element +// - if the new value is some and the array is not empty, it replaces the head +func AtHead[AS []A, A any]() L.Lens[AS, O.Option[A]] { + return L.MakeLens(AA.Head[AS, A], func(as AS, a O.Option[A]) AS { + return O.MonadFold(a, AA.Empty[AS], func(v A) AS { + if AA.IsEmpty(as) { + return AA.Of[AS, A](v) + } + cpy := AA.Copy(as) + cpy[0] = v + return cpy + }) + }) +} diff --git a/v2/optics/lens/array/head.go b/v2/optics/lens/array/head.go new file mode 100644 index 0000000..fa60113 --- /dev/null +++ b/v2/optics/lens/array/head.go @@ -0,0 +1,30 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package array + +import ( + L "github.com/IBM/fp-go/v2/optics/lens" + G "github.com/IBM/fp-go/v2/optics/lens/array/generic" + O "github.com/IBM/fp-go/v2/option" +) + +// AtHead focusses on the head of an array. The setter works as follows +// - if the new value is none, the result will be an empty array +// - if the new value is some and the array is empty, it creates a new array with one element +// - if the new value is some and the array is not empty, it replaces the head +func AtHead[A any]() L.Lens[[]A, O.Option[A]] { + return G.AtHead[[]A]() +} diff --git a/v2/optics/lens/array/head_test.go b/v2/optics/lens/array/head_test.go new file mode 100644 index 0000000..2c7f305 --- /dev/null +++ b/v2/optics/lens/array/head_test.go @@ -0,0 +1,40 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package array + +import ( + "testing" + + A "github.com/IBM/fp-go/v2/array" + "github.com/IBM/fp-go/v2/eq" + LT "github.com/IBM/fp-go/v2/optics/lens/testing" + O "github.com/IBM/fp-go/v2/option" + S "github.com/IBM/fp-go/v2/string" + "github.com/stretchr/testify/assert" +) + +var ( + sEq = eq.FromEquals(S.Eq) +) + +func TestLaws(t *testing.T) { + headLaws := LT.AssertLaws(t, O.Eq(sEq), A.Eq(sEq))(AtHead[string]()) + + assert.True(t, headLaws(A.Empty[string](), O.None[string]())) + assert.True(t, headLaws(A.Empty[string](), O.Of("a"))) + assert.True(t, headLaws(A.From("a", "b"), O.None[string]())) + assert.True(t, headLaws(A.From("a", "b"), O.Of("c"))) +} diff --git a/v2/optics/lens/doc.go b/v2/optics/lens/doc.go new file mode 100644 index 0000000..ed870a5 --- /dev/null +++ b/v2/optics/lens/doc.go @@ -0,0 +1,488 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +/* +Package lens provides functional optics for zooming into and modifying nested data structures. + +# Overview + +A Lens is a first-class reference to a subpart of a data structure. It provides a composable +way to focus on a particular field within a nested structure, allowing you to get and set +values in an immutable, functional manner. + +Lenses are particularly useful when working with deeply nested immutable data structures, +as they eliminate the need for verbose copying and updating code. + +# Mathematical Foundation + +A Lens[S, A] is defined by two operations: + - Get: S → A (extract a value of type A from a structure of type S) + - Set: A → S → S (update the value of type A in structure S, returning a new S) + +Lenses must satisfy the lens laws: + 1. GetSet: lens.Set(lens.Get(s))(s) == s + 2. SetGet: lens.Get(lens.Set(a)(s)) == a + 3. SetSet: lens.Set(a2)(lens.Set(a1)(s)) == lens.Set(a2)(s) + +# Basic Usage + +Creating a lens for a struct field: + + type Person struct { + Name string + Age int + } + + // Create a lens for the Name field + nameLens := lens.MakeLens( + func(p Person) string { return p.Name }, + func(p Person, name string) Person { + p.Name = name + return p + }, + ) + + person := Person{Name: "Alice", Age: 30} + + // Get the name + name := nameLens.Get(person) // "Alice" + + // Set a new name (returns a new Person) + updated := nameLens.Set("Bob")(person) + // person.Name is still "Alice", updated.Name is "Bob" + +# Composing Lenses + +Lenses can be composed to focus on deeply nested fields: + + type Address struct { + Street string + City string + } + + type Person struct { + Name string + Address Address + } + + addressLens := lens.MakeLens( + func(p Person) Address { return p.Address }, + func(p Person, a Address) Person { + p.Address = a + return p + }, + ) + + streetLens := lens.MakeLens( + func(a Address) string { return a.Street }, + func(a Address, s string) Address { + a.Street = s + return a + }, + ) + + // Compose to access street directly from person + personStreetLens := F.Pipe1( + addressLens, + lens.Compose[Person](streetLens), + ) + + person := Person{ + Name: "Alice", + Address: Address{Street: "Main St", City: "NYC"}, + } + + street := personStreetLens.Get(person) // "Main St" + updated := personStreetLens.Set("Oak Ave")(person) + +# Working with Pointers + +For pointer-based structures, use MakeLensRef which handles copying automatically: + + type Person struct { + Name string + Age int + } + + func (p *Person) GetName() string { + return p.Name + } + + func (p *Person) SetName(name string) *Person { + p.Name = name + return p + } + + // MakeLensRef handles pointer copying + nameLens := lens.MakeLensRef( + (*Person).GetName, + (*Person).SetName, + ) + + person := &Person{Name: "Alice", Age: 30} + updated := nameLens.Set("Bob")(person) + // person.Name is still "Alice", updated is a new pointer + +# Optional Values + +Lenses can work with optional values using Option types: + + type Config struct { + Port *int + Timeout *int + } + + portLens := lens.MakeLens( + func(c Config) *int { return c.Port }, + func(c Config, p *int) Config { + c.Port = p + return c + }, + ) + + // Convert to optional lens + optPortLens := lens.FromNillable(portLens) + + config := Config{Port: nil} + + // Get returns None for nil + port := optPortLens.Get(config) // None[*int] + + // Set with Some updates the value + newPort := 8080 + updated := optPortLens.Set(O.Some(&newPort))(config) + + // Set with None removes the value + cleared := optPortLens.Set(O.None[*int]())(updated) + +# Composing with Optional Values + +ComposeOption allows composing a lens returning an optional value with a regular lens: + + type Database struct { + Host string + Port int + } + + type Config struct { + Database *Database + } + + dbLens := lens.FromNillable(lens.MakeLens( + func(c Config) *Database { return c.Database }, + func(c Config, db *Database) Config { + c.Database = db + return c + }, + )) + + portLens := lens.MakeLensRef( + func(db *Database) int { return db.Port }, + func(db *Database, port int) *Database { + db.Port = port + return db + }, + ) + + defaultDB := &Database{Host: "localhost", Port: 5432} + + // Compose with default value + configPortLens := F.Pipe1( + dbLens, + lens.ComposeOption[Config, int](defaultDB)(portLens), + ) + + config := Config{Database: nil} + + // Get returns None when database is nil + port := configPortLens.Get(config) // None[int] + + // Set creates database with default values + updated := configPortLens.Set(O.Some(3306))(config) + // updated.Database.Port == 3306, Host == "localhost" + +# Modifying Values + +Use Modify to transform a value through a lens: + + type Counter struct { + Value int + } + + valueLens := lens.MakeLens( + func(c Counter) int { return c.Value }, + func(c Counter, v int) Counter { + c.Value = v + return c + }, + ) + + counter := Counter{Value: 5} + + // Increment the counter + incremented := F.Pipe2( + counter, + valueLens, + lens.Modify[Counter](func(v int) int { return v + 1 }), + ) + // incremented.Value == 6 + +# Identity Lens + +The identity lens focuses on the entire structure: + + idLens := lens.Id[Person]() + + person := Person{Name: "Alice", Age: 30} + same := idLens.Get(person) // returns person + updated := idLens.Set(Person{Name: "Bob", Age: 25})(person) + +# Isomorphic Mapping + +IMap allows you to transform the focus type of a lens: + + type Celsius float64 + type Fahrenheit float64 + + celsiusToFahrenheit := func(c Celsius) Fahrenheit { + return Fahrenheit(c*9/5 + 32) + } + + fahrenheitToCelsius := func(f Fahrenheit) Celsius { + return Celsius((f - 32) * 5 / 9) + } + + type Weather struct { + Temperature Celsius + } + + tempCelsiusLens := lens.MakeLens( + func(w Weather) Celsius { return w.Temperature }, + func(w Weather, t Celsius) Weather { + w.Temperature = t + return w + }, + ) + + // Create a lens that works with Fahrenheit + tempFahrenheitLens := F.Pipe1( + tempCelsiusLens, + lens.IMap[Weather](celsiusToFahrenheit, fahrenheitToCelsius), + ) + + weather := Weather{Temperature: 20} // 20°C + tempF := tempFahrenheitLens.Get(weather) // 68°F + updated := tempFahrenheitLens.Set(86)(weather) // Set to 86°F (30°C) + +# Nullable Properties + +FromNullableProp creates a lens that provides a default value for nullable properties: + + type Config struct { + Timeout *int + } + + timeoutLens := lens.MakeLens( + func(c Config) *int { return c.Timeout }, + func(c Config, t *int) Config { + c.Timeout = t + return c + }, + ) + + // Provide default value of 30 for nil timeout + safeTimeoutLens := F.Pipe1( + timeoutLens, + lens.FromNullableProp[Config]( + O.FromNillable[int], + func() *int { v := 30; return &v }(), + ), + ) + + config := Config{Timeout: nil} + timeout := safeTimeoutLens.Get(config) // returns pointer to 30 + +# FromOption + +FromOption creates a lens from an Option property, providing a default value: + + type Settings struct { + MaxRetries O.Option[int] + } + + retriesLens := lens.MakeLens( + func(s Settings) O.Option[int] { return s.MaxRetries }, + func(s Settings, r O.Option[int]) Settings { + s.MaxRetries = r + return s + }, + ) + + // Provide default of 3 retries + safeRetriesLens := F.Pipe1( + retriesLens, + lens.FromOption[Settings](3), + ) + + settings := Settings{MaxRetries: O.None[int]()} + retries := safeRetriesLens.Get(settings) // returns 3 + updated := safeRetriesLens.Set(5)(settings) // sets to Some(5) + +# Predicate-Based Lenses + +FromPredicate creates an optional lens based on a predicate: + + type User struct { + Age int + } + + ageLens := lens.MakeLens( + func(u User) int { return u.Age }, + func(u User, age int) User { + u.Age = age + return u + }, + ) + + // Only consider valid ages (18+) + adultAgeLens := F.Pipe1( + ageLens, + lens.FromPredicate[User](func(age int) bool { + return age >= 18 + }, 0), + ) + + user := User{Age: 25} + age := adultAgeLens.Get(user) // Some(25) + + minor := User{Age: 15} + minorAge := adultAgeLens.Get(minor) // None[int] + +# Real-World Example: Configuration Management + + type DatabaseConfig struct { + Host string + Port int + Username string + Password string + } + + type CacheConfig struct { + TTL int + MaxSize int + } + + type AppConfig struct { + Database *DatabaseConfig + Cache *CacheConfig + Debug bool + } + + // Create lenses for each level + dbLens := lens.FromNillable(lens.MakeLens( + func(c AppConfig) *DatabaseConfig { return c.Database }, + func(c AppConfig, db *DatabaseConfig) AppConfig { + c.Database = db + return c + }, + )) + + dbHostLens := lens.MakeLensRef( + func(db *DatabaseConfig) string { return db.Host }, + func(db *DatabaseConfig, host string) *DatabaseConfig { + db.Host = host + return db + }, + ) + + defaultDB := &DatabaseConfig{ + Host: "localhost", + Port: 5432, + Username: "admin", + Password: "", + } + + // Compose to access database host from app config + appDbHostLens := F.Pipe1( + dbLens, + lens.ComposeOption[AppConfig, string](defaultDB)(dbHostLens), + ) + + config := AppConfig{Database: nil, Debug: true} + + // Get returns None when database is not configured + host := appDbHostLens.Get(config) // None[string] + + // Set creates database with default values + updated := appDbHostLens.Set(O.Some("prod.example.com"))(config) + // updated.Database.Host == "prod.example.com" + // updated.Database.Port == 5432 (from default) + +# Performance Considerations + +Lenses create new copies of data structures on each Set operation. For deeply nested +structures, this can be expensive. Consider: + +1. Using pointer-based structures with MakeLensRef for better performance +2. Batching multiple updates using Modify +3. Using specialized lenses for common patterns (arrays, records, etc.) + +# Type Safety + +Lenses are fully type-safe. The compiler ensures that: +- Get returns the correct type +- Set accepts the correct type +- Composed lenses maintain type relationships + +# Function Reference + +Core Lens Creation: + - MakeLens: Create a lens from getter and setter functions + - MakeLensCurried: Create a lens with curried setter + - MakeLensRef: Create a lens for pointer-based structures + - MakeLensRefCurried: Create a lens for pointers with curried setter + - Id: Create an identity lens + - IdRef: Create an identity lens for pointers + +Composition: + - Compose: Compose two lenses + - ComposeRef: Compose lenses for pointer structures + - ComposeOption: Compose lens returning Option with regular lens + - ComposeOptions: Compose two lenses returning Options + +Transformation: + - Modify: Transform a value through a lens + - IMap: Transform the focus type of a lens + +Optional Value Handling: + - FromNillable: Create optional lens from nullable pointer + - FromNillableRef: Create optional lens from nullable pointer (ref version) + - FromNullableProp: Create lens with default for nullable property + - FromNullablePropRef: Create lens with default for nullable property (ref version) + - FromOption: Create lens from Option property with default + - FromOptionRef: Create lens from Option property with default (ref version) + - FromPredicate: Create optional lens based on predicate + - FromPredicateRef: Create optional lens based on predicate (ref version) + +# Related Packages + + - github.com/IBM/fp-go/v2/optics/iso: Isomorphisms (bidirectional transformations) + - github.com/IBM/fp-go/v2/optics/prism: Prisms (focus on sum types) + - github.com/IBM/fp-go/v2/optics/optional: Optional optics + - github.com/IBM/fp-go/v2/optics/traversal: Traversals (focus on multiple values) + - github.com/IBM/fp-go/v2/option: Optional values + - github.com/IBM/fp-go/v2/endomorphism: Endomorphisms (A → A functions) +*/ +package lens diff --git a/v2/optics/lens/either/either.go b/v2/optics/lens/either/either.go new file mode 100644 index 0000000..996e140 --- /dev/null +++ b/v2/optics/lens/either/either.go @@ -0,0 +1,27 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package either + +import ( + ET "github.com/IBM/fp-go/v2/either" + L "github.com/IBM/fp-go/v2/optics/lens" + LG "github.com/IBM/fp-go/v2/optics/lens/generic" + T "github.com/IBM/fp-go/v2/optics/traversal/either" +) + +func AsTraversal[E, S, A any]() func(L.Lens[S, A]) T.Traversal[E, S, A] { + return LG.AsTraversal[T.Traversal[E, S, A]](ET.MonadMap[E, A, S]) +} diff --git a/v2/optics/lens/generic/lens.go b/v2/optics/lens/generic/lens.go new file mode 100644 index 0000000..3c58194 --- /dev/null +++ b/v2/optics/lens/generic/lens.go @@ -0,0 +1,35 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + L "github.com/IBM/fp-go/v2/optics/lens" +) + +// AsTraversal converts a lens to a traversal +func AsTraversal[R ~func(func(A) HKTA) func(S) HKTS, S, A, HKTS, HKTA any]( + fmap func(HKTA, func(A) S) HKTS, +) func(L.Lens[S, A]) R { + return func(sa L.Lens[S, A]) R { + return func(f func(a A) HKTA) func(S) HKTS { + return func(s S) HKTS { + return fmap(f(sa.Get(s)), func(a A) S { + return sa.Set(a)(s) + }) + } + } + } +} diff --git a/v2/optics/lens/iso/iso.go b/v2/optics/lens/iso/iso.go new file mode 100644 index 0000000..17a6c44 --- /dev/null +++ b/v2/optics/lens/iso/iso.go @@ -0,0 +1,44 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package iso + +import ( + F "github.com/IBM/fp-go/v2/function" + I "github.com/IBM/fp-go/v2/optics/iso" + IL "github.com/IBM/fp-go/v2/optics/iso/lens" + L "github.com/IBM/fp-go/v2/optics/lens" + O "github.com/IBM/fp-go/v2/option" +) + +// FromNillable converts a nillable value to an option and back +func FromNillable[T any]() I.Iso[*T, O.Option[T]] { + return I.MakeIso(F.Flow2( + O.FromPredicate(F.IsNonNil[T]), + O.Map(F.Deref[T]), + ), + O.Fold(F.Constant((*T)(nil)), F.Ref[T]), + ) +} + +// Compose converts a Lens to a property of `A` into a lens to a property of type `B` +// the transformation is done via an ISO +func Compose[S, A, B any](ab I.Iso[A, B]) func(sa L.Lens[S, A]) L.Lens[S, B] { + return F.Pipe2( + ab, + IL.IsoAsLens[A, B], + L.Compose[S, A, B], + ) +} diff --git a/v2/optics/lens/iso/iso_test.go b/v2/optics/lens/iso/iso_test.go new file mode 100644 index 0000000..7956236 --- /dev/null +++ b/v2/optics/lens/iso/iso_test.go @@ -0,0 +1,99 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package iso + +import ( + "testing" + + EQT "github.com/IBM/fp-go/v2/eq/testing" + F "github.com/IBM/fp-go/v2/function" + L "github.com/IBM/fp-go/v2/optics/lens" + LT "github.com/IBM/fp-go/v2/optics/lens/testing" + O "github.com/IBM/fp-go/v2/option" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type ( + Inner struct { + Value *int + Foo string + } + + Outer struct { + inner Inner + } +) + +func (outer Outer) GetInner() Inner { + return outer.inner +} + +func (outer Outer) SetInner(inner Inner) Outer { + outer.inner = inner + return outer +} + +func (inner Inner) GetValue() *int { + return inner.Value +} + +func (inner Inner) SetValue(value *int) Inner { + inner.Value = value + return inner +} + +func TestIso(t *testing.T) { + + eqOptInt := O.Eq(EQT.Eq[int]()) + eqOuter := EQT.Eq[Outer]() + + emptyOuter := Outer{} + + // iso + intIso := FromNillable[int]() + + innerFromOuter := L.MakeLens((Outer).GetInner, (Outer).SetInner) + valueFromInner := L.MakeLens((Inner).GetValue, (Inner).SetValue) + + optValueFromInner := F.Pipe1( + valueFromInner, + Compose[Inner](intIso), + ) + + optValueFromOuter := F.Pipe1( + innerFromOuter, + L.Compose[Outer](optValueFromInner), + ) + + // try some access + require.True(t, eqOptInt.Equals(optValueFromOuter.Get(emptyOuter), O.None[int]())) + + updatedOuter := optValueFromOuter.Set(O.Some(1))(emptyOuter) + + require.True(t, eqOptInt.Equals(optValueFromOuter.Get(updatedOuter), O.Some(1))) + secondOuter := optValueFromOuter.Set(O.None[int]())(updatedOuter) + require.True(t, eqOptInt.Equals(optValueFromOuter.Get(secondOuter), O.None[int]())) + + // check if this obeys laws + laws := LT.AssertLaws( + t, + eqOptInt, + eqOuter, + )(optValueFromOuter) + + assert.True(t, laws(emptyOuter, O.Some(2))) +} diff --git a/v2/optics/lens/lens.go b/v2/optics/lens/lens.go new file mode 100644 index 0000000..f149f3b --- /dev/null +++ b/v2/optics/lens/lens.go @@ -0,0 +1,318 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +// Lens is an optic used to zoom inside a product. +package lens + +import ( + EM "github.com/IBM/fp-go/v2/endomorphism" + "github.com/IBM/fp-go/v2/function" + F "github.com/IBM/fp-go/v2/function" + O "github.com/IBM/fp-go/v2/option" +) + +// setCopy wraps a setter for a pointer into a setter that first creates a copy before +// modifying that copy +func setCopy[SET ~func(*S, A) *S, S, A any](setter SET) func(s *S, a A) *S { + return func(s *S, a A) *S { + cpy := *s + return setter(&cpy, a) + } +} + +// setCopyCurried wraps a setter for a pointer into a setter that first creates a copy before +// modifying that copy +func setCopyCurried[SET ~func(A) Endomorphism[*S], S, A any](setter SET) func(a A) Endomorphism[*S] { + return func(a A) Endomorphism[*S] { + seta := setter(a) + return func(s *S) *S { + cpy := *s + return seta(&cpy) + } + } +} + +// MakeLens creates a [Lens] based on a getter and a setter function. Make sure that the setter creates a (shallow) copy of the +// data. This happens automatically if the data is passed by value. For pointers consider to use `MakeLensRef` +// and for other kinds of data structures that are copied by reference make sure the setter creates the copy. +func MakeLens[GET ~func(S) A, SET ~func(S, A) S, S, A any](get GET, set SET) Lens[S, A] { + return MakeLensCurried(get, function.Curry2(F.Swap(set))) +} + +// MakeLensCurried creates a [Lens] based on a getter and a setter function. Make sure that the setter creates a (shallow) copy of the +// data. This happens automatically if the data is passed by value. For pointers consider to use `MakeLensRef` +// and for other kinds of data structures that are copied by reference make sure the setter creates the copy. +func MakeLensCurried[GET ~func(S) A, SET ~func(A) Endomorphism[S], S, A any](get GET, set SET) Lens[S, A] { + return Lens[S, A]{Get: get, Set: set} +} + +// MakeLensRef creates a [Lens] based on a getter and a setter function. The setter passed in does not have to create a shallow +// copy, the implementation wraps the setter into one that copies the pointer before modifying it +// +// Such a [Lens] assumes that property A of S always exists +func MakeLensRef[GET ~func(*S) A, SET func(*S, A) *S, S, A any](get GET, set SET) Lens[*S, A] { + return MakeLens(get, setCopy(set)) +} + +// MakeLensRefCurried creates a [Lens] based on a getter and a setter function. The setter passed in does not have to create a shallow +// copy, the implementation wraps the setter into one that copies the pointer before modifying it +// +// Such a [Lens] assumes that property A of S always exists +func MakeLensRefCurried[S, A any](get func(*S) A, set func(A) Endomorphism[*S]) Lens[*S, A] { + return MakeLensCurried(get, setCopyCurried(set)) +} + +// id returns a [Lens] implementing the identity operation +func id[GET ~func(S) S, SET ~func(S, S) S, S any](creator func(get GET, set SET) Lens[S, S]) Lens[S, S] { + return creator(F.Identity[S], F.Second[S, S]) +} + +// Id returns a [Lens] implementing the identity operation +func Id[S any]() Lens[S, S] { + return id(MakeLens[Endomorphism[S], func(S, S) S]) +} + +// IdRef returns a [Lens] implementing the identity operation +func IdRef[S any]() Lens[*S, *S] { + return id(MakeLensRef[Endomorphism[*S], func(*S, *S) *S]) +} + +// Compose combines two lenses and allows to narrow down the focus to a sub-lens +func compose[GET ~func(S) B, SET ~func(S, B) S, S, A, B any](creator func(get GET, set SET) Lens[S, B], ab Lens[A, B]) func(Lens[S, A]) Lens[S, B] { + abget := ab.Get + abset := ab.Set + return func(sa Lens[S, A]) Lens[S, B] { + saget := sa.Get + saset := sa.Set + return creator( + F.Flow2(saget, abget), + func(s S, b B) S { + return saset(abset(b)(saget(s)))(s) + }, + ) + } +} + +// Compose combines two lenses and allows to narrow down the focus to a sub-lens +func Compose[S, A, B any](ab Lens[A, B]) func(Lens[S, A]) Lens[S, B] { + return compose(MakeLens[func(S) B, func(S, B) S], ab) +} + +// ComposeOption combines a `Lens` that returns an optional value with a `Lens` that returns a definite value +// the getter returns an `Option[B]` because the container `A` could already be an option +// if the setter is invoked with `Some[B]` then the value of `B` will be set, potentially on a default value of `A` if `A` did not exist +// if the setter is invoked with `None[B]` then the container `A` is reset to `None[A]` because this is the only way to remove `B` +func ComposeOption[S, B, A any](defaultA A) func(ab Lens[A, B]) func(Lens[S, O.Option[A]]) Lens[S, O.Option[B]] { + defa := F.Constant(defaultA) + return func(ab Lens[A, B]) func(Lens[S, O.Option[A]]) Lens[S, O.Option[B]] { + foldab := O.Fold(O.None[B], F.Flow2(ab.Get, O.Some[B])) + return func(sa Lens[S, O.Option[A]]) Lens[S, O.Option[B]] { + // set A on S + seta := F.Flow2( + O.Some[A], + sa.Set, + ) + // remove A from S + unseta := F.Nullary2( + O.None[A], + sa.Set, + ) + return MakeLens( + F.Flow2(sa.Get, foldab), + func(s S, ob O.Option[B]) S { + return F.Pipe2( + ob, + O.Fold(unseta, func(b B) Endomorphism[S] { + setbona := F.Flow2( + ab.Set(b), + seta, + ) + return F.Pipe2( + s, + sa.Get, + O.Fold( + F.Nullary2( + defa, + setbona, + ), + setbona, + ), + ) + }), + EM.Ap(s), + ) + }, + ) + } + } +} + +// ComposeOptions combines a `Lens` that returns an optional value with a `Lens` that returns another optional value +// the getter returns `None[B]` if either `A` or `B` is `None` +// if the setter is called with `Some[B]` and `A` exists, 'A' is updated with `B` +// if the setter is called with `Some[B]` and `A` does not exist, the default of 'A' is updated with `B` +// if the setter is called with `None[B]` and `A` does not exist this is the identity operation on 'S' +// if the setter is called with `None[B]` and `A` does exist, 'B' is removed from 'A' +func ComposeOptions[S, B, A any](defaultA A) func(ab Lens[A, O.Option[B]]) func(Lens[S, O.Option[A]]) Lens[S, O.Option[B]] { + defa := F.Constant(defaultA) + noops := EM.Identity[S] + noneb := O.None[B]() + return func(ab Lens[A, O.Option[B]]) func(Lens[S, O.Option[A]]) Lens[S, O.Option[B]] { + unsetb := ab.Set(noneb) + return func(sa Lens[S, O.Option[A]]) Lens[S, O.Option[B]] { + // sets an A onto S + seta := F.Flow2( + O.Some[A], + sa.Set, + ) + return MakeLensCurried( + F.Flow2( + sa.Get, + O.Chain(ab.Get), + ), + func(b O.Option[B]) Endomorphism[S] { + return func(s S) S { + return O.MonadFold(b, func() Endomorphism[S] { + return F.Pipe2( + s, + sa.Get, + O.Fold(noops, F.Flow2(unsetb, seta)), + ) + }, func(b B) Endomorphism[S] { + // sets a B onto an A + setb := F.Flow2( + ab.Set(O.Some(b)), + seta, + ) + return F.Pipe2( + s, + sa.Get, + O.Fold(F.Nullary2(defa, setb), setb), + ) + })(s) + } + }, + ) + } + } +} + +// Compose combines two lenses and allows to narrow down the focus to a sub-lens +func ComposeRef[S, A, B any](ab Lens[A, B]) func(Lens[*S, A]) Lens[*S, B] { + return compose(MakeLensRef[func(*S) B, func(*S, B) *S], ab) +} + +func modify[FCT ~func(A) A, S, A any](f FCT, sa Lens[S, A], s S) S { + return sa.Set(f(sa.Get(s)))(s) +} + +// Modify changes a property of a [Lens] by invoking a transformation function +// if the transformed property has not changes, the method returns the original state +func Modify[S any, FCT ~func(A) A, A any](f FCT) func(Lens[S, A]) Endomorphism[S] { + return function.Curry3(modify[FCT, S, A])(f) +} + +func IMap[E any, AB ~func(A) B, BA ~func(B) A, A, B any](ab AB, ba BA) func(Lens[E, A]) Lens[E, B] { + return func(ea Lens[E, A]) Lens[E, B] { + return Lens[E, B]{Get: F.Flow2(ea.Get, ab), Set: F.Flow2(ba, ea.Set)} + } +} + +// fromPredicate returns a `Lens` for a property accessibly as a getter and setter that can be optional +// if the optional value is set then the nil value will be set instead +func fromPredicate[GET ~func(S) O.Option[A], SET ~func(S, O.Option[A]) S, S, A any](creator func(get GET, set SET) Lens[S, O.Option[A]], pred func(A) bool, nilValue A) func(sa Lens[S, A]) Lens[S, O.Option[A]] { + fromPred := O.FromPredicate(pred) + return func(sa Lens[S, A]) Lens[S, O.Option[A]] { + fold := O.Fold(F.Bind1of1(sa.Set)(nilValue), sa.Set) + return creator(F.Flow2(sa.Get, fromPred), func(s S, a O.Option[A]) S { + return F.Pipe2( + a, + fold, + EM.Ap(s), + ) + }) + } +} + +// FromPredicate returns a `Lens` for a property accessibly as a getter and setter that can be optional +// if the optional value is set then the nil value will be set instead +func FromPredicate[S, A any](pred func(A) bool, nilValue A) func(sa Lens[S, A]) Lens[S, O.Option[A]] { + return fromPredicate(MakeLens[func(S) O.Option[A], func(S, O.Option[A]) S], pred, nilValue) +} + +// FromPredicateRef returns a `Lens` for a property accessibly as a getter and setter that can be optional +// if the optional value is set then the nil value will be set instead +func FromPredicateRef[S, A any](pred func(A) bool, nilValue A) func(sa Lens[*S, A]) Lens[*S, O.Option[A]] { + return fromPredicate(MakeLensRef[func(*S) O.Option[A], func(*S, O.Option[A]) *S], pred, nilValue) +} + +// FromPredicate returns a `Lens` for a property accessibly as a getter and setter that can be optional +// if the optional value is set then the `nil` value will be set instead +func FromNillable[S, A any](sa Lens[S, *A]) Lens[S, O.Option[*A]] { + return FromPredicate[S](F.IsNonNil[A], nil)(sa) +} + +// FromNillableRef returns a `Lens` for a property accessibly as a getter and setter that can be optional +// if the optional value is set then the `nil` value will be set instead +func FromNillableRef[S, A any](sa Lens[*S, *A]) Lens[*S, O.Option[*A]] { + return FromPredicateRef[S](F.IsNonNil[A], nil)(sa) +} + +// fromNullableProp returns a `Lens` from a property that may be optional. The getter returns a default value for these items +func fromNullableProp[GET ~func(S) A, SET ~func(S, A) S, S, A any](creator func(get GET, set SET) Lens[S, A], isNullable func(A) O.Option[A], defaultValue A) func(sa Lens[S, A]) Lens[S, A] { + return func(sa Lens[S, A]) Lens[S, A] { + return creator(F.Flow3( + sa.Get, + isNullable, + O.GetOrElse(F.Constant(defaultValue)), + ), func(s S, a A) S { + return sa.Set(a)(s) + }, + ) + } +} + +// FromNullableProp returns a `Lens` from a property that may be optional. The getter returns a default value for these items +func FromNullableProp[S, A any](isNullable func(A) O.Option[A], defaultValue A) func(sa Lens[S, A]) Lens[S, A] { + return fromNullableProp(MakeLens[func(S) A, func(S, A) S], isNullable, defaultValue) +} + +// FromNullablePropRef returns a `Lens` from a property that may be optional. The getter returns a default value for these items +func FromNullablePropRef[S, A any](isNullable func(A) O.Option[A], defaultValue A) func(sa Lens[*S, A]) Lens[*S, A] { + return fromNullableProp(MakeLensRef[func(*S) A, func(*S, A) *S], isNullable, defaultValue) +} + +// fromFromOption returns a `Lens` from an option property. The getter returns a default value the setter will always set the some option +func fromOption[GET ~func(S) A, SET ~func(S, A) S, S, A any](creator func(get GET, set SET) Lens[S, A], defaultValue A) func(sa Lens[S, O.Option[A]]) Lens[S, A] { + return func(sa Lens[S, O.Option[A]]) Lens[S, A] { + return creator(F.Flow2( + sa.Get, + O.GetOrElse(F.Constant(defaultValue)), + ), func(s S, a A) S { + return sa.Set(O.Some(a))(s) + }, + ) + } +} + +// FromFromOption returns a `Lens` from an option property. The getter returns a default value the setter will always set the some option +func FromOption[S, A any](defaultValue A) func(sa Lens[S, O.Option[A]]) Lens[S, A] { + return fromOption(MakeLens[func(S) A, func(S, A) S], defaultValue) +} + +// FromFromOptionRef returns a `Lens` from an option property. The getter returns a default value the setter will always set the some option +func FromOptionRef[S, A any](defaultValue A) func(sa Lens[*S, O.Option[A]]) Lens[*S, A] { + return fromOption(MakeLensRef[func(*S) A, func(*S, A) *S], defaultValue) +} diff --git a/v2/optics/lens/lens_test.go b/v2/optics/lens/lens_test.go new file mode 100644 index 0000000..d829269 --- /dev/null +++ b/v2/optics/lens/lens_test.go @@ -0,0 +1,398 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package lens + +import ( + "testing" + + F "github.com/IBM/fp-go/v2/function" + O "github.com/IBM/fp-go/v2/option" + "github.com/stretchr/testify/assert" +) + +type ( + Street struct { + num int + name string + } + + Address struct { + city string + street *Street + } + + Inner struct { + Value int + Foo string + } + + InnerOpt struct { + Value *int + Foo *string + } + + Outer struct { + inner *Inner + } + + OuterOpt struct { + inner *InnerOpt + } +) + +func (outer Outer) GetInner() *Inner { + return outer.inner +} + +func (outer Outer) SetInner(inner *Inner) Outer { + outer.inner = inner + return outer +} + +func (outer OuterOpt) GetInnerOpt() *InnerOpt { + return outer.inner +} + +func (outer OuterOpt) SetInnerOpt(inner *InnerOpt) OuterOpt { + outer.inner = inner + return outer +} + +func (inner *Inner) GetValue() int { + return inner.Value +} + +func (inner *Inner) SetValue(value int) *Inner { + inner.Value = value + return inner +} + +func (inner *InnerOpt) GetValue() *int { + return inner.Value +} + +func (inner *InnerOpt) SetValue(value *int) *InnerOpt { + inner.Value = value + return inner +} + +func (street *Street) GetName() string { + return street.name +} + +func (street *Street) SetName(name string) *Street { + street.name = name + return street +} + +func (addr *Address) GetStreet() *Street { + return addr.street +} + +func (addr *Address) SetStreet(s *Street) *Address { + addr.street = s + return addr +} + +var ( + streetLens = MakeLensRef((*Street).GetName, (*Street).SetName) + addrLens = MakeLensRef((*Address).GetStreet, (*Address).SetStreet) + + sampleStreet = Street{num: 220, name: "Schönaicherstr"} + sampleAddress = Address{city: "Böblingen", street: &sampleStreet} +) + +func TestLens(t *testing.T) { + // read the value + assert.Equal(t, sampleStreet.name, streetLens.Get(&sampleStreet)) + // new street + newName := "Böblingerstr" + // update + old := sampleStreet + updated := streetLens.Set(newName)(&sampleStreet) + assert.Equal(t, old, sampleStreet) + // validate the new name + assert.Equal(t, newName, streetLens.Get(updated)) +} + +func TestAddressCompose(t *testing.T) { + // compose + streetName := Compose[*Address](streetLens)(addrLens) + assert.Equal(t, sampleStreet.name, streetName.Get(&sampleAddress)) + // new street + newName := "Böblingerstr" + updated := streetName.Set(newName)(&sampleAddress) + // check that we have not modified the original + assert.Equal(t, sampleStreet.name, streetName.Get(&sampleAddress)) + assert.Equal(t, newName, streetName.Get(updated)) +} + +func TestIMap(t *testing.T) { + + type S struct { + a int + } + + sa := F.Pipe1( + Id[S](), + IMap[S]( + func(s S) int { return s.a }, + func(a int) S { return S{a} }, + ), + ) + + assert.Equal(t, 1, sa.Get(S{1})) + assert.Equal(t, S{2}, sa.Set(2)(S{1})) +} + +func TestPassByValue(t *testing.T) { + + testLens := MakeLens(func(s Street) string { return s.name }, func(s Street, value string) Street { + s.name = value + return s + }) + + s1 := Street{1, "value1"} + s2 := testLens.Set("value2")(s1) + + assert.Equal(t, "value1", s1.name) + assert.Equal(t, "value2", s2.name) +} + +func TestFromNullableProp(t *testing.T) { + // default inner object + defaultInner := &Inner{ + Value: 0, + Foo: "foo", + } + // access to the value + value := MakeLensRef((*Inner).GetValue, (*Inner).SetValue) + // access to inner + inner := FromNullableProp[Outer](O.FromNillable[Inner], defaultInner)(MakeLens(Outer.GetInner, Outer.SetInner)) + // compose + lens := F.Pipe1( + inner, + Compose[Outer](value), + ) + outer1 := Outer{inner: &Inner{Value: 1, Foo: "a"}} + // the checks + assert.Equal(t, Outer{inner: &Inner{Value: 1, Foo: "foo"}}, lens.Set(1)(Outer{})) + assert.Equal(t, 0, lens.Get(Outer{})) + assert.Equal(t, Outer{inner: &Inner{Value: 1, Foo: "foo"}}, lens.Set(1)(Outer{inner: &Inner{Value: 2, Foo: "foo"}})) + assert.Equal(t, 1, lens.Get(Outer{inner: &Inner{Value: 1, Foo: "foo"}})) + assert.Equal(t, outer1, Modify[Outer](F.Identity[int])(lens)(outer1)) +} + +func TestComposeOption(t *testing.T) { + // default inner object + defaultInner := &Inner{ + Value: 0, + Foo: "foo", + } + // access to the value + value := MakeLensRef((*Inner).GetValue, (*Inner).SetValue) + // access to inner + inner := FromNillable(MakeLens(Outer.GetInner, Outer.SetInner)) + // compose lenses + lens := F.Pipe1( + inner, + ComposeOption[Outer, int](defaultInner)(value), + ) + outer1 := Outer{inner: &Inner{Value: 1, Foo: "a"}} + // the checks + assert.Equal(t, Outer{inner: &Inner{Value: 1, Foo: "foo"}}, lens.Set(O.Some(1))(Outer{})) + assert.Equal(t, O.None[int](), lens.Get(Outer{})) + assert.Equal(t, Outer{inner: &Inner{Value: 1, Foo: "foo"}}, lens.Set(O.Some(1))(Outer{inner: &Inner{Value: 2, Foo: "foo"}})) + assert.Equal(t, O.Some(1), lens.Get(Outer{inner: &Inner{Value: 1, Foo: "foo"}})) + assert.Equal(t, outer1, Modify[Outer](F.Identity[O.Option[int]])(lens)(outer1)) +} + +func TestComposeOptions(t *testing.T) { + // default inner object + defaultValue1 := 1 + defaultFoo1 := "foo1" + defaultInner := &InnerOpt{ + Value: &defaultValue1, + Foo: &defaultFoo1, + } + // access to the value + value := FromNillable(MakeLensRef((*InnerOpt).GetValue, (*InnerOpt).SetValue)) + // access to inner + inner := FromNillable(MakeLens(OuterOpt.GetInnerOpt, OuterOpt.SetInnerOpt)) + // compose lenses + lens := F.Pipe1( + inner, + ComposeOptions[OuterOpt, *int](defaultInner)(value), + ) + // additional settings + defaultValue2 := 2 + defaultFoo2 := "foo2" + outer1 := OuterOpt{inner: &InnerOpt{Value: &defaultValue2, Foo: &defaultFoo2}} + // the checks + assert.Equal(t, OuterOpt{inner: &InnerOpt{Value: &defaultValue1, Foo: &defaultFoo1}}, lens.Set(O.Some(&defaultValue1))(OuterOpt{})) + assert.Equal(t, O.None[*int](), lens.Get(OuterOpt{})) + assert.Equal(t, OuterOpt{inner: &InnerOpt{Value: &defaultValue1, Foo: &defaultFoo2}}, lens.Set(O.Some(&defaultValue1))(OuterOpt{inner: &InnerOpt{Value: &defaultValue2, Foo: &defaultFoo2}})) + assert.Equal(t, O.Some(&defaultValue1), lens.Get(OuterOpt{inner: &InnerOpt{Value: &defaultValue1, Foo: &defaultFoo1}})) + assert.Equal(t, outer1, Modify[OuterOpt](F.Identity[O.Option[*int]])(lens)(outer1)) +} + +func TestIdRef(t *testing.T) { + idLens := IdRef[Street]() + street := &Street{num: 1, name: "Main"} + + assert.Equal(t, street, idLens.Get(street)) + + newStreet := &Street{num: 2, name: "Oak"} + result := idLens.Set(newStreet)(street) + assert.Equal(t, newStreet, result) + assert.Equal(t, 1, street.num) // Original unchanged +} + +func TestComposeRef(t *testing.T) { + composedLens := ComposeRef[Address, *Street](streetLens)(addrLens) + + assert.Equal(t, sampleStreet.name, composedLens.Get(&sampleAddress)) + + newName := "NewStreet" + updated := composedLens.Set(newName)(&sampleAddress) + assert.Equal(t, newName, composedLens.Get(updated)) + assert.Equal(t, sampleStreet.name, sampleAddress.street.name) // Original unchanged +} + +func TestFromPredicateRef(t *testing.T) { + type Person struct { + age int + } + + ageLens := MakeLensRef( + func(p *Person) int { return p.age }, + func(p *Person, age int) *Person { + p.age = age + return p + }, + ) + + adultLens := FromPredicateRef[Person](func(age int) bool { return age >= 18 }, 0)(ageLens) + + adult := &Person{age: 25} + assert.Equal(t, O.Some(25), adultLens.Get(adult)) + + minor := &Person{age: 15} + assert.Equal(t, O.None[int](), adultLens.Get(minor)) +} + +func TestFromNillableRef(t *testing.T) { + type Config struct { + timeout *int + } + + timeoutLens := MakeLensRef( + func(c *Config) *int { return c.timeout }, + func(c *Config, t *int) *Config { + c.timeout = t + return c + }, + ) + + optLens := FromNillableRef(timeoutLens) + + config := &Config{timeout: nil} + assert.Equal(t, O.None[*int](), optLens.Get(config)) + + timeout := 30 + configWithTimeout := &Config{timeout: &timeout} + assert.True(t, O.IsSome(optLens.Get(configWithTimeout))) +} + +func TestFromNullablePropRef(t *testing.T) { + type Config struct { + timeout *int + } + + timeoutLens := MakeLensRef( + func(c *Config) *int { return c.timeout }, + func(c *Config, t *int) *Config { + c.timeout = t + return c + }, + ) + + defaultTimeout := 30 + safeLens := FromNullablePropRef[Config](O.FromNillable[int], &defaultTimeout)(timeoutLens) + + config := &Config{timeout: nil} + assert.Equal(t, &defaultTimeout, safeLens.Get(config)) +} + +func TestFromOptionRef(t *testing.T) { + type Settings struct { + retries O.Option[int] + } + + retriesLens := MakeLensRef( + func(s *Settings) O.Option[int] { return s.retries }, + func(s *Settings, r O.Option[int]) *Settings { + s.retries = r + return s + }, + ) + + safeLens := FromOptionRef[Settings](3)(retriesLens) + + settings := &Settings{retries: O.None[int]()} + assert.Equal(t, 3, safeLens.Get(settings)) + + settingsWithRetries := &Settings{retries: O.Some(5)} + assert.Equal(t, 5, safeLens.Get(settingsWithRetries)) +} + +func TestMakeLensCurried(t *testing.T) { + nameLens := MakeLensCurried( + func(s Street) string { return s.name }, + func(name string) func(Street) Street { + return func(s Street) Street { + s.name = name + return s + } + }, + ) + + street := Street{num: 1, name: "Main"} + assert.Equal(t, "Main", nameLens.Get(street)) + + updated := nameLens.Set("Oak")(street) + assert.Equal(t, "Oak", updated.name) + assert.Equal(t, "Main", street.name) +} + +func TestMakeLensRefCurried(t *testing.T) { + nameLens := MakeLensRefCurried( + func(s *Street) string { return s.name }, + func(name string) func(*Street) *Street { + return func(s *Street) *Street { + s.name = name + return s + } + }, + ) + + street := &Street{num: 1, name: "Main"} + assert.Equal(t, "Main", nameLens.Get(street)) + + updated := nameLens.Set("Oak")(street) + assert.Equal(t, "Oak", updated.name) + assert.Equal(t, "Main", street.name) +} diff --git a/v2/optics/lens/option/option.go b/v2/optics/lens/option/option.go new file mode 100644 index 0000000..99123dc --- /dev/null +++ b/v2/optics/lens/option/option.go @@ -0,0 +1,27 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package option + +import ( + L "github.com/IBM/fp-go/v2/optics/lens" + LG "github.com/IBM/fp-go/v2/optics/lens/generic" + T "github.com/IBM/fp-go/v2/optics/traversal/option" + O "github.com/IBM/fp-go/v2/option" +) + +func AsTraversal[S, A any]() func(L.Lens[S, A]) T.Traversal[S, A] { + return LG.AsTraversal[T.Traversal[S, A]](O.MonadMap[A, S]) +} diff --git a/v2/optics/lens/optional/optional.go b/v2/optics/lens/optional/optional.go new file mode 100644 index 0000000..2c96d14 --- /dev/null +++ b/v2/optics/lens/optional/optional.go @@ -0,0 +1,34 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package optional + +import ( + F "github.com/IBM/fp-go/v2/function" + L "github.com/IBM/fp-go/v2/optics/lens" + OPT "github.com/IBM/fp-go/v2/optics/optional" + O "github.com/IBM/fp-go/v2/option" +) + +func lensAsOptional[S, A any](creator func(get func(S) O.Option[A], set func(S, A) S) OPT.Optional[S, A], sa L.Lens[S, A]) OPT.Optional[S, A] { + return creator(F.Flow2(sa.Get, O.Some[A]), func(s S, a A) S { + return sa.Set(a)(s) + }) +} + +// LensAsOptional converts a Lens into an Optional +func LensAsOptional[S, A any](sa L.Lens[S, A]) OPT.Optional[S, A] { + return lensAsOptional(OPT.MakeOptional[S, A], sa) +} diff --git a/v2/optics/lens/record/generic/record.go b/v2/optics/lens/record/generic/record.go new file mode 100644 index 0000000..8db769c --- /dev/null +++ b/v2/optics/lens/record/generic/record.go @@ -0,0 +1,49 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + F "github.com/IBM/fp-go/v2/function" + I "github.com/IBM/fp-go/v2/identity" + L "github.com/IBM/fp-go/v2/optics/lens" + O "github.com/IBM/fp-go/v2/option" + RR "github.com/IBM/fp-go/v2/record/generic" +) + +// AtRecord returns a lens that focusses on a value in a record +func AtRecord[M ~map[K]V, V any, K comparable](key K) L.Lens[M, O.Option[V]] { + addKey := F.Bind1of2(RR.UpsertAt[M, K, V])(key) + delKey := F.Bind1of1(RR.DeleteAt[M, K, V])(key) + fold := O.Fold( + delKey, + addKey, + ) + return L.MakeLens( + RR.Lookup[M](key), + func(m M, v O.Option[V]) M { + return F.Pipe2( + v, + fold, + I.Ap[M, M](m), + ) + }, + ) +} + +// AtKey returns a `Lens` focused on a required key of a `ReadonlyRecord` +func AtKey[M ~map[K]V, S any, V any, K comparable](key K) func(sa L.Lens[S, M]) L.Lens[S, O.Option[V]] { + return L.Compose[S](AtRecord[M](key)) +} diff --git a/v2/optics/lens/record/record.go b/v2/optics/lens/record/record.go new file mode 100644 index 0000000..71d9c24 --- /dev/null +++ b/v2/optics/lens/record/record.go @@ -0,0 +1,32 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package record + +import ( + L "github.com/IBM/fp-go/v2/optics/lens" + G "github.com/IBM/fp-go/v2/optics/lens/record/generic" + O "github.com/IBM/fp-go/v2/option" +) + +// AtRecord returns a lens that focusses on a value in a record +func AtRecord[V any, K comparable](key K) L.Lens[map[K]V, O.Option[V]] { + return G.AtRecord[map[K]V](key) +} + +// AtKey returns a `Lens` focused on a required key of a `ReadonlyRecord` +func AtKey[S any, V any, K comparable](key K) func(sa L.Lens[S, map[K]V]) L.Lens[S, O.Option[V]] { + return G.AtKey[map[K]V, S](key) +} diff --git a/v2/optics/lens/record/record_test.go b/v2/optics/lens/record/record_test.go new file mode 100644 index 0000000..06ef07a --- /dev/null +++ b/v2/optics/lens/record/record_test.go @@ -0,0 +1,41 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package record + +import ( + "testing" + + F "github.com/IBM/fp-go/v2/function" + L "github.com/IBM/fp-go/v2/optics/lens" + O "github.com/IBM/fp-go/v2/option" + "github.com/stretchr/testify/assert" +) + +type ( + S = map[string]int +) + +func TestAtKey(t *testing.T) { + sa := F.Pipe1( + L.Id[S](), + AtKey[S, int]("a"), + ) + + assert.Equal(t, O.Some(1), sa.Get(S{"a": 1})) + assert.Equal(t, S{"a": 2, "b": 2}, sa.Set(O.Some(2))(S{"a": 1, "b": 2})) + assert.Equal(t, S{"a": 1, "b": 2}, sa.Set(O.Some(1))(S{"b": 2})) + assert.Equal(t, S{"b": 2}, sa.Set(O.None[int]())(S{"a": 1, "b": 2})) +} diff --git a/v2/optics/lens/testing/laws.go b/v2/optics/lens/testing/laws.go new file mode 100644 index 0000000..cc32be6 --- /dev/null +++ b/v2/optics/lens/testing/laws.go @@ -0,0 +1,80 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package testing + +import ( + "testing" + + E "github.com/IBM/fp-go/v2/eq" + L "github.com/IBM/fp-go/v2/optics/lens" + "github.com/stretchr/testify/assert" +) + +// LensGet tests the law: +// get(set(a)(s)) = a +func LensGet[S, A any]( + t *testing.T, + eqa E.Eq[A], +) func(l L.Lens[S, A]) func(s S, a A) bool { + + return func(l L.Lens[S, A]) func(s S, a A) bool { + + return func(s S, a A) bool { + return assert.True(t, eqa.Equals(l.Get(l.Set(a)(s)), a), "Lens get(set(a)(s)) = a") + } + } +} + +// LensSet tests the laws: +// set(get(s))(s) = s +// set(a)(set(a)(s)) = set(a)(s) +func LensSet[S, A any]( + t *testing.T, + eqs E.Eq[S], +) func(l L.Lens[S, A]) func(s S, a A) bool { + + return func(l L.Lens[S, A]) func(s S, a A) bool { + + return func(s S, a A) bool { + return assert.True(t, eqs.Equals(l.Set(l.Get(s))(s), s), "Lens set(get(s))(s) = s") && assert.True(t, eqs.Equals(l.Set(a)(l.Set(a)(s)), l.Set(a)(s)), "Lens set(a)(set(a)(s)) = set(a)(s)") + } + } +} + +// AssertLaws tests the lens laws +// +// get(set(a)(s)) = a +// set(get(s))(s) = s +// set(a)(set(a)(s)) = set(a)(s) +func AssertLaws[S, A any]( + t *testing.T, + eqa E.Eq[A], + eqs E.Eq[S], +) func(l L.Lens[S, A]) func(s S, a A) bool { + + lenGet := LensGet[S](t, eqa) + lenSet := LensSet[S, A](t, eqs) + + return func(l L.Lens[S, A]) func(s S, a A) bool { + + get := lenGet(l) + set := lenSet(l) + + return func(s S, a A) bool { + return get(s, a) && set(s, a) + } + } +} diff --git a/v2/optics/lens/testing/laws_test.go b/v2/optics/lens/testing/laws_test.go new file mode 100644 index 0000000..36c7782 --- /dev/null +++ b/v2/optics/lens/testing/laws_test.go @@ -0,0 +1,265 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package testing + +import ( + "testing" + + EQT "github.com/IBM/fp-go/v2/eq/testing" + F "github.com/IBM/fp-go/v2/function" + I "github.com/IBM/fp-go/v2/identity" + L "github.com/IBM/fp-go/v2/optics/lens" + LI "github.com/IBM/fp-go/v2/optics/lens/iso" + O "github.com/IBM/fp-go/v2/option" + "github.com/stretchr/testify/assert" +) + +type ( + Street struct { + num int + name string + } + + Address struct { + city string + street *Street + } + + Inner struct { + Value int + Foo string + } + + InnerOpt struct { + Value *int + Foo *string + } + + Outer struct { + inner *Inner + } + + OuterOpt struct { + inner *InnerOpt + } +) + +func (outer *OuterOpt) GetInner() *InnerOpt { + return outer.inner +} + +func (outer *OuterOpt) SetInner(inner *InnerOpt) *OuterOpt { + outer.inner = inner + return outer +} + +func (inner *InnerOpt) GetValue() *int { + return inner.Value +} + +func (inner *InnerOpt) SetValue(value *int) *InnerOpt { + inner.Value = value + return inner +} + +func (outer *Outer) GetInner() *Inner { + return outer.inner +} + +func (outer *Outer) SetInner(inner *Inner) *Outer { + outer.inner = inner + return outer +} + +func (inner *Inner) GetValue() int { + return inner.Value +} + +func (inner *Inner) SetValue(value int) *Inner { + inner.Value = value + return inner +} + +func (street *Street) GetName() string { + return street.name +} + +func (street *Street) SetName(name string) *Street { + street.name = name + return street +} + +func (addr *Address) GetStreet() *Street { + return addr.street +} + +func (addr *Address) SetStreet(s *Street) *Address { + addr.street = s + return addr +} + +var ( + streetLens = L.MakeLensRef((*Street).GetName, (*Street).SetName) + addrLens = L.MakeLensRef((*Address).GetStreet, (*Address).SetStreet) + outerLens = L.FromNillableRef(L.MakeLensRef((*Outer).GetInner, (*Outer).SetInner)) + valueLens = L.MakeLensRef((*Inner).GetValue, (*Inner).SetValue) + + outerOptLens = L.FromNillableRef(L.MakeLensRef((*OuterOpt).GetInner, (*OuterOpt).SetInner)) + valueOptLens = L.MakeLensRef((*InnerOpt).GetValue, (*InnerOpt).SetValue) + + sampleStreet = Street{num: 220, name: "Schönaicherstr"} + sampleAddress = Address{city: "Böblingen", street: &sampleStreet} + sampleStreet2 = Street{num: 220, name: "Neue Str"} + + defaultInner = Inner{ + Value: -1, + Foo: "foo", + } + + emptyOuter = Outer{} + + defaultInnerOpt = InnerOpt{ + Value: &defaultInner.Value, + Foo: &defaultInner.Foo, + } + + emptyOuterOpt = OuterOpt{} +) + +func TestStreetLensLaws(t *testing.T) { + // some comparison + eqs := EQT.Eq[*Street]() + eqa := EQT.Eq[string]() + + laws := AssertLaws( + t, + eqa, + eqs, + )(streetLens) + + cpy := sampleStreet + assert.True(t, laws(&sampleStreet, "Neue Str.")) + assert.Equal(t, cpy, sampleStreet) +} + +func TestAddrLensLaws(t *testing.T) { + // some comparison + eqs := EQT.Eq[*Address]() + eqa := EQT.Eq[*Street]() + + laws := AssertLaws( + t, + eqa, + eqs, + )(addrLens) + + cpyAddr := sampleAddress + cpyStreet := sampleStreet2 + assert.True(t, laws(&sampleAddress, &sampleStreet2)) + assert.Equal(t, cpyAddr, sampleAddress) + assert.Equal(t, cpyStreet, sampleStreet2) +} + +func TestCompose(t *testing.T) { + // some comparison + eqs := EQT.Eq[*Address]() + eqa := EQT.Eq[string]() + + streetName := L.Compose[*Address](streetLens)(addrLens) + + laws := AssertLaws( + t, + eqa, + eqs, + )(streetName) + + cpyAddr := sampleAddress + cpyStreet := sampleStreet + assert.True(t, laws(&sampleAddress, "Neue Str.")) + assert.Equal(t, cpyAddr, sampleAddress) + assert.Equal(t, cpyStreet, sampleStreet) +} + +func TestOuterLensLaws(t *testing.T) { + // some equal predicates + eqValue := EQT.Eq[int]() + eqOptValue := O.Eq(eqValue) + // lens to access a value from outer + valueFromOuter := L.ComposeOption[*Outer, int](&defaultInner)(valueLens)(outerLens) + // try to access the value, this should get an option + assert.True(t, eqOptValue.Equals(valueFromOuter.Get(&emptyOuter), O.None[int]())) + // update the object + withValue := valueFromOuter.Set(O.Some(1))(&emptyOuter) + assert.True(t, eqOptValue.Equals(valueFromOuter.Get(&emptyOuter), O.None[int]())) + assert.True(t, eqOptValue.Equals(valueFromOuter.Get(withValue), O.Some(1))) + // updating with none should remove the inner + nextValue := valueFromOuter.Set(O.None[int]())(withValue) + assert.True(t, eqOptValue.Equals(valueFromOuter.Get(nextValue), O.None[int]())) + // check if this meets the laws + + eqOuter := EQT.Eq[*Outer]() + + laws := AssertLaws( + t, + eqOptValue, + eqOuter, + )(valueFromOuter) + + assert.True(t, laws(&emptyOuter, O.Some(2))) + assert.True(t, laws(&emptyOuter, O.None[int]())) + + assert.True(t, laws(withValue, O.Some(2))) + assert.True(t, laws(withValue, O.None[int]())) +} + +func TestOuterOptLensLaws(t *testing.T) { + // some equal predicates + eqValue := EQT.Eq[int]() + eqOptValue := O.Eq(eqValue) + intIso := LI.FromNillable[int]() + // lens to access a value from outer + valueFromOuter := F.Pipe3( + valueOptLens, + LI.Compose[*InnerOpt](intIso), + L.ComposeOptions[*OuterOpt, int](&defaultInnerOpt), + I.Ap[L.Lens[*OuterOpt, O.Option[int]]](outerOptLens), + ) + + // try to access the value, this should get an option + assert.True(t, eqOptValue.Equals(valueFromOuter.Get(&emptyOuterOpt), O.None[int]())) + // update the object + withValue := valueFromOuter.Set(O.Some(1))(&emptyOuterOpt) + assert.True(t, eqOptValue.Equals(valueFromOuter.Get(&emptyOuterOpt), O.None[int]())) + assert.True(t, eqOptValue.Equals(valueFromOuter.Get(withValue), O.Some(1))) + // updating with none should remove the inner + nextValue := valueFromOuter.Set(O.None[int]())(withValue) + assert.True(t, eqOptValue.Equals(valueFromOuter.Get(nextValue), O.None[int]())) + // check if this meets the laws + + eqOuter := EQT.Eq[*OuterOpt]() + + laws := AssertLaws( + t, + eqOptValue, + eqOuter, + )(valueFromOuter) + + assert.True(t, laws(&emptyOuterOpt, O.Some(2))) + assert.True(t, laws(&emptyOuterOpt, O.None[int]())) + + assert.True(t, laws(withValue, O.Some(2))) + assert.True(t, laws(withValue, O.None[int]())) +} diff --git a/v2/optics/lens/types.go b/v2/optics/lens/types.go new file mode 100644 index 0000000..35137e4 --- /dev/null +++ b/v2/optics/lens/types.go @@ -0,0 +1,31 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +// Lens is an optic used to zoom inside a product. +package lens + +import ( + "github.com/IBM/fp-go/v2/endomorphism" +) + +type ( + Endomorphism[A any] = endomorphism.Endomorphism[A] + + // Lens is a reference to a subpart of a data type + Lens[S, A any] struct { + Get func(s S) A + Set func(a A) Endomorphism[S] + } +) diff --git a/v2/optics/optional/lens/lens.go b/v2/optics/optional/lens/lens.go new file mode 100644 index 0000000..d9a73e4 --- /dev/null +++ b/v2/optics/optional/lens/lens.go @@ -0,0 +1,32 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package lens + +import ( + F "github.com/IBM/fp-go/v2/function" + L "github.com/IBM/fp-go/v2/optics/lens" + LO "github.com/IBM/fp-go/v2/optics/lens/optional" + OPT "github.com/IBM/fp-go/v2/optics/optional" +) + +// Compose composes a lens with an optional +func Compose[S, A, B any](ab L.Lens[A, B]) func(sa OPT.Optional[S, A]) OPT.Optional[S, B] { + return F.Pipe2( + ab, + LO.LensAsOptional[A, B], + OPT.Compose[S, A, B], + ) +} diff --git a/v2/optics/optional/lens/lens_test.go b/v2/optics/optional/lens/lens_test.go new file mode 100644 index 0000000..9707a68 --- /dev/null +++ b/v2/optics/optional/lens/lens_test.go @@ -0,0 +1,78 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package lens + +import ( + "testing" + + F "github.com/IBM/fp-go/v2/function" + L "github.com/IBM/fp-go/v2/optics/lens" + OPT "github.com/IBM/fp-go/v2/optics/optional" + OPTP "github.com/IBM/fp-go/v2/optics/optional/prism" + O "github.com/IBM/fp-go/v2/option" + "github.com/stretchr/testify/assert" +) + +type Inner struct { + A int +} + +type State = O.Option[*Inner] + +func (inner *Inner) getA() int { + return inner.A +} + +func (inner *Inner) setA(a int) *Inner { + inner.A = a + return inner +} + +func TestCompose(t *testing.T) { + + inner1 := Inner{1} + + lensa := L.MakeLensRef((*Inner).getA, (*Inner).setA) + + sa := F.Pipe1( + OPT.Id[State](), + OPTP.Some[State, *Inner], + ) + ab := F.Pipe1( + L.IdRef[Inner](), + L.ComposeRef[Inner](lensa), + ) + sb := F.Pipe1( + sa, + Compose[State](ab), + ) + // check get access + assert.Equal(t, O.None[int](), sb.GetOption(O.None[*Inner]())) + assert.Equal(t, O.Of(1), sb.GetOption(O.Of(&inner1))) + + // check set access + res := F.Pipe1( + sb.Set(2)(O.Of(&inner1)), + O.Map(func(i *Inner) int { + return i.A + }), + ) + assert.Equal(t, O.Of(2), res) + assert.Equal(t, 1, inner1.A) + + assert.Equal(t, O.None[*Inner](), sb.Set(2)(O.None[*Inner]())) + +} diff --git a/v2/optics/optional/optional.go b/v2/optics/optional/optional.go new file mode 100644 index 0000000..27e9466 --- /dev/null +++ b/v2/optics/optional/optional.go @@ -0,0 +1,191 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +// Optional is an optic used to zoom inside a product. Unlike the `Lens`, the element that the `Optional` focuses +// on may not exist. +package optional + +import ( + EM "github.com/IBM/fp-go/v2/endomorphism" + F "github.com/IBM/fp-go/v2/function" + O "github.com/IBM/fp-go/v2/option" +) + +// Optional is an optional reference to a subpart of a data type +type Optional[S, A any] struct { + GetOption func(s S) O.Option[A] + Set func(a A) EM.Endomorphism[S] +} + +// setCopy wraps a setter for a pointer into a setter that first creates a copy before +// modifying that copy +func setCopy[SET ~func(*S, A) *S, S, A any](setter SET) func(s *S, a A) *S { + return func(s *S, a A) *S { + cpy := *s + return setter(&cpy, a) + } +} + +// MakeOptional creates an Optional based on a getter and a setter function. Make sure that the setter creates a (shallow) copy of the +// data. This happens automatically if the data is passed by value. For pointers consider to use `MakeOptionalRef` +// and for other kinds of data structures that are copied by reference make sure the setter creates the copy. +func MakeOptional[S, A any](get func(S) O.Option[A], set func(S, A) S) Optional[S, A] { + return Optional[S, A]{GetOption: get, Set: EM.Curry2(F.Swap(set))} +} + +// MakeOptionalRef creates an Optional based on a getter and a setter function. The setter passed in does not have to create a shallow +// copy, the implementation wraps the setter into one that copies the pointer before modifying it +func MakeOptionalRef[S, A any](get func(*S) O.Option[A], set func(*S, A) *S) Optional[*S, A] { + return MakeOptional(get, setCopy(set)) +} + +// Id returns am optional implementing the identity operation +func id[S any](creator func(get func(S) O.Option[S], set func(S, S) S) Optional[S, S]) Optional[S, S] { + return creator(O.Some[S], F.Second[S, S]) +} + +// Id returns am optional implementing the identity operation +func Id[S any]() Optional[S, S] { + return id(MakeOptional[S, S]) +} + +// Id returns am optional implementing the identity operation +func IdRef[S any]() Optional[*S, *S] { + return id(MakeOptionalRef[S, *S]) +} + +func optionalModifyOption[S, A any](f func(A) A, optional Optional[S, A], s S) O.Option[S] { + return F.Pipe1( + optional.GetOption(s), + O.Map(func(a A) S { + return optional.Set(f(a))(s) + }), + ) +} + +func optionalModify[S, A any](f func(A) A, optional Optional[S, A], s S) S { + return F.Pipe1( + optionalModifyOption(f, optional, s), + O.GetOrElse(F.Constant(s)), + ) +} + +// Compose combines two Optional and allows to narrow down the focus to a sub-Optional +func compose[S, A, B any](creator func(get func(S) O.Option[B], set func(S, B) S) Optional[S, B], ab Optional[A, B]) func(Optional[S, A]) Optional[S, B] { + abget := ab.GetOption + abset := ab.Set + return func(sa Optional[S, A]) Optional[S, B] { + saget := sa.GetOption + return creator( + F.Flow2(saget, O.Chain(abget)), + func(s S, b B) S { + return optionalModify(abset(b), sa, s) + }, + ) + } +} + +// Compose combines two Optional and allows to narrow down the focus to a sub-Optional +func Compose[S, A, B any](ab Optional[A, B]) func(Optional[S, A]) Optional[S, B] { + return compose(MakeOptional[S, B], ab) +} + +// ComposeRef combines two Optional and allows to narrow down the focus to a sub-Optional +func ComposeRef[S, A, B any](ab Optional[A, B]) func(Optional[*S, A]) Optional[*S, B] { + return compose(MakeOptionalRef[S, B], ab) +} + +// fromPredicate implements the function generically for both the ref and the direct case +func fromPredicate[S, A any](creator func(get func(S) O.Option[A], set func(S, A) S) Optional[S, A], pred func(A) bool) func(func(S) A, func(S, A) S) Optional[S, A] { + fromPred := O.FromPredicate(pred) + return func(get func(S) A, set func(S, A) S) Optional[S, A] { + return creator( + F.Flow2(get, fromPred), + func(s S, _ A) S { + return F.Pipe3( + s, + get, + fromPred, + O.Fold(F.Constant(s), F.Bind1st(set, s)), + ) + }, + ) + } +} + +// FromPredicate creates an optional from getter and setter functions. It checks +// for optional values and the correct update procedure +func FromPredicate[S, A any](pred func(A) bool) func(func(S) A, func(S, A) S) Optional[S, A] { + return fromPredicate(MakeOptional[S, A], pred) +} + +// FromPredicate creates an optional from getter and setter functions. It checks +// for optional values and the correct update procedure +func FromPredicateRef[S, A any](pred func(A) bool) func(func(*S) A, func(*S, A) *S) Optional[*S, A] { + return fromPredicate(MakeOptionalRef[S, A], pred) +} + +func imap[S, A, B any](sa Optional[S, A], ab func(A) B, ba func(B) A) Optional[S, B] { + return MakeOptional( + F.Flow2(sa.GetOption, O.Map(ab)), + func(s S, b B) S { + return sa.Set(ba(b))(s) + }, + ) +} + +// IMap implements a bidirectional mapping of the transform +func IMap[S, A, B any](ab func(A) B, ba func(B) A) func(Optional[S, A]) Optional[S, B] { + return func(sa Optional[S, A]) Optional[S, B] { + return imap(sa, ab, ba) + } +} + +func ModifyOption[S, A any](f func(A) A) func(Optional[S, A]) func(S) O.Option[S] { + return func(o Optional[S, A]) func(S) O.Option[S] { + return func(s S) O.Option[S] { + return optionalModifyOption(f, o, s) + } + } +} + +func SetOption[S, A any](a A) func(Optional[S, A]) func(S) O.Option[S] { + return ModifyOption[S](F.Constant1[A](a)) +} + +func ichain[S, A, B any](sa Optional[S, A], ab func(A) O.Option[B], ba func(B) O.Option[A]) Optional[S, B] { + return MakeOptional( + F.Flow2(sa.GetOption, O.Chain(ab)), + func(s S, b B) S { + return O.MonadFold(ba(b), EM.Identity[S], sa.Set)(s) + }, + ) +} + +// IChain implements a bidirectional mapping of the transform if the transform can produce optionals (e.g. in case of type mappings) +func IChain[S, A, B any](ab func(A) O.Option[B], ba func(B) O.Option[A]) func(Optional[S, A]) Optional[S, B] { + return func(sa Optional[S, A]) Optional[S, B] { + return ichain(sa, ab, ba) + } +} + +// IChainAny implements a bidirectional mapping to and from any +func IChainAny[S, A any]() func(Optional[S, any]) Optional[S, A] { + fromAny := O.ToType[A] + toAny := O.ToAny[A] + return func(sa Optional[S, any]) Optional[S, A] { + return ichain(sa, fromAny, toAny) + } +} diff --git a/v2/optics/optional/optional_test.go b/v2/optics/optional/optional_test.go new file mode 100644 index 0000000..2f38d57 --- /dev/null +++ b/v2/optics/optional/optional_test.go @@ -0,0 +1,64 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package optional + +import ( + "testing" + + F "github.com/IBM/fp-go/v2/function" + O "github.com/IBM/fp-go/v2/option" + + "github.com/stretchr/testify/assert" +) + +type ( + Phone struct { + number string + } + + Employment struct { + phone *Phone + } + + Info struct { + employment *Employment + } + + Response struct { + info *Info + } +) + +func (response *Response) GetInfo() *Info { + return response.info +} + +func (response *Response) SetInfo(info *Info) *Response { + response.info = info + return response +} + +var ( + responseOptional = FromPredicateRef[Response](F.IsNonNil[Info])((*Response).GetInfo, (*Response).SetInfo) + + sampleResponse = Response{info: &Info{}} + sampleEmptyResponse = Response{} +) + +func TestOptional(t *testing.T) { + assert.Equal(t, O.Of(sampleResponse.info), responseOptional.GetOption(&sampleResponse)) + assert.Equal(t, O.None[*Info](), responseOptional.GetOption(&sampleEmptyResponse)) +} diff --git a/v2/optics/optional/prism/prism.go b/v2/optics/optional/prism/prism.go new file mode 100644 index 0000000..4150e51 --- /dev/null +++ b/v2/optics/optional/prism/prism.go @@ -0,0 +1,42 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package prism + +import ( + F "github.com/IBM/fp-go/v2/function" + OPT "github.com/IBM/fp-go/v2/optics/optional" + P "github.com/IBM/fp-go/v2/optics/prism" + O "github.com/IBM/fp-go/v2/option" +) + +// AsOptional converts a prism into an optional +func AsOptional[S, A any](sa P.Prism[S, A]) OPT.Optional[S, A] { + return OPT.MakeOptional( + sa.GetOption, + func(s S, a A) S { + return P.Set[S](a)(sa)(s) + }, + ) +} + +func PrismSome[A any]() P.Prism[O.Option[A], A] { + return P.MakePrism(F.Identity[O.Option[A]], O.Some[A]) +} + +// Some returns a `Optional` from a `Optional` focused on the `Some` of a `Option` type. +func Some[S, A any](soa OPT.Optional[S, O.Option[A]]) OPT.Optional[S, A] { + return OPT.Compose[S](AsOptional(PrismSome[A]()))(soa) +} diff --git a/v2/optics/optional/record/generic/generic.go b/v2/optics/optional/record/generic/generic.go new file mode 100644 index 0000000..ff1a079 --- /dev/null +++ b/v2/optics/optional/record/generic/generic.go @@ -0,0 +1,37 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + OP "github.com/IBM/fp-go/v2/optics/optional" + O "github.com/IBM/fp-go/v2/option" + RR "github.com/IBM/fp-go/v2/record/generic" +) + +func setter[M ~map[K]V, K comparable, V any](key K) func(M, V) M { + return func(dst M, value V) M { + return RR.UpsertAt[M](key, value)(dst) + } +} + +func getter[M ~map[K]V, K comparable, V any](key K) func(M) O.Option[V] { + return RR.Lookup[M](key) +} + +// AtKey returns a Optional that gets and sets properties of a map +func AtKey[M ~map[K]V, K comparable, V any](key K) OP.Optional[M, V] { + return OP.MakeOptional(getter[M](key), setter[M](key)) +} diff --git a/v2/optics/optional/record/record.go b/v2/optics/optional/record/record.go new file mode 100644 index 0000000..0682a94 --- /dev/null +++ b/v2/optics/optional/record/record.go @@ -0,0 +1,26 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package record + +import ( + OP "github.com/IBM/fp-go/v2/optics/optional" + G "github.com/IBM/fp-go/v2/optics/optional/record/generic" +) + +// FromProperty returns a Optional that gets and sets properties of a map +func AtKey[K comparable, V any](key K) OP.Optional[map[K]V, V] { + return G.AtKey[map[K]V](key) +} diff --git a/v2/optics/optional/record/record_test.go b/v2/optics/optional/record/record_test.go new file mode 100644 index 0000000..93af9f2 --- /dev/null +++ b/v2/optics/optional/record/record_test.go @@ -0,0 +1,100 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package record + +import ( + "testing" + + F "github.com/IBM/fp-go/v2/function" + OP "github.com/IBM/fp-go/v2/optics/optional" + O "github.com/IBM/fp-go/v2/option" + ON "github.com/IBM/fp-go/v2/option/number" + RR "github.com/IBM/fp-go/v2/record" + + "github.com/stretchr/testify/assert" +) + +type ( + GenericMap = map[string]any +) + +func TestOptionalRecord(t *testing.T) { + // sample record + r := RR.Singleton("key", "value") + + // extract values + optKey := AtKey[string, string]("key") + optKey1 := AtKey[string, string]("key1") + + // check if we can get the key + assert.Equal(t, O.Of("value"), optKey.GetOption(r)) + assert.Equal(t, O.None[string](), optKey1.GetOption(r)) + + // check if we can set a value + r1 := optKey1.Set("value1")(r) + + // check if we can get the key + assert.Equal(t, O.Of("value"), optKey.GetOption(r)) + assert.Equal(t, O.None[string](), optKey1.GetOption(r)) + // check if we can get the key + assert.Equal(t, O.Of("value"), optKey.GetOption(r1)) + assert.Equal(t, O.Of("value1"), optKey1.GetOption(r1)) +} + +func TestOptionalWithType(t *testing.T) { + // sample record + r := RR.Singleton("key", "1") + // convert between string and int + // writes a key + optStringKey := AtKey[string, string]("key") + optIntKey := F.Pipe1( + optStringKey, + OP.IChain[map[string]string](ON.Atoi, ON.Itoa), + ) + // test the scenarions + assert.Equal(t, O.Of("1"), optStringKey.GetOption(r)) + assert.Equal(t, O.Of(1), optIntKey.GetOption(r)) + // modify + r1 := optIntKey.Set(2)(r) + assert.Equal(t, O.Of("2"), optStringKey.GetOption(r1)) + assert.Equal(t, O.Of(2), optIntKey.GetOption(r1)) +} + +// func TestNestedRecord(t *testing.T) { +// // some sample data +// x := GenericMap{ +// "a": GenericMap{ +// "b": "1", +// }, +// } +// // accessor for first level +// optA := F.Pipe1( +// AtKey[string, any]("a"), +// OP.IChainAny[GenericMap, GenericMap](), +// ) +// optB := F.Pipe2( +// AtKey[string, any]("b"), +// OP.IChainAny[GenericMap, string](), +// OP.IChain[GenericMap](ON.Atoi, ON.Itoa), +// ) +// // go directly to b +// optAB := F.Pipe1( +// optA, +// OP.Compose[GenericMap](optB), +// ) +// // access the value of b +// assert.Equal(t, O.Of(1), optAB.GetOption(x)) +// } diff --git a/v2/optics/prism/doc.go b/v2/optics/prism/doc.go new file mode 100644 index 0000000..b2c8245 --- /dev/null +++ b/v2/optics/prism/doc.go @@ -0,0 +1,436 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +/* +Package prism provides prisms - optics for focusing on variants within sum types. + +# Overview + +A Prism is an optic used to select a specific variant from a sum type (also known as +tagged unions, discriminated unions, or algebraic data types). Unlike lenses which +focus on fields that always exist, prisms focus on values that may or may not be +present depending on which variant is active. + +Prisms are essential for working with: + - Either types (Left/Right) + - Option types (Some/None) + - Result types (Success/Failure) + - Custom sum types + - Error handling patterns + +# Mathematical Foundation + +A Prism[S, A] consists of two operations: + - GetOption: S → Option[A] (try to extract A from S, may fail) + - ReverseGet: A → S (construct S from A, always succeeds) + +Prisms must satisfy the prism laws: + 1. GetOptionReverseGet: prism.GetOption(prism.ReverseGet(a)) == Some(a) + 2. ReverseGetGetOption: if GetOption(s) == Some(a), then ReverseGet(a) produces equivalent s + +These laws ensure that: + - Constructing and then extracting always succeeds + - Extracting and then constructing preserves the value + +# Basic Usage + +Creating a prism for an Either type: + + type Result interface{ isResult() } + type Success struct{ Value int } + type Failure struct{ Error string } + + func (Success) isResult() {} + func (Failure) isResult() {} + + successPrism := prism.MakePrism( + func(r Result) option.Option[int] { + if s, ok := r.(Success); ok { + return option.Some(s.Value) + } + return option.None[int]() + }, + func(v int) Result { + return Success{Value: v} + }, + ) + + // Try to extract value from Success + result := Success{Value: 42} + value := successPrism.GetOption(result) // Some(42) + + // Try to extract value from Failure + failure := Failure{Error: "oops"} + value = successPrism.GetOption(failure) // None[int] + + // Construct a Success from a value + newResult := successPrism.ReverseGet(100) // Success{Value: 100} + +# Identity Prism + +The identity prism focuses on the entire value: + + idPrism := prism.Id[int]() + + value := idPrism.GetOption(42) // Some(42) + result := idPrism.ReverseGet(42) // 42 + +# From Predicate + +Create a prism that matches values satisfying a predicate: + + positivePrism := prism.FromPredicate(func(n int) bool { + return n > 0 + }) + + // Matches positive numbers + value := positivePrism.GetOption(42) // Some(42) + value = positivePrism.GetOption(-5) // None[int] + + // ReverseGet always succeeds (doesn't check predicate) + result := positivePrism.ReverseGet(42) // 42 + +# Composing Prisms + +Prisms can be composed to focus on nested sum types: + + type Outer interface{ isOuter() } + type OuterA struct{ Inner Inner } + type OuterB struct{ Value string } + + type Inner interface{ isInner() } + type InnerX struct{ Data int } + type InnerY struct{ Info string } + + outerAPrism := prism.MakePrism( + func(o Outer) option.Option[Inner] { + if a, ok := o.(OuterA); ok { + return option.Some(a.Inner) + } + return option.None[Inner]() + }, + func(i Inner) Outer { return OuterA{Inner: i} }, + ) + + innerXPrism := prism.MakePrism( + func(i Inner) option.Option[int] { + if x, ok := i.(InnerX); ok { + return option.Some(x.Data) + } + return option.None[int]() + }, + func(d int) Inner { return InnerX{Data: d} }, + ) + + // Compose to access InnerX data from Outer + composedPrism := F.Pipe1( + outerAPrism, + prism.Compose[Outer](innerXPrism), + ) + + outer := OuterA{Inner: InnerX{Data: 42}} + data := composedPrism.GetOption(outer) // Some(42) + +# Modifying Through Prisms + +Apply transformations when the variant matches: + + type Status interface{ isStatus() } + type Active struct{ Count int } + type Inactive struct{} + + activePrism := prism.MakePrism( + func(s Status) option.Option[int] { + if a, ok := s.(Active); ok { + return option.Some(a.Count) + } + return option.None[int]() + }, + func(count int) Status { return Active{Count: count} }, + ) + + status := Active{Count: 5} + + // Increment count if active + updated := prism.Set(10)(activePrism)(status) + // Result: Active{Count: 10} + + // Try to modify inactive status (no change) + inactive := Inactive{} + unchanged := prism.Set(10)(activePrism)(inactive) + // Result: Inactive{} (unchanged) + +# Working with Option Types + +The Some function creates a prism focused on the Some variant of an Option: + + type Config struct { + Timeout option.Option[int] + } + + timeoutPrism := prism.MakePrism( + func(c Config) option.Option[option.Option[int]] { + return option.Some(c.Timeout) + }, + func(t option.Option[int]) Config { + return Config{Timeout: t} + }, + ) + + // Focus on the Some value + somePrism := prism.Some(timeoutPrism) + + config := Config{Timeout: option.Some(30)} + timeout := somePrism.GetOption(config) // Some(30) + + configNone := Config{Timeout: option.None[int]()} + timeout = somePrism.GetOption(configNone) // None[int] + +# Bidirectional Mapping + +Transform the focus type of a prism: + + type Message interface{ isMessage() } + type TextMessage struct{ Content string } + type ImageMessage struct{ URL string } + + textPrism := prism.MakePrism( + func(m Message) option.Option[string] { + if t, ok := m.(TextMessage); ok { + return option.Some(t.Content) + } + return option.None[string]() + }, + func(content string) Message { + return TextMessage{Content: content} + }, + ) + + // Map to uppercase + upperPrism := F.Pipe1( + textPrism, + prism.IMap( + strings.ToUpper, + strings.ToLower, + ), + ) + + msg := TextMessage{Content: "hello"} + upper := upperPrism.GetOption(msg) // Some("HELLO") + +# Real-World Example: Error Handling + + type Result[T any] interface{ isResult() } + + type Success[T any] struct { + Value T + } + + type Failure[T any] struct { + Error error + } + + func (Success[T]) isResult() {} + func (Failure[T]) isResult() {} + + func SuccessPrism[T any]() prism.Prism[Result[T], T] { + return prism.MakePrism( + func(r Result[T]) option.Option[T] { + if s, ok := r.(Success[T]); ok { + return option.Some(s.Value) + } + return option.None[T]() + }, + func(v T) Result[T] { + return Success[T]{Value: v} + }, + ) + } + + func FailurePrism[T any]() prism.Prism[Result[T], error] { + return prism.MakePrism( + func(r Result[T]) option.Option[error] { + if f, ok := r.(Failure[T]); ok { + return option.Some(f.Error) + } + return option.None[error]() + }, + func(e error) Result[T] { + return Failure[T]{Error: e} + }, + ) + } + + // Use the prisms + result := Success[int]{Value: 42} + + successPrism := SuccessPrism[int]() + value := successPrism.GetOption(result) // Some(42) + + failurePrism := FailurePrism[int]() + err := failurePrism.GetOption(result) // None[error] + + // Transform success values + doubled := prism.Set(84)(successPrism)(result) + // Result: Success{Value: 84} + +# Real-World Example: JSON Parsing + + type JSONValue interface{ isJSON() } + type JSONString struct{ Value string } + type JSONNumber struct{ Value float64 } + type JSONBool struct{ Value bool } + type JSONNull struct{} + + stringPrism := prism.MakePrism( + func(j JSONValue) option.Option[string] { + if s, ok := j.(JSONString); ok { + return option.Some(s.Value) + } + return option.None[string]() + }, + func(s string) JSONValue { + return JSONString{Value: s} + }, + ) + + numberPrism := prism.MakePrism( + func(j JSONValue) option.Option[float64] { + if n, ok := j.(JSONNumber); ok { + return option.Some(n.Value) + } + return option.None[float64]() + }, + func(n float64) JSONValue { + return JSONNumber{Value: n} + }, + ) + + // Parse and extract values + jsonValue := JSONString{Value: "hello"} + str := stringPrism.GetOption(jsonValue) // Some("hello") + num := numberPrism.GetOption(jsonValue) // None[float64] + +# Real-World Example: State Machine + + type State interface{ isState() } + type Idle struct{} + type Running struct{ Progress int } + type Completed struct{ Result string } + type Failed struct{ Error error } + + runningPrism := prism.MakePrism( + func(s State) option.Option[int] { + if r, ok := s.(Running); ok { + return option.Some(r.Progress) + } + return option.None[int]() + }, + func(progress int) State { + return Running{Progress: progress} + }, + ) + + // Update progress if running + state := Running{Progress: 50} + updated := prism.Set(75)(runningPrism)(state) + // Result: Running{Progress: 75} + + // Try to update when not running (no change) + idle := Idle{} + unchanged := prism.Set(75)(runningPrism)(idle) + // Result: Idle{} (unchanged) + +# Prisms vs Lenses + +While both are optics, they serve different purposes: + +**Prisms:** + - Focus on variants of sum types + - GetOption may fail (returns Option) + - ReverseGet always succeeds + - Used for pattern matching + - Example: Either[Error, Value] → Value + +**Lenses:** + - Focus on fields of product types + - Get always succeeds + - Set always succeeds + - Used for field access + - Example: Person → Name + +# Prism Laws + +Prisms must satisfy two laws: + +**Law 1: GetOptionReverseGet** + + prism.GetOption(prism.ReverseGet(a)) == Some(a) + +Constructing a value and then extracting it always succeeds. + +**Law 2: ReverseGetGetOption** + + if prism.GetOption(s) == Some(a) + then prism.ReverseGet(a) should produce equivalent s + +If extraction succeeds, reconstructing should produce an equivalent value. + +# Performance Considerations + +Prisms are efficient: + - No reflection - uses type assertions + - Minimal allocations + - Composition creates function closures + - GetOption short-circuits on mismatch + +For best performance: + - Cache composed prisms + - Use type switches for multiple prisms + - Consider batch operations when possible + +# Type Safety + +Prisms are fully type-safe: + - Compile-time type checking + - Type assertions are explicit + - Generic type parameters ensure correctness + - Composition maintains type relationships + +# Function Reference + +Core Functions: + - MakePrism: Create a prism from GetOption and ReverseGet functions + - Id: Create an identity prism + - FromPredicate: Create a prism from a predicate function + - Compose: Compose two prisms + +Transformation: + - Set: Set a value through a prism (no-op if variant doesn't match) + - IMap: Bidirectionally map a prism + +Specialized: + - Some: Focus on the Some variant of an Option + +# Related Packages + + - github.com/IBM/fp-go/v2/optics/lens: Lenses for product types + - github.com/IBM/fp-go/v2/optics/iso: Isomorphisms + - github.com/IBM/fp-go/v2/optics/optional: Optional optics + - github.com/IBM/fp-go/v2/option: Optional values + - github.com/IBM/fp-go/v2/either: Sum types + - github.com/IBM/fp-go/v2/function: Function composition +*/ +package prism diff --git a/v2/optics/prism/prism.go b/v2/optics/prism/prism.go new file mode 100644 index 0000000..b6b78be --- /dev/null +++ b/v2/optics/prism/prism.go @@ -0,0 +1,118 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +// Prism is an optic used to select part of a sum type. +package prism + +import ( + EM "github.com/IBM/fp-go/v2/endomorphism" + F "github.com/IBM/fp-go/v2/function" + O "github.com/IBM/fp-go/v2/option" +) + +type ( + // Prism is an optic used to select part of a sum type. + Prism[S, A any] interface { + GetOption(s S) O.Option[A] + ReverseGet(a A) S + } + + prismImpl[S, A any] struct { + get func(S) O.Option[A] + rev func(A) S + } +) + +func (prism prismImpl[S, A]) GetOption(s S) O.Option[A] { + return prism.get(s) +} + +func (prism prismImpl[S, A]) ReverseGet(a A) S { + return prism.rev(a) +} + +func MakePrism[S, A any](get func(S) O.Option[A], rev func(A) S) Prism[S, A] { + return prismImpl[S, A]{get, rev} +} + +// Id returns a prism implementing the identity operation +func Id[S any]() Prism[S, S] { + return MakePrism(O.Some[S], F.Identity[S]) +} + +func FromPredicate[S any](pred func(S) bool) Prism[S, S] { + return MakePrism(O.FromPredicate(pred), F.Identity[S]) +} + +// Compose composes a `Prism` with a `Prism`. +func Compose[S, A, B any](ab Prism[A, B]) func(Prism[S, A]) Prism[S, B] { + return func(sa Prism[S, A]) Prism[S, B] { + return MakePrism(F.Flow2( + sa.GetOption, + O.Chain(ab.GetOption), + ), F.Flow2( + ab.ReverseGet, + sa.ReverseGet, + )) + } +} + +func prismModifyOption[S, A any](f func(A) A, sa Prism[S, A], s S) O.Option[S] { + return F.Pipe2( + s, + sa.GetOption, + O.Map(F.Flow2( + f, + sa.ReverseGet, + )), + ) +} + +func prismModify[S, A any](f func(A) A, sa Prism[S, A], s S) S { + return F.Pipe1( + prismModifyOption(f, sa, s), + O.GetOrElse(F.Constant(s)), + ) +} + +func prismSet[S, A any](a A) func(Prism[S, A]) EM.Endomorphism[S] { + return EM.Curry3(prismModify[S, A])(F.Constant1[A](a)) +} + +func Set[S, A any](a A) func(Prism[S, A]) EM.Endomorphism[S] { + return EM.Curry3(prismModify[S, A])(F.Constant1[A](a)) +} + +func prismSome[A any]() Prism[O.Option[A], A] { + return MakePrism(F.Identity[O.Option[A]], O.Some[A]) +} + +// Some returns a `Prism` from a `Prism` focused on the `Some` of a `Option` type. +func Some[S, A any](soa Prism[S, O.Option[A]]) Prism[S, A] { + return Compose[S](prismSome[A]())(soa) +} + +func imap[S any, AB ~func(A) B, BA ~func(B) A, A, B any](sa Prism[S, A], ab AB, ba BA) Prism[S, B] { + return MakePrism( + F.Flow2(sa.GetOption, O.Map(ab)), + F.Flow2(ba, sa.ReverseGet), + ) +} + +func IMap[S any, AB ~func(A) B, BA ~func(B) A, A, B any](ab AB, ba BA) func(Prism[S, A]) Prism[S, B] { + return func(sa Prism[S, A]) Prism[S, B] { + return imap(sa, ab, ba) + } +} diff --git a/v2/optics/prism/prism_test.go b/v2/optics/prism/prism_test.go new file mode 100644 index 0000000..c5881ca --- /dev/null +++ b/v2/optics/prism/prism_test.go @@ -0,0 +1,260 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package prism + +import ( + "testing" + + F "github.com/IBM/fp-go/v2/function" + O "github.com/IBM/fp-go/v2/option" + "github.com/stretchr/testify/assert" +) + +func TestSome(t *testing.T) { + somePrism := MakePrism(F.Identity[O.Option[int]], O.Some[int]) + + assert.Equal(t, O.Some(1), somePrism.GetOption(O.Some(1))) +} + +func TestId(t *testing.T) { + idPrism := Id[int]() + + // GetOption always returns Some for identity + assert.Equal(t, O.Some(42), idPrism.GetOption(42)) + + // ReverseGet is identity + assert.Equal(t, 42, idPrism.ReverseGet(42)) +} + +func TestFromPredicate(t *testing.T) { + // Prism for positive numbers + positivePrism := FromPredicate(func(n int) bool { + return n > 0 + }) + + // Matches positive numbers + assert.Equal(t, O.Some(42), positivePrism.GetOption(42)) + assert.Equal(t, O.Some(1), positivePrism.GetOption(1)) + + // Doesn't match non-positive numbers + assert.Equal(t, O.None[int](), positivePrism.GetOption(0)) + assert.Equal(t, O.None[int](), positivePrism.GetOption(-5)) + + // ReverseGet always succeeds (doesn't check predicate) + assert.Equal(t, 42, positivePrism.ReverseGet(42)) + assert.Equal(t, -5, positivePrism.ReverseGet(-5)) +} + +func TestCompose(t *testing.T) { + // Prism for Some values + somePrism := MakePrism( + F.Identity[O.Option[int]], + O.Some[int], + ) + + // Prism for positive numbers + positivePrism := FromPredicate(func(n int) bool { + return n > 0 + }) + + // Compose: Option[int] -> int (if Some and positive) + composedPrism := F.Pipe1( + somePrism, + Compose[O.Option[int]](positivePrism), + ) + + // Test with Some positive + assert.Equal(t, O.Some(42), composedPrism.GetOption(O.Some(42))) + + // Test with Some non-positive + assert.Equal(t, O.None[int](), composedPrism.GetOption(O.Some(-5))) + + // Test with None + assert.Equal(t, O.None[int](), composedPrism.GetOption(O.None[int]())) + + // ReverseGet constructs Some + assert.Equal(t, O.Some(42), composedPrism.ReverseGet(42)) +} + +func TestSet(t *testing.T) { + // Prism for Some values + somePrism := MakePrism( + F.Identity[O.Option[int]], + O.Some[int], + ) + + // Set value when it matches + result := Set[O.Option[int], int](100)(somePrism)(O.Some(42)) + assert.Equal(t, O.Some(100), result) + + // No change when it doesn't match + result = Set[O.Option[int], int](100)(somePrism)(O.None[int]()) + assert.Equal(t, O.None[int](), result) +} + +func TestSomeFunction(t *testing.T) { + // Prism that focuses on an Option field + type Config struct { + Timeout O.Option[int] + } + + configPrism := MakePrism( + func(c Config) O.Option[O.Option[int]] { + return O.Some(c.Timeout) + }, + func(t O.Option[int]) Config { + return Config{Timeout: t} + }, + ) + + // Focus on the Some value + somePrism := Some(configPrism) + + // Extract from Some + config := Config{Timeout: O.Some(30)} + assert.Equal(t, O.Some(30), somePrism.GetOption(config)) + + // Extract from None + configNone := Config{Timeout: O.None[int]()} + assert.Equal(t, O.None[int](), somePrism.GetOption(configNone)) + + // ReverseGet constructs Config with Some + result := somePrism.ReverseGet(60) + assert.Equal(t, Config{Timeout: O.Some(60)}, result) +} + +func TestIMap(t *testing.T) { + // Prism for Some values + somePrism := MakePrism( + F.Identity[O.Option[int]], + O.Some[int], + ) + + // Map to string and back + stringPrism := F.Pipe1( + somePrism, + IMap[O.Option[int]]( + func(n int) string { + if n == 42 { + return "42" + } + return "100" + }, + func(s string) int { + if s == "42" { + return 42 + } + return 100 + }, + ), + ) + + // GetOption maps the value + result := stringPrism.GetOption(O.Some(42)) + assert.Equal(t, O.Some("42"), result) + + // GetOption on None + result = stringPrism.GetOption(O.None[int]()) + assert.Equal(t, O.None[string](), result) + + // ReverseGet maps back + opt := stringPrism.ReverseGet("100") + assert.Equal(t, O.Some(100), opt) +} + +func TestPrismLaws(t *testing.T) { + // Test prism laws with a simple prism + somePrism := MakePrism( + F.Identity[O.Option[int]], + O.Some[int], + ) + + // Law 1: GetOptionReverseGet + // prism.GetOption(prism.ReverseGet(a)) == Some(a) + a := 42 + result := somePrism.GetOption(somePrism.ReverseGet(a)) + assert.Equal(t, O.Some(a), result) + + // Law 2: ReverseGetGetOption + // if GetOption(s) == Some(a), then ReverseGet(a) should produce equivalent s + s := O.Some(42) + extracted := somePrism.GetOption(s) + if O.IsSome(extracted) { + reconstructed := somePrism.ReverseGet(O.GetOrElse(F.Constant(0))(extracted)) + assert.Equal(t, s, reconstructed) + } +} + +func TestPrismModifyOption(t *testing.T) { + // Test the internal prismModifyOption function through Set + somePrism := MakePrism( + F.Identity[O.Option[int]], + O.Some[int], + ) + + // Modify when match + setFn := Set[O.Option[int], int](100) + result := setFn(somePrism)(O.Some(42)) + assert.Equal(t, O.Some(100), result) + + // No modification when no match + result = setFn(somePrism)(O.None[int]()) + assert.Equal(t, O.None[int](), result) +} + +// Custom sum type for testing +type testResult interface{ isResult() } +type testSuccess struct{ Value int } +type testFailure struct{ Error string } + +func (testSuccess) isResult() {} +func (testFailure) isResult() {} + +func TestPrismWithCustomType(t *testing.T) { + // Create prism for Success variant + successPrism := MakePrism( + func(r testResult) O.Option[int] { + if s, ok := r.(testSuccess); ok { + return O.Some(s.Value) + } + return O.None[int]() + }, + func(v int) testResult { + return testSuccess{Value: v} + }, + ) + + // Test GetOption with Success + success := testSuccess{Value: 42} + assert.Equal(t, O.Some(42), successPrism.GetOption(success)) + + // Test GetOption with Failure + failure := testFailure{Error: "oops"} + assert.Equal(t, O.None[int](), successPrism.GetOption(failure)) + + // Test ReverseGet + result := successPrism.ReverseGet(100) + assert.Equal(t, testSuccess{Value: 100}, result) + + // Test Set with Success + setFn := Set[testResult, int](200) + updated := setFn(successPrism)(success) + assert.Equal(t, testSuccess{Value: 200}, updated) + + // Test Set with Failure (no change) + unchanged := setFn(successPrism)(failure) + assert.Equal(t, failure, unchanged) +} diff --git a/v2/optics/prism/traversal.go b/v2/optics/prism/traversal.go new file mode 100644 index 0000000..d13f5bf --- /dev/null +++ b/v2/optics/prism/traversal.go @@ -0,0 +1,46 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package prism + +import ( + F "github.com/IBM/fp-go/v2/function" + O "github.com/IBM/fp-go/v2/option" +) + +// AsTraversal converts a prism to a traversal +func AsTraversal[R ~func(func(A) HKTA) func(S) HKTS, S, A, HKTS, HKTA any]( + fof func(S) HKTS, + fmap func(HKTA, func(A) S) HKTS, +) func(Prism[S, A]) R { + return func(sa Prism[S, A]) R { + return func(f func(a A) HKTA) func(S) HKTS { + return func(s S) HKTS { + return F.Pipe2( + s, + sa.GetOption, + O.Fold( + F.Nullary2(F.Constant(s), fof), + func(a A) HKTS { + return fmap(f(a), func(a A) S { + return prismModify(F.Constant1[A](a), sa, s) + }) + }, + ), + ) + } + } + } +} diff --git a/v2/optics/traversal/array/array.go b/v2/optics/traversal/array/array.go new file mode 100644 index 0000000..56db5ea --- /dev/null +++ b/v2/optics/traversal/array/array.go @@ -0,0 +1,16 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package array diff --git a/v2/optics/traversal/array/const/traversal.go b/v2/optics/traversal/array/const/traversal.go new file mode 100644 index 0000000..9d36a7f --- /dev/null +++ b/v2/optics/traversal/array/const/traversal.go @@ -0,0 +1,28 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package identity + +import ( + C "github.com/IBM/fp-go/v2/constant" + M "github.com/IBM/fp-go/v2/monoid" + AR "github.com/IBM/fp-go/v2/optics/traversal/array/generic/const" + G "github.com/IBM/fp-go/v2/optics/traversal/generic" +) + +// FromArray returns a traversal from an array for the identity [Monoid] +func FromArray[E, A any](m M.Monoid[E]) G.Traversal[[]A, A, C.Const[E, []A], C.Const[E, A]] { + return AR.FromArray[[]A, E, A](m) +} diff --git a/v2/optics/traversal/array/generic/const/traversal.go b/v2/optics/traversal/array/generic/const/traversal.go new file mode 100644 index 0000000..cbf2bed --- /dev/null +++ b/v2/optics/traversal/array/generic/const/traversal.go @@ -0,0 +1,32 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + C "github.com/IBM/fp-go/v2/constant" + M "github.com/IBM/fp-go/v2/monoid" + AR "github.com/IBM/fp-go/v2/optics/traversal/array/generic" + G "github.com/IBM/fp-go/v2/optics/traversal/generic" +) + +// FromArray returns a traversal from an array for the const monad +func FromArray[GA ~[]A, E, A any](m M.Monoid[E]) G.Traversal[GA, A, C.Const[E, GA], C.Const[E, A]] { + return AR.FromArray[GA, GA, A, A, C.Const[E, A], C.Const[E, func(A) GA], C.Const[E, GA]]( + C.Of[E, GA](m), + C.Map[E, GA, func(A) GA], + C.Ap[E, A, GA](m), + ) +} diff --git a/v2/optics/traversal/array/generic/identity/traversal.go b/v2/optics/traversal/array/generic/identity/traversal.go new file mode 100644 index 0000000..7763fe8 --- /dev/null +++ b/v2/optics/traversal/array/generic/identity/traversal.go @@ -0,0 +1,31 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + I "github.com/IBM/fp-go/v2/identity" + AR "github.com/IBM/fp-go/v2/optics/traversal/array/generic" + G "github.com/IBM/fp-go/v2/optics/traversal/generic" +) + +// FromArray returns a traversal from an array for the identity monad +func FromArray[GA ~[]A, A any]() G.Traversal[GA, A, GA, A] { + return AR.FromArray[GA, GA, A, A, A, func(A) GA, GA]( + I.Of[GA], + I.Map[GA, func(A) GA], + I.Ap[GA, A], + ) +} diff --git a/v2/optics/traversal/array/generic/traversal.go b/v2/optics/traversal/array/generic/traversal.go new file mode 100644 index 0000000..c03531b --- /dev/null +++ b/v2/optics/traversal/array/generic/traversal.go @@ -0,0 +1,34 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + AR "github.com/IBM/fp-go/v2/internal/array" + G "github.com/IBM/fp-go/v2/optics/traversal/generic" +) + +// FromArray returns a traversal from an array +func FromArray[GA ~[]A, GB ~[]B, A, B, HKTB, HKTAB, HKTRB any]( + fof func(GB) HKTRB, + fmap func(func(GB) func(B) GB) func(HKTRB) HKTAB, + fap func(HKTB) func(HKTAB) HKTRB, +) G.Traversal[GA, A, HKTRB, HKTB] { + return func(f func(A) HKTB) func(s GA) HKTRB { + return func(s GA) HKTRB { + return AR.MonadTraverse(fof, fmap, fap, s, f) + } + } +} diff --git a/v2/optics/traversal/array/identity/traversal.go b/v2/optics/traversal/array/identity/traversal.go new file mode 100644 index 0000000..5b1d1c8 --- /dev/null +++ b/v2/optics/traversal/array/identity/traversal.go @@ -0,0 +1,26 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package identity + +import ( + AR "github.com/IBM/fp-go/v2/optics/traversal/array/generic/identity" + G "github.com/IBM/fp-go/v2/optics/traversal/generic" +) + +// FromArray returns a traversal from an array for the identity monad +func FromArray[A any]() G.Traversal[[]A, A, []A, A] { + return AR.FromArray[[]A, A]() +} diff --git a/v2/optics/traversal/either/traversal.go b/v2/optics/traversal/either/traversal.go new file mode 100644 index 0000000..631f688 --- /dev/null +++ b/v2/optics/traversal/either/traversal.go @@ -0,0 +1,34 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package either + +import ( + ET "github.com/IBM/fp-go/v2/either" + T "github.com/IBM/fp-go/v2/optics/traversal/generic" +) + +type ( + Traversal[E, S, A any] T.Traversal[S, A, ET.Either[E, S], ET.Either[E, A]] +) + +func Compose[ + E, S, A, B any](ab Traversal[E, A, B]) func(Traversal[E, S, A]) Traversal[E, S, B] { + return T.Compose[ + Traversal[E, A, B], + Traversal[E, S, A], + Traversal[E, S, B], + ](ab) +} diff --git a/v2/optics/traversal/generic/traversal.go b/v2/optics/traversal/generic/traversal.go new file mode 100644 index 0000000..730a857 --- /dev/null +++ b/v2/optics/traversal/generic/traversal.go @@ -0,0 +1,73 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + AR "github.com/IBM/fp-go/v2/array/generic" + C "github.com/IBM/fp-go/v2/constant" + F "github.com/IBM/fp-go/v2/function" +) + +type ( + Traversal[S, A, HKTS, HKTA any] func(func(A) HKTA) func(S) HKTS +) + +func Compose[ + TAB ~func(func(B) HKTB) func(A) HKTA, + TSA ~func(func(A) HKTA) func(S) HKTS, + TSB ~func(func(B) HKTB) func(S) HKTS, + S, A, B, HKTS, HKTA, HKTB any](ab TAB) func(TSA) TSB { + return func(sa TSA) TSB { + return F.Flow2(ab, sa) + } +} + +func FromTraversable[ + TAB ~func(func(A) HKTFA) func(HKTTA) HKTAA, + A, + HKTTA, + HKTFA, + HKTAA any]( + traverseF func(HKTTA, func(A) HKTFA) HKTAA, +) TAB { + return F.Bind1st(F.Bind2nd[HKTTA, func(A) HKTFA, HKTAA], traverseF) +} + +// FoldMap maps each target to a `Monoid` and combines the result +func FoldMap[M, S, A any](f func(A) M) func(sa Traversal[S, A, C.Const[M, S], C.Const[M, A]]) func(S) M { + return func(sa Traversal[S, A, C.Const[M, S], C.Const[M, A]]) func(S) M { + return F.Flow2( + F.Pipe1( + F.Flow2(f, C.Make[M, A]), + sa, + ), + C.Unwrap[M, S], + ) + } +} + +// Fold maps each target to a `Monoid` and combines the result +func Fold[S, A any](sa Traversal[S, A, C.Const[A, S], C.Const[A, A]]) func(S) A { + return FoldMap[A, S, A](F.Identity[A])(sa) +} + +// GetAll gets all the targets of a traversal +func GetAll[GA ~[]A, S, A any](s S) func(sa Traversal[S, A, C.Const[GA, S], C.Const[GA, A]]) GA { + fmap := FoldMap[GA, S, A](AR.Of[GA, A]) + return func(sa Traversal[S, A, C.Const[GA, S], C.Const[GA, A]]) GA { + return fmap(sa)(s) + } +} diff --git a/v2/optics/traversal/option/traversal.go b/v2/optics/traversal/option/traversal.go new file mode 100644 index 0000000..b5415e0 --- /dev/null +++ b/v2/optics/traversal/option/traversal.go @@ -0,0 +1,34 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package option + +import ( + T "github.com/IBM/fp-go/v2/optics/traversal/generic" + O "github.com/IBM/fp-go/v2/option" +) + +type ( + Traversal[S, A any] T.Traversal[S, A, O.Option[S], O.Option[A]] +) + +func Compose[ + S, A, B any](ab Traversal[A, B]) func(Traversal[S, A]) Traversal[S, B] { + return T.Compose[ + Traversal[A, B], + Traversal[S, A], + Traversal[S, B], + ](ab) +} diff --git a/v2/optics/traversal/record/const/traversal.go b/v2/optics/traversal/record/const/traversal.go new file mode 100644 index 0000000..34bca46 --- /dev/null +++ b/v2/optics/traversal/record/const/traversal.go @@ -0,0 +1,28 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + C "github.com/IBM/fp-go/v2/constant" + M "github.com/IBM/fp-go/v2/monoid" + G "github.com/IBM/fp-go/v2/optics/traversal/generic" + RR "github.com/IBM/fp-go/v2/optics/traversal/record/generic/const" +) + +// FromRecord returns a traversal from an array for the const monad +func FromRecord[E, K comparable, A any](m M.Monoid[E]) G.Traversal[map[K]A, A, C.Const[E, map[K]A], C.Const[E, A]] { + return RR.FromRecord[map[K]A, E, K, A](m) +} diff --git a/v2/optics/traversal/record/generic/const/traversal.go b/v2/optics/traversal/record/generic/const/traversal.go new file mode 100644 index 0000000..c71483a --- /dev/null +++ b/v2/optics/traversal/record/generic/const/traversal.go @@ -0,0 +1,32 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + C "github.com/IBM/fp-go/v2/constant" + M "github.com/IBM/fp-go/v2/monoid" + G "github.com/IBM/fp-go/v2/optics/traversal/generic" + RR "github.com/IBM/fp-go/v2/optics/traversal/record/generic" +) + +// FromRecord returns a traversal from an array for the const monad +func FromRecord[MA ~map[K]A, E, K comparable, A any](m M.Monoid[E]) G.Traversal[MA, A, C.Const[E, MA], C.Const[E, A]] { + return RR.FromRecord[MA, MA, K, A, A, C.Const[E, A], C.Const[E, func(A) MA], C.Const[E, MA]]( + C.Of[E, MA](m), + C.Map[E, MA, func(A) MA], + C.Ap[E, A, MA](m), + ) +} diff --git a/v2/optics/traversal/record/generic/identity/traversal.go b/v2/optics/traversal/record/generic/identity/traversal.go new file mode 100644 index 0000000..a6640c6 --- /dev/null +++ b/v2/optics/traversal/record/generic/identity/traversal.go @@ -0,0 +1,31 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + I "github.com/IBM/fp-go/v2/identity" + G "github.com/IBM/fp-go/v2/optics/traversal/generic" + RR "github.com/IBM/fp-go/v2/optics/traversal/record/generic" +) + +// FromRecord returns a traversal from a record for the identity monad +func FromRecord[MA ~map[K]A, K comparable, A any]() G.Traversal[MA, A, MA, A] { + return RR.FromRecord[MA, MA, K, A, A, A, func(A) MA, MA]( + I.Of[MA], + I.Map[MA, func(A) MA], + I.Ap[MA, A], + ) +} diff --git a/v2/optics/traversal/record/generic/traversal.go b/v2/optics/traversal/record/generic/traversal.go new file mode 100644 index 0000000..3663e3c --- /dev/null +++ b/v2/optics/traversal/record/generic/traversal.go @@ -0,0 +1,34 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + R "github.com/IBM/fp-go/v2/internal/record" + G "github.com/IBM/fp-go/v2/optics/traversal/generic" +) + +// FromRecord returns a traversal from a record +func FromRecord[MA ~map[K]A, MB ~map[K]B, K comparable, A, B, HKTB, HKTAB, HKTRB any]( + fof func(MB) HKTRB, + fmap func(func(MB) func(B) MB) func(HKTRB) HKTAB, + fap func(HKTB) func(HKTAB) HKTRB, +) G.Traversal[MA, A, HKTRB, HKTB] { + return func(f func(A) HKTB) func(s MA) HKTRB { + return func(s MA) HKTRB { + return R.MonadTraverse(fof, fmap, fap, s, f) + } + } +} diff --git a/v2/optics/traversal/record/identity/traversal.go b/v2/optics/traversal/record/identity/traversal.go new file mode 100644 index 0000000..b4d66b1 --- /dev/null +++ b/v2/optics/traversal/record/identity/traversal.go @@ -0,0 +1,26 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package identity + +import ( + G "github.com/IBM/fp-go/v2/optics/traversal/generic" + RR "github.com/IBM/fp-go/v2/optics/traversal/record/generic/identity" +) + +// FromRecord returns a traversal from an array for the identity monad +func FromRecord[K comparable, A any]() G.Traversal[map[K]A, A, map[K]A, A] { + return RR.FromRecord[map[K]A, K, A]() +} diff --git a/v2/optics/traversal/record/traversal.go b/v2/optics/traversal/record/traversal.go new file mode 100644 index 0000000..f5e3652 --- /dev/null +++ b/v2/optics/traversal/record/traversal.go @@ -0,0 +1,16 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package record diff --git a/v2/optics/traversal/traversal.go b/v2/optics/traversal/traversal.go new file mode 100644 index 0000000..bb716c6 --- /dev/null +++ b/v2/optics/traversal/traversal.go @@ -0,0 +1,66 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package traversal + +import ( + C "github.com/IBM/fp-go/v2/constant" + F "github.com/IBM/fp-go/v2/function" + G "github.com/IBM/fp-go/v2/optics/traversal/generic" +) + +// Id is the identity constructor of a traversal +func Id[S, A any]() G.Traversal[S, S, A, A] { + return F.Identity[func(S) A] +} + +// Modify applies a transformation function to a traversal +func Modify[S, A any](f func(A) A) func(sa G.Traversal[S, A, S, A]) func(S) S { + return func(sa G.Traversal[S, A, S, A]) func(S) S { + return sa(f) + } +} + +// Set sets a constant value for all values of the traversal +func Set[S, A any](a A) func(sa G.Traversal[S, A, S, A]) func(S) S { + return Modify[S, A](F.Constant1[A](a)) +} + +// FoldMap maps each target to a `Monoid` and combines the result +func FoldMap[M, S, A any](f func(A) M) func(sa G.Traversal[S, A, C.Const[M, S], C.Const[M, A]]) func(S) M { + return G.FoldMap[M, S, A](f) +} + +// Fold maps each target to a `Monoid` and combines the result +func Fold[S, A any](sa G.Traversal[S, A, C.Const[A, S], C.Const[A, A]]) func(S) A { + return G.Fold[S, A](sa) +} + +// GetAll gets all the targets of a traversal +func GetAll[S, A any](s S) func(sa G.Traversal[S, A, C.Const[[]A, S], C.Const[[]A, A]]) []A { + return G.GetAll[[]A, S, A](s) +} + +// Compose composes two traversables +func Compose[ + S, A, B, HKTS, HKTA, HKTB any](ab G.Traversal[A, B, HKTA, HKTB]) func(sa G.Traversal[S, A, HKTS, HKTA]) G.Traversal[S, B, HKTS, HKTB] { + return G.Compose[ + G.Traversal[A, B, HKTA, HKTB], + G.Traversal[S, A, HKTS, HKTA], + G.Traversal[S, B, HKTS, HKTB], + S, A, B, + HKTS, HKTA, HKTB, + ](ab) +} diff --git a/v2/optics/traversal/traversal_test.go b/v2/optics/traversal/traversal_test.go new file mode 100644 index 0000000..8de88d8 --- /dev/null +++ b/v2/optics/traversal/traversal_test.go @@ -0,0 +1,79 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package traversal + +import ( + "testing" + + AR "github.com/IBM/fp-go/v2/array" + C "github.com/IBM/fp-go/v2/constant" + F "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/internal/utils" + N "github.com/IBM/fp-go/v2/number" + AT "github.com/IBM/fp-go/v2/optics/traversal/array/const" + AI "github.com/IBM/fp-go/v2/optics/traversal/array/identity" + "github.com/stretchr/testify/assert" +) + +func TestGetAll(t *testing.T) { + + as := AR.From(1, 2, 3) + + tr := AT.FromArray[[]int, int](AR.Monoid[int]()) + + sa := F.Pipe1( + Id[[]int, C.Const[[]int, []int]](), + Compose[[]int, []int, int, C.Const[[]int, []int]](tr), + ) + + getall := GetAll[[]int, int](as)(sa) + + assert.Equal(t, AR.From(1, 2, 3), getall) +} + +func TestFold(t *testing.T) { + + monoidSum := N.MonoidSum[int]() + + as := AR.From(1, 2, 3) + + tr := AT.FromArray[int, int](monoidSum) + + sa := F.Pipe1( + Id[[]int, C.Const[int, []int]](), + Compose[[]int, []int, int, C.Const[int, []int]](tr), + ) + + folded := Fold[[]int, int](sa)(as) + + assert.Equal(t, 6, folded) +} + +func TestTraverse(t *testing.T) { + + as := AR.From(1, 2, 3) + + tr := AI.FromArray[int]() + + sa := F.Pipe1( + Id[[]int, []int](), + Compose[[]int, []int, int, []int, []int, int](tr), + ) + + res := sa(utils.Double)(as) + + assert.Equal(t, AR.From(2, 4, 6), res) +} diff --git a/v2/option/apply.go b/v2/option/apply.go new file mode 100644 index 0000000..43946df --- /dev/null +++ b/v2/option/apply.go @@ -0,0 +1,47 @@ +// Copyright (c) 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package option + +import ( + M "github.com/IBM/fp-go/v2/monoid" + S "github.com/IBM/fp-go/v2/semigroup" +) + +// ApplySemigroup lifts a Semigroup over a type A to a Semigroup over Option[A]. +// The resulting semigroup combines two Options using the applicative functor pattern. +// +// Example: +// +// intSemigroup := semigroup.MakeSemigroup(func(a, b int) int { return a + b }) +// optSemigroup := ApplySemigroup(intSemigroup) +// result := optSemigroup.Concat(Some(2), Some(3)) // Some(5) +// result := optSemigroup.Concat(Some(2), None[int]()) // None +func ApplySemigroup[A any](s S.Semigroup[A]) S.Semigroup[Option[A]] { + return S.ApplySemigroup(MonadMap[A, func(A) A], MonadAp[A, A], s) +} + +// ApplicativeMonoid returns a Monoid that concatenates Option instances via their applicative functor. +// This combines the monoid structure of the underlying type with the Option structure. +// +// Example: +// +// intMonoid := monoid.MakeMonoid(func(a, b int) int { return a + b }, 0) +// optMonoid := ApplicativeMonoid(intMonoid) +// result := optMonoid.Concat(Some(2), Some(3)) // Some(5) +// result := optMonoid.Empty() // Some(0) +func ApplicativeMonoid[A any](m M.Monoid[A]) M.Monoid[Option[A]] { + return M.ApplicativeMonoid(Of[A], MonadMap[A, func(A) A], MonadAp[A, A], m) +} diff --git a/v2/option/array.go b/v2/option/array.go new file mode 100644 index 0000000..1b9853c --- /dev/null +++ b/v2/option/array.go @@ -0,0 +1,141 @@ +// Copyright (c) 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package option + +import ( + F "github.com/IBM/fp-go/v2/function" + RA "github.com/IBM/fp-go/v2/internal/array" +) + +// TraverseArrayG transforms an array by applying a function that returns an Option to each element. +// Returns Some containing the array of results if all operations succeed, None if any fails. +// This is the generic version that works with custom slice types. +// +// Example: +// +// parse := func(s string) Option[int] { +// n, err := strconv.Atoi(s) +// if err != nil { return None[int]() } +// return Some(n) +// } +// result := TraverseArrayG[[]string, []int](parse)([]string{"1", "2", "3"}) // Some([1, 2, 3]) +// result := TraverseArrayG[[]string, []int](parse)([]string{"1", "x", "3"}) // None +func TraverseArrayG[GA ~[]A, GB ~[]B, A, B any](f func(A) Option[B]) func(GA) Option[GB] { + return RA.Traverse[GA]( + Of[GB], + Map[GB, func(B) GB], + Ap[GB, B], + + f, + ) +} + +// TraverseArray transforms an array by applying a function that returns an Option to each element. +// Returns Some containing the array of results if all operations succeed, None if any fails. +// +// Example: +// +// validate := func(x int) Option[int] { +// if x > 0 { return Some(x * 2) } +// return None[int]() +// } +// result := TraverseArray(validate)([]int{1, 2, 3}) // Some([2, 4, 6]) +// result := TraverseArray(validate)([]int{1, -1, 3}) // None +func TraverseArray[A, B any](f func(A) Option[B]) func([]A) Option[[]B] { + return TraverseArrayG[[]A, []B](f) +} + +// TraverseArrayWithIndexG transforms an array by applying an indexed function that returns an Option. +// The function receives both the index and the element. +// This is the generic version that works with custom slice types. +// +// Example: +// +// f := func(i int, s string) Option[string] { +// return Some(fmt.Sprintf("%d:%s", i, s)) +// } +// result := TraverseArrayWithIndexG[[]string, []string](f)([]string{"a", "b"}) // Some(["0:a", "1:b"]) +func TraverseArrayWithIndexG[GA ~[]A, GB ~[]B, A, B any](f func(int, A) Option[B]) func(GA) Option[GB] { + return RA.TraverseWithIndex[GA]( + Of[GB], + Map[GB, func(B) GB], + Ap[GB, B], + + f, + ) +} + +// TraverseArrayWithIndex transforms an array by applying an indexed function that returns an Option. +// The function receives both the index and the element. +// +// Example: +// +// f := func(i int, x int) Option[int] { +// if x > i { return Some(x) } +// return None[int]() +// } +// result := TraverseArrayWithIndex(f)([]int{1, 2, 3}) // Some([1, 2, 3]) +func TraverseArrayWithIndex[A, B any](f func(int, A) Option[B]) func([]A) Option[[]B] { + return TraverseArrayWithIndexG[[]A, []B](f) +} + +// SequenceArrayG converts an array of Options into an Option of an array. +// Returns Some containing all values if all Options are Some, None if any is None. +// This is the generic version that works with custom slice types. +// +// Example: +// +// type MySlice []int +// result := SequenceArrayG[MySlice]([]Option[int]{Some(1), Some(2)}) // Some(MySlice{1, 2}) +// result := SequenceArrayG[MySlice]([]Option[int]{Some(1), None[int]()}) // None +func SequenceArrayG[GA ~[]A, GOA ~[]Option[A], A any](ma GOA) Option[GA] { + return TraverseArrayG[GOA, GA](F.Identity[Option[A]])(ma) +} + +// SequenceArray converts an array of Options into an Option of an array. +// Returns Some containing all values if all Options are Some, None if any is None. +// +// Example: +// +// result := SequenceArray([]Option[int]{Some(1), Some(2), Some(3)}) // Some([1, 2, 3]) +// result := SequenceArray([]Option[int]{Some(1), None[int](), Some(3)}) // None +func SequenceArray[A any](ma []Option[A]) Option[[]A] { + return SequenceArrayG[[]A](ma) +} + +// CompactArrayG filters an array of Options, keeping only the Some values and discarding None values. +// This is the generic version that works with custom slice types. +// +// Example: +// +// type MySlice []int +// input := []Option[int]{Some(1), None[int](), Some(3)} +// result := CompactArrayG[[]Option[int], MySlice](input) // MySlice{1, 3} +func CompactArrayG[A1 ~[]Option[A], A2 ~[]A, A any](fa A1) A2 { + return RA.Reduce(fa, func(out A2, value Option[A]) A2 { + return MonadFold(value, F.Constant(out), F.Bind1st(RA.Append[A2, A], out)) + }, make(A2, 0, len(fa))) +} + +// CompactArray filters an array of Options, keeping only the Some values and discarding None values. +// +// Example: +// +// input := []Option[int]{Some(1), None[int](), Some(3), Some(5), None[int]()} +// result := CompactArray(input) // [1, 3, 5] +func CompactArray[A any](fa []Option[A]) []A { + return CompactArrayG[[]Option[A], []A](fa) +} diff --git a/v2/option/array_test.go b/v2/option/array_test.go new file mode 100644 index 0000000..a9090f3 --- /dev/null +++ b/v2/option/array_test.go @@ -0,0 +1,50 @@ +// Copyright (c) 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package option + +import ( + "fmt" + "testing" + + TST "github.com/IBM/fp-go/v2/internal/testing" + "github.com/stretchr/testify/assert" +) + +func TestCompactArray(t *testing.T) { + ar := []Option[string]{ + Of("ok"), + None[string](), + Of("ok"), + } + + res := CompactArray(ar) + assert.Equal(t, 2, len(res)) +} + +func TestSequenceArray(t *testing.T) { + + s := TST.SequenceArrayTest( + FromStrictEquals[bool](), + Pointed[string](), + Pointed[bool](), + Functor[[]string, bool](), + SequenceArray[string], + ) + + for i := 0; i < 10; i++ { + t.Run(fmt.Sprintf("TestSequenceArray %d", i), s(i)) + } +} diff --git a/v2/option/bind.go b/v2/option/bind.go new file mode 100644 index 0000000..0872766 --- /dev/null +++ b/v2/option/bind.go @@ -0,0 +1,150 @@ +// Copyright (c) 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package option + +import ( + A "github.com/IBM/fp-go/v2/internal/apply" + C "github.com/IBM/fp-go/v2/internal/chain" + F "github.com/IBM/fp-go/v2/internal/functor" +) + +// Do creates an empty context of type S to be used with the Bind operation. +// This is the starting point for building up a context using do-notation style. +// +// Example: +// +// type Result struct { +// x int +// y string +// } +// result := Do(Result{}) +func Do[S any]( + empty S, +) Option[S] { + return Of(empty) +} + +// Bind attaches the result of a computation to a context S1 to produce a context S2. +// This is used in do-notation style to sequentially build up a context. +// +// Example: +// +// type State struct { x int; y int } +// result := F.Pipe2( +// Do(State{}), +// Bind(func(x int) func(State) State { +// return func(s State) State { s.x = x; return s } +// }, func(s State) Option[int] { return Some(42) }), +// ) +func Bind[S1, S2, A any]( + setter func(A) func(S1) S2, + f func(S1) Option[A], +) func(Option[S1]) Option[S2] { + return C.Bind( + Chain[S1, S2], + Map[A, S2], + setter, + f, + ) +} + +// Let attaches the result of a pure computation to a context S1 to produce a context S2. +// Unlike Bind, the computation function returns a plain value, not an Option. +// +// Example: +// +// type State struct { x int; computed int } +// result := F.Pipe2( +// Do(State{x: 5}), +// Let(func(c int) func(State) State { +// return func(s State) State { s.computed = c; return s } +// }, func(s State) int { return s.x * 2 }), +// ) +func Let[S1, S2, B any]( + key func(B) func(S1) S2, + f func(S1) B, +) func(Option[S1]) Option[S2] { + return F.Let( + Map[S1, S2], + key, + f, + ) +} + +// LetTo attaches a constant value to a context S1 to produce a context S2. +// +// Example: +// +// type State struct { x int; name string } +// result := F.Pipe2( +// Do(State{x: 5}), +// LetTo(func(n string) func(State) State { +// return func(s State) State { s.name = n; return s } +// }, "example"), +// ) +func LetTo[S1, S2, B any]( + key func(B) func(S1) S2, + b B, +) func(Option[S1]) Option[S2] { + return F.LetTo( + Map[S1, S2], + key, + b, + ) +} + +// BindTo initializes a new state S1 from a value T. +// This is typically used as the first operation after creating an Option value. +// +// Example: +// +// type State struct { value int } +// result := F.Pipe1( +// Some(42), +// BindTo(func(x int) State { return State{value: x} }), +// ) +func BindTo[S1, T any]( + setter func(T) S1, +) func(Option[T]) Option[S1] { + return C.BindTo( + Map[T, S1], + setter, + ) +} + +// ApS attaches a value to a context S1 to produce a context S2 by considering the context and the value concurrently. +// This uses the applicative functor pattern, allowing parallel composition. +// +// Example: +// +// type State struct { x int; y int } +// result := F.Pipe2( +// Do(State{}), +// ApS(func(x int) func(State) State { +// return func(s State) State { s.x = x; return s } +// }, Some(42)), +// ) +func ApS[S1, S2, T any]( + setter func(T) func(S1) S2, + fa Option[T], +) func(Option[S1]) Option[S2] { + return A.ApS( + Ap[S2, T], + Map[S1, func(T) S2], + setter, + fa, + ) +} diff --git a/v2/option/bind_test.go b/v2/option/bind_test.go new file mode 100644 index 0000000..2c37907 --- /dev/null +++ b/v2/option/bind_test.go @@ -0,0 +1,56 @@ +// Copyright (c) 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package option + +import ( + "testing" + + F "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/internal/utils" + "github.com/stretchr/testify/assert" +) + +func getLastName(s utils.Initial) Option[string] { + return Of("Doe") +} + +func getGivenName(s utils.WithLastName) Option[string] { + return Of("John") +} + +func TestBind(t *testing.T) { + + res := F.Pipe3( + Do(utils.Empty), + Bind(utils.SetLastName, getLastName), + Bind(utils.SetGivenName, getGivenName), + Map(utils.GetFullName), + ) + + assert.Equal(t, res, Of("John Doe")) +} + +func TestApS(t *testing.T) { + + res := F.Pipe3( + Do(utils.Empty), + ApS(utils.SetLastName, Of("Doe")), + ApS(utils.SetGivenName, Of("John")), + Map(utils.GetFullName), + ) + + assert.Equal(t, res, Of("John Doe")) +} diff --git a/v2/option/core.go b/v2/option/core.go new file mode 100644 index 0000000..c0f144e --- /dev/null +++ b/v2/option/core.go @@ -0,0 +1,190 @@ +// Copyright (c) 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package option + +import ( + "bytes" + "encoding/json" + "fmt" + "reflect" +) + +var ( + // jsonNull is the cached representation of the `null` serialization in JSON + jsonNull = []byte("null") +) + +// Option defines a data structure that logically holds a value or not. +// It represents an optional value: every Option is either Some and contains a value, +// or None, and does not contain a value. +// +// Option is commonly used to represent the result of operations that may fail, +// as an alternative to returning nil pointers or using error values. +// +// Example: +// +// var opt Option[int] = Some(42) // Contains a value +// var opt Option[int] = None[int]() // Contains no value +type Option[A any] struct { + isSome bool + value A +} + +// optString prints some debug info for the object +// +//go:noinline +func optString(isSome bool, value any) string { + if isSome { + return fmt.Sprintf("Some[%T](%v)", value, value) + } + return fmt.Sprintf("None[%T]", value) +} + +// optFormat prints some debug info for the object +// +//go:noinline +func optFormat(isSome bool, value any, f fmt.State, c rune) { + switch c { + case 's': + fmt.Fprint(f, optString(isSome, value)) + default: + fmt.Fprint(f, optString(isSome, value)) + } +} + +// String prints some debug info for the object +func (s Option[A]) String() string { + return optString(s.isSome, s.value) +} + +// Format prints some debug info for the object +func (s Option[A]) Format(f fmt.State, c rune) { + optFormat(s.isSome, s.value, f, c) +} + +func optMarshalJSON(isSome bool, value any) ([]byte, error) { + if isSome { + return json.Marshal(value) + } + return jsonNull, nil +} + +func (s Option[A]) MarshalJSON() ([]byte, error) { + return optMarshalJSON(s.isSome, s.value) +} + +// optUnmarshalJSON unmarshals the [Option] from a JSON string +// +//go:noinline +func optUnmarshalJSON(isSome *bool, value any, data []byte) error { + // decode the value + if bytes.Equal(data, jsonNull) { + *isSome = false + reflect.ValueOf(value).Elem().SetZero() + return nil + } + *isSome = true + return json.Unmarshal(data, value) +} + +func (s *Option[A]) UnmarshalJSON(data []byte) error { + return optUnmarshalJSON(&s.isSome, &s.value, data) +} + +// IsNone checks if an Option is None (contains no value). +// +// Example: +// +// opt := None[int]() +// IsNone(opt) // true +// opt := Some(42) +// IsNone(opt) // false +func IsNone[T any](val Option[T]) bool { + return !val.isSome +} + +// Some creates an Option that contains a value. +// +// Example: +// +// opt := Some(42) // Option containing 42 +// opt := Some("hello") // Option containing "hello" +func Some[T any](value T) Option[T] { + return Option[T]{isSome: true, value: value} +} + +// Of creates an Option that contains a value. +// This is an alias for Some and is used in monadic contexts. +// +// Example: +// +// opt := Of(42) // Option containing 42 +func Of[T any](value T) Option[T] { + return Some(value) +} + +// None creates an Option that contains no value. +// +// Example: +// +// opt := None[int]() // Empty Option of type int +// opt := None[string]() // Empty Option of type string +func None[T any]() Option[T] { + return Option[T]{isSome: false} +} + +// IsSome checks if an Option contains a value. +// +// Example: +// +// opt := Some(42) +// IsSome(opt) // true +// opt := None[int]() +// IsSome(opt) // false +func IsSome[T any](val Option[T]) bool { + return val.isSome +} + +// MonadFold performs a fold operation on an Option. +// If the Option is Some, applies onSome to the value. +// If the Option is None, calls onNone. +// +// Example: +// +// opt := Some(42) +// result := MonadFold(opt, +// func() string { return "no value" }, +// func(x int) string { return fmt.Sprintf("value: %d", x) }, +// ) // "value: 42" +func MonadFold[A, B any](ma Option[A], onNone func() B, onSome func(A) B) B { + if IsSome(ma) { + return onSome(ma.value) + } + return onNone() +} + +// Unwrap extracts the value and presence flag from an Option. +// Returns the value and true if Some, or zero value and false if None. +// +// Example: +// +// opt := Some(42) +// val, ok := Unwrap(opt) // val = 42, ok = true +// opt := None[int]() +// val, ok := Unwrap(opt) // val = 0, ok = false +func Unwrap[A any](ma Option[A]) (A, bool) { + return ma.value, ma.isSome +} diff --git a/v2/option/doc.go b/v2/option/doc.go new file mode 100644 index 0000000..b702482 --- /dev/null +++ b/v2/option/doc.go @@ -0,0 +1,119 @@ +// Copyright (c) 2025 IBM Corp. +// All rights reserved. +// +// 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. + +// Package option defines the Option data structure and its monadic operations. +// +// Option represents an optional value: every Option is either Some and contains a value, +// or None, and does not contain a value. This is a type-safe alternative to using nil +// pointers or special sentinel values to represent the absence of a value. +// +// # Basic Usage +// +// Create an Option with Some or None: +// +// opt := Some(42) // Option containing 42 +// opt := None[int]() // Empty Option +// opt := Of(42) // Alternative to Some +// +// Check if an Option contains a value: +// +// if IsSome(opt) { +// // opt contains a value +// } +// if IsNone(opt) { +// // opt is empty +// } +// +// Extract values: +// +// value, ok := Unwrap(opt) // Returns (value, true) or (zero, false) +// value := GetOrElse(func() int { return 0 })(opt) // Returns value or default +// +// # Transformations +// +// Map transforms the contained value: +// +// result := Map(func(x int) string { +// return fmt.Sprintf("%d", x) +// })(Some(42)) // Some("42") +// +// Chain sequences operations that may fail: +// +// result := Chain(func(x int) Option[int] { +// if x > 0 { return Some(x * 2) } +// return None[int]() +// })(Some(5)) // Some(10) +// +// Filter keeps values that satisfy a predicate: +// +// result := Filter(func(x int) bool { +// return x > 0 +// })(Some(5)) // Some(5) +// +// # Working with Collections +// +// Transform arrays: +// +// result := TraverseArray(func(x int) Option[int] { +// if x > 0 { return Some(x * 2) } +// return None[int]() +// })([]int{1, 2, 3}) // Some([2, 4, 6]) +// +// Sequence arrays of Options: +// +// result := SequenceArray([]Option[int]{ +// Some(1), Some(2), Some(3), +// }) // Some([1, 2, 3]) +// +// Compact arrays (remove None values): +// +// result := CompactArray([]Option[int]{ +// Some(1), None[int](), Some(3), +// }) // [1, 3] +// +// # Algebraic Operations +// +// Option supports various algebraic structures: +// +// - Functor: Map operations +// - Applicative: Ap operations for applying wrapped functions +// - Monad: Chain operations for sequencing computations +// - Eq: Equality comparison +// - Ord: Ordering comparison +// - Semigroup/Monoid: Combining Options +// +// # Error Handling +// +// Convert error-returning functions: +// +// result := TryCatch(func() (int, error) { +// return strconv.Atoi("42") +// }) // Some(42) +// +// Convert validation functions: +// +// parse := FromValidation(func(s string) (int, bool) { +// n, err := strconv.Atoi(s) +// return n, err == nil +// }) +// result := parse("42") // Some(42) +// +// # Subpackages +// +// - option/number: Number conversion utilities (Atoi, Itoa) +// - option/testing: Testing utilities for verifying monad laws +package option + +//go:generate go run .. option --count 10 --filename gen.go diff --git a/v2/option/eq.go b/v2/option/eq.go new file mode 100644 index 0000000..e9117e9 --- /dev/null +++ b/v2/option/eq.go @@ -0,0 +1,56 @@ +// Copyright (c) 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package option + +import ( + EQ "github.com/IBM/fp-go/v2/eq" + F "github.com/IBM/fp-go/v2/function" +) + +// Eq constructs an equality predicate for Option[A] given an equality predicate for A. +// Two Options are equal if: +// - Both are None, or +// - Both are Some and their contained values are equal according to the provided Eq +// +// Example: +// +// intEq := eq.FromStrictEquals[int]() +// optEq := Eq(intEq) +// optEq.Equals(Some(42), Some(42)) // true +// optEq.Equals(Some(42), Some(43)) // false +// optEq.Equals(None[int](), None[int]()) // true +// optEq.Equals(Some(42), None[int]()) // false +func Eq[A any](a EQ.Eq[A]) EQ.Eq[Option[A]] { + // some convenient shortcuts + fld := Fold( + F.Constant(Fold(F.ConstTrue, F.Constant1[A](false))), + F.Flow2(F.Curry2(a.Equals), F.Bind1st(Fold[A, bool], F.ConstFalse)), + ) + // convert to an equals predicate + return EQ.FromEquals(F.Uncurry2(fld)) +} + +// FromStrictEquals constructs an Eq for Option[A] using Go's built-in equality (==) for type A. +// This is a convenience function for comparable types. +// +// Example: +// +// optEq := FromStrictEquals[int]() +// optEq.Equals(Some(42), Some(42)) // true +// optEq.Equals(None[int](), None[int]()) // true +func FromStrictEquals[A comparable]() EQ.Eq[Option[A]] { + return Eq(EQ.FromStrictEquals[A]()) +} diff --git a/v2/option/eq_test.go b/v2/option/eq_test.go new file mode 100644 index 0000000..1efa8f8 --- /dev/null +++ b/v2/option/eq_test.go @@ -0,0 +1,41 @@ +// Copyright (c) 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package option + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestEq(t *testing.T) { + + r1 := Of(1) + r2 := Of(1) + r3 := Of(2) + + n1 := None[int]() + + eq := FromStrictEquals[int]() + + assert.True(t, eq.Equals(r1, r1)) + assert.True(t, eq.Equals(r1, r2)) + assert.False(t, eq.Equals(r1, r3)) + assert.False(t, eq.Equals(r1, n1)) + + assert.True(t, eq.Equals(n1, n1)) + assert.False(t, eq.Equals(n1, r2)) +} diff --git a/v2/option/examples_create_test.go b/v2/option/examples_create_test.go new file mode 100644 index 0000000..c85e3e3 --- /dev/null +++ b/v2/option/examples_create_test.go @@ -0,0 +1,55 @@ +// Copyright (c) 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package option + +import "fmt" + +func ExampleOption_creation() { + + // Build an Option + none1 := None[int]() + some1 := Some("value") + + // Build from a value + fromNillable := FromNillable[string] + nonFromNil := fromNillable(nil) // None[*string] + value := "value" + someFromPointer := fromNillable(&value) // Some[*string](xxx) + + // some predicate + isEven := func(num int) bool { + return num%2 == 0 + } + + fromEven := FromPredicate(isEven) + noneFromPred := fromEven(3) // None[int] + someFromPred := fromEven(4) // Some[int](4) + + fmt.Println(none1) + fmt.Println(some1) + fmt.Println(nonFromNil) + fmt.Println(IsSome(someFromPointer)) + fmt.Println(noneFromPred) + fmt.Println(someFromPred) + + // Output: + // None[int] + // Some[string](value) + // None[*string] + // true + // None[int] + // Some[int](4) +} diff --git a/v2/option/examples_extract_test.go b/v2/option/examples_extract_test.go new file mode 100644 index 0000000..b69da85 --- /dev/null +++ b/v2/option/examples_extract_test.go @@ -0,0 +1,73 @@ +// Copyright (c) 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package option + +import ( + "fmt" + + F "github.com/IBM/fp-go/v2/function" + N "github.com/IBM/fp-go/v2/number" +) + +func ExampleOption_extraction() { + + noneValue := None[int]() + someValue := Of(42) + + // Convert Option[T] to T + fromNone, okFromNone := Unwrap(noneValue) // 0, false + fromSome, okFromSome := Unwrap(someValue) // 42, true + + // Convert Option[T] with a default value + noneWithDefault := GetOrElse(F.Constant(0))(noneValue) // 0 + someWithDefault := GetOrElse(F.Constant(0))(someValue) // 42 + + // Apply a different function on None/Some(...) + doubleOrZero := Fold( + F.Constant(0), // none case + N.Mul(2), // some case + ) // func(ma Option[int]) int + + doubleFromNone := doubleOrZero(noneValue) // 0 + doubleFromSome := doubleOrZero(someValue) // 84 + + // Pro-tip: Fold is short for the following: + doubleOfZeroBis := F.Flow2( + Map(N.Mul(2)), // some case + GetOrElse(F.Constant(0)), // none case + ) + doubleFromNoneBis := doubleOfZeroBis(noneValue) // 0 + doubleFromSomeBis := doubleOfZeroBis(someValue) // 84 + + fmt.Printf("%d, %t\n", fromNone, okFromNone) + fmt.Printf("%d, %t\n", fromSome, okFromSome) + fmt.Println(noneWithDefault) + fmt.Println(someWithDefault) + fmt.Println(doubleFromNone) + fmt.Println(doubleFromSome) + fmt.Println(doubleFromNoneBis) + fmt.Println(doubleFromSomeBis) + + // Output: + // 0, false + // 42, true + // 0 + // 42 + // 0 + // 84 + // 0 + // 84 +} diff --git a/v2/option/functor.go b/v2/option/functor.go new file mode 100644 index 0000000..12f0ca6 --- /dev/null +++ b/v2/option/functor.go @@ -0,0 +1,39 @@ +// Copyright (c) 2024 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package option + +import ( + "github.com/IBM/fp-go/v2/internal/functor" +) + +type optionFunctor[A, B any] struct{} + +func (o *optionFunctor[A, B]) Map(f func(A) B) func(Option[A]) Option[B] { + return Map[A, B](f) +} + +// Functor implements the functoric operations for Option. +// A functor is a type that can be mapped over, transforming the contained value +// while preserving the structure. +// +// Example: +// +// f := Functor[int, string]() +// mapper := f.Map(func(x int) string { return fmt.Sprintf("%d", x) }) +// result := mapper(Some(42)) // Some("42") +func Functor[A, B any]() functor.Functor[A, B, Option[A], Option[B]] { + return &optionFunctor[A, B]{} +} diff --git a/v2/option/gen.go b/v2/option/gen.go new file mode 100644 index 0000000..451de3b --- /dev/null +++ b/v2/option/gen.go @@ -0,0 +1,689 @@ +// Code generated by go generate; DO NOT EDIT. +// This file was generated by robots at +// 2025-03-09 23:53:08.2750287 +0100 CET m=+0.001545801 + +package option + + +import ( + A "github.com/IBM/fp-go/v2/internal/apply" + T "github.com/IBM/fp-go/v2/tuple" +) +// optionize converts a nullary function to an option +func optionize[R any](f func() (R, bool)) Option[R] { + if r, ok := f(); ok { + return Some(r) + } + return None[R]() +} + +// Optionize0 converts a function with 0 parameters returning a tuple of a return value R and a boolean into a function with 0 parameters returning an Option[R] +func Optionize0[F ~func() (R, bool), R any](f F) func() Option[R] { + return func() Option[R] { + return optionize(func() (R, bool) { + return f() + }) + } +} + +// Unoptionize0 converts a function with 0 parameters returning a tuple of a return value R and a boolean into a function with 0 parameters returning an Option[R] +func Unoptionize0[F ~func() Option[R], R any](f F) func() (R, bool) { + return func() (R, bool) { + return Unwrap(f()) + } +} + +// Optionize1 converts a function with 1 parameters returning a tuple of a return value R and a boolean into a function with 1 parameters returning an Option[R] +func Optionize1[F ~func(T0) (R, bool), T0, R any](f F) func(T0) Option[R] { + return func(t0 T0) Option[R] { + return optionize(func() (R, bool) { + return f(t0) + }) + } +} + +// Unoptionize1 converts a function with 1 parameters returning a tuple of a return value R and a boolean into a function with 1 parameters returning an Option[R] +func Unoptionize1[F ~func(T0) Option[R], T0, R any](f F) func(T0) (R, bool) { + return func(t0 T0) (R, bool) { + return Unwrap(f(t0)) + } +} + +// SequenceT1 converts 1 parameters of [Option[T]] into a [Option[Tuple1]]. +func SequenceT1[T1 any](t1 Option[T1]) Option[T.Tuple1[T1]] { + return A.SequenceT1( + Map[T1, T.Tuple1[T1]], + t1, + ) +} + +// SequenceTuple1 converts a [Tuple1] of [Option[T]] into an [Option[Tuple1]]. +func SequenceTuple1[T1 any](t T.Tuple1[Option[T1]]) Option[T.Tuple1[T1]] { + return A.SequenceTuple1( + Map[T1, T.Tuple1[T1]], + t, + ) +} + +// TraverseTuple1 converts a [Tuple1] of [A] via transformation functions transforming [A] to [Option[A]] into a [Option[Tuple1]]. +func TraverseTuple1[F1 ~func(A1) Option[T1], A1, T1 any](f1 F1) func (T.Tuple1[A1]) Option[T.Tuple1[T1]] { + return func(t T.Tuple1[A1]) Option[T.Tuple1[T1]] { + return A.TraverseTuple1( + Map[T1, T.Tuple1[T1]], + f1, + t, + ) + } +} + +// Optionize2 converts a function with 2 parameters returning a tuple of a return value R and a boolean into a function with 2 parameters returning an Option[R] +func Optionize2[F ~func(T0, T1) (R, bool), T0, T1, R any](f F) func(T0, T1) Option[R] { + return func(t0 T0, t1 T1) Option[R] { + return optionize(func() (R, bool) { + return f(t0, t1) + }) + } +} + +// Unoptionize2 converts a function with 2 parameters returning a tuple of a return value R and a boolean into a function with 2 parameters returning an Option[R] +func Unoptionize2[F ~func(T0, T1) Option[R], T0, T1, R any](f F) func(T0, T1) (R, bool) { + return func(t0 T0, t1 T1) (R, bool) { + return Unwrap(f(t0, t1)) + } +} + +// SequenceT2 converts 2 parameters of [Option[T]] into a [Option[Tuple2]]. +func SequenceT2[T1, T2 any](t1 Option[T1], t2 Option[T2]) Option[T.Tuple2[T1, T2]] { + return A.SequenceT2( + Map[T1, func(T2) T.Tuple2[T1, T2]], + Ap[T.Tuple2[T1, T2], T2], + t1, + t2, + ) +} + +// SequenceTuple2 converts a [Tuple2] of [Option[T]] into an [Option[Tuple2]]. +func SequenceTuple2[T1, T2 any](t T.Tuple2[Option[T1], Option[T2]]) Option[T.Tuple2[T1, T2]] { + return A.SequenceTuple2( + Map[T1, func(T2) T.Tuple2[T1, T2]], + Ap[T.Tuple2[T1, T2], T2], + t, + ) +} + +// TraverseTuple2 converts a [Tuple2] of [A] via transformation functions transforming [A] to [Option[A]] into a [Option[Tuple2]]. +func TraverseTuple2[F1 ~func(A1) Option[T1], F2 ~func(A2) Option[T2], A1, T1, A2, T2 any](f1 F1, f2 F2) func (T.Tuple2[A1, A2]) Option[T.Tuple2[T1, T2]] { + return func(t T.Tuple2[A1, A2]) Option[T.Tuple2[T1, T2]] { + return A.TraverseTuple2( + Map[T1, func(T2) T.Tuple2[T1, T2]], + Ap[T.Tuple2[T1, T2], T2], + f1, + f2, + t, + ) + } +} + +// Optionize3 converts a function with 3 parameters returning a tuple of a return value R and a boolean into a function with 3 parameters returning an Option[R] +func Optionize3[F ~func(T0, T1, T2) (R, bool), T0, T1, T2, R any](f F) func(T0, T1, T2) Option[R] { + return func(t0 T0, t1 T1, t2 T2) Option[R] { + return optionize(func() (R, bool) { + return f(t0, t1, t2) + }) + } +} + +// Unoptionize3 converts a function with 3 parameters returning a tuple of a return value R and a boolean into a function with 3 parameters returning an Option[R] +func Unoptionize3[F ~func(T0, T1, T2) Option[R], T0, T1, T2, R any](f F) func(T0, T1, T2) (R, bool) { + return func(t0 T0, t1 T1, t2 T2) (R, bool) { + return Unwrap(f(t0, t1, t2)) + } +} + +// SequenceT3 converts 3 parameters of [Option[T]] into a [Option[Tuple3]]. +func SequenceT3[T1, T2, T3 any](t1 Option[T1], t2 Option[T2], t3 Option[T3]) Option[T.Tuple3[T1, T2, T3]] { + return A.SequenceT3( + Map[T1, func(T2) func(T3) T.Tuple3[T1, T2, T3]], + Ap[func(T3) T.Tuple3[T1, T2, T3], T2], + Ap[T.Tuple3[T1, T2, T3], T3], + t1, + t2, + t3, + ) +} + +// SequenceTuple3 converts a [Tuple3] of [Option[T]] into an [Option[Tuple3]]. +func SequenceTuple3[T1, T2, T3 any](t T.Tuple3[Option[T1], Option[T2], Option[T3]]) Option[T.Tuple3[T1, T2, T3]] { + return A.SequenceTuple3( + Map[T1, func(T2) func(T3) T.Tuple3[T1, T2, T3]], + Ap[func(T3) T.Tuple3[T1, T2, T3], T2], + Ap[T.Tuple3[T1, T2, T3], T3], + t, + ) +} + +// TraverseTuple3 converts a [Tuple3] of [A] via transformation functions transforming [A] to [Option[A]] into a [Option[Tuple3]]. +func TraverseTuple3[F1 ~func(A1) Option[T1], F2 ~func(A2) Option[T2], F3 ~func(A3) Option[T3], A1, T1, A2, T2, A3, T3 any](f1 F1, f2 F2, f3 F3) func (T.Tuple3[A1, A2, A3]) Option[T.Tuple3[T1, T2, T3]] { + return func(t T.Tuple3[A1, A2, A3]) Option[T.Tuple3[T1, T2, T3]] { + return A.TraverseTuple3( + Map[T1, func(T2) func(T3) T.Tuple3[T1, T2, T3]], + Ap[func(T3) T.Tuple3[T1, T2, T3], T2], + Ap[T.Tuple3[T1, T2, T3], T3], + f1, + f2, + f3, + t, + ) + } +} + +// Optionize4 converts a function with 4 parameters returning a tuple of a return value R and a boolean into a function with 4 parameters returning an Option[R] +func Optionize4[F ~func(T0, T1, T2, T3) (R, bool), T0, T1, T2, T3, R any](f F) func(T0, T1, T2, T3) Option[R] { + return func(t0 T0, t1 T1, t2 T2, t3 T3) Option[R] { + return optionize(func() (R, bool) { + return f(t0, t1, t2, t3) + }) + } +} + +// Unoptionize4 converts a function with 4 parameters returning a tuple of a return value R and a boolean into a function with 4 parameters returning an Option[R] +func Unoptionize4[F ~func(T0, T1, T2, T3) Option[R], T0, T1, T2, T3, R any](f F) func(T0, T1, T2, T3) (R, bool) { + return func(t0 T0, t1 T1, t2 T2, t3 T3) (R, bool) { + return Unwrap(f(t0, t1, t2, t3)) + } +} + +// SequenceT4 converts 4 parameters of [Option[T]] into a [Option[Tuple4]]. +func SequenceT4[T1, T2, T3, T4 any](t1 Option[T1], t2 Option[T2], t3 Option[T3], t4 Option[T4]) Option[T.Tuple4[T1, T2, T3, T4]] { + return A.SequenceT4( + Map[T1, func(T2) func(T3) func(T4) T.Tuple4[T1, T2, T3, T4]], + Ap[func(T3) func(T4) T.Tuple4[T1, T2, T3, T4], T2], + Ap[func(T4) T.Tuple4[T1, T2, T3, T4], T3], + Ap[T.Tuple4[T1, T2, T3, T4], T4], + t1, + t2, + t3, + t4, + ) +} + +// SequenceTuple4 converts a [Tuple4] of [Option[T]] into an [Option[Tuple4]]. +func SequenceTuple4[T1, T2, T3, T4 any](t T.Tuple4[Option[T1], Option[T2], Option[T3], Option[T4]]) Option[T.Tuple4[T1, T2, T3, T4]] { + return A.SequenceTuple4( + Map[T1, func(T2) func(T3) func(T4) T.Tuple4[T1, T2, T3, T4]], + Ap[func(T3) func(T4) T.Tuple4[T1, T2, T3, T4], T2], + Ap[func(T4) T.Tuple4[T1, T2, T3, T4], T3], + Ap[T.Tuple4[T1, T2, T3, T4], T4], + t, + ) +} + +// TraverseTuple4 converts a [Tuple4] of [A] via transformation functions transforming [A] to [Option[A]] into a [Option[Tuple4]]. +func TraverseTuple4[F1 ~func(A1) Option[T1], F2 ~func(A2) Option[T2], F3 ~func(A3) Option[T3], F4 ~func(A4) Option[T4], A1, T1, A2, T2, A3, T3, A4, T4 any](f1 F1, f2 F2, f3 F3, f4 F4) func (T.Tuple4[A1, A2, A3, A4]) Option[T.Tuple4[T1, T2, T3, T4]] { + return func(t T.Tuple4[A1, A2, A3, A4]) Option[T.Tuple4[T1, T2, T3, T4]] { + return A.TraverseTuple4( + Map[T1, func(T2) func(T3) func(T4) T.Tuple4[T1, T2, T3, T4]], + Ap[func(T3) func(T4) T.Tuple4[T1, T2, T3, T4], T2], + Ap[func(T4) T.Tuple4[T1, T2, T3, T4], T3], + Ap[T.Tuple4[T1, T2, T3, T4], T4], + f1, + f2, + f3, + f4, + t, + ) + } +} + +// Optionize5 converts a function with 5 parameters returning a tuple of a return value R and a boolean into a function with 5 parameters returning an Option[R] +func Optionize5[F ~func(T0, T1, T2, T3, T4) (R, bool), T0, T1, T2, T3, T4, R any](f F) func(T0, T1, T2, T3, T4) Option[R] { + return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4) Option[R] { + return optionize(func() (R, bool) { + return f(t0, t1, t2, t3, t4) + }) + } +} + +// Unoptionize5 converts a function with 5 parameters returning a tuple of a return value R and a boolean into a function with 5 parameters returning an Option[R] +func Unoptionize5[F ~func(T0, T1, T2, T3, T4) Option[R], T0, T1, T2, T3, T4, R any](f F) func(T0, T1, T2, T3, T4) (R, bool) { + return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4) (R, bool) { + return Unwrap(f(t0, t1, t2, t3, t4)) + } +} + +// SequenceT5 converts 5 parameters of [Option[T]] into a [Option[Tuple5]]. +func SequenceT5[T1, T2, T3, T4, T5 any](t1 Option[T1], t2 Option[T2], t3 Option[T3], t4 Option[T4], t5 Option[T5]) Option[T.Tuple5[T1, T2, T3, T4, T5]] { + return A.SequenceT5( + Map[T1, func(T2) func(T3) func(T4) func(T5) T.Tuple5[T1, T2, T3, T4, T5]], + Ap[func(T3) func(T4) func(T5) T.Tuple5[T1, T2, T3, T4, T5], T2], + Ap[func(T4) func(T5) T.Tuple5[T1, T2, T3, T4, T5], T3], + Ap[func(T5) T.Tuple5[T1, T2, T3, T4, T5], T4], + Ap[T.Tuple5[T1, T2, T3, T4, T5], T5], + t1, + t2, + t3, + t4, + t5, + ) +} + +// SequenceTuple5 converts a [Tuple5] of [Option[T]] into an [Option[Tuple5]]. +func SequenceTuple5[T1, T2, T3, T4, T5 any](t T.Tuple5[Option[T1], Option[T2], Option[T3], Option[T4], Option[T5]]) Option[T.Tuple5[T1, T2, T3, T4, T5]] { + return A.SequenceTuple5( + Map[T1, func(T2) func(T3) func(T4) func(T5) T.Tuple5[T1, T2, T3, T4, T5]], + Ap[func(T3) func(T4) func(T5) T.Tuple5[T1, T2, T3, T4, T5], T2], + Ap[func(T4) func(T5) T.Tuple5[T1, T2, T3, T4, T5], T3], + Ap[func(T5) T.Tuple5[T1, T2, T3, T4, T5], T4], + Ap[T.Tuple5[T1, T2, T3, T4, T5], T5], + t, + ) +} + +// TraverseTuple5 converts a [Tuple5] of [A] via transformation functions transforming [A] to [Option[A]] into a [Option[Tuple5]]. +func TraverseTuple5[F1 ~func(A1) Option[T1], F2 ~func(A2) Option[T2], F3 ~func(A3) Option[T3], F4 ~func(A4) Option[T4], F5 ~func(A5) Option[T5], A1, T1, A2, T2, A3, T3, A4, T4, A5, T5 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5) func (T.Tuple5[A1, A2, A3, A4, A5]) Option[T.Tuple5[T1, T2, T3, T4, T5]] { + return func(t T.Tuple5[A1, A2, A3, A4, A5]) Option[T.Tuple5[T1, T2, T3, T4, T5]] { + return A.TraverseTuple5( + Map[T1, func(T2) func(T3) func(T4) func(T5) T.Tuple5[T1, T2, T3, T4, T5]], + Ap[func(T3) func(T4) func(T5) T.Tuple5[T1, T2, T3, T4, T5], T2], + Ap[func(T4) func(T5) T.Tuple5[T1, T2, T3, T4, T5], T3], + Ap[func(T5) T.Tuple5[T1, T2, T3, T4, T5], T4], + Ap[T.Tuple5[T1, T2, T3, T4, T5], T5], + f1, + f2, + f3, + f4, + f5, + t, + ) + } +} + +// Optionize6 converts a function with 6 parameters returning a tuple of a return value R and a boolean into a function with 6 parameters returning an Option[R] +func Optionize6[F ~func(T0, T1, T2, T3, T4, T5) (R, bool), T0, T1, T2, T3, T4, T5, R any](f F) func(T0, T1, T2, T3, T4, T5) Option[R] { + return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5) Option[R] { + return optionize(func() (R, bool) { + return f(t0, t1, t2, t3, t4, t5) + }) + } +} + +// Unoptionize6 converts a function with 6 parameters returning a tuple of a return value R and a boolean into a function with 6 parameters returning an Option[R] +func Unoptionize6[F ~func(T0, T1, T2, T3, T4, T5) Option[R], T0, T1, T2, T3, T4, T5, R any](f F) func(T0, T1, T2, T3, T4, T5) (R, bool) { + return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5) (R, bool) { + return Unwrap(f(t0, t1, t2, t3, t4, t5)) + } +} + +// SequenceT6 converts 6 parameters of [Option[T]] into a [Option[Tuple6]]. +func SequenceT6[T1, T2, T3, T4, T5, T6 any](t1 Option[T1], t2 Option[T2], t3 Option[T3], t4 Option[T4], t5 Option[T5], t6 Option[T6]) Option[T.Tuple6[T1, T2, T3, T4, T5, T6]] { + return A.SequenceT6( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6]], + Ap[func(T3) func(T4) func(T5) func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6], T2], + Ap[func(T4) func(T5) func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6], T3], + Ap[func(T5) func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6], T4], + Ap[func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6], T5], + Ap[T.Tuple6[T1, T2, T3, T4, T5, T6], T6], + t1, + t2, + t3, + t4, + t5, + t6, + ) +} + +// SequenceTuple6 converts a [Tuple6] of [Option[T]] into an [Option[Tuple6]]. +func SequenceTuple6[T1, T2, T3, T4, T5, T6 any](t T.Tuple6[Option[T1], Option[T2], Option[T3], Option[T4], Option[T5], Option[T6]]) Option[T.Tuple6[T1, T2, T3, T4, T5, T6]] { + return A.SequenceTuple6( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6]], + Ap[func(T3) func(T4) func(T5) func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6], T2], + Ap[func(T4) func(T5) func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6], T3], + Ap[func(T5) func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6], T4], + Ap[func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6], T5], + Ap[T.Tuple6[T1, T2, T3, T4, T5, T6], T6], + t, + ) +} + +// TraverseTuple6 converts a [Tuple6] of [A] via transformation functions transforming [A] to [Option[A]] into a [Option[Tuple6]]. +func TraverseTuple6[F1 ~func(A1) Option[T1], F2 ~func(A2) Option[T2], F3 ~func(A3) Option[T3], F4 ~func(A4) Option[T4], F5 ~func(A5) Option[T5], F6 ~func(A6) Option[T6], A1, T1, A2, T2, A3, T3, A4, T4, A5, T5, A6, T6 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6) func (T.Tuple6[A1, A2, A3, A4, A5, A6]) Option[T.Tuple6[T1, T2, T3, T4, T5, T6]] { + return func(t T.Tuple6[A1, A2, A3, A4, A5, A6]) Option[T.Tuple6[T1, T2, T3, T4, T5, T6]] { + return A.TraverseTuple6( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6]], + Ap[func(T3) func(T4) func(T5) func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6], T2], + Ap[func(T4) func(T5) func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6], T3], + Ap[func(T5) func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6], T4], + Ap[func(T6) T.Tuple6[T1, T2, T3, T4, T5, T6], T5], + Ap[T.Tuple6[T1, T2, T3, T4, T5, T6], T6], + f1, + f2, + f3, + f4, + f5, + f6, + t, + ) + } +} + +// Optionize7 converts a function with 7 parameters returning a tuple of a return value R and a boolean into a function with 7 parameters returning an Option[R] +func Optionize7[F ~func(T0, T1, T2, T3, T4, T5, T6) (R, bool), T0, T1, T2, T3, T4, T5, T6, R any](f F) func(T0, T1, T2, T3, T4, T5, T6) Option[R] { + return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6) Option[R] { + return optionize(func() (R, bool) { + return f(t0, t1, t2, t3, t4, t5, t6) + }) + } +} + +// Unoptionize7 converts a function with 7 parameters returning a tuple of a return value R and a boolean into a function with 7 parameters returning an Option[R] +func Unoptionize7[F ~func(T0, T1, T2, T3, T4, T5, T6) Option[R], T0, T1, T2, T3, T4, T5, T6, R any](f F) func(T0, T1, T2, T3, T4, T5, T6) (R, bool) { + return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6) (R, bool) { + return Unwrap(f(t0, t1, t2, t3, t4, t5, t6)) + } +} + +// SequenceT7 converts 7 parameters of [Option[T]] into a [Option[Tuple7]]. +func SequenceT7[T1, T2, T3, T4, T5, T6, T7 any](t1 Option[T1], t2 Option[T2], t3 Option[T3], t4 Option[T4], t5 Option[T5], t6 Option[T6], t7 Option[T7]) Option[T.Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return A.SequenceT7( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7], T2], + Ap[func(T4) func(T5) func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7], T3], + Ap[func(T5) func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7], T4], + Ap[func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7], T5], + Ap[func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7], T6], + Ap[T.Tuple7[T1, T2, T3, T4, T5, T6, T7], T7], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + ) +} + +// SequenceTuple7 converts a [Tuple7] of [Option[T]] into an [Option[Tuple7]]. +func SequenceTuple7[T1, T2, T3, T4, T5, T6, T7 any](t T.Tuple7[Option[T1], Option[T2], Option[T3], Option[T4], Option[T5], Option[T6], Option[T7]]) Option[T.Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return A.SequenceTuple7( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7], T2], + Ap[func(T4) func(T5) func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7], T3], + Ap[func(T5) func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7], T4], + Ap[func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7], T5], + Ap[func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7], T6], + Ap[T.Tuple7[T1, T2, T3, T4, T5, T6, T7], T7], + t, + ) +} + +// TraverseTuple7 converts a [Tuple7] of [A] via transformation functions transforming [A] to [Option[A]] into a [Option[Tuple7]]. +func TraverseTuple7[F1 ~func(A1) Option[T1], F2 ~func(A2) Option[T2], F3 ~func(A3) Option[T3], F4 ~func(A4) Option[T4], F5 ~func(A5) Option[T5], F6 ~func(A6) Option[T6], F7 ~func(A7) Option[T7], A1, T1, A2, T2, A3, T3, A4, T4, A5, T5, A6, T6, A7, T7 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7) func (T.Tuple7[A1, A2, A3, A4, A5, A6, A7]) Option[T.Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return func(t T.Tuple7[A1, A2, A3, A4, A5, A6, A7]) Option[T.Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return A.TraverseTuple7( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7], T2], + Ap[func(T4) func(T5) func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7], T3], + Ap[func(T5) func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7], T4], + Ap[func(T6) func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7], T5], + Ap[func(T7) T.Tuple7[T1, T2, T3, T4, T5, T6, T7], T6], + Ap[T.Tuple7[T1, T2, T3, T4, T5, T6, T7], T7], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + t, + ) + } +} + +// Optionize8 converts a function with 8 parameters returning a tuple of a return value R and a boolean into a function with 8 parameters returning an Option[R] +func Optionize8[F ~func(T0, T1, T2, T3, T4, T5, T6, T7) (R, bool), T0, T1, T2, T3, T4, T5, T6, T7, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7) Option[R] { + return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7) Option[R] { + return optionize(func() (R, bool) { + return f(t0, t1, t2, t3, t4, t5, t6, t7) + }) + } +} + +// Unoptionize8 converts a function with 8 parameters returning a tuple of a return value R and a boolean into a function with 8 parameters returning an Option[R] +func Unoptionize8[F ~func(T0, T1, T2, T3, T4, T5, T6, T7) Option[R], T0, T1, T2, T3, T4, T5, T6, T7, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7) (R, bool) { + return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7) (R, bool) { + return Unwrap(f(t0, t1, t2, t3, t4, t5, t6, t7)) + } +} + +// SequenceT8 converts 8 parameters of [Option[T]] into a [Option[Tuple8]]. +func SequenceT8[T1, T2, T3, T4, T5, T6, T7, T8 any](t1 Option[T1], t2 Option[T2], t3 Option[T3], t4 Option[T4], t5 Option[T5], t6 Option[T6], t7 Option[T7], t8 Option[T8]) Option[T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return A.SequenceT8( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T3], + Ap[func(T5) func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T4], + Ap[func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T5], + Ap[func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T6], + Ap[func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T7], + Ap[T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T8], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + t8, + ) +} + +// SequenceTuple8 converts a [Tuple8] of [Option[T]] into an [Option[Tuple8]]. +func SequenceTuple8[T1, T2, T3, T4, T5, T6, T7, T8 any](t T.Tuple8[Option[T1], Option[T2], Option[T3], Option[T4], Option[T5], Option[T6], Option[T7], Option[T8]]) Option[T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return A.SequenceTuple8( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T3], + Ap[func(T5) func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T4], + Ap[func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T5], + Ap[func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T6], + Ap[func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T7], + Ap[T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T8], + t, + ) +} + +// TraverseTuple8 converts a [Tuple8] of [A] via transformation functions transforming [A] to [Option[A]] into a [Option[Tuple8]]. +func TraverseTuple8[F1 ~func(A1) Option[T1], F2 ~func(A2) Option[T2], F3 ~func(A3) Option[T3], F4 ~func(A4) Option[T4], F5 ~func(A5) Option[T5], F6 ~func(A6) Option[T6], F7 ~func(A7) Option[T7], F8 ~func(A8) Option[T8], A1, T1, A2, T2, A3, T3, A4, T4, A5, T5, A6, T6, A7, T7, A8, T8 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8) func (T.Tuple8[A1, A2, A3, A4, A5, A6, A7, A8]) Option[T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return func(t T.Tuple8[A1, A2, A3, A4, A5, A6, A7, A8]) Option[T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return A.TraverseTuple8( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T3], + Ap[func(T5) func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T4], + Ap[func(T6) func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T5], + Ap[func(T7) func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T6], + Ap[func(T8) T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T7], + Ap[T.Tuple8[T1, T2, T3, T4, T5, T6, T7, T8], T8], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + f8, + t, + ) + } +} + +// Optionize9 converts a function with 9 parameters returning a tuple of a return value R and a boolean into a function with 9 parameters returning an Option[R] +func Optionize9[F ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8) (R, bool), T0, T1, T2, T3, T4, T5, T6, T7, T8, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7, T8) Option[R] { + return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8) Option[R] { + return optionize(func() (R, bool) { + return f(t0, t1, t2, t3, t4, t5, t6, t7, t8) + }) + } +} + +// Unoptionize9 converts a function with 9 parameters returning a tuple of a return value R and a boolean into a function with 9 parameters returning an Option[R] +func Unoptionize9[F ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8) Option[R], T0, T1, T2, T3, T4, T5, T6, T7, T8, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7, T8) (R, bool) { + return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8) (R, bool) { + return Unwrap(f(t0, t1, t2, t3, t4, t5, t6, t7, t8)) + } +} + +// SequenceT9 converts 9 parameters of [Option[T]] into a [Option[Tuple9]]. +func SequenceT9[T1, T2, T3, T4, T5, T6, T7, T8, T9 any](t1 Option[T1], t2 Option[T2], t3 Option[T3], t4 Option[T4], t5 Option[T5], t6 Option[T6], t7 Option[T7], t8 Option[T8], t9 Option[T9]) Option[T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return A.SequenceT9( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T3], + Ap[func(T5) func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T4], + Ap[func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T5], + Ap[func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T6], + Ap[func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T7], + Ap[func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T8], + Ap[T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T9], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + t8, + t9, + ) +} + +// SequenceTuple9 converts a [Tuple9] of [Option[T]] into an [Option[Tuple9]]. +func SequenceTuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9 any](t T.Tuple9[Option[T1], Option[T2], Option[T3], Option[T4], Option[T5], Option[T6], Option[T7], Option[T8], Option[T9]]) Option[T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return A.SequenceTuple9( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T3], + Ap[func(T5) func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T4], + Ap[func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T5], + Ap[func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T6], + Ap[func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T7], + Ap[func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T8], + Ap[T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T9], + t, + ) +} + +// TraverseTuple9 converts a [Tuple9] of [A] via transformation functions transforming [A] to [Option[A]] into a [Option[Tuple9]]. +func TraverseTuple9[F1 ~func(A1) Option[T1], F2 ~func(A2) Option[T2], F3 ~func(A3) Option[T3], F4 ~func(A4) Option[T4], F5 ~func(A5) Option[T5], F6 ~func(A6) Option[T6], F7 ~func(A7) Option[T7], F8 ~func(A8) Option[T8], F9 ~func(A9) Option[T9], A1, T1, A2, T2, A3, T3, A4, T4, A5, T5, A6, T6, A7, T7, A8, T8, A9, T9 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9) func (T.Tuple9[A1, A2, A3, A4, A5, A6, A7, A8, A9]) Option[T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return func(t T.Tuple9[A1, A2, A3, A4, A5, A6, A7, A8, A9]) Option[T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return A.TraverseTuple9( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T3], + Ap[func(T5) func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T4], + Ap[func(T6) func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T5], + Ap[func(T7) func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T6], + Ap[func(T8) func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T7], + Ap[func(T9) T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T8], + Ap[T.Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9], T9], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + f8, + f9, + t, + ) + } +} + +// Optionize10 converts a function with 10 parameters returning a tuple of a return value R and a boolean into a function with 10 parameters returning an Option[R] +func Optionize10[F ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9) (R, bool), T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9) Option[R] { + return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9) Option[R] { + return optionize(func() (R, bool) { + return f(t0, t1, t2, t3, t4, t5, t6, t7, t8, t9) + }) + } +} + +// Unoptionize10 converts a function with 10 parameters returning a tuple of a return value R and a boolean into a function with 10 parameters returning an Option[R] +func Unoptionize10[F ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9) Option[R], T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9) (R, bool) { + return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9) (R, bool) { + return Unwrap(f(t0, t1, t2, t3, t4, t5, t6, t7, t8, t9)) + } +} + +// SequenceT10 converts 10 parameters of [Option[T]] into a [Option[Tuple10]]. +func SequenceT10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any](t1 Option[T1], t2 Option[T2], t3 Option[T3], t4 Option[T4], t5 Option[T5], t6 Option[T6], t7 Option[T7], t8 Option[T8], t9 Option[T9], t10 Option[T10]) Option[T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return A.SequenceT10( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T3], + Ap[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T4], + Ap[func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T5], + Ap[func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T6], + Ap[func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T7], + Ap[func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T8], + Ap[func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T9], + Ap[T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T10], + t1, + t2, + t3, + t4, + t5, + t6, + t7, + t8, + t9, + t10, + ) +} + +// SequenceTuple10 converts a [Tuple10] of [Option[T]] into an [Option[Tuple10]]. +func SequenceTuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any](t T.Tuple10[Option[T1], Option[T2], Option[T3], Option[T4], Option[T5], Option[T6], Option[T7], Option[T8], Option[T9], Option[T10]]) Option[T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return A.SequenceTuple10( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T3], + Ap[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T4], + Ap[func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T5], + Ap[func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T6], + Ap[func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T7], + Ap[func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T8], + Ap[func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T9], + Ap[T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T10], + t, + ) +} + +// TraverseTuple10 converts a [Tuple10] of [A] via transformation functions transforming [A] to [Option[A]] into a [Option[Tuple10]]. +func TraverseTuple10[F1 ~func(A1) Option[T1], F2 ~func(A2) Option[T2], F3 ~func(A3) Option[T3], F4 ~func(A4) Option[T4], F5 ~func(A5) Option[T5], F6 ~func(A6) Option[T6], F7 ~func(A7) Option[T7], F8 ~func(A8) Option[T8], F9 ~func(A9) Option[T9], F10 ~func(A10) Option[T10], A1, T1, A2, T2, A3, T3, A4, T4, A5, T5, A6, T6, A7, T7, A8, T8, A9, T9, A10, T10 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10) func (T.Tuple10[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10]) Option[T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return func(t T.Tuple10[A1, A2, A3, A4, A5, A6, A7, A8, A9, A10]) Option[T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return A.TraverseTuple10( + Map[T1, func(T2) func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]], + Ap[func(T3) func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T2], + Ap[func(T4) func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T3], + Ap[func(T5) func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T4], + Ap[func(T6) func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T5], + Ap[func(T7) func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T6], + Ap[func(T8) func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T7], + Ap[func(T9) func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T8], + Ap[func(T10) T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T9], + Ap[T.Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10], T10], + f1, + f2, + f3, + f4, + f5, + f6, + f7, + f8, + f9, + f10, + t, + ) + } +} diff --git a/v2/option/logger.go b/v2/option/logger.go new file mode 100644 index 0000000..ae62fe6 --- /dev/null +++ b/v2/option/logger.go @@ -0,0 +1,69 @@ +// Copyright (c) 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package option + +import ( + "log" + + F "github.com/IBM/fp-go/v2/function" + L "github.com/IBM/fp-go/v2/logging" +) + +func _log[A any](left func(string, ...any), right func(string, ...any), prefix string) func(Option[A]) Option[A] { + return Fold( + func() Option[A] { + left("%s", prefix) + return None[A]() + }, + func(a A) Option[A] { + right("%s: %v", prefix, a) + return Some(a) + }) +} + +// Logger creates a logging function for Options that logs the state (None or Some with value) +// and returns the original Option unchanged. This is useful for debugging pipelines. +// +// Parameters: +// - loggers: optional log.Logger instances to use for logging (defaults to standard logger) +// +// Returns a function that takes a prefix string and returns a function that logs and passes through an Option. +// +// Example: +// +// logger := Logger[int]() +// result := F.Pipe2( +// Some(42), +// logger("step1"), // logs "step1: 42" +// Map(func(x int) int { return x * 2 }), +// ) // Some(84) +// +// result := F.Pipe1( +// None[int](), +// logger("step1"), // logs "step1" +// ) // None +func Logger[A any](loggers ...*log.Logger) func(string) func(Option[A]) Option[A] { + left, right := L.LoggingCallbacks(loggers...) + return func(prefix string) func(Option[A]) Option[A] { + delegate := _log[A](left, right, prefix) + return func(ma Option[A]) Option[A] { + return F.Pipe1( + delegate(ma), + ChainTo[A](ma), + ) + } + } +} diff --git a/v2/option/monad.go b/v2/option/monad.go new file mode 100644 index 0000000..7958922 --- /dev/null +++ b/v2/option/monad.go @@ -0,0 +1,59 @@ +// Copyright (c) 2024 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package option + +import ( + "github.com/IBM/fp-go/v2/internal/monad" +) + +type optionMonad[A, B any] struct{} + +func (o *optionMonad[A, B]) Of(a A) Option[A] { + return Of[A](a) +} + +func (o *optionMonad[A, B]) Map(f func(A) B) func(Option[A]) Option[B] { + return Map[A, B](f) +} + +func (o *optionMonad[A, B]) Chain(f func(A) Option[B]) func(Option[A]) Option[B] { + return Chain[A, B](f) +} + +func (o *optionMonad[A, B]) Ap(fa Option[A]) func(Option[func(A) B]) Option[B] { + return Ap[B, A](fa) +} + +// Monad implements the monadic operations for Option. +// A monad provides a way to chain computations that may fail, handling the +// None case automatically. +// +// The monad interface includes: +// - Of: wraps a value in an Option +// - Map: transforms the contained value +// - Chain: sequences Option-returning operations +// - Ap: applies an Option-wrapped function to an Option-wrapped value +// +// Example: +// +// m := Monad[int, string]() +// result := m.Chain(func(x int) Option[string] { +// if x > 0 { return Some(fmt.Sprintf("%d", x)) } +// return None[string]() +// })(Some(42)) // Some("42") +func Monad[A, B any]() monad.Monad[A, B, Option[A], Option[B], Option[func(A) B]] { + return &optionMonad[A, B]{} +} diff --git a/v2/option/monoid.go b/v2/option/monoid.go new file mode 100644 index 0000000..c099628 --- /dev/null +++ b/v2/option/monoid.go @@ -0,0 +1,111 @@ +// Copyright (c) 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package option + +import ( + F "github.com/IBM/fp-go/v2/function" + M "github.com/IBM/fp-go/v2/monoid" + S "github.com/IBM/fp-go/v2/semigroup" +) + +// Semigroup returns a function that lifts a Semigroup over type A to a Semigroup over Option[A]. +// The resulting semigroup combines two Options according to these rules: +// - If both are Some, concatenates their values using the provided Semigroup +// - If one is None, returns the other +// - If both are None, returns None +// +// Example: +// +// intSemigroup := semigroup.MakeSemigroup(func(a, b int) int { return a + b }) +// optSemigroup := Semigroup[int]()(intSemigroup) +// optSemigroup.Concat(Some(2), Some(3)) // Some(5) +// optSemigroup.Concat(Some(2), None[int]()) // Some(2) +// optSemigroup.Concat(None[int](), Some(3)) // Some(3) +func Semigroup[A any]() func(S.Semigroup[A]) S.Semigroup[Option[A]] { + return func(s S.Semigroup[A]) S.Semigroup[Option[A]] { + concat := s.Concat + return S.MakeSemigroup( + func(x, y Option[A]) Option[A] { + return MonadFold(x, F.Constant(y), func(left A) Option[A] { + return MonadFold(y, F.Constant(x), func(right A) Option[A] { + return Some(concat(left, right)) + }) + }) + }, + ) + } +} + +// Monoid returns a function that lifts a Semigroup over type A to a Monoid over Option[A]. +// The monoid returns the left-most non-None value. If both operands are Some, their inner +// values are concatenated using the provided Semigroup. The empty value is None. +// +// Truth table: +// +// | x | y | concat(x, y) | +// | ------- | ------- | ------------------ | +// | none | none | none | +// | some(a) | none | some(a) | +// | none | some(b) | some(b) | +// | some(a) | some(b) | some(concat(a, b)) | +// +// Example: +// +// intSemigroup := semigroup.MakeSemigroup(func(a, b int) int { return a + b }) +// optMonoid := Monoid[int]()(intSemigroup) +// optMonoid.Concat(Some(2), Some(3)) // Some(5) +// optMonoid.Empty() // None +func Monoid[A any]() func(S.Semigroup[A]) M.Monoid[Option[A]] { + sg := Semigroup[A]() + return func(s S.Semigroup[A]) M.Monoid[Option[A]] { + return M.MakeMonoid(sg(s).Concat, None[A]()) + } +} + +// AlternativeMonoid creates a Monoid for Option[A] using the alternative semantics. +// This combines the applicative functor structure with the alternative (Alt) operation. +// +// Example: +// +// intMonoid := monoid.MakeMonoid(func(a, b int) int { return a + b }, 0) +// optMonoid := AlternativeMonoid(intMonoid) +// result := optMonoid.Concat(Some(2), Some(3)) // Some(5) +func AlternativeMonoid[A any](m M.Monoid[A]) M.Monoid[Option[A]] { + return M.AlternativeMonoid( + Of[A], + MonadMap[A, func(A) A], + MonadAp[A, A], + MonadAlt[A], + m, + ) +} + +// AltMonoid creates a Monoid for Option[A] using the Alt operation. +// This monoid returns the first Some value, or None if both are None. +// The empty value is None. +// +// Example: +// +// optMonoid := AltMonoid[int]() +// optMonoid.Concat(Some(2), Some(3)) // Some(2) - returns first Some +// optMonoid.Concat(None[int](), Some(3)) // Some(3) +// optMonoid.Empty() // None +func AltMonoid[A any]() M.Monoid[Option[A]] { + return M.AltMonoid( + None[A], + MonadAlt[A], + ) +} diff --git a/v2/option/number/number.go b/v2/option/number/number.go new file mode 100644 index 0000000..1191352 --- /dev/null +++ b/v2/option/number/number.go @@ -0,0 +1,49 @@ +// Copyright (c) 2025 IBM Corp. +// All rights reserved. +// +// 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. + +// Package number provides Option-based utilities for number conversions. +package number + +import ( + "strconv" + + F "github.com/IBM/fp-go/v2/function" + O "github.com/IBM/fp-go/v2/option" +) + +func atoi(value string) (int, bool) { + data, err := strconv.Atoi(value) + return data, err == nil +} + +var ( + // Atoi converts a string to an integer, returning Some(int) on success or None on failure. + // + // Example: + // + // result := Atoi("42") // Some(42) + // result := Atoi("abc") // None + // result := Atoi("") // None + Atoi = O.Optionize1(atoi) + + // Itoa converts an integer to a string, always returning Some(string). + // + // Example: + // + // result := Itoa(42) // Some("42") + // result := Itoa(-10) // Some("-10") + // result := Itoa(0) // Some("0") + Itoa = F.Flow2(strconv.Itoa, O.Of[string]) +) diff --git a/v2/option/option.go b/v2/option/option.go new file mode 100644 index 0000000..38ede06 --- /dev/null +++ b/v2/option/option.go @@ -0,0 +1,388 @@ +// Copyright (c) 2025 IBM Corp. +// All rights reserved. +// +// 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. + +// package option implements the Option monad, a data type that can have a defined value or none +package option + +import ( + F "github.com/IBM/fp-go/v2/function" + C "github.com/IBM/fp-go/v2/internal/chain" + FC "github.com/IBM/fp-go/v2/internal/functor" +) + +// fromPredicate creates an Option based on a predicate function. +// If the predicate returns true for the value, it returns Some(a), otherwise None. +func fromPredicate[A any](a A, pred func(A) bool) Option[A] { + if pred(a) { + return Some(a) + } + return None[A]() +} + +// FromPredicate returns a function that creates an Option based on a predicate. +// The returned function will wrap a value in Some if the predicate is satisfied, otherwise None. +// +// Example: +// +// isPositive := FromPredicate(func(n int) bool { return n > 0 }) +// result := isPositive(5) // Some(5) +// result := isPositive(-1) // None +func FromPredicate[A any](pred func(A) bool) func(A) Option[A] { + return F.Bind2nd(fromPredicate[A], pred) +} + +// FromNillable converts a pointer to an Option. +// Returns Some if the pointer is non-nil, None otherwise. +// +// Example: +// +// var ptr *int = nil +// result := FromNillable(ptr) // None +// val := 42 +// result := FromNillable(&val) // Some(&val) +func FromNillable[A any](a *A) Option[*A] { + return fromPredicate(a, F.IsNonNil[A]) +} + +// FromValidation converts a validation function (returning value and bool) to an Option-returning function. +// This is an alias for Optionize1. +// +// Example: +// +// parseNum := FromValidation(func(s string) (int, bool) { +// n, err := strconv.Atoi(s) +// return n, err == nil +// }) +// result := parseNum("42") // Some(42) +func FromValidation[A, B any](f func(A) (B, bool)) func(A) Option[B] { + return Optionize1(f) +} + +// MonadAp applies a function wrapped in an Option to a value wrapped in an Option. +// If either the function or the value is None, returns None. +// This is the monadic form of the applicative functor. +// +// Example: +// +// fab := Some(func(x int) int { return x * 2 }) +// fa := Some(5) +// result := MonadAp(fab, fa) // Some(10) +func MonadAp[B, A any](fab Option[func(A) B], fa Option[A]) Option[B] { + return MonadFold(fab, None[B], func(ab func(A) B) Option[B] { + return MonadFold(fa, None[B], F.Flow2(ab, Some[B])) + }) +} + +// Ap is the curried applicative functor for Option. +// Returns a function that applies an Option-wrapped function to the given Option value. +// +// Example: +// +// fa := Some(5) +// applyTo5 := Ap[int](fa) +// fab := Some(func(x int) int { return x * 2 }) +// result := applyTo5(fab) // Some(10) +func Ap[B, A any](fa Option[A]) func(Option[func(A) B]) Option[B] { + return F.Bind2nd(MonadAp[B, A], fa) +} + +// MonadMap applies a function to the value inside an Option. +// If the Option is None, returns None. This is the monadic form of Map. +// +// Example: +// +// fa := Some(5) +// result := MonadMap(fa, func(x int) int { return x * 2 }) // Some(10) +func MonadMap[A, B any](fa Option[A], f func(A) B) Option[B] { + return MonadChain(fa, F.Flow2(f, Some[B])) +} + +// Map returns a function that applies a transformation to the value inside an Option. +// If the Option is None, returns None. +// +// Example: +// +// double := Map(func(x int) int { return x * 2 }) +// result := double(Some(5)) // Some(10) +// result := double(None[int]()) // None +func Map[A, B any](f func(a A) B) func(Option[A]) Option[B] { + return Chain(F.Flow2(f, Some[B])) +} + +// MonadMapTo replaces the value inside an Option with a constant value. +// If the Option is None, returns None. This is the monadic form of MapTo. +// +// Example: +// +// fa := Some(5) +// result := MonadMapTo(fa, "hello") // Some("hello") +func MonadMapTo[A, B any](fa Option[A], b B) Option[B] { + return MonadMap(fa, F.Constant1[A](b)) +} + +// MapTo returns a function that replaces the value inside an Option with a constant. +// +// Example: +// +// replaceWith42 := MapTo[string, int](42) +// result := replaceWith42(Some("hello")) // Some(42) +func MapTo[A, B any](b B) func(Option[A]) Option[B] { + return F.Bind2nd(MonadMapTo[A, B], b) +} + +// TryCatch executes a function that may return an error and converts the result to an Option. +// Returns Some(value) if no error occurred, None if an error occurred. +// +// Example: +// +// result := TryCatch(func() (int, error) { +// return strconv.Atoi("42") +// }) // Some(42) +func TryCatch[A any](f func() (A, error)) Option[A] { + val, err := f() + if err != nil { + return None[A]() + } + return Some(val) +} + +// Fold provides a way to handle both Some and None cases of an Option. +// Returns a function that applies onNone if the Option is None, or onSome if it's Some. +// +// Example: +// +// handler := Fold( +// func() string { return "no value" }, +// func(x int) string { return fmt.Sprintf("value: %d", x) }, +// ) +// result := handler(Some(42)) // "value: 42" +// result := handler(None[int]()) // "no value" +func Fold[A, B any](onNone func() B, onSome func(a A) B) func(ma Option[A]) B { + return func(ma Option[A]) B { + return MonadFold(ma, onNone, onSome) + } +} + +// MonadGetOrElse extracts the value from an Option or returns a default value. +// This is the monadic form of GetOrElse. +// +// Example: +// +// result := MonadGetOrElse(Some(42), func() int { return 0 }) // 42 +// result := MonadGetOrElse(None[int](), func() int { return 0 }) // 0 +func MonadGetOrElse[A any](fa Option[A], onNone func() A) A { + return MonadFold(fa, onNone, F.Identity[A]) +} + +// GetOrElse returns a function that extracts the value from an Option or returns a default. +// +// Example: +// +// getOrZero := GetOrElse(func() int { return 0 }) +// result := getOrZero(Some(42)) // 42 +// result := getOrZero(None[int]()) // 0 +func GetOrElse[A any](onNone func() A) func(Option[A]) A { + return Fold(onNone, F.Identity[A]) +} + +// MonadChain applies a function that returns an Option to the value inside an Option. +// This is the monadic bind operation. If the input is None, returns None. +// +// Example: +// +// fa := Some(5) +// result := MonadChain(fa, func(x int) Option[int] { +// if x > 0 { return Some(x * 2) } +// return None[int]() +// }) // Some(10) +func MonadChain[A, B any](fa Option[A], f func(A) Option[B]) Option[B] { + return MonadFold(fa, None[B], f) +} + +// Chain returns a function that applies an Option-returning function to an Option value. +// This is the curried form of the monadic bind operation. +// +// Example: +// +// validate := Chain(func(x int) Option[int] { +// if x > 0 { return Some(x * 2) } +// return None[int]() +// }) +// result := validate(Some(5)) // Some(10) +func Chain[A, B any](f func(A) Option[B]) func(Option[A]) Option[B] { + return Fold(None[B], f) +} + +// MonadChainTo ignores the first Option and returns the second Option. +// Useful for sequencing operations where the first result is not needed. +// +// Example: +// +// result := MonadChainTo(Some(5), Some("hello")) // Some("hello") +func MonadChainTo[A, B any](_ Option[A], mb Option[B]) Option[B] { + return mb +} + +// ChainTo returns a function that ignores its input Option and returns a fixed Option. +// +// Example: +// +// replaceWith := ChainTo(Some("hello")) +// result := replaceWith(Some(42)) // Some("hello") +func ChainTo[A, B any](mb Option[B]) func(Option[A]) Option[B] { + return F.Bind2nd(MonadChainTo[A, B], mb) +} + +// MonadChainFirst applies a function that returns an Option but keeps the original value. +// If either operation results in None, returns None. +// +// Example: +// +// result := MonadChainFirst(Some(5), func(x int) Option[string] { +// return Some(fmt.Sprintf("%d", x)) +// }) // Some(5) - original value is kept +func MonadChainFirst[A, B any](ma Option[A], f func(A) Option[B]) Option[A] { + return C.MonadChainFirst( + MonadChain[A, A], + MonadMap[B, A], + ma, + f, + ) +} + +// ChainFirst returns a function that applies an Option-returning function but keeps the original value. +// +// Example: +// +// logAndKeep := ChainFirst(func(x int) Option[string] { +// fmt.Println(x) +// return Some("logged") +// }) +// result := logAndKeep(Some(5)) // Some(5) +func ChainFirst[A, B any](f func(A) Option[B]) func(Option[A]) Option[A] { + return C.ChainFirst( + Chain[A, A], + Map[B, A], + f, + ) +} + +// Flatten removes one level of nesting from a nested Option. +// +// Example: +// +// nested := Some(Some(42)) +// result := Flatten(nested) // Some(42) +// nested := Some(None[int]()) +// result := Flatten(nested) // None +func Flatten[A any](mma Option[Option[A]]) Option[A] { + return MonadChain(mma, F.Identity[Option[A]]) +} + +// MonadAlt returns the first Option if it's Some, otherwise returns the alternative. +// This is the monadic form of the Alt operation. +// +// Example: +// +// result := MonadAlt(Some(5), func() Option[int] { return Some(10) }) // Some(5) +// result := MonadAlt(None[int](), func() Option[int] { return Some(10) }) // Some(10) +func MonadAlt[A any](fa Option[A], that func() Option[A]) Option[A] { + return MonadFold(fa, that, Of[A]) +} + +// Alt returns a function that provides an alternative Option if the input is None. +// +// Example: +// +// withDefault := Alt(func() Option[int] { return Some(0) }) +// result := withDefault(Some(5)) // Some(5) +// result := withDefault(None[int]()) // Some(0) +func Alt[A any](that func() Option[A]) func(Option[A]) Option[A] { + return Fold(that, Of[A]) +} + +// MonadSequence2 sequences two Options and applies a function to their values. +// Returns None if either Option is None. +// +// Example: +// +// result := MonadSequence2(Some(2), Some(3), func(a, b int) Option[int] { +// return Some(a + b) +// }) // Some(5) +func MonadSequence2[T1, T2, R any](o1 Option[T1], o2 Option[T2], f func(T1, T2) Option[R]) Option[R] { + return MonadFold(o1, None[R], func(t1 T1) Option[R] { + return MonadFold(o2, None[R], func(t2 T2) Option[R] { + return f(t1, t2) + }) + }) +} + +// Sequence2 returns a function that sequences two Options with a combining function. +// +// Example: +// +// add := Sequence2(func(a, b int) Option[int] { return Some(a + b) }) +// result := add(Some(2), Some(3)) // Some(5) +func Sequence2[T1, T2, R any](f func(T1, T2) Option[R]) func(Option[T1], Option[T2]) Option[R] { + return func(o1 Option[T1], o2 Option[T2]) Option[R] { + return MonadSequence2(o1, o2, f) + } +} + +// Reduce folds an Option into a single value using a reducer function. +// If the Option is None, returns the initial value. +// +// Example: +// +// sum := Reduce(func(acc, val int) int { return acc + val }, 0) +// result := sum(Some(5)) // 5 +// result := sum(None[int]()) // 0 +func Reduce[A, B any](f func(B, A) B, initial B) func(Option[A]) B { + return Fold(F.Constant(initial), F.Bind1st(f, initial)) +} + +// Filter keeps the Option if it's Some and the predicate is satisfied, otherwise returns None. +// +// Example: +// +// isPositive := Filter(func(x int) bool { return x > 0 }) +// result := isPositive(Some(5)) // Some(5) +// result := isPositive(Some(-1)) // None +// result := isPositive(None[int]()) // None +func Filter[A any](pred func(A) bool) func(Option[A]) Option[A] { + return Fold(None[A], F.Ternary(pred, Of[A], F.Ignore1of1[A](None[A]))) +} + +// MonadFlap applies a value to a function wrapped in an Option. +// This is the monadic form of Flap. +// +// Example: +// +// fab := Some(func(x int) int { return x * 2 }) +// result := MonadFlap(fab, 5) // Some(10) +func MonadFlap[B, A any](fab Option[func(A) B], a A) Option[B] { + return FC.MonadFlap(MonadMap[func(A) B, B], fab, a) +} + +// Flap returns a function that applies a value to an Option-wrapped function. +// +// Example: +// +// applyFive := Flap[int](5) +// fab := Some(func(x int) int { return x * 2 }) +// result := applyFive(fab) // Some(10) +func Flap[B, A any](a A) func(Option[func(A) B]) Option[B] { + return FC.Flap(Map[func(A) B, B], a) +} diff --git a/v2/option/option_coverage_test.go b/v2/option/option_coverage_test.go new file mode 100644 index 0000000..4363c78 --- /dev/null +++ b/v2/option/option_coverage_test.go @@ -0,0 +1,567 @@ +// Copyright (c) 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package option + +import ( + "fmt" + "testing" + + T "github.com/IBM/fp-go/v2/tuple" + "github.com/stretchr/testify/assert" +) + +// Test Logger function +func TestLogger(t *testing.T) { + logger := Logger[int]() + logFunc := logger("test") + + // Test with Some + result := logFunc(Some(42)) + assert.Equal(t, Some(42), result) + + // Test with None + result = logFunc(None[int]()) + assert.Equal(t, None[int](), result) +} + +// Test TraverseArrayG with custom slice types +func TestTraverseArrayG(t *testing.T) { + type MySlice []int + type MyResultSlice []string + + f := func(x int) Option[string] { + if x > 0 { + return Some(fmt.Sprintf("%d", x)) + } + return None[string]() + } + + result := TraverseArrayG[MySlice, MyResultSlice](f)(MySlice{1, 2, 3}) + expected := Some(MyResultSlice{"1", "2", "3"}) + assert.Equal(t, expected, result) + + // Test with failure + result = TraverseArrayG[MySlice, MyResultSlice](f)(MySlice{1, -1, 3}) + assert.Equal(t, None[MyResultSlice](), result) +} + +// Test SequenceArrayG with custom slice types +func TestSequenceArrayG(t *testing.T) { + type MySlice []int + + input := []Option[int]{Some(1), Some(2), Some(3)} + result := SequenceArrayG[MySlice](input) + expected := Some(MySlice{1, 2, 3}) + assert.Equal(t, expected, result) + + // Test with None + input = []Option[int]{Some(1), None[int](), Some(3)} + result = SequenceArrayG[MySlice](input) + assert.Equal(t, None[MySlice](), result) +} + +// Test CompactArrayG with custom slice types +func TestCompactArrayG(t *testing.T) { + type MySlice []int + + input := []Option[int]{Some(1), None[int](), Some(3), Some(5)} + result := CompactArrayG[[]Option[int], MySlice](input) + expected := MySlice{1, 3, 5} + assert.Equal(t, expected, result) +} + +// Test TraverseRecordG with custom map types +func TestTraverseRecordG(t *testing.T) { + type MyMap map[string]int + type MyResultMap map[string]string + + f := func(x int) Option[string] { + if x > 0 { + return Some(fmt.Sprintf("%d", x)) + } + return None[string]() + } + + input := MyMap{"a": 1, "b": 2} + result := TraverseRecordG[MyMap, MyResultMap](f)(input) + + assert.True(t, IsSome(result)) + unwrapped, _ := Unwrap(result) + assert.Equal(t, "1", unwrapped["a"]) + assert.Equal(t, "2", unwrapped["b"]) +} + +// Test SequenceRecordG with custom map types +func TestSequenceRecordG(t *testing.T) { + type MyMap map[string]int + + input := map[string]Option[int]{"a": Some(1), "b": Some(2)} + result := SequenceRecordG[MyMap](input) + + assert.True(t, IsSome(result)) + unwrapped, _ := Unwrap(result) + assert.Equal(t, 1, unwrapped["a"]) + assert.Equal(t, 2, unwrapped["b"]) +} + +// Test CompactRecordG with custom map types +func TestCompactRecordG(t *testing.T) { + type MyMap map[string]int + + input := map[string]Option[int]{"a": Some(1), "b": None[int](), "c": Some(3)} + result := CompactRecordG[map[string]Option[int], MyMap](input) + + expected := MyMap{"a": 1, "c": 3} + assert.Equal(t, expected, result) +} + +// Test Optionize3 through Optionize10 +func TestOptionize3(t *testing.T) { + f := func(a, b, c int) (int, bool) { + if a > 0 && b > 0 && c > 0 { + return a + b + c, true + } + return 0, false + } + + optF := Optionize3(f) + assert.Equal(t, Some(6), optF(1, 2, 3)) + assert.Equal(t, None[int](), optF(-1, 2, 3)) +} + +func TestOptionize4(t *testing.T) { + f := func(a, b, c, d int) (int, bool) { + sum := a + b + c + d + return sum, sum > 0 + } + + optF := Optionize4(f) + assert.Equal(t, Some(10), optF(1, 2, 3, 4)) + assert.Equal(t, None[int](), optF(-5, 1, 1, 1)) +} + +func TestOptionize5(t *testing.T) { + f := func(a, b, c, d, e int) (int, bool) { + sum := a + b + c + d + e + return sum, sum > 0 + } + + optF := Optionize5(f) + assert.Equal(t, Some(15), optF(1, 2, 3, 4, 5)) +} + +// Test Unoptionize3 through Unoptionize10 +func TestUnoptionize3(t *testing.T) { + f := func(a, b, c int) Option[int] { + if a > 0 && b > 0 && c > 0 { + return Some(a + b + c) + } + return None[int]() + } + + unoptF := Unoptionize3(f) + val, ok := unoptF(1, 2, 3) + assert.True(t, ok) + assert.Equal(t, 6, val) + + _, ok = unoptF(-1, 2, 3) + assert.False(t, ok) +} + +func TestUnoptionize4(t *testing.T) { + f := func(a, b, c, d int) Option[int] { + return Some(a + b + c + d) + } + + unoptF := Unoptionize4(f) + val, ok := unoptF(1, 2, 3, 4) + assert.True(t, ok) + assert.Equal(t, 10, val) +} + +// Test SequenceT5 through SequenceT10 +func TestSequenceT5(t *testing.T) { + result := SequenceT5(Some(1), Some(2), Some(3), Some(4), Some(5)) + expected := Some(T.MakeTuple5(1, 2, 3, 4, 5)) + assert.Equal(t, expected, result) + + // Test with None + result = SequenceT5(Some(1), None[int](), Some(3), Some(4), Some(5)) + assert.Equal(t, None[T.Tuple5[int, int, int, int, int]](), result) +} + +func TestSequenceT6(t *testing.T) { + result := SequenceT6(Some(1), Some(2), Some(3), Some(4), Some(5), Some(6)) + expected := Some(T.MakeTuple6(1, 2, 3, 4, 5, 6)) + assert.Equal(t, expected, result) +} + +func TestSequenceT7(t *testing.T) { + result := SequenceT7(Some(1), Some(2), Some(3), Some(4), Some(5), Some(6), Some(7)) + expected := Some(T.MakeTuple7(1, 2, 3, 4, 5, 6, 7)) + assert.Equal(t, expected, result) +} + +func TestSequenceT8(t *testing.T) { + result := SequenceT8(Some(1), Some(2), Some(3), Some(4), Some(5), Some(6), Some(7), Some(8)) + expected := Some(T.MakeTuple8(1, 2, 3, 4, 5, 6, 7, 8)) + assert.Equal(t, expected, result) +} + +func TestSequenceT9(t *testing.T) { + result := SequenceT9(Some(1), Some(2), Some(3), Some(4), Some(5), Some(6), Some(7), Some(8), Some(9)) + expected := Some(T.MakeTuple9(1, 2, 3, 4, 5, 6, 7, 8, 9)) + assert.Equal(t, expected, result) +} + +func TestSequenceT10(t *testing.T) { + result := SequenceT10(Some(1), Some(2), Some(3), Some(4), Some(5), Some(6), Some(7), Some(8), Some(9), Some(10)) + expected := Some(T.MakeTuple10(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)) + assert.Equal(t, expected, result) +} + +// Test SequenceTuple4 through SequenceTuple10 +func TestSequenceTuple4(t *testing.T) { + tuple := T.MakeTuple4(Some(1), Some(2), Some(3), Some(4)) + result := SequenceTuple4(tuple) + expected := Some(T.MakeTuple4(1, 2, 3, 4)) + assert.Equal(t, expected, result) +} + +func TestSequenceTuple5(t *testing.T) { + tuple := T.MakeTuple5(Some(1), Some(2), Some(3), Some(4), Some(5)) + result := SequenceTuple5(tuple) + expected := Some(T.MakeTuple5(1, 2, 3, 4, 5)) + assert.Equal(t, expected, result) +} + +func TestSequenceTuple6(t *testing.T) { + tuple := T.MakeTuple6(Some(1), Some(2), Some(3), Some(4), Some(5), Some(6)) + result := SequenceTuple6(tuple) + expected := Some(T.MakeTuple6(1, 2, 3, 4, 5, 6)) + assert.Equal(t, expected, result) +} + +// Test TraverseTuple3 through TraverseTuple10 +func TestTraverseTuple3(t *testing.T) { + f1 := func(x int) Option[int] { return Some(x * 2) } + f2 := func(s string) Option[string] { return Some(s + "!") } + f3 := func(b bool) Option[bool] { return Some(!b) } + + traverse := TraverseTuple3(f1, f2, f3) + tuple := T.MakeTuple3(5, "hello", true) + result := traverse(tuple) + + expected := Some(T.MakeTuple3(10, "hello!", false)) + assert.Equal(t, expected, result) +} + +func TestTraverseTuple4(t *testing.T) { + f1 := func(x int) Option[int] { return Some(x * 2) } + f2 := func(x int) Option[int] { return Some(x + 1) } + f3 := func(x int) Option[int] { return Some(x - 1) } + f4 := func(x int) Option[int] { return Some(x * 3) } + + traverse := TraverseTuple4(f1, f2, f3, f4) + tuple := T.MakeTuple4(1, 2, 3, 4) + result := traverse(tuple) + + expected := Some(T.MakeTuple4(2, 3, 2, 12)) + assert.Equal(t, expected, result) +} + +// Test JSON marshaling edge cases +func TestJSONMarshalNone(t *testing.T) { + none := None[int]() + data, err := none.MarshalJSON() + assert.NoError(t, err) + assert.Equal(t, []byte("null"), data) +} + +func TestJSONMarshalSome(t *testing.T) { + some := Some(42) + data, err := some.MarshalJSON() + assert.NoError(t, err) + assert.Equal(t, []byte("42"), data) +} + +// Test Format method +func TestFormat(t *testing.T) { + some := Some(42) + formatted := fmt.Sprintf("%s", some) + assert.Contains(t, formatted, "Some") + assert.Contains(t, formatted, "42") + + none := None[int]() + formatted = fmt.Sprintf("%s", none) + assert.Contains(t, formatted, "None") +} + +// Test edge cases for MonadFold +func TestMonadFoldEdgeCases(t *testing.T) { + // Test with complex types + type ComplexType struct { + value int + name string + } + + some := Some(ComplexType{value: 42, name: "test"}) + result := MonadFold(some, + func() string { return "none" }, + func(ct ComplexType) string { return ct.name }, + ) + assert.Equal(t, "test", result) + + none := None[ComplexType]() + result = MonadFold(none, + func() string { return "none" }, + func(ct ComplexType) string { return ct.name }, + ) + assert.Equal(t, "none", result) +} + +// Test TraverseArrayWithIndexG +func TestTraverseArrayWithIndexG(t *testing.T) { + type MySlice []int + type MyResultSlice []string + + f := func(i int, x int) Option[string] { + return Some(fmt.Sprintf("%d:%d", i, x)) + } + + result := TraverseArrayWithIndexG[MySlice, MyResultSlice](f)(MySlice{10, 20, 30}) + expected := Some(MyResultSlice{"0:10", "1:20", "2:30"}) + assert.Equal(t, expected, result) +} + +// Test TraverseRecordWithIndexG +func TestTraverseRecordWithIndexG(t *testing.T) { + type MyMap map[string]int + type MyResultMap map[string]string + + f := func(k string, v int) Option[string] { + return Some(fmt.Sprintf("%s=%d", k, v)) + } + + input := MyMap{"a": 1, "b": 2} + result := TraverseRecordWithIndexG[MyMap, MyResultMap](f)(input) + + assert.True(t, IsSome(result)) +} + +// Test SequenceTuple1 +func TestSequenceTuple1(t *testing.T) { + tuple := T.MakeTuple1(Some(42)) + result := SequenceTuple1(tuple) + expected := Some(T.MakeTuple1(42)) + assert.Equal(t, expected, result) + + // Test with None + tuple = T.MakeTuple1(None[int]()) + result = SequenceTuple1(tuple) + assert.Equal(t, None[T.Tuple1[int]](), result) +} + +// Test TraverseTuple1 +func TestTraverseTuple1(t *testing.T) { + f := func(x int) Option[int] { return Some(x * 2) } + + traverse := TraverseTuple1(f) + tuple := T.MakeTuple1(5) + result := traverse(tuple) + + expected := Some(T.MakeTuple1(10)) + assert.Equal(t, expected, result) +} + +// Test Unoptionize2 +func TestUnoptionize2(t *testing.T) { + f := func(a, b int) Option[int] { + return Some(a + b) + } + + unoptF := Unoptionize2(f) + val, ok := unoptF(2, 3) + assert.True(t, ok) + assert.Equal(t, 5, val) +} + +// Test Unoptionize5 +func TestUnoptionize5(t *testing.T) { + f := func(a, b, c, d, e int) Option[int] { + return Some(a + b + c + d + e) + } + + unoptF := Unoptionize5(f) + val, ok := unoptF(1, 2, 3, 4, 5) + assert.True(t, ok) + assert.Equal(t, 15, val) +} + +// Test Unoptionize6 +func TestUnoptionize6(t *testing.T) { + f := func(a, b, c, d, e, f int) Option[int] { + return Some(a + b + c + d + e + f) + } + + unoptF := Unoptionize6(f) + val, ok := unoptF(1, 2, 3, 4, 5, 6) + assert.True(t, ok) + assert.Equal(t, 21, val) +} + +// Test Optionize6 +func TestOptionize6(t *testing.T) { + f := func(a, b, c, d, e, f int) (int, bool) { + sum := a + b + c + d + e + f + return sum, sum > 0 + } + + optF := Optionize6(f) + assert.Equal(t, Some(21), optF(1, 2, 3, 4, 5, 6)) +} + +// Test Optionize7 +func TestOptionize7(t *testing.T) { + f := func(a, b, c, d, e, f, g int) (int, bool) { + sum := a + b + c + d + e + f + g + return sum, sum > 0 + } + + optF := Optionize7(f) + assert.Equal(t, Some(28), optF(1, 2, 3, 4, 5, 6, 7)) +} + +// Test Unoptionize7 +func TestUnoptionize7(t *testing.T) { + f := func(a, b, c, d, e, f, g int) Option[int] { + return Some(a + b + c + d + e + f + g) + } + + unoptF := Unoptionize7(f) + val, ok := unoptF(1, 2, 3, 4, 5, 6, 7) + assert.True(t, ok) + assert.Equal(t, 28, val) +} + +// Test Optionize8 +func TestOptionize8(t *testing.T) { + f := func(a, b, c, d, e, f, g, h int) (int, bool) { + sum := a + b + c + d + e + f + g + h + return sum, sum > 0 + } + + optF := Optionize8(f) + assert.Equal(t, Some(36), optF(1, 2, 3, 4, 5, 6, 7, 8)) +} + +// Test Unoptionize8 +func TestUnoptionize8(t *testing.T) { + f := func(a, b, c, d, e, f, g, h int) Option[int] { + return Some(a + b + c + d + e + f + g + h) + } + + unoptF := Unoptionize8(f) + val, ok := unoptF(1, 2, 3, 4, 5, 6, 7, 8) + assert.True(t, ok) + assert.Equal(t, 36, val) +} + +// Test Optionize9 +func TestOptionize9(t *testing.T) { + f := func(a, b, c, d, e, f, g, h, i int) (int, bool) { + sum := a + b + c + d + e + f + g + h + i + return sum, sum > 0 + } + + optF := Optionize9(f) + assert.Equal(t, Some(45), optF(1, 2, 3, 4, 5, 6, 7, 8, 9)) +} + +// Test Unoptionize9 +func TestUnoptionize9(t *testing.T) { + f := func(a, b, c, d, e, f, g, h, i int) Option[int] { + return Some(a + b + c + d + e + f + g + h + i) + } + + unoptF := Unoptionize9(f) + val, ok := unoptF(1, 2, 3, 4, 5, 6, 7, 8, 9) + assert.True(t, ok) + assert.Equal(t, 45, val) +} + +// Test Optionize10 +func TestOptionize10(t *testing.T) { + f := func(a, b, c, d, e, f, g, h, i, j int) (int, bool) { + sum := a + b + c + d + e + f + g + h + i + j + return sum, sum > 0 + } + + optF := Optionize10(f) + assert.Equal(t, Some(55), optF(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)) +} + +// Test Unoptionize10 +func TestUnoptionize10(t *testing.T) { + f := func(a, b, c, d, e, f, g, h, i, j int) Option[int] { + return Some(a + b + c + d + e + f + g + h + i + j) + } + + unoptF := Unoptionize10(f) + val, ok := unoptF(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + assert.True(t, ok) + assert.Equal(t, 55, val) +} + +// Test SequenceTuple7 +func TestSequenceTuple7(t *testing.T) { + tuple := T.MakeTuple7(Some(1), Some(2), Some(3), Some(4), Some(5), Some(6), Some(7)) + result := SequenceTuple7(tuple) + expected := Some(T.MakeTuple7(1, 2, 3, 4, 5, 6, 7)) + assert.Equal(t, expected, result) +} + +// Test SequenceTuple8 +func TestSequenceTuple8(t *testing.T) { + tuple := T.MakeTuple8(Some(1), Some(2), Some(3), Some(4), Some(5), Some(6), Some(7), Some(8)) + result := SequenceTuple8(tuple) + expected := Some(T.MakeTuple8(1, 2, 3, 4, 5, 6, 7, 8)) + assert.Equal(t, expected, result) +} + +// Test SequenceTuple9 +func TestSequenceTuple9(t *testing.T) { + tuple := T.MakeTuple9(Some(1), Some(2), Some(3), Some(4), Some(5), Some(6), Some(7), Some(8), Some(9)) + result := SequenceTuple9(tuple) + expected := Some(T.MakeTuple9(1, 2, 3, 4, 5, 6, 7, 8, 9)) + assert.Equal(t, expected, result) +} + +// Test SequenceTuple10 +func TestSequenceTuple10(t *testing.T) { + tuple := T.MakeTuple10(Some(1), Some(2), Some(3), Some(4), Some(5), Some(6), Some(7), Some(8), Some(9), Some(10)) + result := SequenceTuple10(tuple) + expected := Some(T.MakeTuple10(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)) + assert.Equal(t, expected, result) +} + +// Test TryCatch with success case +func TestTryCatchSuccess(t *testing.T) { + result := TryCatch(func() (int, error) { + return 42, nil + }) + assert.Equal(t, Some(42), result) +} diff --git a/v2/option/option_extended_test.go b/v2/option/option_extended_test.go new file mode 100644 index 0000000..c5bf368 --- /dev/null +++ b/v2/option/option_extended_test.go @@ -0,0 +1,556 @@ +// Copyright (c) 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package option + +import ( + "fmt" + "testing" + + F "github.com/IBM/fp-go/v2/function" + M "github.com/IBM/fp-go/v2/monoid" + P "github.com/IBM/fp-go/v2/pair" + S "github.com/IBM/fp-go/v2/semigroup" + T "github.com/IBM/fp-go/v2/tuple" + "github.com/stretchr/testify/assert" +) + +// Test FromNillable +func TestFromNillable(t *testing.T) { + var nilPtr *int = nil + assert.Equal(t, None[*int](), FromNillable(nilPtr)) + + val := 42 + ptr := &val + result := FromNillable(ptr) + assert.True(t, IsSome(result)) + unwrapped, ok := Unwrap(result) + assert.True(t, ok) + assert.Equal(t, &val, unwrapped) +} + +// Test FromValidation +func TestFromValidation(t *testing.T) { + validate := func(x int) (int, bool) { + if x > 0 { + return x * 2, true + } + return 0, false + } + + f := FromValidation(validate) + assert.Equal(t, Some(10), f(5)) + assert.Equal(t, None[int](), f(-1)) +} + +// Test MonadAp +func TestMonadAp(t *testing.T) { + double := func(x int) int { return x * 2 } + + assert.Equal(t, Some(10), MonadAp(Some(double), Some(5))) + assert.Equal(t, None[int](), MonadAp(Some(double), None[int]())) + assert.Equal(t, None[int](), MonadAp(None[func(int) int](), Some(5))) + assert.Equal(t, None[int](), MonadAp(None[func(int) int](), None[int]())) +} + +// Test MonadMap +func TestMonadMap(t *testing.T) { + double := func(x int) int { return x * 2 } + + assert.Equal(t, Some(10), MonadMap(Some(5), double)) + assert.Equal(t, None[int](), MonadMap(None[int](), double)) +} + +// Test MonadMapTo +func TestMonadMapTo(t *testing.T) { + assert.Equal(t, Some("hello"), MonadMapTo(Some(42), "hello")) + assert.Equal(t, None[string](), MonadMapTo(None[int](), "hello")) +} + +// Test MapTo +func TestMapTo(t *testing.T) { + replaceWith42 := MapTo[string, int](42) + assert.Equal(t, Some(42), replaceWith42(Some("hello"))) + assert.Equal(t, None[int](), replaceWith42(None[string]())) +} + +// Test MonadGetOrElse +func TestMonadGetOrElse(t *testing.T) { + defaultVal := func() int { return 0 } + + assert.Equal(t, 42, MonadGetOrElse(Some(42), defaultVal)) + assert.Equal(t, 0, MonadGetOrElse(None[int](), defaultVal)) +} + +// Test GetOrElse +func TestGetOrElse(t *testing.T) { + getOrZero := GetOrElse(func() int { return 0 }) + + assert.Equal(t, 42, getOrZero(Some(42))) + assert.Equal(t, 0, getOrZero(None[int]())) +} + +// Test MonadChain +func TestMonadChain(t *testing.T) { + validate := func(x int) Option[int] { + if x > 0 { + return Some(x * 2) + } + return None[int]() + } + + assert.Equal(t, Some(10), MonadChain(Some(5), validate)) + assert.Equal(t, None[int](), MonadChain(Some(-1), validate)) + assert.Equal(t, None[int](), MonadChain(None[int](), validate)) +} + +// Test MonadChainTo +func TestMonadChainTo(t *testing.T) { + assert.Equal(t, Some("hello"), MonadChainTo(Some(42), Some("hello"))) + assert.Equal(t, None[string](), MonadChainTo(Some(42), None[string]())) + assert.Equal(t, Some("hello"), MonadChainTo(None[int](), Some("hello"))) +} + +// Test ChainTo +func TestChainTo(t *testing.T) { + replaceWith := ChainTo[int, string](Some("hello")) + assert.Equal(t, Some("hello"), replaceWith(Some(42))) + assert.Equal(t, Some("hello"), replaceWith(None[int]())) +} + +// Test MonadChainFirst +func TestMonadChainFirst(t *testing.T) { + sideEffect := func(x int) Option[string] { + return Some(fmt.Sprintf("%d", x)) + } + + assert.Equal(t, Some(5), MonadChainFirst(Some(5), sideEffect)) + assert.Equal(t, None[int](), MonadChainFirst(None[int](), sideEffect)) +} + +// Test ChainFirst +func TestChainFirst(t *testing.T) { + sideEffect := func(x int) Option[string] { + return Some(fmt.Sprintf("%d", x)) + } + chainFirst := ChainFirst(sideEffect) + + assert.Equal(t, Some(5), chainFirst(Some(5))) + assert.Equal(t, None[int](), chainFirst(None[int]())) +} + +// Test MonadAlt +func TestMonadAlt(t *testing.T) { + alternative := func() Option[int] { return Some(10) } + + assert.Equal(t, Some(5), MonadAlt(Some(5), alternative)) + assert.Equal(t, Some(10), MonadAlt(None[int](), alternative)) +} + +// Test MonadSequence2 +func TestMonadSequence2(t *testing.T) { + combine := func(a, b int) Option[int] { + return Some(a + b) + } + + assert.Equal(t, Some(5), MonadSequence2(Some(2), Some(3), combine)) + assert.Equal(t, None[int](), MonadSequence2(None[int](), Some(3), combine)) + assert.Equal(t, None[int](), MonadSequence2(Some(2), None[int](), combine)) +} + +// Test Sequence2 +func TestSequence2(t *testing.T) { + add := Sequence2(func(a, b int) Option[int] { return Some(a + b) }) + + assert.Equal(t, Some(5), add(Some(2), Some(3))) + assert.Equal(t, None[int](), add(None[int](), Some(3))) +} + +// Test Filter +func TestFilter(t *testing.T) { + isPositive := Filter(func(x int) bool { return x > 0 }) + + assert.Equal(t, Some(5), isPositive(Some(5))) + assert.Equal(t, None[int](), isPositive(Some(-1))) + assert.Equal(t, None[int](), isPositive(None[int]())) +} + +// Test MonadFlap +func TestMonadFlap(t *testing.T) { + double := func(x int) int { return x * 2 } + + assert.Equal(t, Some(10), MonadFlap(Some(double), 5)) + assert.Equal(t, None[int](), MonadFlap(None[func(int) int](), 5)) +} + +// Test Flap +func TestFlap(t *testing.T) { + applyFive := Flap[int](5) + double := func(x int) int { return x * 2 } + + assert.Equal(t, Some(10), applyFive(Some(double))) + assert.Equal(t, None[int](), applyFive(None[func(int) int]())) +} + +// Test Unwrap +func TestUnwrap(t *testing.T) { + val, ok := Unwrap(Some(42)) + assert.True(t, ok) + assert.Equal(t, 42, val) + + val, ok = Unwrap(None[int]()) + assert.False(t, ok) + assert.Equal(t, 0, val) +} + +// Test String and Format +func TestStringFormat(t *testing.T) { + opt := Some(42) + str := opt.String() + assert.Contains(t, str, "Some") + assert.Contains(t, str, "42") + + none := None[int]() + str = none.String() + assert.Contains(t, str, "None") +} + +// Test Semigroup +func TestSemigroup(t *testing.T) { + intSemigroup := S.MakeSemigroup(func(a, b int) int { return a + b }) + optSemigroup := Semigroup[int]()(intSemigroup) + + assert.Equal(t, Some(5), optSemigroup.Concat(Some(2), Some(3))) + assert.Equal(t, Some(2), optSemigroup.Concat(Some(2), None[int]())) + assert.Equal(t, Some(3), optSemigroup.Concat(None[int](), Some(3))) + assert.Equal(t, None[int](), optSemigroup.Concat(None[int](), None[int]())) +} + +// Test Monoid +func TestMonoid(t *testing.T) { + intSemigroup := S.MakeSemigroup(func(a, b int) int { return a + b }) + optMonoid := Monoid[int]()(intSemigroup) + + assert.Equal(t, Some(5), optMonoid.Concat(Some(2), Some(3))) + assert.Equal(t, None[int](), optMonoid.Empty()) +} + +// Test ApplySemigroup +func TestApplySemigroup(t *testing.T) { + intSemigroup := S.MakeSemigroup(func(a, b int) int { return a + b }) + optSemigroup := ApplySemigroup(intSemigroup) + + assert.Equal(t, Some(5), optSemigroup.Concat(Some(2), Some(3))) + assert.Equal(t, None[int](), optSemigroup.Concat(Some(2), None[int]())) +} + +// Test ApplicativeMonoid +func TestApplicativeMonoid(t *testing.T) { + intMonoid := M.MakeMonoid(func(a, b int) int { return a + b }, 0) + optMonoid := ApplicativeMonoid(intMonoid) + + assert.Equal(t, Some(5), optMonoid.Concat(Some(2), Some(3))) + assert.Equal(t, Some(0), optMonoid.Empty()) +} + +// Test AlternativeMonoid +func TestAlternativeMonoid(t *testing.T) { + intMonoid := M.MakeMonoid(func(a, b int) int { return a + b }, 0) + optMonoid := AlternativeMonoid(intMonoid) + + // AlternativeMonoid uses applicative semantics, so it combines values + assert.Equal(t, Some(5), optMonoid.Concat(Some(2), Some(3))) + assert.Equal(t, Some(3), optMonoid.Concat(None[int](), Some(3))) + assert.Equal(t, Some(0), optMonoid.Empty()) +} + +// Test AltMonoid +func TestAltMonoid(t *testing.T) { + optMonoid := AltMonoid[int]() + + assert.Equal(t, Some(2), optMonoid.Concat(Some(2), Some(3))) + assert.Equal(t, Some(3), optMonoid.Concat(None[int](), Some(3))) + assert.Equal(t, None[int](), optMonoid.Empty()) +} + +// Test Do, Let, LetTo, BindTo +func TestDoLetLetToBindTo(t *testing.T) { + type State struct { + x int + y int + computed int + name string + } + + result := F.Pipe4( + Do(State{}), + Let(func(c int) func(State) State { + return func(s State) State { s.x = c; return s } + }, func(s State) int { return 5 }), + LetTo(func(n string) func(State) State { + return func(s State) State { s.name = n; return s } + }, "test"), + Bind(func(y int) func(State) State { + return func(s State) State { s.y = y; return s } + }, func(s State) Option[int] { return Some(10) }), + Map(func(s State) State { + s.computed = s.x + s.y + return s + }), + ) + + expected := Some(State{x: 5, y: 10, computed: 15, name: "test"}) + assert.Equal(t, expected, result) +} + +// Test BindTo +func TestBindToFunction(t *testing.T) { + type State struct { + value int + } + + result := F.Pipe1( + Some(42), + BindTo(func(x int) State { return State{value: x} }), + ) + + assert.Equal(t, Some(State{value: 42}), result) +} + +// Test Functor +func TestFunctor(t *testing.T) { + f := Functor[int, string]() + mapper := f.Map(func(x int) string { return fmt.Sprintf("%d", x) }) + + assert.Equal(t, Some("42"), mapper(Some(42))) + assert.Equal(t, None[string](), mapper(None[int]())) +} + +// Test Monad +func TestMonad(t *testing.T) { + m := Monad[int, string]() + + // Test Of + assert.Equal(t, Some(42), m.Of(42)) + + // Test Map + mapper := m.Map(func(x int) string { return fmt.Sprintf("%d", x) }) + assert.Equal(t, Some("42"), mapper(Some(42))) + + // Test Chain + chainer := m.Chain(func(x int) Option[string] { + if x > 0 { + return Some(fmt.Sprintf("%d", x)) + } + return None[string]() + }) + assert.Equal(t, Some("42"), chainer(Some(42))) + + // Test Ap + double := func(x int) string { return fmt.Sprintf("%d", x*2) } + ap := m.Ap(Some(5)) + assert.Equal(t, Some("10"), ap(Some(double))) +} + +// Test Pointed +func TestPointed(t *testing.T) { + p := Pointed[int]() + assert.Equal(t, Some(42), p.Of(42)) +} + +// Test ToAny +func TestToAny(t *testing.T) { + result := ToAny(42) + assert.True(t, IsSome(result)) + + val, ok := Unwrap(result) + assert.True(t, ok) + assert.Equal(t, 42, val) +} + +// Test TraverseArray +func TestTraverseArray(t *testing.T) { + validate := func(x int) Option[int] { + if x > 0 { + return Some(x * 2) + } + return None[int]() + } + + result := TraverseArray(validate)([]int{1, 2, 3}) + assert.Equal(t, Some([]int{2, 4, 6}), result) + + result = TraverseArray(validate)([]int{1, -1, 3}) + assert.Equal(t, None[[]int](), result) +} + +// Test TraverseArrayWithIndex +func TestTraverseArrayWithIndex(t *testing.T) { + f := func(i int, x int) Option[int] { + if x > i { + return Some(x + i) + } + return None[int]() + } + + result := TraverseArrayWithIndex(f)([]int{1, 2, 3}) + assert.Equal(t, Some([]int{1, 3, 5}), result) +} + +// Test TraverseRecord +func TestTraverseRecord(t *testing.T) { + validate := func(x int) Option[string] { + if x > 0 { + return Some(fmt.Sprintf("%d", x)) + } + return None[string]() + } + + input := map[string]int{"a": 1, "b": 2} + result := TraverseRecord[string](validate)(input) + + expected := Some(map[string]string{"a": "1", "b": "2"}) + assert.Equal(t, expected, result) +} + +// Test TraverseRecordWithIndex +func TestTraverseRecordWithIndex(t *testing.T) { + f := func(k string, v int) Option[string] { + return Some(fmt.Sprintf("%s:%d", k, v)) + } + + input := map[string]int{"a": 1, "b": 2} + result := TraverseRecordWithIndex(f)(input) + + assert.True(t, IsSome(result)) +} + +// Test SequencePair +func TestSequencePair(t *testing.T) { + pair := P.MakePair(Some(1), Some("hello")) + result := SequencePair(pair) + + assert.True(t, IsSome(result)) + + pair2 := P.MakePair(Some(1), None[string]()) + result2 := SequencePair(pair2) + assert.True(t, IsNone(result2)) +} + +// Test Optionize functions +func TestOptionize0(t *testing.T) { + f := func() (int, bool) { + return 42, true + } + + optF := Optionize0(f) + assert.Equal(t, Some(42), optF()) +} + +func TestOptionize1(t *testing.T) { + f := func(x int) (int, bool) { + if x > 0 { + return x * 2, true + } + return 0, false + } + + optF := Optionize1(f) + assert.Equal(t, Some(10), optF(5)) + assert.Equal(t, None[int](), optF(-1)) +} + +func TestOptionize2(t *testing.T) { + f := func(x, y int) (int, bool) { + if x > 0 && y > 0 { + return x + y, true + } + return 0, false + } + + optF := Optionize2(f) + assert.Equal(t, Some(5), optF(2, 3)) + assert.Equal(t, None[int](), optF(-1, 3)) +} + +// Test Unoptionize functions +func TestUnoptionize0(t *testing.T) { + f := func() Option[int] { + return Some(42) + } + + unoptF := Unoptionize0(f) + val, ok := unoptF() + assert.True(t, ok) + assert.Equal(t, 42, val) +} + +func TestUnoptionize1(t *testing.T) { + f := func(x int) Option[int] { + if x > 0 { + return Some(x * 2) + } + return None[int]() + } + + unoptF := Unoptionize1(f) + val, ok := unoptF(5) + assert.True(t, ok) + assert.Equal(t, 10, val) + + _, ok = unoptF(-1) + assert.False(t, ok) +} + +// Test SequenceTuple functions +func TestSequenceTuple2(t *testing.T) { + tuple := T.MakeTuple2(Some(1), Some("hello")) + result := SequenceTuple2(tuple) + + expected := Some(T.MakeTuple2(1, "hello")) + assert.Equal(t, expected, result) +} + +func TestSequenceTuple3(t *testing.T) { + tuple := T.MakeTuple3(Some(1), Some("hello"), Some(true)) + result := SequenceTuple3(tuple) + + expected := Some(T.MakeTuple3(1, "hello", true)) + assert.Equal(t, expected, result) +} + +// Test TraverseTuple functions +func TestTraverseTuple2(t *testing.T) { + f1 := func(x int) Option[int] { return Some(x * 2) } + f2 := func(s string) Option[string] { return Some(s + "!") } + + traverse := TraverseTuple2(f1, f2) + tuple := T.MakeTuple2(5, "hello") + result := traverse(tuple) + + expected := Some(T.MakeTuple2(10, "hello!")) + assert.Equal(t, expected, result) +} + +// Test FromStrictCompare +func TestFromStrictCompare(t *testing.T) { + optOrd := FromStrictCompare[int]() + + assert.Equal(t, 0, optOrd.Compare(Some(5), Some(5))) + assert.Equal(t, -1, optOrd.Compare(Some(3), Some(5))) + assert.Equal(t, 1, optOrd.Compare(Some(5), Some(3))) + assert.Equal(t, -1, optOrd.Compare(None[int](), Some(5))) + assert.Equal(t, 1, optOrd.Compare(Some(5), None[int]())) +} diff --git a/v2/option/option_test.go b/v2/option/option_test.go new file mode 100644 index 0000000..e172058 --- /dev/null +++ b/v2/option/option_test.go @@ -0,0 +1,176 @@ +// Copyright (c) 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package option + +import ( + "encoding/json" + "fmt" + "testing" + + F "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/internal/utils" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +type ( + SampleData struct { + Value string + OptValue Option[string] + } +) + +func TestJson(t *testing.T) { + + sample := SampleData{ + Value: "value", + OptValue: Of("optValue"), + } + + data, err := json.Marshal(&sample) + require.NoError(t, err) + + var deser SampleData + err = json.Unmarshal(data, &deser) + require.NoError(t, err) + + assert.Equal(t, sample, deser) + + sample = SampleData{ + Value: "value", + OptValue: None[string](), + } + + data, err = json.Marshal(&sample) + require.NoError(t, err) + + err = json.Unmarshal(data, &deser) + require.NoError(t, err) + + assert.Equal(t, sample, deser) +} + +func TestDefault(t *testing.T) { + var e Option[string] + + assert.Equal(t, None[string](), e) +} + +func TestReduce(t *testing.T) { + + assert.Equal(t, 2, F.Pipe1(None[int](), Reduce(utils.Sum, 2))) + assert.Equal(t, 5, F.Pipe1(Some(3), Reduce(utils.Sum, 2))) +} + +func TestIsNone(t *testing.T) { + assert.True(t, IsNone(None[int]())) + assert.False(t, IsNone(Of(1))) +} + +func TestIsSome(t *testing.T) { + assert.True(t, IsSome(Of(1))) + assert.False(t, IsSome(None[int]())) +} + +func TestMapOption(t *testing.T) { + + assert.Equal(t, F.Pipe1(Some(2), Map(utils.Double)), Some(4)) + + assert.Equal(t, F.Pipe1(None[int](), Map(utils.Double)), None[int]()) +} + +func TestTryCachOption(t *testing.T) { + + res := TryCatch(utils.Error) + + assert.Equal(t, None[int](), res) +} + +func TestAp(t *testing.T) { + assert.Equal(t, Some(4), F.Pipe1( + Some(utils.Double), + Ap[int, int](Some(2)), + )) + + assert.Equal(t, None[int](), F.Pipe1( + Some(utils.Double), + Ap[int, int](None[int]()), + )) + + assert.Equal(t, None[int](), F.Pipe1( + None[func(int) int](), + Ap[int, int](Some(2)), + )) + + assert.Equal(t, None[int](), F.Pipe1( + None[func(int) int](), + Ap[int, int](None[int]()), + )) +} + +func TestChain(t *testing.T) { + f := func(n int) Option[int] { return Some(n * 2) } + g := func(_ int) Option[int] { return None[int]() } + + assert.Equal(t, Some(2), F.Pipe1( + Some(1), + Chain(f), + )) + + assert.Equal(t, None[int](), F.Pipe1( + None[int](), + Chain(f), + )) + + assert.Equal(t, None[int](), F.Pipe1( + Some(1), + Chain(g), + )) + + assert.Equal(t, None[int](), F.Pipe1( + None[int](), + Chain(g), + )) +} + +func TestFlatten(t *testing.T) { + assert.Equal(t, Of(1), F.Pipe1(Of(Of(1)), Flatten[int])) +} + +func TestFold(t *testing.T) { + f := F.Constant("none") + g := func(s string) string { return fmt.Sprintf("some%d", len(s)) } + + fold := Fold(f, g) + + assert.Equal(t, "none", fold(None[string]())) + assert.Equal(t, "some3", fold(Some("abc"))) +} + +func TestFromPredicate(t *testing.T) { + p := func(n int) bool { return n > 2 } + f := FromPredicate(p) + + assert.Equal(t, None[int](), f(1)) + assert.Equal(t, Some(3), f(3)) +} + +func TestAlt(t *testing.T) { + assert.Equal(t, Some(1), F.Pipe1(Some(1), Alt(F.Constant(Some(2))))) + assert.Equal(t, Some(2), F.Pipe1(Some(2), Alt(F.Constant(None[int]())))) + assert.Equal(t, Some(1), F.Pipe1(None[int](), Alt(F.Constant(Some(1))))) + assert.Equal(t, None[int](), F.Pipe1(None[int](), Alt(F.Constant(None[int]())))) +} diff --git a/v2/option/ord.go b/v2/option/ord.go new file mode 100644 index 0000000..88ca65b --- /dev/null +++ b/v2/option/ord.go @@ -0,0 +1,58 @@ +// Copyright (c) 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package option + +import ( + C "github.com/IBM/fp-go/v2/constraints" + F "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/ord" +) + +// Ord constructs an ordering for Option[A] given an ordering for A. +// The ordering follows these rules: +// - None is considered less than any Some value +// - Two None values are equal +// - Two Some values are compared using the provided Ord for A +// +// Example: +// +// intOrd := ord.FromStrictCompare[int]() +// optOrd := Ord(intOrd) +// optOrd.Compare(None[int](), Some(5)) // -1 (None < Some) +// optOrd.Compare(Some(3), Some(5)) // -1 (3 < 5) +// optOrd.Compare(Some(5), Some(3)) // 1 (5 > 3) +// optOrd.Compare(None[int](), None[int]()) // 0 (equal) +func Ord[A any](a ord.Ord[A]) ord.Ord[Option[A]] { + // some convenient shortcuts + fld := Fold( + F.Constant(Fold(F.Constant(0), F.Constant1[A](-1))), + F.Flow2(F.Curry2(a.Compare), F.Bind1st(Fold[A, int], F.Constant(1))), + ) + // convert to an ordering predicate + return ord.MakeOrd(F.Uncurry2(fld), Eq(ord.ToEq(a)).Equals) +} + +// FromStrictCompare constructs an Ord for Option[A] using Go's built-in comparison operators for type A. +// This is a convenience function for ordered types (types that support <, >, ==). +// +// Example: +// +// optOrd := FromStrictCompare[int]() +// optOrd.Compare(Some(5), Some(10)) // -1 +// optOrd.Compare(None[int](), Some(5)) // -1 +func FromStrictCompare[A C.Ordered]() ord.Ord[Option[A]] { + return Ord(ord.FromStrictCompare[A]()) +} diff --git a/v2/option/ord_test.go b/v2/option/ord_test.go new file mode 100644 index 0000000..e69d223 --- /dev/null +++ b/v2/option/ord_test.go @@ -0,0 +1,46 @@ +// Copyright (c) 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package option + +import ( + "testing" + + S "github.com/IBM/fp-go/v2/string" + "github.com/stretchr/testify/assert" +) + +// it('getOrd', () => { +// const OS = _.getOrd(S.Ord) +// U.deepStrictEqual(OS.compare(_.none, _.none), 0) +// U.deepStrictEqual(OS.compare(_.some('a'), _.none), 1) +// U.deepStrictEqual(OS.compare(_.none, _.some('a')), -1) +// U.deepStrictEqual(OS.compare(_.some('a'), _.some('a')), 0) +// U.deepStrictEqual(OS.compare(_.some('a'), _.some('b')), -1) +// U.deepStrictEqual(OS.compare(_.some('b'), _.some('a')), 1) +// }) + +func TestOrd(t *testing.T) { + + os := Ord(S.Ord) + + assert.Equal(t, 0, os.Compare(None[string](), None[string]())) + assert.Equal(t, 1, os.Compare(Some("a"), None[string]())) + assert.Equal(t, -1, os.Compare(None[string](), Some("a"))) + assert.Equal(t, 0, os.Compare(Some("a"), Some("a"))) + assert.Equal(t, -1, os.Compare(Some("a"), Some("b"))) + assert.Equal(t, 1, os.Compare(Some("b"), Some("a"))) + +} diff --git a/v2/option/pair.go b/v2/option/pair.go new file mode 100644 index 0000000..81a43c9 --- /dev/null +++ b/v2/option/pair.go @@ -0,0 +1,39 @@ +// Copyright (c) 2024 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package option + +import ( + P "github.com/IBM/fp-go/v2/pair" + PG "github.com/IBM/fp-go/v2/pair/generic" +) + +// SequencePair converts a Pair of Options into an Option of a Pair. +// Returns Some containing the pair of values if both Options are Some, None if either is None. +// +// Example: +// +// pair := P.MakePair(Some(1), Some("hello")) +// result := SequencePair(pair) // Some(Pair(1, "hello")) +// +// pair := P.MakePair(Some(1), None[string]()) +// result := SequencePair(pair) // None +func SequencePair[T1, T2 any](t P.Pair[Option[T1], Option[T2]]) Option[P.Pair[T1, T2]] { + return PG.SequencePair( + Map[T1, func(T2) P.Pair[T1, T2]], + Ap[P.Pair[T1, T2], T2], + t, + ) +} diff --git a/v2/option/pointed.go b/v2/option/pointed.go new file mode 100644 index 0000000..624aa98 --- /dev/null +++ b/v2/option/pointed.go @@ -0,0 +1,37 @@ +// Copyright (c) 2024 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package option + +import ( + "github.com/IBM/fp-go/v2/internal/pointed" +) + +type optionPointed[A any] struct{} + +func (o *optionPointed[A]) Of(a A) Option[A] { + return Of[A](a) +} + +// Pointed implements the Pointed operations for Option. +// A pointed functor is a functor with an Of operation that wraps a value. +// +// Example: +// +// p := Pointed[int]() +// result := p.Of(42) // Some(42) +func Pointed[A any]() pointed.Pointed[A, Option[A]] { + return &optionPointed[A]{} +} diff --git a/v2/option/record.go b/v2/option/record.go new file mode 100644 index 0000000..769d64d --- /dev/null +++ b/v2/option/record.go @@ -0,0 +1,150 @@ +// Copyright (c) 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package option + +import ( + F "github.com/IBM/fp-go/v2/function" + RR "github.com/IBM/fp-go/v2/internal/record" +) + +// TraverseRecordG transforms a record (map) by applying a function that returns an Option to each value. +// Returns Some containing the map of results if all operations succeed, None if any fails. +// This is the generic version that works with custom map types. +// +// Example: +// +// validate := func(x int) Option[int] { +// if x > 0 { return Some(x * 2) } +// return None[int]() +// } +// input := map[string]int{"a": 1, "b": 2} +// result := TraverseRecordG[map[string]int, map[string]int](validate)(input) // Some(map[a:2 b:4]) +func TraverseRecordG[GA ~map[K]A, GB ~map[K]B, K comparable, A, B any](f func(A) Option[B]) func(GA) Option[GB] { + return RR.Traverse[GA]( + Of[GB], + Map[GB, func(B) GB], + Ap[GB, B], + + f, + ) +} + +// TraverseRecord transforms a record (map) by applying a function that returns an Option to each value. +// Returns Some containing the map of results if all operations succeed, None if any fails. +// +// Example: +// +// validate := func(x int) Option[string] { +// if x > 0 { return Some(fmt.Sprintf("%d", x)) } +// return None[string]() +// } +// input := map[string]int{"a": 1, "b": 2} +// result := TraverseRecord(validate)(input) // Some(map[a:"1" b:"2"]) +func TraverseRecord[K comparable, A, B any](f func(A) Option[B]) func(map[K]A) Option[map[K]B] { + return TraverseRecordG[map[K]A, map[K]B](f) +} + +// TraverseRecordWithIndexG transforms a record by applying a function that receives both key and value. +// Returns Some containing the map of results if all operations succeed, None if any fails. +// This is the generic version that works with custom map types. +// +// Example: +// +// f := func(k string, v int) Option[string] { +// return Some(fmt.Sprintf("%s:%d", k, v)) +// } +// input := map[string]int{"a": 1, "b": 2} +// result := TraverseRecordWithIndexG[map[string]int, map[string]string](f)(input) // Some(map[a:"a:1" b:"b:2"]) +func TraverseRecordWithIndexG[GA ~map[K]A, GB ~map[K]B, K comparable, A, B any](f func(K, A) Option[B]) func(GA) Option[GB] { + return RR.TraverseWithIndex[GA]( + Of[GB], + Map[GB, func(B) GB], + Ap[GB, B], + + f, + ) +} + +// TraverseRecordWithIndex transforms a record by applying a function that receives both key and value. +// Returns Some containing the map of results if all operations succeed, None if any fails. +// +// Example: +// +// f := func(k string, v int) Option[int] { +// if v > 0 { return Some(v) } +// return None[int]() +// } +// input := map[string]int{"a": 1, "b": 2} +// result := TraverseRecordWithIndex(f)(input) // Some(map[a:1 b:2]) +func TraverseRecordWithIndex[K comparable, A, B any](f func(K, A) Option[B]) func(map[K]A) Option[map[K]B] { + return TraverseRecordWithIndexG[map[K]A, map[K]B](f) +} + +// SequenceRecordG converts a map of Options into an Option of a map. +// Returns Some containing all key-value pairs if all Options are Some, None if any is None. +// This is the generic version that works with custom map types. +// +// Example: +// +// type MyMap map[string]int +// input := map[string]Option[int]{"a": Some(1), "b": Some(2)} +// result := SequenceRecordG[MyMap](input) // Some(MyMap{"a": 1, "b": 2}) +func SequenceRecordG[GA ~map[K]A, GOA ~map[K]Option[A], K comparable, A any](ma GOA) Option[GA] { + return TraverseRecordG[GOA, GA](F.Identity[Option[A]])(ma) +} + +// SequenceRecord converts a map of Options into an Option of a map. +// Returns Some containing all key-value pairs if all Options are Some, None if any is None. +// +// Example: +// +// input := map[string]Option[int]{"a": Some(1), "b": Some(2), "c": Some(3)} +// result := SequenceRecord(input) // Some(map[a:1 b:2 c:3]) +// input := map[string]Option[int]{"a": Some(1), "b": None[int]()} +// result := SequenceRecord(input) // None +func SequenceRecord[K comparable, A any](ma map[K]Option[A]) Option[map[K]A] { + return SequenceRecordG[map[K]A](ma) +} + +func upsertAtReadWrite[M ~map[K]V, K comparable, V any](r M, k K, v V) M { + r[k] = v + return r +} + +// CompactRecordG filters a map of Options, keeping only the Some values and discarding None values. +// This is the generic version that works with custom map types. +// +// Example: +// +// type MyMap map[string]int +// input := map[string]Option[int]{"a": Some(1), "b": None[int](), "c": Some(3)} +// result := CompactRecordG[map[string]Option[int], MyMap](input) // MyMap{"a": 1, "c": 3} +func CompactRecordG[M1 ~map[K]Option[A], M2 ~map[K]A, K comparable, A any](m M1) M2 { + bnd := F.Bind12of3(upsertAtReadWrite[M2]) + return RR.ReduceWithIndex(m, func(key K, m M2, value Option[A]) M2 { + return MonadFold(value, F.Constant(m), bnd(m, key)) + }, make(M2)) +} + +// CompactRecord filters a map of Options, keeping only the Some values and discarding None values. +// +// Example: +// +// input := map[string]Option[int]{"a": Some(1), "b": None[int](), "c": Some(3)} +// result := CompactRecord(input) // map[a:1 c:3] +func CompactRecord[K comparable, A any](m map[K]Option[A]) map[K]A { + return CompactRecordG[map[K]Option[A], map[K]A](m) +} diff --git a/v2/option/record_test.go b/v2/option/record_test.go new file mode 100644 index 0000000..1800990 --- /dev/null +++ b/v2/option/record_test.go @@ -0,0 +1,54 @@ +// Copyright (c) 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package option + +import ( + "fmt" + "testing" + + TST "github.com/IBM/fp-go/v2/internal/testing" + "github.com/stretchr/testify/assert" +) + +func TestCompactRecord(t *testing.T) { + // make the map + m := make(map[string]Option[int]) + m["foo"] = None[int]() + m["bar"] = Some(1) + // compact it + m1 := CompactRecord(m) + // check expected + exp := map[string]int{ + "bar": 1, + } + + assert.Equal(t, exp, m1) +} + +func TestSequenceRecord(t *testing.T) { + + s := TST.SequenceRecordTest( + FromStrictEquals[bool](), + Pointed[string](), + Pointed[bool](), + Functor[map[string]string, bool](), + SequenceRecord[string, string], + ) + + for i := 0; i < 10; i++ { + t.Run(fmt.Sprintf("TestSequenceRecord %d", i), s(i)) + } +} diff --git a/v2/option/sequence.go b/v2/option/sequence.go new file mode 100644 index 0000000..b76c795 --- /dev/null +++ b/v2/option/sequence.go @@ -0,0 +1,69 @@ +// Copyright (c) 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package option + +import ( + F "github.com/IBM/fp-go/v2/function" +) + +// Sequence converts an Option of some higher kinded type into the higher kinded type of an Option. +// This is a generic sequencing operation that works with any applicative functor. +// +// Parameters: +// - mof: wraps an Option in the target higher kinded type +// - mmap: maps a function over the higher kinded type +// +// Example (conceptual - typically used with other monadic types): +// +// // Sequencing Option[IO[A]] to IO[Option[A]] +// result := Sequence[int, IO[int], IO[Option[int]]]( +// func(opt Option[int]) IO[Option[int]] { return IO.Of(opt) }, +// func(f func(int) Option[int]) func(IO[int]) IO[Option[int]] { ... }, +// ) +func Sequence[A, HKTA, HKTOA any]( + mof func(Option[A]) HKTOA, + mmap func(func(A) Option[A]) func(HKTA) HKTOA, +) func(Option[HKTA]) HKTOA { + return Fold(F.Nullary2(None[A], mof), mmap(Some[A])) +} + +// Traverse converts an Option by applying a function that produces a higher kinded type, +// then sequences the result. This combines mapping and sequencing in one operation. +// +// Parameters: +// - mof: wraps an Option in the target higher kinded type +// - mmap: maps a function over the higher kinded type +// +// Returns a function that takes a transformation function and an Option, producing +// the higher kinded type containing an Option. +// +// Example (conceptual - typically used with other monadic types): +// +// // Traversing Option[A] with a function A -> IO[B] to get IO[Option[B]] +// result := Traverse[int, string, IO[string], IO[Option[string]]]( +// func(opt Option[string]) IO[Option[string]] { return IO.Of(opt) }, +// func(f func(string) Option[string]) func(IO[string]) IO[Option[string]] { ... }, +// ) +func Traverse[A, B, HKTB, HKTOB any]( + mof func(Option[B]) HKTOB, + mmap func(func(B) Option[B]) func(HKTB) HKTOB, +) func(func(A) HKTB) func(Option[A]) HKTOB { + onNone := F.Nullary2(None[B], mof) + onSome := mmap(Some[B]) + return func(f func(A) HKTB) func(Option[A]) HKTOB { + return Fold(onNone, F.Flow2(f, onSome)) + } +} diff --git a/v2/option/sequence_test.go b/v2/option/sequence_test.go new file mode 100644 index 0000000..b2b0c6d --- /dev/null +++ b/v2/option/sequence_test.go @@ -0,0 +1,44 @@ +// Copyright (c) 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package option + +import ( + "testing" + + T "github.com/IBM/fp-go/v2/tuple" + "github.com/stretchr/testify/assert" +) + +func TestSequenceT(t *testing.T) { + // one argumemt + s1 := SequenceT1[int] + assert.Equal(t, Of(T.MakeTuple1(1)), s1(Of(1))) + + // two arguments + s2 := SequenceT2[int, string] + assert.Equal(t, Of(T.MakeTuple2(1, "a")), s2(Of(1), Of("a"))) + + // three arguments + s3 := SequenceT3[int, string, bool] + assert.Equal(t, Of(T.MakeTuple3(1, "a", true)), s3(Of(1), Of("a"), Of(true))) + + // four arguments + s4 := SequenceT4[int, string, bool, int] + assert.Equal(t, Of(T.MakeTuple4(1, "a", true, 2)), s4(Of(1), Of("a"), Of(true), Of(2))) + + // three with one none + assert.Equal(t, None[T.Tuple3[int, string, bool]](), s3(Of(1), Of("a"), None[bool]())) +} diff --git a/v2/option/testing/laws.go b/v2/option/testing/laws.go new file mode 100644 index 0000000..8d61c13 --- /dev/null +++ b/v2/option/testing/laws.go @@ -0,0 +1,102 @@ +// Copyright (c) 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package testing + +import ( + "testing" + + EQ "github.com/IBM/fp-go/v2/eq" + L "github.com/IBM/fp-go/v2/internal/monad/testing" + O "github.com/IBM/fp-go/v2/option" +) + +// AssertLaws asserts the monad laws for the Option monad. +// This function verifies that Option satisfies the functor, applicative, and monad laws. +// +// The laws tested include: +// - Functor laws: identity and composition +// - Applicative laws: identity, composition, homomorphism, and interchange +// - Monad laws: left identity, right identity, and associativity +// +// Parameters: +// - t: testing instance +// - eqa, eqb, eqc: equality predicates for types A, B, and C +// - ab: a function from A to B for testing +// - bc: a function from B to C for testing +// +// Returns a function that takes a value of type A and returns true if all laws hold. +// +// Example: +// +// func TestOptionLaws(t *testing.T) { +// eqInt := eq.FromStrictEquals[int]() +// eqString := eq.FromStrictEquals[string]() +// eqBool := eq.FromStrictEquals[bool]() +// +// ab := func(x int) string { return fmt.Sprintf("%d", x) } +// bc := func(s string) bool { return len(s) > 0 } +// +// assert := AssertLaws(t, eqInt, eqString, eqBool, ab, bc) +// assert(42) // verifies laws hold for value 42 +// } +func AssertLaws[A, B, C any](t *testing.T, + eqa EQ.Eq[A], + eqb EQ.Eq[B], + eqc EQ.Eq[C], + + ab func(A) B, + bc func(B) C, +) func(a A) bool { + + return L.AssertLaws(t, + O.Eq(eqa), + O.Eq(eqb), + O.Eq(eqc), + + O.Of[A], + O.Of[B], + O.Of[C], + + O.Of[func(A) A], + O.Of[func(A) B], + O.Of[func(B) C], + O.Of[func(func(A) B) B], + + O.MonadMap[A, A], + O.MonadMap[A, B], + O.MonadMap[A, C], + O.MonadMap[B, C], + + O.MonadMap[func(B) C, func(func(A) B) func(A) C], + + O.MonadChain[A, A], + O.MonadChain[A, B], + O.MonadChain[A, C], + O.MonadChain[B, C], + + O.MonadAp[A, A], + O.MonadAp[B, A], + O.MonadAp[C, B], + O.MonadAp[C, A], + + O.MonadAp[B, func(A) B], + O.MonadAp[func(A) C, func(A) B], + + ab, + bc, + ) + +} diff --git a/v2/option/testing/laws_test.go b/v2/option/testing/laws_test.go new file mode 100644 index 0000000..63b23e1 --- /dev/null +++ b/v2/option/testing/laws_test.go @@ -0,0 +1,47 @@ +// Copyright (c) 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package testing + +import ( + "fmt" + "testing" + + EQ "github.com/IBM/fp-go/v2/eq" + "github.com/stretchr/testify/assert" +) + +func TestMonadLaws(t *testing.T) { + // some comparison + eqa := EQ.FromStrictEquals[bool]() + eqb := EQ.FromStrictEquals[int]() + eqc := EQ.FromStrictEquals[string]() + + ab := func(a bool) int { + if a { + return 1 + } + return 0 + } + + bc := func(b int) string { + return fmt.Sprintf("value %d", b) + } + + laws := AssertLaws(t, eqa, eqb, eqc, ab, bc) + + assert.True(t, laws(true)) + assert.True(t, laws(false)) +} diff --git a/v2/option/type.go b/v2/option/type.go new file mode 100644 index 0000000..e8ccf12 --- /dev/null +++ b/v2/option/type.go @@ -0,0 +1,53 @@ +// Copyright (c) 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package option + +import ( + F "github.com/IBM/fp-go/v2/function" +) + +func toType[T any](a any) (T, bool) { + b, ok := a.(T) + return b, ok +} + +// ToType attempts to convert a value of type any to a specific type T using type assertion. +// Returns Some(value) if the type assertion succeeds, None if it fails. +// +// Example: +// +// var x any = 42 +// result := ToType[int](x) // Some(42) +// +// var y any = "hello" +// result := ToType[int](y) // None (wrong type) +func ToType[T any](src any) Option[T] { + return F.Pipe1( + src, + Optionize1(toType[T]), + ) +} + +// ToAny converts a value of any type to Option[any]. +// This always succeeds and returns Some containing the value as any. +// +// Example: +// +// result := ToAny(42) // Some(any(42)) +// result := ToAny("hello") // Some(any("hello")) +func ToAny[T any](src T) Option[any] { + return Of(any(src)) +} diff --git a/v2/option/type_test.go b/v2/option/type_test.go new file mode 100644 index 0000000..d400dae --- /dev/null +++ b/v2/option/type_test.go @@ -0,0 +1,37 @@ +// Copyright (c) 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package option + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestTypeConversion(t *testing.T) { + + var src any = "Carsten" + + dst := ToType[string](src) + assert.Equal(t, Some("Carsten"), dst) +} + +func TestInvalidConversion(t *testing.T) { + var src any = make(map[string]string) + + dst := ToType[int](src) + assert.Equal(t, None[int](), dst) +} diff --git a/v2/ord/doc.go b/v2/ord/doc.go new file mode 100644 index 0000000..af20d3f --- /dev/null +++ b/v2/ord/doc.go @@ -0,0 +1,389 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +/* +Package ord provides the Ord type class for types that support total ordering. + +# Overview + +An Ord represents a total ordering on a type. It extends Eq (equality) with +a comparison function that returns -1, 0, or 1 to indicate less than, equal to, +or greater than relationships. + +The Ord interface: + + type Ord[T any] interface { + Eq[T] // Provides Equals(x, y T) bool + Compare(x, y T) int // Returns -1, 0, or 1 + } + +Ord laws: + - Reflexivity: Compare(x, x) = 0 + - Antisymmetry: if Compare(x, y) <= 0 and Compare(y, x) <= 0 then x = y + - Transitivity: if Compare(x, y) <= 0 and Compare(y, z) <= 0 then Compare(x, z) <= 0 + - Totality: Compare(x, y) <= 0 or Compare(y, x) <= 0 + +# Basic Usage + +Creating an Ord for integers: + + intOrd := ord.FromStrictCompare[int]() + + result := intOrd.Compare(5, 3) // 1 (5 > 3) + result := intOrd.Compare(3, 5) // -1 (3 < 5) + result := intOrd.Compare(5, 5) // 0 (5 == 5) + + equal := intOrd.Equals(5, 5) // true + +Creating a custom Ord: + + type Person struct { + Name string + Age int + } + + // Order by age + personOrd := ord.MakeOrd( + func(p1, p2 Person) int { + if p1.Age < p2.Age { + return -1 + } else if p1.Age > p2.Age { + return 1 + } + return 0 + }, + func(p1, p2 Person) bool { + return p1.Age == p2.Age + }, + ) + +Creating Ord from compare function only: + + stringOrd := ord.FromCompare(func(a, b string) int { + if a < b { + return -1 + } else if a > b { + return 1 + } + return 0 + }) + // Equals is automatically derived from Compare + +# Comparison Functions + +Lt - Less than: + + intOrd := ord.FromStrictCompare[int]() + isLessThan5 := ord.Lt(intOrd)(5) + + result := isLessThan5(3) // true + result := isLessThan5(5) // false + result := isLessThan5(7) // false + +Leq - Less than or equal: + + isAtMost5 := ord.Leq(intOrd)(5) + + result := isAtMost5(3) // true + result := isAtMost5(5) // true + result := isAtMost5(7) // false + +Gt - Greater than: + + isGreaterThan5 := ord.Gt(intOrd)(5) + + result := isGreaterThan5(3) // false + result := isGreaterThan5(5) // false + result := isGreaterThan5(7) // true + +Geq - Greater than or equal: + + isAtLeast5 := ord.Geq(intOrd)(5) + + result := isAtLeast5(3) // false + result := isAtLeast5(5) // true + result := isAtLeast5(7) // true + +Between - Check if value is in range [low, high): + + intOrd := ord.FromStrictCompare[int]() + isBetween3And7 := ord.Between(intOrd)(3, 7) + + result := isBetween3And7(2) // false + result := isBetween3And7(3) // true + result := isBetween3And7(5) // true + result := isBetween3And7(7) // false + result := isBetween3And7(8) // false + +# Min and Max + +Min - Get the minimum of two values: + + intOrd := ord.FromStrictCompare[int]() + min := ord.Min(intOrd) + + result := min(5, 3) // 3 + result := min(3, 5) // 3 + result := min(5, 5) // 5 (first argument when equal) + +Max - Get the maximum of two values: + + max := ord.Max(intOrd) + + result := max(5, 3) // 5 + result := max(3, 5) // 5 + result := max(5, 5) // 5 (first argument when equal) + +Clamp - Restrict a value to a range: + + intOrd := ord.FromStrictCompare[int]() + clamp := ord.Clamp(intOrd)(0, 100) + + result := clamp(-10) // 0 (clamped to minimum) + result := clamp(50) // 50 (within range) + result := clamp(150) // 100 (clamped to maximum) + +# Transforming Ord + +Reverse - Invert the ordering: + + intOrd := ord.FromStrictCompare[int]() + reversedOrd := ord.Reverse(intOrd) + + result := intOrd.Compare(5, 3) // 1 (5 > 3) + result := reversedOrd.Compare(5, 3) // -1 (3 > 5 in reversed order) + + min := ord.Min(reversedOrd) + result := min(5, 3) // 5 (max in original order) + +Contramap - Transform the input before comparing: + + type Person struct { + Name string + Age int + } + + intOrd := ord.FromStrictCompare[int]() + + // Order persons by age + personOrd := ord.Contramap(func(p Person) int { + return p.Age + })(intOrd) + + p1 := Person{Name: "Alice", Age: 30} + p2 := Person{Name: "Bob", Age: 25} + + result := personOrd.Compare(p1, p2) // 1 (30 > 25) + +ToEq - Convert Ord to Eq: + + intOrd := ord.FromStrictCompare[int]() + intEq := ord.ToEq(intOrd) + + result := intEq.Equals(5, 5) // true + result := intEq.Equals(5, 3) // false + +# Semigroup and Monoid + +Semigroup - Combine orderings (try first, then second): + + type Person struct { + LastName string + FirstName string + } + + stringOrd := ord.FromStrictCompare[string]() + + // Order by last name + byLastName := ord.Contramap(func(p Person) string { + return p.LastName + })(stringOrd) + + // Order by first name + byFirstName := ord.Contramap(func(p Person) string { + return p.FirstName + })(stringOrd) + + // Combine: order by last name, then first name + sg := ord.Semigroup[Person]() + personOrd := sg.Concat(byLastName, byFirstName) + + p1 := Person{LastName: "Smith", FirstName: "Alice"} + p2 := Person{LastName: "Smith", FirstName: "Bob"} + + result := personOrd.Compare(p1, p2) // -1 (Alice < Bob) + +Monoid - Semigroup with identity (always equal): + + m := ord.Monoid[int]() + + // Empty ordering considers everything equal + emptyOrd := m.Empty() + result := emptyOrd.Compare(5, 3) // 0 (always equal) + + // Concat with empty returns the original + intOrd := ord.FromStrictCompare[int]() + result := m.Concat(intOrd, emptyOrd) // same as intOrd + +MaxSemigroup - Semigroup that returns maximum: + + intOrd := ord.FromStrictCompare[int]() + maxSg := ord.MaxSemigroup(intOrd) + + result := maxSg.Concat(5, 3) // 5 + result := maxSg.Concat(3, 5) // 5 + +MinSemigroup - Semigroup that returns minimum: + + minSg := ord.MinSemigroup(intOrd) + + result := minSg.Concat(5, 3) // 3 + result := minSg.Concat(3, 5) // 3 + +# Practical Examples + +Sorting with custom order: + + import ( + "sort" + O "github.com/IBM/fp-go/v2/ord" + ) + + type Person struct { + Name string + Age int + } + + people := []Person{ + {Name: "Alice", Age: 30}, + {Name: "Bob", Age: 25}, + {Name: "Charlie", Age: 35}, + } + + intOrd := O.FromStrictCompare[int]() + personOrd := O.Contramap(func(p Person) int { + return p.Age + })(intOrd) + + sort.Slice(people, func(i, j int) bool { + return personOrd.Compare(people[i], people[j]) < 0 + }) + // people is now sorted by age + +Finding min/max in a slice: + + import ( + A "github.com/IBM/fp-go/v2/array" + O "github.com/IBM/fp-go/v2/ord" + ) + + numbers := []int{5, 2, 8, 1, 9, 3} + intOrd := O.FromStrictCompare[int]() + + min := O.Min(intOrd) + max := O.Max(intOrd) + + // Find minimum + minimum := A.Reduce(numbers, min, numbers[0]) // 1 + + // Find maximum + maximum := A.Reduce(numbers, max, numbers[0]) // 9 + +Multi-level sorting: + + type Employee struct { + Department string + Name string + Salary int + } + + stringOrd := O.FromStrictCompare[string]() + intOrd := O.FromStrictCompare[int]() + + // Order by department + byDept := O.Contramap(func(e Employee) string { + return e.Department + })(stringOrd) + + // Order by salary (descending) + bySalary := O.Reverse(O.Contramap(func(e Employee) int { + return e.Salary + })(intOrd)) + + // Order by name + byName := O.Contramap(func(e Employee) string { + return e.Name + })(stringOrd) + + // Combine: dept, then salary (desc), then name + sg := O.Semigroup[Employee]() + employeeOrd := sg.Concat(sg.Concat(byDept, bySalary), byName) + +Filtering with comparisons: + + import ( + A "github.com/IBM/fp-go/v2/array" + O "github.com/IBM/fp-go/v2/ord" + ) + + numbers := []int{1, 5, 3, 8, 2, 9, 4} + intOrd := O.FromStrictCompare[int]() + + // Filter numbers greater than 5 + gt5 := O.Gt(intOrd)(5) + result := A.Filter(gt5)(numbers) // [8, 9] + + // Filter numbers between 3 and 7 + between3And7 := O.Between(intOrd)(3, 7) + result := A.Filter(between3And7)(numbers) // [5, 3, 4] + +# Functions + +Core operations: + - MakeOrd[T any](func(T, T) int, func(T, T) bool) Ord[T] - Create Ord from compare and equals + - FromCompare[T any](func(T, T) int) Ord[T] - Create Ord from compare (derives equals) + - FromStrictCompare[A Ordered]() Ord[A] - Create Ord for built-in ordered types + - ToEq[T any](Ord[T]) Eq[T] - Convert Ord to Eq + +Transformations: + - Reverse[T any](Ord[T]) Ord[T] - Invert the ordering + - Contramap[A, B any](func(B) A) func(Ord[A]) Ord[B] - Transform input before comparing + +Comparisons: + - Lt[A any](Ord[A]) func(A) func(A) bool - Less than + - Leq[A any](Ord[A]) func(A) func(A) bool - Less than or equal + - Gt[A any](Ord[A]) func(A) func(A) bool - Greater than + - Geq[A any](Ord[A]) func(A) func(A) bool - Greater than or equal + - Between[A any](Ord[A]) func(A, A) func(A) bool - Check if in range [low, high) + +Min/Max/Clamp: + - Min[A any](Ord[A]) func(A, A) A - Get minimum of two values + - Max[A any](Ord[A]) func(A, A) A - Get maximum of two values + - Clamp[A any](Ord[A]) func(A, A) func(A) A - Clamp value to range + +Algebraic structures: + - Semigroup[A any]() Semigroup[Ord[A]] - Combine orderings + - Monoid[A any]() Monoid[Ord[A]] - Semigroup with identity (always equal) + - MaxSemigroup[A any](Ord[A]) Semigroup[A] - Semigroup returning maximum + - MinSemigroup[A any](Ord[A]) Semigroup[A] - Semigroup returning minimum + +# Related Packages + + - eq: Equality type class (parent of Ord) + - constraints: Type constraints for generics + - semigroup: Associative binary operation + - monoid: Semigroup with identity element +*/ +package ord diff --git a/v2/ord/monoid.go b/v2/ord/monoid.go new file mode 100644 index 0000000..1c5ff3f --- /dev/null +++ b/v2/ord/monoid.go @@ -0,0 +1,52 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package ord + +import ( + F "github.com/IBM/fp-go/v2/function" + M "github.com/IBM/fp-go/v2/monoid" + S "github.com/IBM/fp-go/v2/semigroup" +) + +// Semigroup implements a two level ordering +func Semigroup[A any]() S.Semigroup[Ord[A]] { + return S.MakeSemigroup(func(first, second Ord[A]) Ord[A] { + return FromCompare(func(a, b A) int { + ox := first.Compare(a, b) + if ox != 0 { + return ox + } + return second.Compare(a, b) + }) + }) +} + +// Monoid implements a two level ordering such that +// - its `Concat(ord1, ord2)` operation will order first by `ord1`, and then by `ord2` +// - its `Empty` value is an `Ord` that always considers compared elements equal +func Monoid[A any]() M.Monoid[Ord[A]] { + return M.MakeMonoid(Semigroup[A]().Concat, FromCompare(F.Constant2[A, A](0))) +} + +// MaxSemigroup returns a semigroup where `concat` will return the maximum, based on the provided order. +func MaxSemigroup[A any](o Ord[A]) S.Semigroup[A] { + return S.MakeSemigroup(Max(o)) +} + +// MaxSemigroup returns a semigroup where `concat` will return the minimum, based on the provided order. +func MinSemigroup[A any](o Ord[A]) S.Semigroup[A] { + return S.MakeSemigroup(Min(o)) +} diff --git a/v2/ord/ord.go b/v2/ord/ord.go new file mode 100644 index 0000000..2585a8a --- /dev/null +++ b/v2/ord/ord.go @@ -0,0 +1,180 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package ord + +import ( + C "github.com/IBM/fp-go/v2/constraints" + E "github.com/IBM/fp-go/v2/eq" + F "github.com/IBM/fp-go/v2/function" + P "github.com/IBM/fp-go/v2/predicate" +) + +type Ord[T any] interface { + E.Eq[T] + Compare(x, y T) int +} + +type ord[T any] struct { + c func(x, y T) int + e func(x, y T) bool +} + +func (self ord[T]) Equals(x, y T) bool { + return self.e(x, y) +} + +func (self ord[T]) Compare(x, y T) int { + return self.c(x, y) +} + +// ToEq converts an [Ord] to [E.Eq] +func ToEq[T any](o Ord[T]) E.Eq[T] { + return o +} + +// MakeOrd creates an instance of an Ord +func MakeOrd[T any](c func(x, y T) int, e func(x, y T) bool) Ord[T] { + return ord[T]{c: c, e: e} +} + +// MakeOrd creates an instance of an Ord from a compare function +func FromCompare[T any](compare func(T, T) int) Ord[T] { + return MakeOrd(compare, func(x, y T) bool { + return compare(x, y) == 0 + }) +} + +// Reverse creates an inverted ordering +func Reverse[T any](o Ord[T]) Ord[T] { + return MakeOrd(func(y, x T) int { + return o.Compare(x, y) + }, o.Equals) +} + +// Contramap creates an ordering under a transformation function +func Contramap[A, B any](f func(B) A) func(Ord[A]) Ord[B] { + return func(o Ord[A]) Ord[B] { + return MakeOrd(func(x, y B) int { + return o.Compare(f(x), f(y)) + }, func(x, y B) bool { + return o.Equals(f(x), f(y)) + }) + } +} + +// Min takes the minimum of two values. If they are considered equal, the first argument is chosen +func Min[A any](o Ord[A]) func(A, A) A { + return func(a, b A) A { + if o.Compare(a, b) < 1 { + return a + } + return b + } +} + +// Max takes the maximum of two values. If they are considered equal, the first argument is chosen +func Max[A any](o Ord[A]) func(A, A) A { + return func(a, b A) A { + if o.Compare(a, b) >= 0 { + return a + } + return b + } +} + +// Clamp clamps a value between a minimum and a maximum +func Clamp[A any](o Ord[A]) func(A, A) func(A) A { + return func(low, hi A) func(A) A { + clow := F.Bind2nd(o.Compare, low) + chi := F.Bind2nd(o.Compare, hi) + return func(a A) A { + if clow(a) <= 0 { + return low + } + if chi(a) >= 0 { + return hi + } + return a + } + } +} + +func strictCompare[A C.Ordered](a, b A) int { + if a < b { + return -1 + } else if a > b { + return +1 + } else { + return 0 + } +} + +func strictEq[A comparable](a, b A) bool { + return a == b +} + +// FromStrictCompare implements the ordering based on the built in native order +func FromStrictCompare[A C.Ordered]() Ord[A] { + return MakeOrd(strictCompare[A], strictEq[A]) +} + +// Lt tests whether one value is strictly less than another +func Lt[A any](o Ord[A]) func(A) func(A) bool { + return func(second A) func(A) bool { + return func(first A) bool { + return o.Compare(first, second) < 0 + } + } +} + +// Leq Tests whether one value is less or equal than another +func Leq[A any](O Ord[A]) func(A) func(A) bool { + return func(second A) func(A) bool { + return func(first A) bool { + return O.Compare(first, second) <= 0 + } + } +} + +/** + * Test whether one value is strictly greater than another + */ +func Gt[A any](O Ord[A]) func(A) func(A) bool { + return func(second A) func(A) bool { + return func(first A) bool { + return O.Compare(first, second) > 0 + } + } +} + +// Geq tests whether one value is greater or equal than another +func Geq[A any](O Ord[A]) func(A) func(A) bool { + return func(second A) func(A) bool { + return func(first A) bool { + return O.Compare(first, second) >= 0 + } + } +} + +// Between tests whether a value is between a minimum (inclusive) and a maximum (exclusive) +func Between[A any](O Ord[A]) func(A, A) func(A) bool { + lt := Lt(O) + geq := Geq(O) + return func(lo, hi A) func(A) bool { + // returns the predicate + return P.And(lt(hi))(geq(lo)) + } +} diff --git a/v2/ord/ord_test.go b/v2/ord/ord_test.go new file mode 100644 index 0000000..1f8e558 --- /dev/null +++ b/v2/ord/ord_test.go @@ -0,0 +1,583 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package ord + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +// Test MakeOrd +func TestMakeOrd(t *testing.T) { + intOrd := MakeOrd( + func(a, b int) int { + if a < b { + return -1 + } else if a > b { + return 1 + } + return 0 + }, + func(a, b int) bool { + return a == b + }, + ) + + assert.Equal(t, -1, intOrd.Compare(3, 5)) + assert.Equal(t, 1, intOrd.Compare(5, 3)) + assert.Equal(t, 0, intOrd.Compare(5, 5)) + + assert.True(t, intOrd.Equals(5, 5)) + assert.False(t, intOrd.Equals(5, 3)) +} + +// Test FromCompare +func TestFromCompare(t *testing.T) { + intOrd := FromCompare(func(a, b int) int { + if a < b { + return -1 + } else if a > b { + return 1 + } + return 0 + }) + + // Test compare + assert.Equal(t, -1, intOrd.Compare(3, 5)) + assert.Equal(t, 1, intOrd.Compare(5, 3)) + assert.Equal(t, 0, intOrd.Compare(5, 5)) + + // Test equals (derived from compare) + assert.True(t, intOrd.Equals(5, 5)) + assert.False(t, intOrd.Equals(5, 3)) +} + +// Test FromStrictCompare +func TestFromStrictCompare(t *testing.T) { + intOrd := FromStrictCompare[int]() + + tests := []struct { + name string + a int + b int + expected int + }{ + {"less than", 3, 5, -1}, + {"greater than", 5, 3, 1}, + {"equal", 5, 5, 0}, + {"negative numbers", -5, -3, -1}, + {"mixed signs", -3, 5, -1}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := intOrd.Compare(tt.a, tt.b) + assert.Equal(t, tt.expected, result) + }) + } + + // Test equals + assert.True(t, intOrd.Equals(5, 5)) + assert.False(t, intOrd.Equals(5, 3)) +} + +// Test FromStrictCompare with strings +func TestFromStrictCompare_String(t *testing.T) { + stringOrd := FromStrictCompare[string]() + + assert.Equal(t, -1, stringOrd.Compare("apple", "banana")) + assert.Equal(t, 1, stringOrd.Compare("banana", "apple")) + assert.Equal(t, 0, stringOrd.Compare("apple", "apple")) + + assert.True(t, stringOrd.Equals("apple", "apple")) + assert.False(t, stringOrd.Equals("apple", "banana")) +} + +// Test FromStrictCompare with floats +func TestFromStrictCompare_Float(t *testing.T) { + floatOrd := FromStrictCompare[float64]() + + assert.Equal(t, -1, floatOrd.Compare(3.14, 3.15)) + assert.Equal(t, 1, floatOrd.Compare(3.15, 3.14)) + assert.Equal(t, 0, floatOrd.Compare(3.14, 3.14)) +} + +// Test Reverse +func TestReverse(t *testing.T) { + intOrd := FromStrictCompare[int]() + reversedOrd := Reverse(intOrd) + + // Original order + assert.Equal(t, -1, intOrd.Compare(3, 5)) + assert.Equal(t, 1, intOrd.Compare(5, 3)) + + // Reversed order + assert.Equal(t, 1, reversedOrd.Compare(3, 5)) + assert.Equal(t, -1, reversedOrd.Compare(5, 3)) + assert.Equal(t, 0, reversedOrd.Compare(5, 5)) + + // Equals should be the same + assert.True(t, reversedOrd.Equals(5, 5)) + assert.False(t, reversedOrd.Equals(5, 3)) +} + +// Test Contramap +func TestContramap(t *testing.T) { + type Person struct { + Name string + Age int + } + + intOrd := FromStrictCompare[int]() + + // Order persons by age + personOrd := Contramap(func(p Person) int { + return p.Age + })(intOrd) + + p1 := Person{Name: "Alice", Age: 30} + p2 := Person{Name: "Bob", Age: 25} + p3 := Person{Name: "Charlie", Age: 30} + + assert.Equal(t, 1, personOrd.Compare(p1, p2)) // 30 > 25 + assert.Equal(t, -1, personOrd.Compare(p2, p1)) // 25 < 30 + assert.Equal(t, 0, personOrd.Compare(p1, p3)) // 30 == 30 + + assert.True(t, personOrd.Equals(p1, p3)) + assert.False(t, personOrd.Equals(p1, p2)) +} + +// Test ToEq +func TestToEq(t *testing.T) { + intOrd := FromStrictCompare[int]() + intEq := ToEq(intOrd) + + assert.True(t, intEq.Equals(5, 5)) + assert.False(t, intEq.Equals(5, 3)) +} + +// Test Min +func TestMin(t *testing.T) { + intOrd := FromStrictCompare[int]() + min := Min(intOrd) + + tests := []struct { + name string + a int + b int + expected int + }{ + {"a smaller", 3, 5, 3}, + {"b smaller", 5, 3, 3}, + {"equal", 5, 5, 5}, + {"negative", -5, -3, -5}, + {"mixed signs", -5, 3, -5}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := min(tt.a, tt.b) + assert.Equal(t, tt.expected, result) + }) + } +} + +// Test Max +func TestMax(t *testing.T) { + intOrd := FromStrictCompare[int]() + max := Max(intOrd) + + tests := []struct { + name string + a int + b int + expected int + }{ + {"a larger", 5, 3, 5}, + {"b larger", 3, 5, 5}, + {"equal", 5, 5, 5}, + {"negative", -5, -3, -3}, + {"mixed signs", -5, 3, 3}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := max(tt.a, tt.b) + assert.Equal(t, tt.expected, result) + }) + } +} + +// Test Clamp +func TestClamp(t *testing.T) { + intOrd := FromStrictCompare[int]() + clamp := Clamp(intOrd)(0, 100) + + tests := []struct { + name string + input int + expected int + }{ + {"below minimum", -10, 0}, + {"at minimum", 0, 0}, + {"within range", 50, 50}, + {"at maximum", 100, 100}, + {"above maximum", 150, 100}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := clamp(tt.input) + assert.Equal(t, tt.expected, result) + }) + } +} + +// Test Lt (less than) +func TestLt(t *testing.T) { + intOrd := FromStrictCompare[int]() + isLessThan5 := Lt(intOrd)(5) + + tests := []struct { + name string + input int + expected bool + }{ + {"less than", 3, true}, + {"equal", 5, false}, + {"greater than", 7, false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := isLessThan5(tt.input) + assert.Equal(t, tt.expected, result) + }) + } +} + +// Test Leq (less than or equal) +func TestLeq(t *testing.T) { + intOrd := FromStrictCompare[int]() + isAtMost5 := Leq(intOrd)(5) + + tests := []struct { + name string + input int + expected bool + }{ + {"less than", 3, true}, + {"equal", 5, true}, + {"greater than", 7, false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := isAtMost5(tt.input) + assert.Equal(t, tt.expected, result) + }) + } +} + +// Test Gt (greater than) +func TestGt(t *testing.T) { + intOrd := FromStrictCompare[int]() + isGreaterThan5 := Gt(intOrd)(5) + + tests := []struct { + name string + input int + expected bool + }{ + {"less than", 3, false}, + {"equal", 5, false}, + {"greater than", 7, true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := isGreaterThan5(tt.input) + assert.Equal(t, tt.expected, result) + }) + } +} + +// Test Geq (greater than or equal) +func TestGeq(t *testing.T) { + intOrd := FromStrictCompare[int]() + isAtLeast5 := Geq(intOrd)(5) + + tests := []struct { + name string + input int + expected bool + }{ + {"less than", 3, false}, + {"equal", 5, true}, + {"greater than", 7, true}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := isAtLeast5(tt.input) + assert.Equal(t, tt.expected, result) + }) + } +} + +// Test Between +func TestBetween(t *testing.T) { + intOrd := FromStrictCompare[int]() + isBetween3And7 := Between(intOrd)(3, 7) + + tests := []struct { + name string + input int + expected bool + }{ + {"below range", 2, false}, + {"at lower bound", 3, true}, + {"within range", 5, true}, + {"at upper bound", 7, false}, + {"above range", 8, false}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := isBetween3And7(tt.input) + assert.Equal(t, tt.expected, result) + }) + } +} + +// Test Semigroup +func TestSemigroup(t *testing.T) { + type Person struct { + LastName string + FirstName string + } + + stringOrd := FromStrictCompare[string]() + + // Order by last name + byLastName := Contramap(func(p Person) string { + return p.LastName + })(stringOrd) + + // Order by first name + byFirstName := Contramap(func(p Person) string { + return p.FirstName + })(stringOrd) + + // Combine: order by last name, then first name + sg := Semigroup[Person]() + personOrd := sg.Concat(byLastName, byFirstName) + + p1 := Person{LastName: "Smith", FirstName: "Alice"} + p2 := Person{LastName: "Smith", FirstName: "Bob"} + p3 := Person{LastName: "Jones", FirstName: "Charlie"} + + // Same last name, different first name + assert.Equal(t, -1, personOrd.Compare(p1, p2)) // Alice < Bob + + // Different last names + assert.Equal(t, 1, personOrd.Compare(p1, p3)) // Smith > Jones +} + +// Test Monoid +func TestMonoid(t *testing.T) { + m := Monoid[int]() + + // Empty ordering considers everything equal + emptyOrd := m.Empty() + assert.Equal(t, 0, emptyOrd.Compare(5, 3)) + assert.Equal(t, 0, emptyOrd.Compare(3, 5)) + assert.True(t, emptyOrd.Equals(5, 3)) + + // Concat with empty returns the original + intOrd := FromStrictCompare[int]() + combined := m.Concat(intOrd, emptyOrd) + + assert.Equal(t, -1, combined.Compare(3, 5)) + assert.Equal(t, 1, combined.Compare(5, 3)) + assert.Equal(t, 0, combined.Compare(5, 5)) +} + +// Test MaxSemigroup +func TestMaxSemigroup(t *testing.T) { + intOrd := FromStrictCompare[int]() + maxSg := MaxSemigroup(intOrd) + + tests := []struct { + name string + a int + b int + expected int + }{ + {"a larger", 5, 3, 5}, + {"b larger", 3, 5, 5}, + {"equal", 5, 5, 5}, + {"negative", -5, -3, -3}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := maxSg.Concat(tt.a, tt.b) + assert.Equal(t, tt.expected, result) + }) + } +} + +// Test MinSemigroup +func TestMinSemigroup(t *testing.T) { + intOrd := FromStrictCompare[int]() + minSg := MinSemigroup(intOrd) + + tests := []struct { + name string + a int + b int + expected int + }{ + {"a smaller", 3, 5, 3}, + {"b smaller", 5, 3, 3}, + {"equal", 5, 5, 5}, + {"negative", -5, -3, -5}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := minSg.Concat(tt.a, tt.b) + assert.Equal(t, tt.expected, result) + }) + } +} + +// Test Ord laws +func TestOrdLaws(t *testing.T) { + intOrd := FromStrictCompare[int]() + testValues := []int{1, 2, 3, 5, 10} + + for _, x := range testValues { + // Reflexivity: Compare(x, x) = 0 + assert.Equal(t, 0, intOrd.Compare(x, x), "Reflexivity failed for %d", x) + + for _, y := range testValues { + cxy := intOrd.Compare(x, y) + cyx := intOrd.Compare(y, x) + + // Antisymmetry: if Compare(x, y) <= 0 and Compare(y, x) <= 0 then x = y + if cxy <= 0 && cyx <= 0 { + assert.Equal(t, x, y, "Antisymmetry failed for %d and %d", x, y) + } + + // Totality: Compare(x, y) <= 0 or Compare(y, x) <= 0 + assert.True(t, cxy <= 0 || cyx <= 0, "Totality failed for %d and %d", x, y) + + for _, z := range testValues { + cyz := intOrd.Compare(y, z) + cxz := intOrd.Compare(x, z) + + // Transitivity: if Compare(x, y) <= 0 and Compare(y, z) <= 0 then Compare(x, z) <= 0 + if cxy <= 0 && cyz <= 0 { + assert.True(t, cxz <= 0, "Transitivity failed for %d, %d, %d", x, y, z) + } + } + } + } +} + +// Test complex example with multi-level sorting +func TestComplexSorting(t *testing.T) { + type Employee struct { + Department string + Salary int + Name string + } + + stringOrd := FromStrictCompare[string]() + intOrd := FromStrictCompare[int]() + + // Order by department + byDept := Contramap(func(e Employee) string { + return e.Department + })(stringOrd) + + // Order by salary (descending) + bySalary := Reverse(Contramap(func(e Employee) int { + return e.Salary + })(intOrd)) + + // Order by name + byName := Contramap(func(e Employee) string { + return e.Name + })(stringOrd) + + // Combine: dept, then salary (desc), then name + sg := Semigroup[Employee]() + employeeOrd := sg.Concat(sg.Concat(byDept, bySalary), byName) + + e1 := Employee{Department: "IT", Salary: 100000, Name: "Alice"} + e2 := Employee{Department: "IT", Salary: 100000, Name: "Bob"} + e3 := Employee{Department: "IT", Salary: 90000, Name: "Charlie"} + e4 := Employee{Department: "HR", Salary: 80000, Name: "David"} + + // Same dept, same salary, different name + assert.Equal(t, -1, employeeOrd.Compare(e1, e2)) // Alice < Bob + + // Same dept, different salary + assert.Equal(t, -1, employeeOrd.Compare(e1, e3)) // 100000 > 90000 (reversed) + + // Different dept + assert.Equal(t, 1, employeeOrd.Compare(e1, e4)) // IT > HR +} + +// Benchmark tests +func BenchmarkCompare(b *testing.B) { + intOrd := FromStrictCompare[int]() + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = intOrd.Compare(i, i+1) + } +} + +func BenchmarkMin(b *testing.B) { + intOrd := FromStrictCompare[int]() + min := Min(intOrd) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = min(i, i+1) + } +} + +func BenchmarkMax(b *testing.B) { + intOrd := FromStrictCompare[int]() + max := Max(intOrd) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = max(i, i+1) + } +} + +func BenchmarkClamp(b *testing.B) { + intOrd := FromStrictCompare[int]() + clamp := Clamp(intOrd)(0, 100) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = clamp(i % 150) + } +} diff --git a/v2/pair/doc.go b/v2/pair/doc.go new file mode 100644 index 0000000..08b9264 --- /dev/null +++ b/v2/pair/doc.go @@ -0,0 +1,451 @@ +// Copyright (c) 2024 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +/* +Package pair provides a strongly-typed data structure for holding two values with functional operations. + +# Overview + +A Pair is a data structure that holds exactly two values of potentially different types. +Unlike tuples which are position-based, Pair provides semantic operations for working with +"head" and "tail" (or "first" and "second") values, along with rich functional programming +capabilities including Functor, Applicative, and Monad operations. + +The Pair type: + + type Pair[A, B any] struct { + h, t any // head and tail (internal) + } + +# Basic Usage + +Creating pairs: + + // Create a pair from two values + p := pair.MakePair("hello", 42) // Pair[string, int] + + // Create a pair with the same value in both positions + p := pair.Of(5) // Pair[int, int]{5, 5} + + // Convert from tuple + t := tuple.MakeTuple2("world", 100) + p := pair.FromTuple(t) // Pair[string, int] + +Accessing values: + + p := pair.MakePair("hello", 42) + + head := pair.Head(p) // "hello" + tail := pair.Tail(p) // 42 + + // Alternative names + first := pair.First(p) // "hello" (same as Head) + second := pair.Second(p) // 42 (same as Tail) + +# Transforming Pairs + +Map operations transform one or both values: + + p := pair.MakePair(5, "hello") + + // Map the tail (second) value + p2 := pair.MonadMapTail(p, func(s string) int { + return len(s) + }) // Pair[int, int]{5, 5} + + // Map the head (first) value + p3 := pair.MonadMapHead(p, func(n int) string { + return fmt.Sprintf("%d", n) + }) // Pair[string, string]{"5", "hello"} + + // Map both values + p4 := pair.MonadBiMap(p, + func(n int) string { return fmt.Sprintf("%d", n) }, + func(s string) int { return len(s) }, + ) // Pair[string, int]{"5", 5} + +Curried versions for composition: + + import F "github.com/IBM/fp-go/v2/function" + + // Create a mapper function + doubleHead := pair.MapHead[string](func(n int) int { + return n * 2 + }) + + p := pair.MakePair(5, "hello") + result := doubleHead(p) // Pair[int, string]{10, "hello"} + + // Compose multiple transformations + transform := F.Flow2( + pair.MapHead[string](func(n int) int { return n * 2 }), + pair.MapTail[int](func(s string) int { return len(s) }), + ) + result := transform(p) // Pair[int, int]{10, 5} + +# Swapping + +Swap exchanges the head and tail values: + + p := pair.MakePair("hello", 42) + swapped := pair.Swap(p) // Pair[int, string]{42, "hello"} + +# Monadic Operations + +Pair supports monadic operations on both head and tail, requiring a Semigroup +for combining values: + + import ( + SG "github.com/IBM/fp-go/v2/semigroup" + N "github.com/IBM/fp-go/v2/number" + ) + + // Chain on the tail (requires semigroup for head) + intSum := N.SemigroupSum[int]() + + p := pair.MakePair(5, "hello") + result := pair.MonadChainTail(intSum, p, func(s string) pair.Pair[int, int] { + return pair.MakePair(len(s), len(s) * 2) + }) // Pair[int, int]{10, 10} (5 + 5 from semigroup, 10 from function) + + // Chain on the head (requires semigroup for tail) + strConcat := SG.MakeSemigroup(func(a, b string) string { return a + b }) + + p2 := pair.MakePair(5, "hello") + result2 := pair.MonadChainHead(strConcat, p2, func(n int) pair.Pair[string, string] { + return pair.MakePair(fmt.Sprintf("%d", n), "!") + }) // Pair[string, string]{"5", "hello!"} + +Curried versions: + + intSum := N.SemigroupSum[int]() + chain := pair.ChainTail(intSum, func(s string) pair.Pair[int, int] { + return pair.MakePair(len(s), len(s) * 2) + }) + + p := pair.MakePair(5, "hello") + result := chain(p) // Pair[int, int]{10, 10} + +# Applicative Operations + +Apply functions wrapped in pairs to values in pairs: + + import N "github.com/IBM/fp-go/v2/number" + + intSum := N.SemigroupSum[int]() + + // Function in a pair + pf := pair.MakePair(10, func(s string) int { return len(s) }) + + // Value in a pair + pv := pair.MakePair(5, "hello") + + // Apply (on tail) + result := pair.MonadApTail(intSum, pf, pv) + // Pair[int, int]{15, 5} (10+5 from semigroup, len("hello") from function) + + // Apply (on head) + strConcat := SG.MakeSemigroup(func(a, b string) string { return a + b }) + pf2 := pair.MakePair(func(n int) string { return fmt.Sprintf("%d", n) }, "!") + pv2 := pair.MakePair(42, "hello") + + result2 := pair.MonadApHead(strConcat, pf2, pv2) + // Pair[string, string]{"42", "!hello"} + +# Function Conversion + +Convert between regular functions and pair-taking functions: + + // Regular function + add := func(a, b int) int { return a + b } + + // Convert to pair-taking function + pairedAdd := pair.Paired(add) + result := pairedAdd(pair.MakePair(3, 4)) // 7 + + // Convert back + unpairedAdd := pair.Unpaired(pairedAdd) + result = unpairedAdd(3, 4) // 7 + +Merge curried functions: + + // Curried function + add := func(b int) func(a int) int { + return func(a int) int { return a + b } + } + + // Apply to pair + merge := pair.Merge(add) + result := merge(pair.MakePair(3, 4)) // 7 (applies 4 then 3) + +# Equality + +Compare pairs for equality: + + import ( + EQ "github.com/IBM/fp-go/v2/eq" + ) + + // Create equality for pairs + pairEq := pair.Eq( + EQ.FromStrictEquals[string](), + EQ.FromStrictEquals[int](), + ) + + p1 := pair.MakePair("hello", 42) + p2 := pair.MakePair("hello", 42) + p3 := pair.MakePair("world", 42) + + pairEq.Equals(p1, p2) // true + pairEq.Equals(p1, p3) // false + +For comparable types: + + pairEq := pair.FromStrictEquals[string, int]() + + p1 := pair.MakePair("hello", 42) + p2 := pair.MakePair("hello", 42) + + pairEq.Equals(p1, p2) // true + +# Tuple Conversion + +Convert between Pair and Tuple2: + + import "github.com/IBM/fp-go/v2/tuple" + + // Pair to Tuple + p := pair.MakePair("hello", 42) + t := pair.ToTuple(p) // tuple.Tuple2[string, int] + + // Tuple to Pair + t := tuple.MakeTuple2("world", 100) + p := pair.FromTuple(t) // Pair[string, int] + +# Type Class Instances + +Pair provides type class instances for functional programming: + +Functor - Map over values: + + import M "github.com/IBM/fp-go/v2/monoid" + + // Functor for tail + functor := pair.FunctorTail[int, string, int]() + mapper := functor.Map(func(s string) int { return len(s) }) + + p := pair.MakePair(5, "hello") + result := mapper(p) // Pair[int, int]{5, 5} + +Pointed - Wrap values: + + import M "github.com/IBM/fp-go/v2/monoid" + + // Pointed for tail (requires monoid for head) + intSum := M.MonoidSum[int]() + pointed := pair.PointedTail[string](intSum) + + p := pointed.Of("hello") // Pair[int, string]{0, "hello"} + +Applicative - Apply wrapped functions: + + import M "github.com/IBM/fp-go/v2/monoid" + + intSum := M.MonoidSum[int]() + applicative := pair.ApplicativeTail[string, int, int](intSum) + + // Create a pair with a function + pf := applicative.Of(func(s string) int { return len(s) }) + + // Apply to a value + pv := pair.MakePair(5, "hello") + result := applicative.Ap(pv)(pf) // Pair[int, int]{5, 5} + +Monad - Chain operations: + + import M "github.com/IBM/fp-go/v2/monoid" + + intSum := M.MonoidSum[int]() + monad := pair.MonadTail[string, int, int](intSum) + + p := monad.Of("hello") + result := monad.Chain(func(s string) pair.Pair[int, int] { + return pair.MakePair(len(s), len(s) * 2) + })(p) // Pair[int, int]{5, 10} + +# Practical Examples + +Example 1: Accumulating Results with Context + + import ( + N "github.com/IBM/fp-go/v2/number" + F "github.com/IBM/fp-go/v2/function" + ) + + // Process items while accumulating a count + intSum := N.SemigroupSum[int]() + + processItem := func(item string) pair.Pair[int, string] { + return pair.MakePair(1, strings.ToUpper(item)) + } + + items := []string{"hello", "world", "foo"} + initial := pair.MakePair(0, "") + + result := F.Pipe2( + items, + A.Map(processItem), + A.Reduce(func(acc, curr pair.Pair[int, string]) pair.Pair[int, string] { + return pair.MonadBiMap( + pair.MakePair( + pair.First(acc) + pair.First(curr), + pair.Second(acc) + " " + pair.Second(curr), + ), + F.Identity[int], + strings.TrimSpace, + ) + }, initial), + ) + // Result: Pair[int, string]{3, "HELLO WORLD FOO"} + +Example 2: Tracking Computation Steps + + type Log []string + + logConcat := SG.MakeSemigroup(func(a, b Log) Log { + return append(a, b...) + }) + + compute := func(n int) pair.Pair[Log, int] { + return pair.MakePair( + Log{fmt.Sprintf("computed %d", n)}, + n * 2, + ) + } + + p := pair.MakePair(Log{"start"}, 5) + result := pair.MonadChainTail(logConcat, p, compute) + // Pair[Log, int]{ + // []string{"start", "computed 5"}, + // 10 + // } + +Example 3: Writer Monad Pattern + + import M "github.com/IBM/fp-go/v2/monoid" + + // Use pair as a writer monad + stringMonoid := M.MakeMonoid( + func(a, b string) string { return a + b }, + "", + ) + + monad := pair.MonadTail[string, string, int](stringMonoid) + + // Log and compute + logAndDouble := func(n int) pair.Pair[string, int] { + return pair.MakePair( + fmt.Sprintf("doubled %d; ", n), + n * 2, + ) + } + + logAndAdd := func(n int) pair.Pair[string, int] { + return pair.MakePair( + fmt.Sprintf("added 10; ", n), + n + 10, + ) + } + + result := F.Pipe2( + monad.Of(5), + monad.Chain(logAndDouble), + monad.Chain(logAndAdd), + ) + // Pair[string, int]{"doubled 5; added 10; ", 20} + +# Function Reference + +Creation: + - MakePair[A, B any](A, B) Pair[A, B] - Create a pair from two values + - Of[A any](A) Pair[A, A] - Create a pair with same value in both positions + - FromTuple[A, B any](tuple.Tuple2[A, B]) Pair[A, B] - Convert tuple to pair + - ToTuple[A, B any](Pair[A, B]) tuple.Tuple2[A, B] - Convert pair to tuple + +Access: + - Head[A, B any](Pair[A, B]) A - Get the head (first) value + - Tail[A, B any](Pair[A, B]) B - Get the tail (second) value + - First[A, B any](Pair[A, B]) A - Get the first value (alias for Head) + - Second[A, B any](Pair[A, B]) B - Get the second value (alias for Tail) + +Transformations: + - MonadMapHead[B, A, A1 any](Pair[A, B], func(A) A1) Pair[A1, B] - Map head value + - MonadMapTail[A, B, B1 any](Pair[A, B], func(B) B1) Pair[A, B1] - Map tail value + - MonadMap[B, A, A1 any](Pair[A, B], func(A) A1) Pair[A1, B] - Map head value (alias) + - MonadBiMap[A, B, A1, B1 any](Pair[A, B], func(A) A1, func(B) B1) Pair[A1, B1] - Map both values + - MapHead[B, A, A1 any](func(A) A1) func(Pair[A, B]) Pair[A1, B] - Curried map head + - MapTail[A, B, B1 any](func(B) B1) func(Pair[A, B]) Pair[A, B1] - Curried map tail + - Map[A, B, B1 any](func(B) B1) func(Pair[A, B]) Pair[A, B1] - Curried map tail (alias) + - BiMap[A, B, A1, B1 any](func(A) A1, func(B) B1) func(Pair[A, B]) Pair[A1, B1] - Curried bimap + - Swap[A, B any](Pair[A, B]) Pair[B, A] - Swap head and tail + +Monadic Operations: + - MonadChainHead[B, A, A1 any](Semigroup[B], Pair[A, B], func(A) Pair[A1, B]) Pair[A1, B] + - MonadChainTail[A, B, B1 any](Semigroup[A], Pair[A, B], func(B) Pair[A, B1]) Pair[A, B1] + - MonadChain[A, B, B1 any](Semigroup[A], Pair[A, B], func(B) Pair[A, B1]) Pair[A, B1] + - ChainHead[B, A, A1 any](Semigroup[B], func(A) Pair[A1, B]) func(Pair[A, B]) Pair[A1, B] + - ChainTail[A, B, B1 any](Semigroup[A], func(B) Pair[A, B1]) func(Pair[A, B]) Pair[A, B1] + - Chain[A, B, B1 any](Semigroup[A], func(B) Pair[A, B1]) func(Pair[A, B]) Pair[A, B1] + +Applicative Operations: + - MonadApHead[B, A, A1 any](Semigroup[B], Pair[func(A) A1, B], Pair[A, B]) Pair[A1, B] + - MonadApTail[A, B, B1 any](Semigroup[A], Pair[A, func(B) B1], Pair[A, B]) Pair[A, B1] + - MonadAp[A, B, B1 any](Semigroup[A], Pair[A, func(B) B1], Pair[A, B]) Pair[A, B1] + - ApHead[B, A, A1 any](Semigroup[B], Pair[A, B]) func(Pair[func(A) A1, B]) Pair[A1, B] + - ApTail[A, B, B1 any](Semigroup[A], Pair[A, B]) func(Pair[A, func(B) B1]) Pair[A, B1] + - Ap[A, B, B1 any](Semigroup[A], Pair[A, B]) func(Pair[A, func(B) B1]) Pair[A, B1] + +Function Conversion: + - Paired[F ~func(T1, T2) R, T1, T2, R any](F) func(Pair[T1, T2]) R + - Unpaired[F ~func(Pair[T1, T2]) R, T1, T2, R any](F) func(T1, T2) R + - Merge[F ~func(B) func(A) R, A, B, R any](F) func(Pair[A, B]) R + +Equality: + - Eq[A, B any](Eq[A], Eq[B]) Eq[Pair[A, B]] - Create equality for pairs + - FromStrictEquals[A, B comparable]() Eq[Pair[A, B]] - Equality for comparable types + +Type Classes: + - MonadHead[A, B, A1 any](Monoid[B]) Monad[A, A1, Pair[A, B], Pair[A1, B], Pair[func(A) A1, B]] + - MonadTail[B, A, B1 any](Monoid[A]) Monad[B, B1, Pair[A, B], Pair[A, B1], Pair[A, func(B) B1]] + - Monad[B, A, B1 any](Monoid[A]) Monad[B, B1, Pair[A, B], Pair[A, B1], Pair[A, func(B) B1]] + - PointedHead[A, B any](Monoid[B]) Pointed[A, Pair[A, B]] + - PointedTail[B, A any](Monoid[A]) Pointed[B, Pair[A, B]] + - Pointed[B, A any](Monoid[A]) Pointed[B, Pair[A, B]] + - FunctorHead[A, B, A1 any]() Functor[A, A1, Pair[A, B], Pair[A1, B]] + - FunctorTail[B, A, B1 any]() Functor[B, B1, Pair[A, B], Pair[A, B1]] + - Functor[B, A, B1 any]() Functor[B, B1, Pair[A, B], Pair[A, B1]] + - ApplicativeHead[A, B, A1 any](Monoid[B]) Applicative[A, A1, Pair[A, B], Pair[A1, B], Pair[func(A) A1, B]] + - ApplicativeTail[B, A, B1 any](Monoid[A]) Applicative[B, B1, Pair[A, B], Pair[A, B1], Pair[A, func(B) B1]] + - Applicative[B, A, B1 any](Monoid[A]) Applicative[B, B1, Pair[A, B], Pair[A, B1], Pair[A, func(B) B1]] + +# Related Packages + + - github.com/IBM/fp-go/v2/tuple - Position-based heterogeneous tuples + - github.com/IBM/fp-go/v2/semigroup - Associative binary operations + - github.com/IBM/fp-go/v2/monoid - Semigroups with identity + - github.com/IBM/fp-go/v2/eq - Equality type class + - github.com/IBM/fp-go/v2/function - Function composition utilities +*/ +package pair diff --git a/v2/pair/eq.go b/v2/pair/eq.go new file mode 100644 index 0000000..b7e251f --- /dev/null +++ b/v2/pair/eq.go @@ -0,0 +1,54 @@ +// Copyright (c) 2024 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package pair + +import "github.com/IBM/fp-go/v2/eq" + +// Eq constructs an equality predicate for [Pair] from equality predicates for both components. +// Two pairs are considered equal if both their head values are equal and their tail values are equal. +// +// Example: +// +// import EQ "github.com/IBM/fp-go/v2/eq" +// +// pairEq := pair.Eq( +// EQ.FromStrictEquals[string](), +// EQ.FromStrictEquals[int](), +// ) +// p1 := pair.MakePair("hello", 42) +// p2 := pair.MakePair("hello", 42) +// p3 := pair.MakePair("world", 42) +// pairEq.Equals(p1, p2) // true +// pairEq.Equals(p1, p3) // false +func Eq[A, B any](a eq.Eq[A], b eq.Eq[B]) eq.Eq[Pair[A, B]] { + return eq.FromEquals(func(l, r Pair[A, B]) bool { + return a.Equals(Head(l), Head(r)) && b.Equals(Tail(l), Tail(r)) + }) + +} + +// FromStrictEquals constructs an [eq.Eq] for [Pair] using the built-in equality operator (==) +// for both components. This is only available when both type parameters are comparable. +// +// Example: +// +// pairEq := pair.FromStrictEquals[string, int]() +// p1 := pair.MakePair("hello", 42) +// p2 := pair.MakePair("hello", 42) +// pairEq.Equals(p1, p2) // true +func FromStrictEquals[A, B comparable]() eq.Eq[Pair[A, B]] { + return Eq(eq.FromStrictEquals[A](), eq.FromStrictEquals[B]()) +} diff --git a/v2/pair/generic/sequence.go b/v2/pair/generic/sequence.go new file mode 100644 index 0000000..a4c12d1 --- /dev/null +++ b/v2/pair/generic/sequence.go @@ -0,0 +1,71 @@ +// Copyright (c) 2024 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + F "github.com/IBM/fp-go/v2/function" + P "github.com/IBM/fp-go/v2/pair" +) + +// SequencePair is a utility function used to implement the sequence operation for higher kinded types based only on map and ap. +// The function takes a [Pair] of higher higher kinded types and returns a higher kinded type of a [Pair] with the resolved values. +func SequencePair[ + MAP ~func(func(T1) func(T2) P.Pair[T1, T2]) func(HKT_T1) HKT_F_T2, + AP1 ~func(HKT_T2) func(HKT_F_T2) HKT_PAIR, + T1, + T2, + HKT_T1, // HKT[T1] + HKT_T2, // HKT[T2] + HKT_F_T2, // HKT[func(T2) P.Pair[T1, T2]] + HKT_PAIR any, // HKT[Pair[T1, T2]] +]( + fmap MAP, + fap1 AP1, + t P.Pair[HKT_T1, HKT_T2], +) HKT_PAIR { + return F.Pipe2( + P.Head(t), + fmap(F.Curry2(P.MakePair[T1, T2])), + fap1(P.Tail(t)), + ) +} + +// TraversePair is a utility function used to implement the sequence operation for higher kinded types based only on map and ap. +// The function takes a [Pair] of base types and 2 functions that transform these based types into higher higher kinded types. It returns a higher kinded type of a [Pair] with the resolved values. +func TraversePair[ + MAP ~func(func(T1) func(T2) P.Pair[T1, T2]) func(HKT_T1) HKT_F_T2, + AP1 ~func(HKT_T2) func(HKT_F_T2) HKT_PAIR, + F1 ~func(A1) HKT_T1, + F2 ~func(A2) HKT_T2, + A1, T1, + A2, T2, + HKT_T1, // HKT[T1] + HKT_T2, // HKT[T2] + HKT_F_T2, // HKT[func(T2) P.Pair[T1, T2]] + HKT_PAIR any, // HKT[Pair[T1, T2]] +]( + fmap MAP, + fap1 AP1, + f1 F1, + f2 F2, + t P.Pair[A1, A2], +) HKT_PAIR { + return F.Pipe2( + f1(P.Head(t)), + fmap(F.Curry2(P.MakePair[T1, T2])), + fap1(f2(P.Tail(t))), + ) +} diff --git a/v2/pair/monad.go b/v2/pair/monad.go new file mode 100644 index 0000000..09eed82 --- /dev/null +++ b/v2/pair/monad.go @@ -0,0 +1,315 @@ +// Copyright (c) 2024 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package pair + +import ( + "github.com/IBM/fp-go/v2/internal/applicative" + "github.com/IBM/fp-go/v2/internal/functor" + "github.com/IBM/fp-go/v2/internal/monad" + "github.com/IBM/fp-go/v2/internal/pointed" + "github.com/IBM/fp-go/v2/monoid" + "github.com/IBM/fp-go/v2/semigroup" +) + +type ( + pairPointedHead[A, B any] struct { + m monoid.Monoid[B] + } + + pairFunctorHead[A, B, A1 any] struct { + } + + pairApplicativeHead[A, B, A1 any] struct { + s semigroup.Semigroup[B] + m monoid.Monoid[B] + } + + pairMonadHead[A, B, A1 any] struct { + s semigroup.Semigroup[B] + m monoid.Monoid[B] + } + + pairPointedTail[A, B any] struct { + m monoid.Monoid[A] + } + + pairFunctorTail[A, B, B1 any] struct { + } + + pairApplicativeTail[A, B, B1 any] struct { + s semigroup.Semigroup[A] + m monoid.Monoid[A] + } + + pairMonadTail[A, B, B1 any] struct { + s semigroup.Semigroup[A] + m monoid.Monoid[A] + } +) + +func (o *pairMonadHead[A, B, A1]) Of(a A) Pair[A, B] { + return MakePair(a, o.m.Empty()) +} + +func (o *pairMonadHead[A, B, A1]) Map(f func(A) A1) func(Pair[A, B]) Pair[A1, B] { + return MapHead[B](f) +} + +func (o *pairMonadHead[A, B, A1]) Chain(f func(A) Pair[A1, B]) func(Pair[A, B]) Pair[A1, B] { + return ChainHead(o.s, f) +} + +func (o *pairMonadHead[A, B, A1]) Ap(fa Pair[A, B]) func(Pair[func(A) A1, B]) Pair[A1, B] { + return ApHead[B, A, A1](o.s, fa) +} + +func (o *pairPointedHead[A, B]) Of(a A) Pair[A, B] { + return MakePair(a, o.m.Empty()) +} + +func (o *pairFunctorHead[A, B, A1]) Map(f func(A) A1) func(Pair[A, B]) Pair[A1, B] { + return MapHead[B](f) +} + +func (o *pairApplicativeHead[A, B, A1]) Map(f func(A) A1) func(Pair[A, B]) Pair[A1, B] { + return MapHead[B](f) +} + +func (o *pairApplicativeHead[A, B, A1]) Ap(fa Pair[A, B]) func(Pair[func(A) A1, B]) Pair[A1, B] { + return ApHead[B, A, A1](o.s, fa) +} + +func (o *pairApplicativeHead[A, B, A1]) Of(a A) Pair[A, B] { + return MakePair(a, o.m.Empty()) +} + +// MonadHead implements the monadic operations for [Pair] operating on the head value. +// Requires a monoid for the tail type to provide an identity element for the Of operation +// and a semigroup for combining tail values in Chain and Ap operations. +// +// Example: +// +// import M "github.com/IBM/fp-go/v2/monoid" +// +// stringMonoid := M.MakeMonoid( +// func(a, b string) string { return a + b }, +// "", +// ) +// monad := pair.MonadHead[int, string, int](stringMonoid) +// p := monad.Of(42) // Pair[int, string]{42, ""} +func MonadHead[A, B, A1 any](m monoid.Monoid[B]) monad.Monad[A, A1, Pair[A, B], Pair[A1, B], Pair[func(A) A1, B]] { + return &pairMonadHead[A, B, A1]{s: monoid.ToSemigroup(m), m: m} +} + +// PointedHead implements the pointed operations for [Pair] operating on the head value. +// Requires a monoid for the tail type to provide an identity element. +// +// Example: +// +// import M "github.com/IBM/fp-go/v2/monoid" +// +// stringMonoid := M.MakeMonoid( +// func(a, b string) string { return a + b }, +// "", +// ) +// pointed := pair.PointedHead[int, string](stringMonoid) +// p := pointed.Of(42) // Pair[int, string]{42, ""} +func PointedHead[A, B any](m monoid.Monoid[B]) pointed.Pointed[A, Pair[A, B]] { + return &pairPointedHead[A, B]{m: m} +} + +// FunctorHead implements the functor operations for [Pair] operating on the head value. +// +// Example: +// +// functor := pair.FunctorHead[int, string, string]() +// mapper := functor.Map(func(n int) string { return fmt.Sprintf("%d", n) }) +// p := pair.MakePair(42, "hello") +// p2 := mapper(p) // Pair[string, string]{"42", "hello"} +func FunctorHead[A, B, A1 any]() functor.Functor[A, A1, Pair[A, B], Pair[A1, B]] { + return &pairFunctorHead[A, B, A1]{} +} + +// ApplicativeHead implements the applicative operations for [Pair] operating on the head value. +// Requires a monoid for the tail type to provide an identity element for the Of operation +// and a semigroup for combining tail values in the Ap operation. +// +// Example: +// +// import M "github.com/IBM/fp-go/v2/monoid" +// +// stringMonoid := M.MakeMonoid( +// func(a, b string) string { return a + b }, +// "", +// ) +// applicative := pair.ApplicativeHead[int, string, string](stringMonoid) +// pf := applicative.Of(func(n int) string { return fmt.Sprintf("%d", n) }) +// pv := pair.MakePair(42, "!") +// result := applicative.Ap(pv)(pf) // Pair[string, string]{"42", "!"} +func ApplicativeHead[A, B, A1 any](m monoid.Monoid[B]) applicative.Applicative[A, A1, Pair[A, B], Pair[A1, B], Pair[func(A) A1, B]] { + return &pairApplicativeHead[A, B, A1]{s: monoid.ToSemigroup(m), m: m} +} + +func (o *pairMonadTail[A, B, B1]) Of(b B) Pair[A, B] { + return MakePair(o.m.Empty(), b) +} + +func (o *pairMonadTail[A, B, B1]) Map(f func(B) B1) func(Pair[A, B]) Pair[A, B1] { + return MapTail[A](f) +} + +func (o *pairMonadTail[A, B, B1]) Chain(f func(B) Pair[A, B1]) func(Pair[A, B]) Pair[A, B1] { + return ChainTail(o.s, f) +} + +func (o *pairMonadTail[A, B, B1]) Ap(fa Pair[A, B]) func(Pair[A, func(B) B1]) Pair[A, B1] { + return ApTail[A, B, B1](o.s, fa) +} + +func (o *pairPointedTail[A, B]) Of(b B) Pair[A, B] { + return MakePair(o.m.Empty(), b) +} + +func (o *pairFunctorTail[A, B, B1]) Map(f func(B) B1) func(Pair[A, B]) Pair[A, B1] { + return MapTail[A](f) +} + +func (o *pairApplicativeTail[A, B, B1]) Map(f func(B) B1) func(Pair[A, B]) Pair[A, B1] { + return MapTail[A](f) +} + +func (o *pairApplicativeTail[A, B, B1]) Ap(fa Pair[A, B]) func(Pair[A, func(B) B1]) Pair[A, B1] { + return ApTail[A, B, B1](o.s, fa) +} + +func (o *pairApplicativeTail[A, B, B1]) Of(b B) Pair[A, B] { + return MakePair(o.m.Empty(), b) +} + +// MonadTail implements the monadic operations for [Pair] operating on the tail value. +// Requires a monoid for the head type to provide an identity element for the Of operation +// and a semigroup for combining head values in Chain and Ap operations. +// +// Example: +// +// import M "github.com/IBM/fp-go/v2/monoid" +// +// intSum := M.MonoidSum[int]() +// monad := pair.MonadTail[string, int, int](intSum) +// p := monad.Of("hello") // Pair[int, string]{0, "hello"} +func MonadTail[B, A, B1 any](m monoid.Monoid[A]) monad.Monad[B, B1, Pair[A, B], Pair[A, B1], Pair[A, func(B) B1]] { + return &pairMonadTail[A, B, B1]{s: monoid.ToSemigroup(m), m: m} +} + +// PointedTail implements the pointed operations for [Pair] operating on the tail value. +// Requires a monoid for the head type to provide an identity element. +// +// Example: +// +// import M "github.com/IBM/fp-go/v2/monoid" +// +// intSum := M.MonoidSum[int]() +// pointed := pair.PointedTail[string, int](intSum) +// p := pointed.Of("hello") // Pair[int, string]{0, "hello"} +func PointedTail[B, A any](m monoid.Monoid[A]) pointed.Pointed[B, Pair[A, B]] { + return &pairPointedTail[A, B]{m: m} +} + +// FunctorTail implements the functor operations for [Pair] operating on the tail value. +// +// Example: +// +// functor := pair.FunctorTail[string, int, int]() +// mapper := functor.Map(func(s string) int { return len(s) }) +// p := pair.MakePair(5, "hello") +// p2 := mapper(p) // Pair[int, int]{5, 5} +func FunctorTail[B, A, B1 any]() functor.Functor[B, B1, Pair[A, B], Pair[A, B1]] { + return &pairFunctorTail[A, B, B1]{} +} + +// ApplicativeTail implements the applicative operations for [Pair] operating on the tail value. +// Requires a monoid for the head type to provide an identity element for the Of operation +// and a semigroup for combining head values in the Ap operation. +// +// Example: +// +// import M "github.com/IBM/fp-go/v2/monoid" +// +// intSum := M.MonoidSum[int]() +// applicative := pair.ApplicativeTail[string, int, int](intSum) +// pf := applicative.Of(func(s string) int { return len(s) }) +// pv := pair.MakePair(5, "hello") +// result := applicative.Ap(pv)(pf) // Pair[int, int]{5, 5} +func ApplicativeTail[B, A, B1 any](m monoid.Monoid[A]) applicative.Applicative[B, B1, Pair[A, B], Pair[A, B1], Pair[A, func(B) B1]] { + return &pairApplicativeTail[A, B, B1]{s: monoid.ToSemigroup(m), m: m} +} + +// Monad implements the monadic operations for [Pair] operating on the tail value (alias for MonadTail). +// This is the default monad instance for Pair. +// +// Example: +// +// import M "github.com/IBM/fp-go/v2/monoid" +// +// intSum := M.MonoidSum[int]() +// monad := pair.Monad[string, int, int](intSum) +// p := monad.Of("hello") // Pair[int, string]{0, "hello"} +func Monad[B, A, B1 any](m monoid.Monoid[A]) monad.Monad[B, B1, Pair[A, B], Pair[A, B1], Pair[A, func(B) B1]] { + return MonadTail[B, A, B1](m) +} + +// Pointed implements the pointed operations for [Pair] operating on the tail value (alias for PointedTail). +// This is the default pointed instance for Pair. +// +// Example: +// +// import M "github.com/IBM/fp-go/v2/monoid" +// +// intSum := M.MonoidSum[int]() +// pointed := pair.Pointed[string, int](intSum) +// p := pointed.Of("hello") // Pair[int, string]{0, "hello"} +func Pointed[B, A any](m monoid.Monoid[A]) pointed.Pointed[B, Pair[A, B]] { + return PointedTail[B](m) +} + +// Functor implements the functor operations for [Pair] operating on the tail value (alias for FunctorTail). +// This is the default functor instance for Pair. +// +// Example: +// +// functor := pair.Functor[string, int, int]() +// mapper := functor.Map(func(s string) int { return len(s) }) +// p := pair.MakePair(5, "hello") +// p2 := mapper(p) // Pair[int, int]{5, 5} +func Functor[B, A, B1 any]() functor.Functor[B, B1, Pair[A, B], Pair[A, B1]] { + return FunctorTail[B, A, B1]() +} + +// Applicative implements the applicative operations for [Pair] operating on the tail value (alias for ApplicativeTail). +// This is the default applicative instance for Pair. +// +// Example: +// +// import M "github.com/IBM/fp-go/v2/monoid" +// +// intSum := M.MonoidSum[int]() +// applicative := pair.Applicative[string, int, int](intSum) +// pf := applicative.Of(func(s string) int { return len(s) }) +// pv := pair.MakePair(5, "hello") +// result := applicative.Ap(pv)(pf) // Pair[int, int]{5, 5} +func Applicative[B, A, B1 any](m monoid.Monoid[A]) applicative.Applicative[B, B1, Pair[A, B], Pair[A, B1], Pair[A, func(B) B1]] { + return ApplicativeTail[B, A, B1](m) +} diff --git a/v2/pair/pair.go b/v2/pair/pair.go new file mode 100644 index 0000000..bd4f6ce --- /dev/null +++ b/v2/pair/pair.go @@ -0,0 +1,495 @@ +// Copyright (c) 2024 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package pair + +import ( + "fmt" + + F "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/semigroup" + "github.com/IBM/fp-go/v2/tuple" +) + +// String prints some debug info for the object +// +//go:noinline +func pairString(s *pair) string { + return fmt.Sprintf("Pair[%T, %T](%v, %v)", s.h, s.t, s.h, s.t) +} + +// Format prints some debug info for the object +// +//go:noinline +func pairFormat(e *pair, f fmt.State, c rune) { + switch c { + case 's': + fmt.Fprint(f, pairString(e)) + default: + fmt.Fprint(f, pairString(e)) + } +} + +// String prints some debug info for the object +func (s Pair[A, B]) String() string { + return pairString((*pair)(&s)) +} + +// Format prints some debug info for the object +func (s Pair[A, B]) Format(f fmt.State, c rune) { + pairFormat((*pair)(&s), f, c) +} + +// Of creates a [Pair] with the same value in both the head and tail positions. +// +// Example: +// +// p := pair.Of(42) // Pair[int, int]{42, 42} +func Of[A any](value A) Pair[A, A] { + return Pair[A, A]{h: value, t: value} +} + +// FromTuple creates a [Pair] from a [tuple.Tuple2]. +// The first element of the tuple becomes the head, and the second becomes the tail. +// +// Example: +// +// t := tuple.MakeTuple2("hello", 42) +// p := pair.FromTuple(t) // Pair[string, int]{"hello", 42} +func FromTuple[A, B any](t tuple.Tuple2[A, B]) Pair[A, B] { + return Pair[A, B]{h: t.F1, t: t.F2} +} + +// ToTuple creates a [tuple.Tuple2] from a [Pair]. +// The head becomes the first element, and the tail becomes the second element. +// +// Example: +// +// p := pair.MakePair("hello", 42) +// t := pair.ToTuple(p) // tuple.Tuple2[string, int]{"hello", 42} +func ToTuple[A, B any](t Pair[A, B]) tuple.Tuple2[A, B] { + return tuple.MakeTuple2(Head(t), Tail(t)) +} + +// MakePair creates a [Pair] from two values. +// The first value becomes the head, and the second becomes the tail. +// +// Example: +// +// p := pair.MakePair("hello", 42) // Pair[string, int]{"hello", 42} +func MakePair[A, B any](a A, b B) Pair[A, B] { + return Pair[A, B]{h: a, t: b} +} + +// Head returns the head (first) value of the pair. +// +// Example: +// +// p := pair.MakePair("hello", 42) +// h := pair.Head(p) // "hello" +func Head[A, B any](fa Pair[A, B]) A { + return fa.h.(A) +} + +// Tail returns the tail (second) value of the pair. +// +// Example: +// +// p := pair.MakePair("hello", 42) +// t := pair.Tail(p) // 42 +func Tail[A, B any](fa Pair[A, B]) B { + return fa.t.(B) +} + +// First returns the first value of the pair (alias for Head). +// +// Example: +// +// p := pair.MakePair("hello", 42) +// f := pair.First(p) // "hello" +func First[A, B any](fa Pair[A, B]) A { + return fa.h.(A) +} + +// Second returns the second value of the pair (alias for Tail). +// +// Example: +// +// p := pair.MakePair("hello", 42) +// s := pair.Second(p) // 42 +func Second[A, B any](fa Pair[A, B]) B { + return fa.t.(B) +} + +// MonadMapHead maps a function over the head value of the pair, leaving the tail unchanged. +// +// Example: +// +// p := pair.MakePair(5, "hello") +// p2 := pair.MonadMapHead(p, func(n int) string { +// return fmt.Sprintf("%d", n) +// }) // Pair[string, string]{"5", "hello"} +func MonadMapHead[B, A, A1 any](fa Pair[A, B], f func(A) A1) Pair[A1, B] { + return Pair[A1, B]{f(Head(fa)), fa.t} +} + +// MonadMap maps a function over the head value of the pair (alias for MonadMapHead). +// +// Example: +// +// p := pair.MakePair(5, "hello") +// p2 := pair.MonadMap(p, func(n int) string { +// return fmt.Sprintf("%d", n) +// }) // Pair[string, string]{"5", "hello"} +func MonadMap[B, A, A1 any](fa Pair[A, B], f func(A) A1) Pair[A1, B] { + return MonadMapHead(fa, f) +} + +// MonadMapTail maps a function over the tail value of the pair, leaving the head unchanged. +// +// Example: +// +// p := pair.MakePair(5, "hello") +// p2 := pair.MonadMapTail(p, func(s string) int { +// return len(s) +// }) // Pair[int, int]{5, 5} +func MonadMapTail[A, B, B1 any](fa Pair[A, B], f func(B) B1) Pair[A, B1] { + return Pair[A, B1]{fa.h, f(Tail(fa))} +} + +// MonadBiMap maps functions over both the head and tail values of the pair. +// +// Example: +// +// p := pair.MakePair(5, "hello") +// p2 := pair.MonadBiMap(p, +// func(n int) string { return fmt.Sprintf("%d", n) }, +// func(s string) int { return len(s) }, +// ) // Pair[string, int]{"5", 5} +func MonadBiMap[A, B, A1, B1 any](fa Pair[A, B], f func(A) A1, g func(B) B1) Pair[A1, B1] { + return Pair[A1, B1]{f(Head(fa)), g(Tail(fa))} +} + +// Map returns a function that maps over the tail value of a pair (alias for MapTail). +// This is the curried version of MonadMapTail. +// +// Example: +// +// mapper := pair.Map[int](func(s string) int { return len(s) }) +// p := pair.MakePair(5, "hello") +// p2 := mapper(p) // Pair[int, int]{5, 5} +func Map[A, B, B1 any](f func(B) B1) func(Pair[A, B]) Pair[A, B1] { + return MapTail[A](f) +} + +// MapHead returns a function that maps over the head value of a pair. +// This is the curried version of MonadMapHead. +// +// Example: +// +// mapper := pair.MapHead[string](func(n int) string { +// return fmt.Sprintf("%d", n) +// }) +// p := pair.MakePair(5, "hello") +// p2 := mapper(p) // Pair[string, string]{"5", "hello"} +func MapHead[B, A, A1 any](f func(A) A1) func(Pair[A, B]) Pair[A1, B] { + return F.Bind2nd(MonadMapHead[B, A, A1], f) +} + +// MapTail returns a function that maps over the tail value of a pair. +// This is the curried version of MonadMapTail. +// +// Example: +// +// mapper := pair.MapTail[int](func(s string) int { return len(s) }) +// p := pair.MakePair(5, "hello") +// p2 := mapper(p) // Pair[int, int]{5, 5} +func MapTail[A, B, B1 any](f func(B) B1) func(Pair[A, B]) Pair[A, B1] { + return F.Bind2nd(MonadMapTail[A, B, B1], f) +} + +// BiMap returns a function that maps over both values of a pair. +// This is the curried version of MonadBiMap. +// +// Example: +// +// mapper := pair.BiMap( +// func(n int) string { return fmt.Sprintf("%d", n) }, +// func(s string) int { return len(s) }, +// ) +// p := pair.MakePair(5, "hello") +// p2 := mapper(p) // Pair[string, int]{"5", 5} +func BiMap[A, B, A1, B1 any](f func(A) A1, g func(B) B1) func(Pair[A, B]) Pair[A1, B1] { + return func(fa Pair[A, B]) Pair[A1, B1] { + return MonadBiMap(fa, f, g) + } +} + +// MonadChainHead chains a function over the head value, combining tail values using a semigroup. +// The function receives the head value and returns a new pair. The tail values from both pairs +// are combined using the provided semigroup. +// +// Example: +// +// import SG "github.com/IBM/fp-go/v2/semigroup" +// +// strConcat := SG.MakeSemigroup(func(a, b string) string { return a + b }) +// p := pair.MakePair(5, "hello") +// p2 := pair.MonadChainHead(strConcat, p, func(n int) pair.Pair[string, string] { +// return pair.MakePair(fmt.Sprintf("%d", n), "!") +// }) // Pair[string, string]{"5", "hello!"} +func MonadChainHead[B, A, A1 any](sg semigroup.Semigroup[B], fa Pair[A, B], f func(A) Pair[A1, B]) Pair[A1, B] { + fb := f(Head(fa)) + return Pair[A1, B]{fb.h, sg.Concat(Tail(fa), Tail(fb))} +} + +// MonadChainTail chains a function over the tail value, combining head values using a semigroup. +// The function receives the tail value and returns a new pair. The head values from both pairs +// are combined using the provided semigroup. +// +// Example: +// +// import N "github.com/IBM/fp-go/v2/number" +// +// intSum := N.SemigroupSum[int]() +// p := pair.MakePair(5, "hello") +// p2 := pair.MonadChainTail(intSum, p, func(s string) pair.Pair[int, int] { +// return pair.MakePair(len(s), len(s) * 2) +// }) // Pair[int, int]{10, 10} +func MonadChainTail[A, B, B1 any](sg semigroup.Semigroup[A], fb Pair[A, B], f func(B) Pair[A, B1]) Pair[A, B1] { + fa := f(Tail(fb)) + return Pair[A, B1]{sg.Concat(Head(fb), Head(fa)), fa.t} +} + +// MonadChain chains a function over the tail value (alias for MonadChainTail). +// +// Example: +// +// import N "github.com/IBM/fp-go/v2/number" +// +// intSum := N.SemigroupSum[int]() +// p := pair.MakePair(5, "hello") +// p2 := pair.MonadChain(intSum, p, func(s string) pair.Pair[int, int] { +// return pair.MakePair(len(s), len(s) * 2) +// }) // Pair[int, int]{10, 10} +func MonadChain[A, B, B1 any](sg semigroup.Semigroup[A], fa Pair[A, B], f func(B) Pair[A, B1]) Pair[A, B1] { + return MonadChainTail(sg, fa, f) +} + +// ChainHead returns a function that chains over the head value. +// This is the curried version of MonadChainHead. +// +// Example: +// +// import SG "github.com/IBM/fp-go/v2/semigroup" +// +// strConcat := SG.MakeSemigroup(func(a, b string) string { return a + b }) +// chain := pair.ChainHead(strConcat, func(n int) pair.Pair[string, string] { +// return pair.MakePair(fmt.Sprintf("%d", n), "!") +// }) +// p := pair.MakePair(5, "hello") +// p2 := chain(p) // Pair[string, string]{"5", "hello!"} +func ChainHead[B, A, A1 any](sg semigroup.Semigroup[B], f func(A) Pair[A1, B]) func(Pair[A, B]) Pair[A1, B] { + return func(fa Pair[A, B]) Pair[A1, B] { + return MonadChainHead(sg, fa, f) + } +} + +// ChainTail returns a function that chains over the tail value. +// This is the curried version of MonadChainTail. +// +// Example: +// +// import N "github.com/IBM/fp-go/v2/number" +// +// intSum := N.SemigroupSum[int]() +// chain := pair.ChainTail(intSum, func(s string) pair.Pair[int, int] { +// return pair.MakePair(len(s), len(s) * 2) +// }) +// p := pair.MakePair(5, "hello") +// p2 := chain(p) // Pair[int, int]{10, 10} +func ChainTail[A, B, B1 any](sg semigroup.Semigroup[A], f func(B) Pair[A, B1]) func(Pair[A, B]) Pair[A, B1] { + return func(fa Pair[A, B]) Pair[A, B1] { + return MonadChainTail(sg, fa, f) + } +} + +// Chain returns a function that chains over the tail value (alias for ChainTail). +// +// Example: +// +// import N "github.com/IBM/fp-go/v2/number" +// +// intSum := N.SemigroupSum[int]() +// chain := pair.Chain(intSum, func(s string) pair.Pair[int, int] { +// return pair.MakePair(len(s), len(s) * 2) +// }) +// p := pair.MakePair(5, "hello") +// p2 := chain(p) // Pair[int, int]{10, 10} +func Chain[A, B, B1 any](sg semigroup.Semigroup[A], f func(B) Pair[A, B1]) func(Pair[A, B]) Pair[A, B1] { + return ChainTail(sg, f) +} + +// MonadApHead applies a function wrapped in a pair to a value wrapped in a pair, +// operating on the head values and combining tail values using a semigroup. +// +// Example: +// +// import SG "github.com/IBM/fp-go/v2/semigroup" +// +// strConcat := SG.MakeSemigroup(func(a, b string) string { return a + b }) +// pf := pair.MakePair(func(n int) string { return fmt.Sprintf("%d", n) }, "!") +// pv := pair.MakePair(42, "hello") +// result := pair.MonadApHead(strConcat, pf, pv) // Pair[string, string]{"42", "!hello"} +func MonadApHead[B, A, A1 any](sg semigroup.Semigroup[B], faa Pair[func(A) A1, B], fa Pair[A, B]) Pair[A1, B] { + return Pair[A1, B]{Head(faa)(Head(fa)), sg.Concat(Tail(fa), Tail(faa))} +} + +// MonadApTail applies a function wrapped in a pair to a value wrapped in a pair, +// operating on the tail values and combining head values using a semigroup. +// +// Example: +// +// import N "github.com/IBM/fp-go/v2/number" +// +// intSum := N.SemigroupSum[int]() +// pf := pair.MakePair(10, func(s string) int { return len(s) }) +// pv := pair.MakePair(5, "hello") +// result := pair.MonadApTail(intSum, pf, pv) // Pair[int, int]{15, 5} +func MonadApTail[A, B, B1 any](sg semigroup.Semigroup[A], fbb Pair[A, func(B) B1], fb Pair[A, B]) Pair[A, B1] { + return Pair[A, B1]{sg.Concat(Head(fb), Head(fbb)), Tail(fbb)(Tail(fb))} +} + +// MonadAp applies a function wrapped in a pair to a value wrapped in a pair, +// operating on the tail values (alias for MonadApTail). +// +// Example: +// +// import N "github.com/IBM/fp-go/v2/number" +// +// intSum := N.SemigroupSum[int]() +// pf := pair.MakePair(10, func(s string) int { return len(s) }) +// pv := pair.MakePair(5, "hello") +// result := pair.MonadAp(intSum, pf, pv) // Pair[int, int]{15, 5} +func MonadAp[A, B, B1 any](sg semigroup.Semigroup[A], faa Pair[A, func(B) B1], fa Pair[A, B]) Pair[A, B1] { + return MonadApTail(sg, faa, fa) +} + +// ApHead returns a function that applies a function in a pair to a value in a pair, +// operating on head values. This is the curried version of MonadApHead. +// +// Example: +// +// import SG "github.com/IBM/fp-go/v2/semigroup" +// +// strConcat := SG.MakeSemigroup(func(a, b string) string { return a + b }) +// pv := pair.MakePair(42, "hello") +// ap := pair.ApHead(strConcat, pv) +// pf := pair.MakePair(func(n int) string { return fmt.Sprintf("%d", n) }, "!") +// result := ap(pf) // Pair[string, string]{"42", "!hello"} +func ApHead[B, A, A1 any](sg semigroup.Semigroup[B], fa Pair[A, B]) func(Pair[func(A) A1, B]) Pair[A1, B] { + return func(faa Pair[func(A) A1, B]) Pair[A1, B] { + return MonadApHead(sg, faa, fa) + } +} + +// ApTail returns a function that applies a function in a pair to a value in a pair, +// operating on tail values. This is the curried version of MonadApTail. +// +// Example: +// +// import N "github.com/IBM/fp-go/v2/number" +// +// intSum := N.SemigroupSum[int]() +// pv := pair.MakePair(5, "hello") +// ap := pair.ApTail(intSum, pv) +// pf := pair.MakePair(10, func(s string) int { return len(s) }) +// result := ap(pf) // Pair[int, int]{15, 5} +func ApTail[A, B, B1 any](sg semigroup.Semigroup[A], fb Pair[A, B]) func(Pair[A, func(B) B1]) Pair[A, B1] { + return func(fbb Pair[A, func(B) B1]) Pair[A, B1] { + return MonadApTail(sg, fbb, fb) + } +} + +// Ap returns a function that applies a function in a pair to a value in a pair, +// operating on tail values (alias for ApTail). +// +// Example: +// +// import N "github.com/IBM/fp-go/v2/number" +// +// intSum := N.SemigroupSum[int]() +// pv := pair.MakePair(5, "hello") +// ap := pair.Ap(intSum, pv) +// pf := pair.MakePair(10, func(s string) int { return len(s) }) +// result := ap(pf) // Pair[int, int]{15, 5} +func Ap[A, B, B1 any](sg semigroup.Semigroup[A], fa Pair[A, B]) func(Pair[A, func(B) B1]) Pair[A, B1] { + return ApTail[A, B, B1](sg, fa) +} + +// Swap swaps the head and tail values of a pair. +// +// Example: +// +// p := pair.MakePair("hello", 42) +// swapped := pair.Swap(p) // Pair[int, string]{42, "hello"} +func Swap[A, B any](fa Pair[A, B]) Pair[B, A] { + return MakePair(Tail(fa), Head(fa)) +} + +// Paired converts a function with 2 parameters into a function taking a [Pair]. +// The inverse function is [Unpaired]. +// +// Example: +// +// add := func(a, b int) int { return a + b } +// pairedAdd := pair.Paired(add) +// result := pairedAdd(pair.MakePair(3, 4)) // 7 +func Paired[F ~func(T1, T2) R, T1, T2, R any](f F) func(Pair[T1, T2]) R { + return func(t Pair[T1, T2]) R { + return f(Head(t), Tail(t)) + } +} + +// Unpaired converts a function with a [Pair] parameter into a function with 2 parameters. +// The inverse function is [Paired]. +// +// Example: +// +// pairedAdd := func(p pair.Pair[int, int]) int { +// return pair.Head(p) + pair.Tail(p) +// } +// add := pair.Unpaired(pairedAdd) +// result := add(3, 4) // 7 +func Unpaired[F ~func(Pair[T1, T2]) R, T1, T2, R any](f F) func(T1, T2) R { + return func(t1 T1, t2 T2) R { + return f(MakePair(t1, t2)) + } +} + +// Merge applies a curried function to a pair by applying the tail value first, then the head value. +// +// Example: +// +// add := func(b int) func(a int) int { +// return func(a int) int { return a + b } +// } +// merge := pair.Merge(add) +// result := merge(pair.MakePair(3, 4)) // 7 (applies 4 then 3) +func Merge[F ~func(B) func(A) R, A, B, R any](f F) func(Pair[A, B]) R { + return func(p Pair[A, B]) R { + return f(Tail(p))(Head(p)) + } +} diff --git a/v2/pair/pair_test.go b/v2/pair/pair_test.go new file mode 100644 index 0000000..1810f56 --- /dev/null +++ b/v2/pair/pair_test.go @@ -0,0 +1,568 @@ +// Copyright (c) 2024 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package pair + +import ( + "fmt" + "testing" + + EQ "github.com/IBM/fp-go/v2/eq" + M "github.com/IBM/fp-go/v2/monoid" + N "github.com/IBM/fp-go/v2/number" + SG "github.com/IBM/fp-go/v2/semigroup" + "github.com/IBM/fp-go/v2/tuple" + "github.com/stretchr/testify/assert" +) + +func TestOf(t *testing.T) { + p := Of(42) + assert.Equal(t, 42, Head(p)) + assert.Equal(t, 42, Tail(p)) +} + +func TestMakePair(t *testing.T) { + p := MakePair("hello", 42) + assert.Equal(t, "hello", Head(p)) + assert.Equal(t, 42, Tail(p)) +} + +func TestFromTuple(t *testing.T) { + tup := tuple.MakeTuple2("world", 100) + p := FromTuple(tup) + assert.Equal(t, "world", Head(p)) + assert.Equal(t, 100, Tail(p)) +} + +func TestToTuple(t *testing.T) { + p := MakePair("hello", 42) + tup := ToTuple(p) + assert.Equal(t, "hello", tup.F1) + assert.Equal(t, 42, tup.F2) +} + +func TestHeadAndTail(t *testing.T) { + p := MakePair("test", 123) + assert.Equal(t, "test", Head(p)) + assert.Equal(t, 123, Tail(p)) +} + +func TestFirstAndSecond(t *testing.T) { + p := MakePair("first", "second") + assert.Equal(t, "first", First(p)) + assert.Equal(t, "second", Second(p)) +} + +func TestMonadMapHead(t *testing.T) { + p := MakePair(5, "hello") + p2 := MonadMapHead(p, func(n int) string { + return fmt.Sprintf("%d", n) + }) + assert.Equal(t, "5", Head(p2)) + assert.Equal(t, "hello", Tail(p2)) +} + +func TestMonadMapTail(t *testing.T) { + p := MakePair(5, "hello") + p2 := MonadMapTail(p, func(s string) int { + return len(s) + }) + assert.Equal(t, 5, Head(p2)) + assert.Equal(t, 5, Tail(p2)) +} + +func TestMonadMap(t *testing.T) { + p := MakePair(10, "test") + p2 := MonadMap(p, func(n int) string { + return fmt.Sprintf("value: %d", n) + }) + assert.Equal(t, "value: 10", Head(p2)) + assert.Equal(t, "test", Tail(p2)) +} + +func TestMonadBiMap(t *testing.T) { + p := MakePair(5, "hello") + p2 := MonadBiMap(p, + func(n int) string { return fmt.Sprintf("%d", n) }, + func(s string) int { return len(s) }, + ) + assert.Equal(t, "5", Head(p2)) + assert.Equal(t, 5, Tail(p2)) +} + +func TestMapHead(t *testing.T) { + mapper := MapHead[string](func(n int) string { + return fmt.Sprintf("%d", n) + }) + p := MakePair(42, "world") + p2 := mapper(p) + assert.Equal(t, "42", Head(p2)) + assert.Equal(t, "world", Tail(p2)) +} + +func TestMapTail(t *testing.T) { + mapper := MapTail[int](func(s string) int { + return len(s) + }) + p := MakePair(10, "hello") + p2 := mapper(p) + assert.Equal(t, 10, Head(p2)) + assert.Equal(t, 5, Tail(p2)) +} + +func TestMap(t *testing.T) { + mapper := Map[int](func(s string) int { + return len(s) + }) + p := MakePair(10, "test") + p2 := mapper(p) + assert.Equal(t, 10, Head(p2)) + assert.Equal(t, 4, Tail(p2)) +} + +func TestBiMap(t *testing.T) { + mapper := BiMap( + func(n int) string { return fmt.Sprintf("n=%d", n) }, + func(s string) int { return len(s) }, + ) + p := MakePair(7, "hello") + p2 := mapper(p) + assert.Equal(t, "n=7", Head(p2)) + assert.Equal(t, 5, Tail(p2)) +} + +func TestSwap(t *testing.T) { + p := MakePair("hello", 42) + swapped := Swap(p) + assert.Equal(t, 42, Head(swapped)) + assert.Equal(t, "hello", Tail(swapped)) +} + +func TestMonadChainHead(t *testing.T) { + strConcat := SG.MakeSemigroup(func(a, b string) string { return a + b }) + p := MakePair(5, "hello") + p2 := MonadChainHead(strConcat, p, func(n int) Pair[string, string] { + return MakePair(fmt.Sprintf("%d", n), "!") + }) + assert.Equal(t, "5", Head(p2)) + assert.Equal(t, "hello!", Tail(p2)) +} + +func TestMonadChainTail(t *testing.T) { + intSum := N.SemigroupSum[int]() + p := MakePair(5, "hello") + p2 := MonadChainTail(intSum, p, func(s string) Pair[int, int] { + return MakePair(len(s), len(s)*2) + }) + assert.Equal(t, 10, Head(p2)) // 5 + 5 + assert.Equal(t, 10, Tail(p2)) +} + +func TestMonadChain(t *testing.T) { + intSum := N.SemigroupSum[int]() + p := MakePair(3, "test") + p2 := MonadChain(intSum, p, func(s string) Pair[int, int] { + return MakePair(len(s), len(s)*3) + }) + assert.Equal(t, 7, Head(p2)) // 3 + 4 + assert.Equal(t, 12, Tail(p2)) +} + +func TestChainHead(t *testing.T) { + strConcat := SG.MakeSemigroup(func(a, b string) string { return a + b }) + chain := ChainHead(strConcat, func(n int) Pair[string, string] { + return MakePair(fmt.Sprintf("%d", n), "!") + }) + p := MakePair(42, "hello") + p2 := chain(p) + assert.Equal(t, "42", Head(p2)) + assert.Equal(t, "hello!", Tail(p2)) +} + +func TestChainTail(t *testing.T) { + intSum := N.SemigroupSum[int]() + chain := ChainTail(intSum, func(s string) Pair[int, int] { + return MakePair(len(s), len(s)*2) + }) + p := MakePair(10, "world") + p2 := chain(p) + assert.Equal(t, 15, Head(p2)) // 10 + 5 + assert.Equal(t, 10, Tail(p2)) +} + +func TestChain(t *testing.T) { + intSum := N.SemigroupSum[int]() + chain := Chain(intSum, func(s string) Pair[int, int] { + return MakePair(len(s), len(s)*2) + }) + p := MakePair(5, "hi") + p2 := chain(p) + assert.Equal(t, 7, Head(p2)) // 5 + 2 + assert.Equal(t, 4, Tail(p2)) +} + +func TestMonadApHead(t *testing.T) { + strConcat := SG.MakeSemigroup(func(a, b string) string { return a + b }) + pf := MakePair(func(n int) string { return fmt.Sprintf("%d", n) }, "!") + pv := MakePair(42, "hello") + result := MonadApHead(strConcat, pf, pv) + assert.Equal(t, "42", Head(result)) + assert.Equal(t, "hello!", Tail(result)) +} + +func TestMonadApTail(t *testing.T) { + intSum := N.SemigroupSum[int]() + pf := MakePair(10, func(s string) int { return len(s) }) + pv := MakePair(5, "hello") + result := MonadApTail(intSum, pf, pv) + assert.Equal(t, 15, Head(result)) // 5 + 10 + assert.Equal(t, 5, Tail(result)) +} + +func TestMonadAp(t *testing.T) { + intSum := N.SemigroupSum[int]() + pf := MakePair(7, func(s string) int { return len(s) * 2 }) + pv := MakePair(3, "test") + result := MonadAp(intSum, pf, pv) + assert.Equal(t, 10, Head(result)) // 3 + 7 + assert.Equal(t, 8, Tail(result)) // len("test") * 2 +} + +func TestApHead(t *testing.T) { + strConcat := SG.MakeSemigroup(func(a, b string) string { return a + b }) + pv := MakePair(100, "world") + ap := ApHead[string, int, string](strConcat, pv) + pf := MakePair(func(n int) string { return fmt.Sprintf("num=%d", n) }, "!") + result := ap(pf) + assert.Equal(t, "num=100", Head(result)) + assert.Equal(t, "world!", Tail(result)) +} + +func TestApTail(t *testing.T) { + intSum := N.SemigroupSum[int]() + pv := MakePair(20, "hello") + ap := ApTail[int, string, int](intSum, pv) + pf := MakePair(5, func(s string) int { return len(s) }) + result := ap(pf) + assert.Equal(t, 25, Head(result)) // 20 + 5 + assert.Equal(t, 5, Tail(result)) +} + +func TestAp(t *testing.T) { + intSum := N.SemigroupSum[int]() + pv := MakePair(15, "test") + ap := Ap[int, string, int](intSum, pv) + pf := MakePair(10, func(s string) int { return len(s) * 3 }) + result := ap(pf) + assert.Equal(t, 25, Head(result)) // 15 + 10 + assert.Equal(t, 12, Tail(result)) // len("test") * 3 +} + +func TestPaired(t *testing.T) { + add := func(a, b int) int { return a + b } + pairedAdd := Paired(add) + result := pairedAdd(MakePair(3, 4)) + assert.Equal(t, 7, result) +} + +func TestUnpaired(t *testing.T) { + pairedAdd := func(p Pair[int, int]) int { + return Head(p) + Tail(p) + } + add := Unpaired(pairedAdd) + result := add(5, 7) + assert.Equal(t, 12, result) +} + +func TestMerge(t *testing.T) { + add := func(b int) func(a int) int { + return func(a int) int { return a + b } + } + merge := Merge(add) + result := merge(MakePair(3, 4)) + assert.Equal(t, 7, result) +} + +func TestEq(t *testing.T) { + pairEq := Eq( + EQ.FromStrictEquals[string](), + EQ.FromStrictEquals[int](), + ) + p1 := MakePair("hello", 42) + p2 := MakePair("hello", 42) + p3 := MakePair("world", 42) + p4 := MakePair("hello", 100) + + assert.True(t, pairEq.Equals(p1, p2)) + assert.False(t, pairEq.Equals(p1, p3)) + assert.False(t, pairEq.Equals(p1, p4)) +} + +func TestFromStrictEquals(t *testing.T) { + pairEq := FromStrictEquals[string, int]() + p1 := MakePair("test", 123) + p2 := MakePair("test", 123) + p3 := MakePair("test", 456) + + assert.True(t, pairEq.Equals(p1, p2)) + assert.False(t, pairEq.Equals(p1, p3)) +} + +func TestString(t *testing.T) { + p := MakePair("hello", 42) + str := p.String() + assert.Contains(t, str, "Pair") + assert.Contains(t, str, "hello") + assert.Contains(t, str, "42") +} + +func TestFormat(t *testing.T) { + p := MakePair("test", 100) + str := fmt.Sprintf("%s", p) + assert.Contains(t, str, "Pair") + assert.Contains(t, str, "test") + assert.Contains(t, str, "100") +} + +func TestMonadHead(t *testing.T) { + stringMonoid := M.MakeMonoid( + func(a, b string) string { return a + b }, + "", + ) + monad := MonadHead[int, string, string](stringMonoid) + + // Test Of + p := monad.Of(42) + assert.Equal(t, 42, Head(p)) + assert.Equal(t, "", Tail(p)) + + // Test Map + mapper := monad.Map(func(n int) string { return fmt.Sprintf("%d", n) }) + p2 := mapper(MakePair(100, "!")) + assert.Equal(t, "100", Head(p2)) + assert.Equal(t, "!", Tail(p2)) + + // Test Chain + chain := monad.Chain(func(n int) Pair[string, string] { + return MakePair(fmt.Sprintf("n=%d", n), "!") + }) + p3 := chain(MakePair(7, "hello")) + assert.Equal(t, "n=7", Head(p3)) + assert.Equal(t, "hello!", Tail(p3)) + + // Test Ap + pv := MakePair(5, "world") + ap := monad.Ap(pv) + pf := MakePair(func(n int) string { return fmt.Sprintf("%d", n*2) }, "!") + p4 := ap(pf) + assert.Equal(t, "10", Head(p4)) + assert.Equal(t, "world!", Tail(p4)) +} + +func TestPointedHead(t *testing.T) { + stringMonoid := M.MakeMonoid( + func(a, b string) string { return a + b }, + "", + ) + pointed := PointedHead[int, string](stringMonoid) + p := pointed.Of(42) + assert.Equal(t, 42, Head(p)) + assert.Equal(t, "", Tail(p)) +} + +func TestFunctorHead(t *testing.T) { + functor := FunctorHead[int, string, string]() + mapper := functor.Map(func(n int) string { return fmt.Sprintf("value=%d", n) }) + p := MakePair(42, "test") + p2 := mapper(p) + assert.Equal(t, "value=42", Head(p2)) + assert.Equal(t, "test", Tail(p2)) +} + +func TestApplicativeHead(t *testing.T) { + stringMonoid := M.MakeMonoid( + func(a, b string) string { return a + b }, + "", + ) + applicative := ApplicativeHead[int, string, string](stringMonoid) + + // Test Of + p := applicative.Of(100) + assert.Equal(t, 100, Head(p)) + assert.Equal(t, "", Tail(p)) + + // Test Map + mapper := applicative.Map(func(n int) string { return fmt.Sprintf("%d", n) }) + p2 := mapper(MakePair(42, "!")) + assert.Equal(t, "42", Head(p2)) + assert.Equal(t, "!", Tail(p2)) + + // Test Ap + pv := MakePair(7, "hello") + ap := applicative.Ap(pv) + pf := MakePair(func(n int) string { return fmt.Sprintf("n=%d", n) }, "!") + p3 := ap(pf) + assert.Equal(t, "n=7", Head(p3)) + assert.Equal(t, "hello!", Tail(p3)) +} + +func TestMonadTail(t *testing.T) { + intSum := N.MonoidSum[int]() + monad := MonadTail[string, int, int](intSum) + + // Test Of + p := monad.Of("hello") + assert.Equal(t, 0, Head(p)) + assert.Equal(t, "hello", Tail(p)) + + // Test Map + mapper := monad.Map(func(s string) int { return len(s) }) + p2 := mapper(MakePair(5, "world")) + assert.Equal(t, 5, Head(p2)) + assert.Equal(t, 5, Tail(p2)) + + // Test Chain + chain := monad.Chain(func(s string) Pair[int, int] { + return MakePair(len(s), len(s)*2) + }) + p3 := chain(MakePair(10, "test")) + assert.Equal(t, 14, Head(p3)) // 10 + 4 + assert.Equal(t, 8, Tail(p3)) + + // Test Ap + pv := MakePair(5, "hello") + ap := monad.Ap(pv) + pf := MakePair(10, func(s string) int { return len(s) }) + p4 := ap(pf) + assert.Equal(t, 15, Head(p4)) // 5 + 10 + assert.Equal(t, 5, Tail(p4)) +} + +func TestPointedTail(t *testing.T) { + intSum := N.MonoidSum[int]() + pointed := PointedTail[string, int](intSum) + p := pointed.Of("test") + assert.Equal(t, 0, Head(p)) + assert.Equal(t, "test", Tail(p)) +} + +func TestFunctorTail(t *testing.T) { + functor := FunctorTail[string, int, int]() + mapper := functor.Map(func(s string) int { return len(s) * 2 }) + p := MakePair(10, "hello") + p2 := mapper(p) + assert.Equal(t, 10, Head(p2)) + assert.Equal(t, 10, Tail(p2)) +} + +func TestApplicativeTail(t *testing.T) { + intSum := N.MonoidSum[int]() + applicative := ApplicativeTail[string, int, int](intSum) + + // Test Of + p := applicative.Of("world") + assert.Equal(t, 0, Head(p)) + assert.Equal(t, "world", Tail(p)) + + // Test Map + mapper := applicative.Map(func(s string) int { return len(s) }) + p2 := mapper(MakePair(5, "test")) + assert.Equal(t, 5, Head(p2)) + assert.Equal(t, 4, Tail(p2)) + + // Test Ap + pv := MakePair(10, "hello") + ap := applicative.Ap(pv) + pf := MakePair(5, func(s string) int { return len(s) * 2 }) + p3 := ap(pf) + assert.Equal(t, 15, Head(p3)) // 10 + 5 + assert.Equal(t, 10, Tail(p3)) +} + +func TestMonad(t *testing.T) { + intSum := N.MonoidSum[int]() + monad := Monad[string, int, int](intSum) + + p := monad.Of("test") + assert.Equal(t, 0, Head(p)) + assert.Equal(t, "test", Tail(p)) +} + +func TestPointed(t *testing.T) { + intSum := N.MonoidSum[int]() + pointed := Pointed[string, int](intSum) + + p := pointed.Of("hello") + assert.Equal(t, 0, Head(p)) + assert.Equal(t, "hello", Tail(p)) +} + +func TestFunctor(t *testing.T) { + functor := Functor[string, int, int]() + mapper := functor.Map(func(s string) int { return len(s) }) + p := MakePair(7, "world") + p2 := mapper(p) + assert.Equal(t, 7, Head(p2)) + assert.Equal(t, 5, Tail(p2)) +} + +func TestApplicative(t *testing.T) { + intSum := N.MonoidSum[int]() + applicative := Applicative[string, int, int](intSum) + + p := applicative.Of("test") + assert.Equal(t, 0, Head(p)) + assert.Equal(t, "test", Tail(p)) +} + +// Test edge cases and complex scenarios +func TestComplexChaining(t *testing.T) { + intSum := N.SemigroupSum[int]() + + // Chain multiple operations + p := MakePair(1, "a") + p2 := MonadChainTail(intSum, p, func(s string) Pair[int, string] { + return MakePair(len(s), s+"b") + }) + p3 := MonadChainTail(intSum, p2, func(s string) Pair[int, string] { + return MakePair(len(s), s+"c") + }) + + assert.Equal(t, 4, Head(p3)) // 1 + 1 + 2 + assert.Equal(t, "abc", Tail(p3)) +} + +func TestBiMapWithDifferentTypes(t *testing.T) { + p := MakePair(3.14, true) + p2 := MonadBiMap(p, + func(f float64) int { return int(f * 10) }, + func(b bool) string { + if b { + return "yes" + } + return "no" + }, + ) + assert.Equal(t, 31, Head(p2)) + assert.Equal(t, "yes", Tail(p2)) +} + +func TestSwapTwice(t *testing.T) { + p := MakePair("original", 999) + swapped := Swap(p) + swappedBack := Swap(swapped) + assert.Equal(t, "original", Head(swappedBack)) + assert.Equal(t, 999, Tail(swappedBack)) +} diff --git a/v2/pair/testing/laws.go b/v2/pair/testing/laws.go new file mode 100644 index 0000000..1b94485 --- /dev/null +++ b/v2/pair/testing/laws.go @@ -0,0 +1,155 @@ +// Copyright (c) 2024 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package testing + +import ( + "testing" + + "github.com/IBM/fp-go/v2/eq" + L "github.com/IBM/fp-go/v2/internal/monad/testing" + P "github.com/IBM/fp-go/v2/pair" + + M "github.com/IBM/fp-go/v2/monoid" +) + +// AssertLaws asserts the apply monad laws for the [P.Pair] monad +func assertLawsHead[E, A, B, C any](t *testing.T, + m M.Monoid[E], + + eqe eq.Eq[E], + eqa eq.Eq[A], + eqb eq.Eq[B], + eqc eq.Eq[C], + + ab func(A) B, + bc func(B) C, +) func(a A) bool { + + fofc := P.PointedHead[C](m) + fofaa := P.PointedHead[func(A) A](m) + fofbc := P.PointedHead[func(B) C](m) + fofabb := P.PointedHead[func(func(A) B) B](m) + + fmap := P.FunctorHead[func(B) C, E, func(func(A) B) func(A) C]() + + fapabb := P.ApplicativeHead[func(A) B, E, B](m) + fapabac := P.ApplicativeHead[func(A) B, E, func(A) C](m) + + maa := P.MonadHead[A, E, A](m) + mab := P.MonadHead[A, E, B](m) + mac := P.MonadHead[A, E, C](m) + mbc := P.MonadHead[B, E, C](m) + + return L.MonadAssertLaws(t, + P.Eq(eqa, eqe), + P.Eq(eqb, eqe), + P.Eq(eqc, eqe), + + fofc, + fofaa, + fofbc, + fofabb, + + fmap, + + fapabb, + fapabac, + + maa, + mab, + mac, + mbc, + + ab, + bc, + ) + +} + +// AssertLaws asserts the apply monad laws for the [P.Pair] monad +func assertLawsTail[E, A, B, C any](t *testing.T, + m M.Monoid[E], + + eqe eq.Eq[E], + eqa eq.Eq[A], + eqb eq.Eq[B], + eqc eq.Eq[C], + + ab func(A) B, + bc func(B) C, +) func(a A) bool { + + fofc := P.PointedTail[C](m) + fofaa := P.PointedTail[func(A) A](m) + fofbc := P.PointedTail[func(B) C](m) + fofabb := P.PointedTail[func(func(A) B) B](m) + + fmap := P.FunctorTail[func(B) C, E, func(func(A) B) func(A) C]() + + fapabb := P.ApplicativeTail[func(A) B, E, B](m) + fapabac := P.ApplicativeTail[func(A) B, E, func(A) C](m) + + maa := P.MonadTail[A, E, A](m) + mab := P.MonadTail[A, E, B](m) + mac := P.MonadTail[A, E, C](m) + mbc := P.MonadTail[B, E, C](m) + + return L.MonadAssertLaws(t, + P.Eq(eqe, eqa), + P.Eq(eqe, eqb), + P.Eq(eqe, eqc), + + fofc, + fofaa, + fofbc, + fofabb, + + fmap, + + fapabb, + fapabac, + + maa, + mab, + mac, + mbc, + + ab, + bc, + ) + +} + +// AssertLaws asserts the apply monad laws for the [P.Pair] monad +func AssertLaws[E, A, B, C any](t *testing.T, + m M.Monoid[E], + + eqe eq.Eq[E], + eqa eq.Eq[A], + eqb eq.Eq[B], + eqc eq.Eq[C], + + ab func(A) B, + bc func(B) C, +) func(A) bool { + + head := assertLawsHead(t, m, eqe, eqa, eqb, eqc, ab, bc) + tail := assertLawsTail(t, m, eqe, eqa, eqb, eqc, ab, bc) + + return func(a A) bool { + return head(a) && tail(a) + } +} diff --git a/v2/pair/testing/laws_test.go b/v2/pair/testing/laws_test.go new file mode 100644 index 0000000..9bebc5e --- /dev/null +++ b/v2/pair/testing/laws_test.go @@ -0,0 +1,51 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package testing + +import ( + "fmt" + "testing" + + "github.com/IBM/fp-go/v2/eq" + S "github.com/IBM/fp-go/v2/string" + "github.com/stretchr/testify/assert" +) + +func TestMonadLaws(t *testing.T) { + // some comparison + eqe := eq.FromStrictEquals[string]() + eqa := eq.FromStrictEquals[bool]() + eqb := eq.FromStrictEquals[int]() + eqc := eq.FromStrictEquals[string]() + + m := S.Monoid + + ab := func(a bool) int { + if a { + return 1 + } + return 0 + } + + bc := func(b int) string { + return fmt.Sprintf("value %d", b) + } + + laws := AssertLaws(t, m, eqe, eqa, eqb, eqc, ab, bc) + + assert.True(t, laws(true)) + assert.True(t, laws(false)) +} diff --git a/v2/pair/types.go b/v2/pair/types.go new file mode 100644 index 0000000..4759202 --- /dev/null +++ b/v2/pair/types.go @@ -0,0 +1,25 @@ +// Copyright (c) 2024 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package pair + +type ( + pair struct { + h, t any + } + + // Pair defines a data structure that holds two strongly typed values + Pair[L, R any] pair +) diff --git a/v2/predicate/bool.go b/v2/predicate/bool.go new file mode 100644 index 0000000..2555cfc --- /dev/null +++ b/v2/predicate/bool.go @@ -0,0 +1,78 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package predicate + +// Not negates a predicate, returning a new predicate that returns the opposite boolean value. +// +// Given a predicate that returns true for some input, Not returns a predicate that returns false +// for the same input, and vice versa. +// +// Example: +// +// isPositive := func(n int) bool { return n > 0 } +// isNotPositive := Not(isPositive) +// isNotPositive(5) // false +// isNotPositive(-3) // true +func Not[A any](predicate Predicate[A]) Predicate[A] { + return func(a A) bool { + return !predicate(a) + } +} + +// And creates an operator that combines two predicates using logical AND (&&). +// +// The resulting predicate returns true only if both the first and second predicates return true. +// This function is curried, taking the second predicate first and returning an operator that +// takes the first predicate. +// +// Example: +// +// isPositive := func(n int) bool { return n > 0 } +// isEven := func(n int) bool { return n%2 == 0 } +// isPositiveAndEven := F.Pipe1(isPositive, And(isEven)) +// isPositiveAndEven(4) // true +// isPositiveAndEven(-2) // false +// isPositiveAndEven(3) // false +func And[A any](second Predicate[A]) Operator[A, A] { + return func(first Predicate[A]) Predicate[A] { + return func(a A) bool { + return first(a) && second(a) + } + } +} + +// Or creates an operator that combines two predicates using logical OR (||). +// +// The resulting predicate returns true if either the first or second predicate returns true. +// This function is curried, taking the second predicate first and returning an operator that +// takes the first predicate. +// +// Example: +// +// isPositive := func(n int) bool { return n > 0 } +// isEven := func(n int) bool { return n%2 == 0 } +// isPositiveOrEven := F.Pipe1(isPositive, Or(isEven)) +// isPositiveOrEven(4) // true +// isPositiveOrEven(-2) // true +// isPositiveOrEven(3) // true +// isPositiveOrEven(-3) // false +func Or[A any](second Predicate[A]) Operator[A, A] { + return func(first Predicate[A]) Predicate[A] { + return func(a A) bool { + return first(a) || second(a) + } + } +} diff --git a/v2/predicate/contramap.go b/v2/predicate/contramap.go new file mode 100644 index 0000000..cd85cd3 --- /dev/null +++ b/v2/predicate/contramap.go @@ -0,0 +1,52 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package predicate + +import ( + F "github.com/IBM/fp-go/v2/function" +) + +// ContraMap creates a new predicate by transforming the input before applying an existing predicate. +// +// This is a contravariant functor operation that allows you to adapt a predicate for type A +// to work with type B by providing a function that converts B to A. The resulting predicate +// first applies the mapping function f to transform the input, then applies the original predicate. +// +// This is particularly useful when you have a predicate for one type and want to reuse it +// for a related type without rewriting the predicate logic. +// +// Parameters: +// - f: A function that converts values of type B to type A +// +// Returns: +// - An Operator that transforms a Predicate[A] into a Predicate[B] +// +// Example: +// +// type Person struct { Age int } +// isAdult := func(age int) bool { return age >= 18 } +// getAge := func(p Person) int { return p.Age } +// isPersonAdult := F.Pipe1(isAdult, ContraMap(getAge)) +// isPersonAdult(Person{Age: 25}) // true +// isPersonAdult(Person{Age: 15}) // false +func ContraMap[A, B any](f func(B) A) Operator[A, B] { + return func(pred Predicate[A]) Predicate[B] { + return F.Flow2( + f, + pred, + ) + } +} diff --git a/v2/predicate/coverage.out b/v2/predicate/coverage.out new file mode 100644 index 0000000..aeaa06b --- /dev/null +++ b/v2/predicate/coverage.out @@ -0,0 +1,17 @@ +mode: set +github.com/IBM/fp-go/v2/predicate/bool.go:29.54,30.24 1 1 +github.com/IBM/fp-go/v2/predicate/bool.go:30.24,32.3 1 1 +github.com/IBM/fp-go/v2/predicate/bool.go:49.53,50.47 1 1 +github.com/IBM/fp-go/v2/predicate/bool.go:50.47,51.25 1 1 +github.com/IBM/fp-go/v2/predicate/bool.go:51.25,53.4 1 1 +github.com/IBM/fp-go/v2/predicate/bool.go:72.52,73.47 1 1 +github.com/IBM/fp-go/v2/predicate/bool.go:73.47,74.25 1 1 +github.com/IBM/fp-go/v2/predicate/bool.go:74.25,76.4 1 1 +github.com/IBM/fp-go/v2/predicate/contramap.go:45.54,46.46 1 1 +github.com/IBM/fp-go/v2/predicate/contramap.go:46.46,51.3 1 1 +github.com/IBM/fp-go/v2/predicate/monoid.go:53.41,54.92 1 1 +github.com/IBM/fp-go/v2/predicate/monoid.go:54.92,59.3 1 1 +github.com/IBM/fp-go/v2/predicate/monoid.go:81.41,82.92 1 1 +github.com/IBM/fp-go/v2/predicate/monoid.go:82.92,87.3 1 1 +github.com/IBM/fp-go/v2/predicate/monoid.go:111.35,116.2 1 1 +github.com/IBM/fp-go/v2/predicate/monoid.go:139.35,144.2 1 1 diff --git a/v2/predicate/monoid.go b/v2/predicate/monoid.go new file mode 100644 index 0000000..b85e440 --- /dev/null +++ b/v2/predicate/monoid.go @@ -0,0 +1,144 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package predicate + +import ( + F "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/monoid" + "github.com/IBM/fp-go/v2/semigroup" +) + +type ( + // Semigroup represents a semigroup instance for predicates, providing a way to combine + // two predicates into one using an associative operation. + Semigroup[A any] = semigroup.Semigroup[Predicate[A]] + + // Monoid represents a monoid instance for predicates, extending Semigroup with an + // identity element (empty predicate). + Monoid[A any] = monoid.Monoid[Predicate[A]] +) + +// SemigroupAny creates a semigroup that combines predicates using logical OR (||). +// +// When two predicates are combined with this semigroup, the resulting predicate returns +// true if either of the original predicates returns true. This implements the associative +// operation for disjunction. +// +// Returns: +// - A Semigroup[A] that combines predicates with OR logic +// +// Example: +// +// s := SemigroupAny[int]() +// isPositive := func(n int) bool { return n > 0 } +// isEven := func(n int) bool { return n%2 == 0 } +// isPositiveOrEven := s.Concat(isPositive, isEven) +// isPositiveOrEven(4) // true (even) +// isPositiveOrEven(3) // true (positive) +// isPositiveOrEven(-2) // true (even) +// isPositiveOrEven(-3) // false (neither) +func SemigroupAny[A any]() Semigroup[A] { + return semigroup.MakeSemigroup(func(first Predicate[A], second Predicate[A]) Predicate[A] { + return F.Pipe1( + first, + Or(second), + ) + }) +} + +// SemigroupAll creates a semigroup that combines predicates using logical AND (&&). +// +// When two predicates are combined with this semigroup, the resulting predicate returns +// true only if both of the original predicates return true. This implements the associative +// operation for conjunction. +// +// Returns: +// - A Semigroup[A] that combines predicates with AND logic +// +// Example: +// +// s := SemigroupAll[int]() +// isPositive := func(n int) bool { return n > 0 } +// isEven := func(n int) bool { return n%2 == 0 } +// isPositiveAndEven := s.Concat(isPositive, isEven) +// isPositiveAndEven(4) // true (both) +// isPositiveAndEven(3) // false (not even) +// isPositiveAndEven(-2) // false (not positive) +// isPositiveAndEven(-3) // false (neither) +func SemigroupAll[A any]() Semigroup[A] { + return semigroup.MakeSemigroup(func(first Predicate[A], second Predicate[A]) Predicate[A] { + return F.Pipe1( + first, + And(second), + ) + }) +} + +// MonoidAny creates a monoid that combines predicates using logical OR (||). +// +// This extends SemigroupAny with an identity element: a predicate that always returns false. +// The identity element satisfies the property that combining it with any predicate p yields p. +// This is useful for folding/reducing a collection of predicates where an empty collection +// should result in a predicate that always returns false. +// +// Returns: +// - A Monoid[A] that combines predicates with OR logic and has false as identity +// +// Example: +// +// m := MonoidAny[int]() +// predicates := []Predicate[int]{ +// func(n int) bool { return n > 10 }, +// func(n int) bool { return n < 0 }, +// } +// combined := A.Reduce(m.Empty(), m.Concat)(predicates) +// combined(15) // true (> 10) +// combined(-5) // true (< 0) +// combined(5) // false (neither) +func MonoidAny[A any]() Monoid[A] { + return monoid.MakeMonoid( + SemigroupAny[A]().Concat, + F.Constant1[A](false), + ) +} + +// MonoidAll creates a monoid that combines predicates using logical AND (&&). +// +// This extends SemigroupAll with an identity element: a predicate that always returns true. +// The identity element satisfies the property that combining it with any predicate p yields p. +// This is useful for folding/reducing a collection of predicates where an empty collection +// should result in a predicate that always returns true. +// +// Returns: +// - A Monoid[A] that combines predicates with AND logic and has true as identity +// +// Example: +// +// m := MonoidAll[int]() +// predicates := []Predicate[int]{ +// func(n int) bool { return n > 0 }, +// func(n int) bool { return n < 100 }, +// } +// combined := A.Reduce(m.Empty(), m.Concat)(predicates) +// combined(50) // true (both conditions) +// combined(-5) // false (not > 0) +// combined(150) // false (not < 100) +func MonoidAll[A any]() Monoid[A] { + return monoid.MakeMonoid( + SemigroupAll[A]().Concat, + F.Constant1[A](true), + ) +} diff --git a/v2/predicate/predicate_test.go b/v2/predicate/predicate_test.go new file mode 100644 index 0000000..bc666b2 --- /dev/null +++ b/v2/predicate/predicate_test.go @@ -0,0 +1,410 @@ +// Copyright (c) 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package predicate + +import ( + "testing" + + F "github.com/IBM/fp-go/v2/function" + "github.com/stretchr/testify/assert" +) + +// Test predicates for reuse +var ( + isPositive = func(n int) bool { return n > 0 } + isEven = func(n int) bool { return n%2 == 0 } + isNegative = func(n int) bool { return n < 0 } + isGreaterThan10 = func(n int) bool { return n > 10 } +) + +// TestNot tests the Not function +func TestNot(t *testing.T) { + t.Run("negates true to false", func(t *testing.T) { + notPositive := Not(isPositive) + assert.False(t, notPositive(5)) + assert.False(t, notPositive(1)) + }) + + t.Run("negates false to true", func(t *testing.T) { + notPositive := Not(isPositive) + assert.True(t, notPositive(-5)) + assert.True(t, notPositive(0)) + }) + + t.Run("double negation returns original", func(t *testing.T) { + doubleNegated := Not(Not(isPositive)) + assert.True(t, doubleNegated(5)) + assert.False(t, doubleNegated(-5)) + }) +} + +// TestAnd tests the And function +func TestAnd(t *testing.T) { + t.Run("returns true when both predicates are true", func(t *testing.T) { + isPositiveAndEven := F.Pipe1(isPositive, And(isEven)) + assert.True(t, isPositiveAndEven(2)) + assert.True(t, isPositiveAndEven(4)) + assert.True(t, isPositiveAndEven(100)) + }) + + t.Run("returns false when first predicate is false", func(t *testing.T) { + isPositiveAndEven := F.Pipe1(isPositive, And(isEven)) + assert.False(t, isPositiveAndEven(-2)) + assert.False(t, isPositiveAndEven(-4)) + }) + + t.Run("returns false when second predicate is false", func(t *testing.T) { + isPositiveAndEven := F.Pipe1(isPositive, And(isEven)) + assert.False(t, isPositiveAndEven(1)) + assert.False(t, isPositiveAndEven(3)) + assert.False(t, isPositiveAndEven(5)) + }) + + t.Run("returns false when both predicates are false", func(t *testing.T) { + isPositiveAndEven := F.Pipe1(isPositive, And(isEven)) + assert.False(t, isPositiveAndEven(-1)) + assert.False(t, isPositiveAndEven(-3)) + }) + + t.Run("chains multiple And operations", func(t *testing.T) { + isPositiveEvenAndGreaterThan10 := F.Pipe2( + isPositive, + And(isEven), + And(isGreaterThan10), + ) + assert.True(t, isPositiveEvenAndGreaterThan10(12)) + assert.False(t, isPositiveEvenAndGreaterThan10(8)) + assert.False(t, isPositiveEvenAndGreaterThan10(11)) + }) +} + +// TestOr tests the Or function +func TestOr(t *testing.T) { + t.Run("returns true when first predicate is true", func(t *testing.T) { + isPositiveOrEven := F.Pipe1(isPositive, Or(isEven)) + assert.True(t, isPositiveOrEven(1)) + assert.True(t, isPositiveOrEven(3)) + assert.True(t, isPositiveOrEven(5)) + }) + + t.Run("returns true when second predicate is true", func(t *testing.T) { + isPositiveOrEven := F.Pipe1(isPositive, Or(isEven)) + assert.True(t, isPositiveOrEven(-2)) + assert.True(t, isPositiveOrEven(-4)) + assert.True(t, isPositiveOrEven(0)) + }) + + t.Run("returns true when both predicates are true", func(t *testing.T) { + isPositiveOrEven := F.Pipe1(isPositive, Or(isEven)) + assert.True(t, isPositiveOrEven(2)) + assert.True(t, isPositiveOrEven(4)) + assert.True(t, isPositiveOrEven(100)) + }) + + t.Run("returns false when both predicates are false", func(t *testing.T) { + isPositiveOrEven := F.Pipe1(isPositive, Or(isEven)) + assert.False(t, isPositiveOrEven(-1)) + assert.False(t, isPositiveOrEven(-3)) + assert.False(t, isPositiveOrEven(-5)) + }) + + t.Run("chains multiple Or operations", func(t *testing.T) { + isPositiveOrEvenOrNegative := F.Pipe2( + isPositive, + Or(isEven), + Or(isNegative), + ) + assert.True(t, isPositiveOrEvenOrNegative(5)) // positive + assert.True(t, isPositiveOrEvenOrNegative(2)) // even + assert.True(t, isPositiveOrEvenOrNegative(-3)) // negative + assert.True(t, isPositiveOrEvenOrNegative(0)) // even + }) +} + +// TestContraMap tests the ContraMap function +func TestContraMap(t *testing.T) { + type Person struct { + Name string + Age int + } + + t.Run("transforms predicate to work with different type", func(t *testing.T) { + isAdult := func(age int) bool { return age >= 18 } + getAge := func(p Person) int { return p.Age } + isPersonAdult := F.Pipe1(isAdult, ContraMap(getAge)) + + assert.True(t, isPersonAdult(Person{Name: "Alice", Age: 25})) + assert.True(t, isPersonAdult(Person{Name: "Bob", Age: 18})) + assert.False(t, isPersonAdult(Person{Name: "Charlie", Age: 15})) + }) + + t.Run("works with string length", func(t *testing.T) { + isLongEnough := func(n int) bool { return n >= 5 } + getLength := func(s string) int { return len(s) } + isStringLongEnough := F.Pipe1(isLongEnough, ContraMap(getLength)) + + assert.True(t, isStringLongEnough("hello")) + assert.True(t, isStringLongEnough("world!")) + assert.False(t, isStringLongEnough("hi")) + assert.False(t, isStringLongEnough("")) + }) + + t.Run("composes with other operations", func(t *testing.T) { + type Product struct { + Name string + Price int + } + + isExpensive := func(price int) bool { return price > 100 } + isCheap := func(price int) bool { return price < 50 } + getPrice := func(p Product) int { return p.Price } + + isExpensiveProduct := F.Pipe1(isExpensive, ContraMap(getPrice)) + isCheapProduct := F.Pipe1(isCheap, ContraMap(getPrice)) + isExtremePrice := F.Pipe1(isExpensiveProduct, Or(isCheapProduct)) + + assert.True(t, isExtremePrice(Product{Name: "Luxury", Price: 200})) + assert.True(t, isExtremePrice(Product{Name: "Budget", Price: 30})) + assert.False(t, isExtremePrice(Product{Name: "Mid-range", Price: 75})) + }) +} + +// TestSemigroupAny tests the SemigroupAny function +func TestSemigroupAny(t *testing.T) { + s := SemigroupAny[int]() + + t.Run("combines predicates with OR logic", func(t *testing.T) { + combined := s.Concat(isPositive, isEven) + assert.True(t, combined(4)) // both true + assert.True(t, combined(3)) // first true + assert.True(t, combined(-2)) // second true + assert.False(t, combined(-3)) // both false + }) + + t.Run("is associative", func(t *testing.T) { + // (a OR b) OR c == a OR (b OR c) + left := s.Concat(s.Concat(isPositive, isEven), isNegative) + right := s.Concat(isPositive, s.Concat(isEven, isNegative)) + + testValues := []int{-5, -2, 0, 1, 2, 5} + for _, v := range testValues { + assert.Equal(t, left(v), right(v), "associativity failed for value %d", v) + } + }) + + t.Run("combines multiple predicates", func(t *testing.T) { + combined := s.Concat(s.Concat(isPositive, isEven), isGreaterThan10) + assert.True(t, combined(15)) // positive and > 10 + assert.True(t, combined(2)) // even + assert.True(t, combined(1)) // positive + assert.False(t, combined(-3)) // none + }) +} + +// TestSemigroupAll tests the SemigroupAll function +func TestSemigroupAll(t *testing.T) { + s := SemigroupAll[int]() + + t.Run("combines predicates with AND logic", func(t *testing.T) { + combined := s.Concat(isPositive, isEven) + assert.True(t, combined(4)) // both true + assert.False(t, combined(3)) // first true only + assert.False(t, combined(-2)) // second true only + assert.False(t, combined(-3)) // both false + }) + + t.Run("is associative", func(t *testing.T) { + // (a AND b) AND c == a AND (b AND c) + isLessThan100 := func(n int) bool { return n < 100 } + left := s.Concat(s.Concat(isPositive, isEven), isLessThan100) + right := s.Concat(isPositive, s.Concat(isEven, isLessThan100)) + + testValues := []int{-5, -2, 0, 1, 2, 50, 150} + for _, v := range testValues { + assert.Equal(t, left(v), right(v), "associativity failed for value %d", v) + } + }) + + t.Run("combines multiple predicates", func(t *testing.T) { + combined := s.Concat(s.Concat(isPositive, isEven), isGreaterThan10) + assert.True(t, combined(12)) // all true + assert.False(t, combined(8)) // not > 10 + assert.False(t, combined(11)) // not even + assert.False(t, combined(-2)) // not positive + }) +} + +// TestMonoidAny tests the MonoidAny function +func TestMonoidAny(t *testing.T) { + m := MonoidAny[int]() + + t.Run("has identity element that returns false", func(t *testing.T) { + empty := m.Empty() + assert.False(t, empty(0)) + assert.False(t, empty(5)) + assert.False(t, empty(-5)) + }) + + t.Run("identity is left identity", func(t *testing.T) { + // empty OR p == p + combined := m.Concat(m.Empty(), isPositive) + assert.True(t, combined(5)) + assert.False(t, combined(-5)) + }) + + t.Run("identity is right identity", func(t *testing.T) { + // p OR empty == p + combined := m.Concat(isPositive, m.Empty()) + assert.True(t, combined(5)) + assert.False(t, combined(-5)) + }) + + t.Run("reduces empty list to identity", func(t *testing.T) { + predicates := []Predicate[int]{} + result := m.Empty() + for _, p := range predicates { + result = m.Concat(result, p) + } + assert.False(t, result(5)) + }) + + t.Run("reduces list of predicates", func(t *testing.T) { + predicates := []Predicate[int]{isPositive, isEven, isGreaterThan10} + result := m.Empty() + for _, p := range predicates { + result = m.Concat(result, p) + } + assert.True(t, result(15)) // positive + assert.True(t, result(2)) // even + assert.True(t, result(11)) // > 10 + assert.False(t, result(-3)) // none + }) +} + +// TestMonoidAll tests the MonoidAll function +func TestMonoidAll(t *testing.T) { + m := MonoidAll[int]() + + t.Run("has identity element that returns true", func(t *testing.T) { + empty := m.Empty() + assert.True(t, empty(0)) + assert.True(t, empty(5)) + assert.True(t, empty(-5)) + }) + + t.Run("identity is left identity", func(t *testing.T) { + // empty AND p == p + combined := m.Concat(m.Empty(), isPositive) + assert.True(t, combined(5)) + assert.False(t, combined(-5)) + }) + + t.Run("identity is right identity", func(t *testing.T) { + // p AND empty == p + combined := m.Concat(isPositive, m.Empty()) + assert.True(t, combined(5)) + assert.False(t, combined(-5)) + }) + + t.Run("reduces empty list to identity", func(t *testing.T) { + predicates := []Predicate[int]{} + result := m.Empty() + for _, p := range predicates { + result = m.Concat(result, p) + } + assert.True(t, result(5)) + }) + + t.Run("reduces list of predicates", func(t *testing.T) { + isLessThan100 := func(n int) bool { return n < 100 } + predicates := []Predicate[int]{isPositive, isEven, isLessThan100} + result := m.Empty() + for _, p := range predicates { + result = m.Concat(result, p) + } + assert.True(t, result(50)) // all true + assert.False(t, result(51)) // not even + assert.False(t, result(-2)) // not positive + assert.False(t, result(150)) // not < 100 + }) +} + +// TestComplexScenarios tests complex combinations of predicates +func TestComplexScenarios(t *testing.T) { + t.Run("complex boolean logic", func(t *testing.T) { + // (positive AND even) OR (negative AND odd) + positiveAndEven := F.Pipe1(isPositive, And(isEven)) + isOdd := Not(isEven) + negativeAndOdd := F.Pipe1(isNegative, And(isOdd)) + complex := F.Pipe1(positiveAndEven, Or(negativeAndOdd)) + + assert.True(t, complex(2)) // positive and even + assert.True(t, complex(4)) // positive and even + assert.True(t, complex(-1)) // negative and odd + assert.True(t, complex(-3)) // negative and odd + assert.False(t, complex(1)) // positive but odd + assert.False(t, complex(-2)) // negative but even + assert.False(t, complex(0)) // neither + }) + + t.Run("contramap with complex predicates", func(t *testing.T) { + type User struct { + Name string + Age int + Score int + } + + isAdultAge := func(age int) bool { return age >= 18 } + hasHighScore := func(score int) bool { return score >= 80 } + + getAge := func(u User) int { return u.Age } + getScore := func(u User) int { return u.Score } + + isAdult := F.Pipe1(isAdultAge, ContraMap(getAge)) + hasGoodScore := F.Pipe1(hasHighScore, ContraMap(getScore)) + isQualified := F.Pipe1(isAdult, And(hasGoodScore)) + + assert.True(t, isQualified(User{Name: "Alice", Age: 25, Score: 90})) + assert.False(t, isQualified(User{Name: "Bob", Age: 16, Score: 90})) + assert.False(t, isQualified(User{Name: "Charlie", Age: 25, Score: 70})) + assert.False(t, isQualified(User{Name: "Dave", Age: 16, Score: 70})) + }) + + t.Run("monoid with contramap", func(t *testing.T) { + type Item struct { + Price int + Stock int + } + + m := MonoidAll[Item]() + + isAffordable := func(price int) bool { return price < 100 } + isInStock := func(stock int) bool { return stock > 0 } + + getPrice := func(i Item) int { return i.Price } + getStock := func(i Item) int { return i.Stock } + + isAffordableItem := F.Pipe1(isAffordable, ContraMap(getPrice)) + isInStockItem := F.Pipe1(isInStock, ContraMap(getStock)) + + canBuy := m.Concat(isAffordableItem, isInStockItem) + + assert.True(t, canBuy(Item{Price: 50, Stock: 10})) + assert.False(t, canBuy(Item{Price: 150, Stock: 10})) + assert.False(t, canBuy(Item{Price: 50, Stock: 0})) + assert.False(t, canBuy(Item{Price: 150, Stock: 0})) + }) +} diff --git a/v2/predicate/types.go b/v2/predicate/types.go new file mode 100644 index 0000000..342e56c --- /dev/null +++ b/v2/predicate/types.go @@ -0,0 +1,51 @@ +// Copyright (c) 2025 IBM Corp. +// All rights reserved. +// +// 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. + +// Package predicate provides functional programming utilities for working with predicates. +// +// A predicate is a function that takes a value and returns a boolean, commonly used +// for filtering, validation, and conditional logic. This package offers combinators +// for composing predicates using logical operations (And, Or, Not), transforming +// predicates via ContraMap, and combining multiple predicates using Semigroup and +// Monoid abstractions. +// +// Key features: +// - Boolean combinators: And, Or, Not +// - ContraMap for transforming predicates +// - Semigroup and Monoid instances for combining predicates +// +// Example usage: +// +// import P "github.com/IBM/fp-go/v2/predicate" +// +// // Create predicates +// isPositive := func(n int) bool { return n > 0 } +// isEven := func(n int) bool { return n%2 == 0 } +// +// // Combine predicates +// isPositiveAndEven := F.Pipe1(isPositive, P.And(isEven)) +// isPositiveOrEven := F.Pipe1(isPositive, P.Or(isEven)) +// isNotPositive := P.Not(isPositive) +package predicate + +type ( + // Predicate represents a function that tests a value of type A and returns a boolean. + // It is commonly used for filtering, validation, and conditional logic. + Predicate[A any] = func(A) bool + + // Operator represents a function that transforms a Predicate[A] into a Predicate[B]. + // This is useful for composing and transforming predicates. + Operator[A, B any] = func(Predicate[A]) Predicate[B] +) diff --git a/v2/reader/array.go b/v2/reader/array.go new file mode 100644 index 0000000..234e5bd --- /dev/null +++ b/v2/reader/array.go @@ -0,0 +1,118 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package reader + +import ( + "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/internal/array" +) + +// MonadTraverseArray transforms each element of an array using a function that returns a Reader, +// then collects the results into a single Reader containing an array. +// This is the monadic version that takes the array as the first parameter. +// +// All Readers share the same environment R and are evaluated with it. +// +// Example: +// +// type Config struct { Prefix string } +// numbers := []int{1, 2, 3} +// addPrefix := func(n int) reader.Reader[Config, string] { +// return reader.Asks(func(c Config) string { +// return fmt.Sprintf("%s%d", c.Prefix, n) +// }) +// } +// r := reader.MonadTraverseArray(numbers, addPrefix) +// result := r(Config{Prefix: "num"}) // ["num1", "num2", "num3"] +func MonadTraverseArray[R, A, B any](ma []A, f func(A) Reader[R, B]) Reader[R, []B] { + return array.MonadTraverse[[]A]( + Of[R, []B], + Map[R, []B, func(B) []B], + Ap[[]B, R, B], + ma, + f, + ) +} + +// TraverseArray transforms each element of an array using a function that returns a Reader, +// then collects the results into a single Reader containing an array. +// +// This is useful for performing a Reader computation on each element of an array +// where all computations share the same environment. +// +// Example: +// +// type Config struct { Multiplier int } +// multiply := func(n int) reader.Reader[Config, int] { +// return reader.Asks(func(c Config) int { return n * c.Multiplier }) +// } +// transform := reader.TraverseArray(multiply) +// r := transform([]int{1, 2, 3}) +// result := r(Config{Multiplier: 10}) // [10, 20, 30] +func TraverseArray[R, A, B any](f func(A) Reader[R, B]) func([]A) Reader[R, []B] { + return array.Traverse[[]A]( + Of[R, []B], + Map[R, []B, func(B) []B], + Ap[[]B, R, B], + f, + ) +} + +// TraverseArrayWithIndex transforms each element of an array using a function that takes +// both the index and the element, returning a Reader. The results are collected into +// a single Reader containing an array. +// +// This is useful when the transformation needs to know the position of each element. +// +// Example: +// +// type Config struct { Prefix string } +// addIndexPrefix := func(i int, s string) reader.Reader[Config, string] { +// return reader.Asks(func(c Config) string { +// return fmt.Sprintf("%s[%d]:%s", c.Prefix, i, s) +// }) +// } +// transform := reader.TraverseArrayWithIndex(addIndexPrefix) +// r := transform([]string{"a", "b", "c"}) +// result := r(Config{Prefix: "item"}) // ["item[0]:a", "item[1]:b", "item[2]:c"] +func TraverseArrayWithIndex[R, A, B any](f func(int, A) Reader[R, B]) func([]A) Reader[R, []B] { + return array.TraverseWithIndex[[]A]( + Of[R, []B], + Map[R, []B, func(B) []B], + Ap[[]B, R, B], + f, + ) +} + +// SequenceArray converts an array of Readers into a single Reader containing an array. +// All Readers in the input array share the same environment and are evaluated with it. +// +// This is useful when you have multiple independent Reader computations and want to +// collect all their results. +// +// Example: +// +// type Config struct { X, Y, Z int } +// readers := []reader.Reader[Config, int]{ +// reader.Asks(func(c Config) int { return c.X }), +// reader.Asks(func(c Config) int { return c.Y }), +// reader.Asks(func(c Config) int { return c.Z }), +// } +// r := reader.SequenceArray(readers) +// result := r(Config{X: 1, Y: 2, Z: 3}) // [1, 2, 3] +func SequenceArray[R, A any](ma []Reader[R, A]) Reader[R, []A] { + return MonadTraverseArray(ma, function.Identity[Reader[R, A]]) +} diff --git a/v2/reader/array_test.go b/v2/reader/array_test.go new file mode 100644 index 0000000..61569b0 --- /dev/null +++ b/v2/reader/array_test.go @@ -0,0 +1,95 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package reader + +import ( + "context" + "testing" + + A "github.com/IBM/fp-go/v2/array" + F "github.com/IBM/fp-go/v2/function" + "github.com/stretchr/testify/assert" +) + +func TestSequenceArray(t *testing.T) { + + n := 10 + + readers := A.MakeBy(n, Of[context.Context, int]) + exp := A.MakeBy(n, F.Identity[int]) + + g := F.Pipe1( + readers, + SequenceArray[context.Context, int], + ) + + assert.Equal(t, exp, g(context.Background())) +} + +func TestTraverseArray(t *testing.T) { + type Config struct{ Multiplier int } + config := Config{Multiplier: 10} + + multiply := func(n int) Reader[Config, int] { + return Asks(func(c Config) int { return n * c.Multiplier }) + } + + transform := TraverseArray(multiply) + r := transform([]int{1, 2, 3}) + result := r(config) + + assert.Equal(t, []int{10, 20, 30}, result) +} + +func TestTraverseArrayWithIndex(t *testing.T) { + type Config struct{ Prefix string } + config := Config{Prefix: "item"} + + addIndexPrefix := func(i int, s string) Reader[Config, string] { + return Asks(func(c Config) string { + // Simple string formatting + idx := string(rune('0' + i)) + return c.Prefix + "[" + idx + "]:" + s + }) + } + + transform := TraverseArrayWithIndex(addIndexPrefix) + r := transform([]string{"a", "b", "c"}) + result := r(config) + + assert.Equal(t, 3, len(result)) + assert.Equal(t, "item[0]:a", result[0]) + assert.Equal(t, "item[1]:b", result[1]) + assert.Equal(t, "item[2]:c", result[2]) +} + +func TestMonadTraverseArray(t *testing.T) { + type Config struct{ Prefix string } + config := Config{Prefix: "num"} + + numbers := []int{1, 2, 3} + addPrefix := func(n int) Reader[Config, string] { + return Asks(func(c Config) string { + return c.Prefix + string(rune('0'+n)) + }) + } + + r := MonadTraverseArray(numbers, addPrefix) + result := r(config) + + assert.Equal(t, 3, len(result)) + assert.Contains(t, result[0], "num") +} diff --git a/v2/reader/bind.go b/v2/reader/bind.go new file mode 100644 index 0000000..dad211f --- /dev/null +++ b/v2/reader/bind.go @@ -0,0 +1,210 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package reader + +import ( + "github.com/IBM/fp-go/v2/internal/apply" + "github.com/IBM/fp-go/v2/internal/chain" + "github.com/IBM/fp-go/v2/internal/functor" +) + +// Do creates an empty context of type [S] to be used with the [Bind] operation. +// This is the starting point for the do-notation style of composing Reader computations. +// +// Example: +// +// type State struct { +// Name string +// Age int +// } +// type Config struct { +// DefaultName string +// DefaultAge int +// } +// +// result := function.Pipe3( +// reader.Do[Config](State{}), +// reader.Bind( +// func(name string) func(State) State { +// return func(s State) State { s.Name = name; return s } +// }, +// func(s State) reader.Reader[Config, string] { +// return reader.Asks(func(c Config) string { return c.DefaultName }) +// }, +// ), +// reader.Bind( +// func(age int) func(State) State { +// return func(s State) State { s.Age = age; return s } +// }, +// func(s State) reader.Reader[Config, int] { +// return reader.Asks(func(c Config) int { return c.DefaultAge }) +// }, +// ), +// ) +func Do[R, S any]( + empty S, +) Reader[R, S] { + return Of[R](empty) +} + +// Bind attaches the result of a computation to a context [S1] to produce a context [S2]. +// This enables building up complex computations in a pipeline where each step can depend +// on the results of previous steps and access the shared environment. +// +// The setter function takes the result of the computation and returns a function that +// updates the context from S1 to S2. +// +// Example: +// +// type State struct { Value int } +// type Config struct { Increment int } +// +// addIncrement := reader.Bind( +// func(inc int) func(State) State { +// return func(s State) State { return State{Value: s.Value + inc} } +// }, +// func(s State) reader.Reader[Config, int] { +// return reader.Asks(func(c Config) int { return c.Increment }) +// }, +// ) +func Bind[R, S1, S2, T any]( + setter func(T) func(S1) S2, + f func(S1) Reader[R, T], +) Operator[R, S1, S2] { + return chain.Bind( + Chain[R, S1, S2], + Map[R, T, S2], + setter, + f, + ) +} + +// Let attaches the result of a pure computation to a context [S1] to produce a context [S2]. +// Unlike Bind, the computation function f does not return a Reader, just a plain value. +// This is useful for transformations that don't need to access the environment. +// +// Example: +// +// type State struct { +// FirstName string +// LastName string +// FullName string +// } +// +// addFullName := reader.Let( +// func(full string) func(State) State { +// return func(s State) State { s.FullName = full; return s } +// }, +// func(s State) string { +// return s.FirstName + " " + s.LastName +// }, +// ) +func Let[R, S1, S2, T any]( + setter func(T) func(S1) S2, + f func(S1) T, +) Operator[R, S1, S2] { + return functor.Let( + Map[R, S1, S2], + setter, + f, + ) +} + +// LetTo attaches a constant value to a context [S1] to produce a context [S2]. +// This is useful for adding fixed values to the context without any computation. +// +// Example: +// +// type State struct { +// Name string +// Version string +// } +// +// addVersion := reader.LetTo( +// func(v string) func(State) State { +// return func(s State) State { s.Version = v; return s } +// }, +// "1.0.0", +// ) +func LetTo[R, S1, S2, T any]( + setter func(T) func(S1) S2, + b T, +) Operator[R, S1, S2] { + return functor.LetTo( + Map[R, S1, S2], + setter, + b, + ) +} + +// BindTo initializes a new state [S1] from a value [T]. +// This is typically used to start a binding chain by wrapping an initial Reader value +// into a state structure. +// +// Example: +// +// type State struct { Name string } +// type Config struct { DefaultName string } +// +// getName := reader.Asks(func(c Config) string { return c.DefaultName }) +// initState := reader.BindTo(func(name string) State { +// return State{Name: name} +// }) +// result := initState(getName) +func BindTo[R, S1, T any]( + setter func(T) S1, +) Operator[R, T, S1] { + return chain.BindTo( + Map[R, T, S1], + setter, + ) +} + +// ApS attaches a value to a context [S1] to produce a context [S2] by considering +// the context and the value concurrently (using Applicative rather than Monad). +// +// This is useful when you have independent computations that can be combined without +// one depending on the result of the other. +// +// Example: +// +// type State struct { +// Host string +// Port int +// } +// type Config struct { +// Host string +// Port int +// } +// +// getPort := reader.Asks(func(c Config) int { return c.Port }) +// addPort := reader.ApS( +// func(port int) func(State) State { +// return func(s State) State { s.Port = port; return s } +// }, +// getPort, +// ) +func ApS[R, S1, S2, T any]( + setter func(T) func(S1) S2, + fa Reader[R, T], +) Operator[R, S1, S2] { + return apply.ApS( + Ap[S2, R, T], + Map[R, S1, func(T) S2], + setter, + fa, + ) +} diff --git a/v2/reader/bind_test.go b/v2/reader/bind_test.go new file mode 100644 index 0000000..2c4aaa6 --- /dev/null +++ b/v2/reader/bind_test.go @@ -0,0 +1,115 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package reader + +import ( + "context" + "testing" + + F "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/internal/utils" + "github.com/stretchr/testify/assert" +) + +func getLastName(s utils.Initial) Reader[context.Context, string] { + return Of[context.Context]("Doe") +} + +func getGivenName(s utils.WithLastName) Reader[context.Context, string] { + return Of[context.Context]("John") +} + +func TestBind(t *testing.T) { + + res := F.Pipe3( + Do[context.Context](utils.Empty), + Bind(utils.SetLastName, getLastName), + Bind(utils.SetGivenName, getGivenName), + Map[context.Context](utils.GetFullName), + ) + + assert.Equal(t, res(context.Background()), "John Doe") +} + +func TestApS(t *testing.T) { + + res := F.Pipe3( + Do[context.Context](utils.Empty), + ApS(utils.SetLastName, Of[context.Context]("Doe")), + ApS(utils.SetGivenName, Of[context.Context]("John")), + Map[context.Context](utils.GetFullName), + ) + + assert.Equal(t, res(context.Background()), "John Doe") +} + +func TestLet(t *testing.T) { + type State struct { + FirstName string + LastName string + FullName string + } + + res := F.Pipe2( + Do[context.Context](State{FirstName: "John", LastName: "Doe"}), + Let[context.Context, State, State, string]( + func(full string) func(State) State { + return func(s State) State { s.FullName = full; return s } + }, + func(s State) string { + return s.FirstName + " " + s.LastName + }, + ), + Map[context.Context](func(s State) string { return s.FullName }), + ) + + assert.Equal(t, "John Doe", res(context.Background())) +} + +func TestLetTo(t *testing.T) { + type State struct { + Name string + Version string + } + + res := F.Pipe2( + Do[context.Context](State{Name: "MyApp"}), + LetTo[context.Context, State, State, string]( + func(v string) func(State) State { + return func(s State) State { s.Version = v; return s } + }, + "1.0.0", + ), + Map[context.Context](func(s State) State { return s }), + ) + + result := res(context.Background()) + assert.Equal(t, "MyApp", result.Name) + assert.Equal(t, "1.0.0", result.Version) +} + +func TestBindTo(t *testing.T) { + type State struct{ Name string } + + getName := Asks(func(c context.Context) string { return "TestName" }) + initState := BindTo[context.Context, State, string](func(name string) State { + return State{Name: name} + }) + result := initState(getName) + + state := result(context.Background()) + assert.Equal(t, "TestName", state.Name) +} diff --git a/v2/reader/curry.go b/v2/reader/curry.go new file mode 100644 index 0000000..16d11cb --- /dev/null +++ b/v2/reader/curry.go @@ -0,0 +1,155 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package reader + +import ( + G "github.com/IBM/fp-go/v2/reader/generic" +) + +// These functions curry a Go function with the context as the first parameter into a Reader +// with the context as the last parameter, which is equivalent to a function returning a Reader +// of that context. +// +// This follows the Go convention (https://pkg.go.dev/context) of putting the context as the +// first parameter, while Reader monad convention has the context as the last parameter. + +// Curry0 converts a function that takes a context and returns a value into a Reader. +// +// Example: +// +// type Config struct { Value int } +// getValue := func(c Config) int { return c.Value } +// r := reader.Curry0(getValue) +// result := r(Config{Value: 42}) // 42 +func Curry0[R, A any](f func(R) A) Reader[R, A] { + return G.Curry0[Reader[R, A]](f) +} + +// Curry1 converts a function with context as first parameter into a curried function +// returning a Reader. The context parameter is moved to the end (Reader position). +// +// Example: +// +// type Config struct { Prefix string } +// addPrefix := func(c Config, s string) string { return c.Prefix + s } +// curried := reader.Curry1(addPrefix) +// r := curried("hello") +// result := r(Config{Prefix: ">> "}) // ">> hello" +func Curry1[R, T1, A any](f func(R, T1) A) func(T1) Reader[R, A] { + return G.Curry1[Reader[R, A]](f) +} + +// Curry2 converts a function with context as first parameter and 2 other parameters +// into a curried function returning a Reader. +// +// Example: +// +// type Config struct { Sep string } +// join := func(c Config, a, b string) string { return a + c.Sep + b } +// curried := reader.Curry2(join) +// r := curried("hello")("world") +// result := r(Config{Sep: "-"}) // "hello-world" +func Curry2[R, T1, T2, A any](f func(R, T1, T2) A) func(T1) func(T2) Reader[R, A] { + return G.Curry2[Reader[R, A]](f) +} + +// Curry3 converts a function with context as first parameter and 3 other parameters +// into a curried function returning a Reader. +// +// Example: +// +// type Config struct { Format string } +// format := func(c Config, a, b, d string) string { +// return fmt.Sprintf(c.Format, a, b, d) +// } +// curried := reader.Curry3(format) +// r := curried("a")("b")("c") +// result := r(Config{Format: "%s-%s-%s"}) // "a-b-c" +func Curry3[R, T1, T2, T3, A any](f func(R, T1, T2, T3) A) func(T1) func(T2) func(T3) Reader[R, A] { + return G.Curry3[Reader[R, A]](f) +} + +// Curry4 converts a function with context as first parameter and 4 other parameters +// into a curried function returning a Reader. +// +// Example: +// +// type Config struct { Multiplier int } +// sum := func(c Config, a, b, d, e int) int { +// return (a + b + d + e) * c.Multiplier +// } +// curried := reader.Curry4(sum) +// r := curried(1)(2)(3)(4) +// result := r(Config{Multiplier: 10}) // 100 +func Curry4[R, T1, T2, T3, T4, A any](f func(R, T1, T2, T3, T4) A) func(T1) func(T2) func(T3) func(T4) Reader[R, A] { + return G.Curry4[Reader[R, A]](f) +} + +// Uncurry0 converts a Reader back into a regular function with context as first parameter. +// +// Example: +// +// type Config struct { Value int } +// r := reader.Of[Config](42) +// f := reader.Uncurry0(r) +// result := f(Config{Value: 0}) // 42 +func Uncurry0[R, A any](f Reader[R, A]) func(R) A { + return G.Uncurry0(f) +} + +// Uncurry1 converts a curried function returning a Reader back into a regular function +// with context as first parameter. +// +// Example: +// +// type Config struct { Prefix string } +// curried := func(s string) reader.Reader[Config, string] { +// return reader.Asks(func(c Config) string { return c.Prefix + s }) +// } +// f := reader.Uncurry1(curried) +// result := f(Config{Prefix: ">> "}, "hello") // ">> hello" +func Uncurry1[R, T1, A any](f func(T1) Reader[R, A]) func(R, T1) A { + return G.Uncurry1(f) +} + +// Uncurry2 converts a curried function with 2 parameters returning a Reader back into +// a regular function with context as first parameter. +// +// Example: +// +// type Config struct { Sep string } +// curried := func(a string) func(string) reader.Reader[Config, string] { +// return func(b string) reader.Reader[Config, string] { +// return reader.Asks(func(c Config) string { return a + c.Sep + b }) +// } +// } +// f := reader.Uncurry2(curried) +// result := f(Config{Sep: "-"}, "hello", "world") // "hello-world" +func Uncurry2[R, T1, T2, A any](f func(T1) func(T2) Reader[R, A]) func(R, T1, T2) A { + return G.Uncurry2(f) +} + +// Uncurry3 converts a curried function with 3 parameters returning a Reader back into +// a regular function with context as first parameter. +func Uncurry3[R, T1, T2, T3, A any](f func(T1) func(T2) func(T3) Reader[R, A]) func(R, T1, T2, T3) A { + return G.Uncurry3(f) +} + +// Uncurry4 converts a curried function with 4 parameters returning a Reader back into +// a regular function with context as first parameter. +func Uncurry4[R, T1, T2, T3, T4, A any](f func(T1) func(T2) func(T3) func(T4) Reader[R, A]) func(R, T1, T2, T3, T4) A { + return G.Uncurry4(f) +} diff --git a/v2/reader/curry_test.go b/v2/reader/curry_test.go new file mode 100644 index 0000000..8d7bd6e --- /dev/null +++ b/v2/reader/curry_test.go @@ -0,0 +1,133 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package reader + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCurry0(t *testing.T) { + config := Config{Port: 8080} + getValue := func(c Config) int { return c.Port } + r := Curry0(getValue) + result := r(config) + assert.Equal(t, 8080, result) +} + +func TestCurry1(t *testing.T) { + config := Config{Prefix: ">> "} + addPrefix := func(c Config, s string) string { return c.Prefix + s } + curried := Curry1(addPrefix) + r := curried("hello") + result := r(config) + assert.Equal(t, ">> hello", result) +} + +func TestCurry2(t *testing.T) { + config := Config{Prefix: "-"} + join := func(c Config, a, b string) string { return a + c.Prefix + b } + curried := Curry2(join) + r := curried("hello")("world") + result := r(config) + assert.Equal(t, "hello-world", result) +} + +func TestCurry3(t *testing.T) { + config := Config{Prefix: "-"} + join := func(c Config, a, b, d string) string { + return a + c.Prefix + b + c.Prefix + d + } + curried := Curry3(join) + r := curried("a")("b")("c") + result := r(config) + assert.Equal(t, "a-b-c", result) +} + +func TestCurry4(t *testing.T) { + config := Config{Multiplier: 10} + sum := func(c Config, a, b, d, e int) int { + return (a + b + d + e) * c.Multiplier + } + curried := Curry4(sum) + r := curried(1)(2)(3)(4) + result := r(config) + assert.Equal(t, 100, result) +} + +func TestUncurry0(t *testing.T) { + config := Config{Port: 8080} + r := Of[Config](42) + f := Uncurry0(r) + result := f(config) + assert.Equal(t, 42, result) +} + +func TestUncurry1(t *testing.T) { + config := Config{Prefix: ">> "} + curried := func(s string) Reader[Config, string] { + return Asks(func(c Config) string { return c.Prefix + s }) + } + f := Uncurry1(curried) + result := f(config, "hello") + assert.Equal(t, ">> hello", result) +} + +func TestUncurry2(t *testing.T) { + config := Config{Prefix: "-"} + curried := func(a string) func(string) Reader[Config, string] { + return func(b string) Reader[Config, string] { + return Asks(func(c Config) string { return a + c.Prefix + b }) + } + } + f := Uncurry2(curried) + result := f(config, "hello", "world") + assert.Equal(t, "hello-world", result) +} + +func TestUncurry3(t *testing.T) { + config := Config{Prefix: "-"} + curried := func(a string) func(string) func(string) Reader[Config, string] { + return func(b string) func(string) Reader[Config, string] { + return func(c string) Reader[Config, string] { + return Asks(func(cfg Config) string { + return fmt.Sprintf("%s%s%s%s%s", a, cfg.Prefix, b, cfg.Prefix, c) + }) + } + } + } + f := Uncurry3(curried) + result := f(config, "a", "b", "c") + assert.Equal(t, "a-b-c", result) +} + +func TestUncurry4(t *testing.T) { + config := Config{Multiplier: 10} + curried := func(a int) func(int) func(int) func(int) Reader[Config, int] { + return func(b int) func(int) func(int) Reader[Config, int] { + return func(c int) func(int) Reader[Config, int] { + return func(d int) Reader[Config, int] { + return Asks(func(cfg Config) int { return (a + b + c + d) * cfg.Multiplier }) + } + } + } + } + f := Uncurry4(curried) + result := f(config, 1, 2, 3, 4) + assert.Equal(t, 100, result) +} diff --git a/v2/reader/doc.go b/v2/reader/doc.go new file mode 100644 index 0000000..5fb9a72 --- /dev/null +++ b/v2/reader/doc.go @@ -0,0 +1,80 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +// Package reader provides the Reader monad implementation for functional programming in Go. +// +// The Reader monad is used to pass a shared environment or configuration through a computation +// without explicitly threading it through every function call. It represents a computation that +// depends on some external context of type R and produces a value of type A. +// +// # Core Concept +// +// A Reader[R, A] is simply a function from R to A: func(R) A +// - R is the environment/context type (read-only) +// - A is the result type +// +// # Key Benefits +// +// - Dependency Injection: Pass configuration or dependencies implicitly +// - Composition: Combine readers that share the same environment +// - Testability: Easy to test by providing different environments +// - Avoid Threading: No need to pass context through every function +// +// # Basic Usage +// +// // Define a configuration type +// type Config struct { +// Host string +// Port int +// } +// +// // Create readers that depend on Config +// getHost := reader.Asks(func(c Config) string { return c.Host }) +// getPort := reader.Asks(func(c Config) int { return c.Port }) +// +// // Compose readers +// getURL := reader.Map(func(host string) string { +// return "http://" + host +// })(getHost) +// +// // Run the reader with a config +// config := Config{Host: "localhost", Port: 8080} +// url := getURL(config) // "http://localhost" +// +// # Common Operations +// +// - Ask: Get the current environment +// - Asks: Project a value from the environment +// - Map: Transform the result value +// - Chain: Sequence computations that depend on previous results +// - Local: Modify the environment for a sub-computation +// +// # Monadic Operations +// +// The Reader type implements the Functor, Applicative, and Monad type classes: +// +// - Functor: Map over the result value +// - Applicative: Combine multiple readers with independent computations +// - Monad: Chain readers where later computations depend on earlier results +// +// # Related Packages +// +// - reader/generic: Generic implementations for custom reader types +// - readerio: Reader combined with IO effects +// - readereither: Reader combined with Either for error handling +// - readerioeither: Reader combined with IO and Either +package reader + +//go:generate go run .. reader --count 10 --filename gen.go diff --git a/v2/reader/gen.go b/v2/reader/gen.go new file mode 100644 index 0000000..c3a6257 --- /dev/null +++ b/v2/reader/gen.go @@ -0,0 +1,76 @@ +// Code generated by go generate; DO NOT EDIT. +// This file was generated by robots at +// 2025-03-09 23:53:11.9264583 +0100 CET m=+0.001565701 + +package reader + + +import ( + G "github.com/IBM/fp-go/v2/reader/generic" +) + +// From0 converts a function with 1 parameters returning a [R] into a function with 0 parameters returning a [Reader[C, R]] +// The first parameter is considered to be the context [C] of the reader +func From0[F ~func(C) R, C, R any](f F) func() Reader[C, R] { + return G.From0[Reader[C, R]](f) +} + +// From1 converts a function with 2 parameters returning a [R] into a function with 1 parameters returning a [Reader[C, R]] +// The first parameter is considered to be the context [C] of the reader +func From1[F ~func(C, T0) R, T0, C, R any](f F) func(T0) Reader[C, R] { + return G.From1[Reader[C, R]](f) +} + +// From2 converts a function with 3 parameters returning a [R] into a function with 2 parameters returning a [Reader[C, R]] +// The first parameter is considered to be the context [C] of the reader +func From2[F ~func(C, T0, T1) R, T0, T1, C, R any](f F) func(T0, T1) Reader[C, R] { + return G.From2[Reader[C, R]](f) +} + +// From3 converts a function with 4 parameters returning a [R] into a function with 3 parameters returning a [Reader[C, R]] +// The first parameter is considered to be the context [C] of the reader +func From3[F ~func(C, T0, T1, T2) R, T0, T1, T2, C, R any](f F) func(T0, T1, T2) Reader[C, R] { + return G.From3[Reader[C, R]](f) +} + +// From4 converts a function with 5 parameters returning a [R] into a function with 4 parameters returning a [Reader[C, R]] +// The first parameter is considered to be the context [C] of the reader +func From4[F ~func(C, T0, T1, T2, T3) R, T0, T1, T2, T3, C, R any](f F) func(T0, T1, T2, T3) Reader[C, R] { + return G.From4[Reader[C, R]](f) +} + +// From5 converts a function with 6 parameters returning a [R] into a function with 5 parameters returning a [Reader[C, R]] +// The first parameter is considered to be the context [C] of the reader +func From5[F ~func(C, T0, T1, T2, T3, T4) R, T0, T1, T2, T3, T4, C, R any](f F) func(T0, T1, T2, T3, T4) Reader[C, R] { + return G.From5[Reader[C, R]](f) +} + +// From6 converts a function with 7 parameters returning a [R] into a function with 6 parameters returning a [Reader[C, R]] +// The first parameter is considered to be the context [C] of the reader +func From6[F ~func(C, T0, T1, T2, T3, T4, T5) R, T0, T1, T2, T3, T4, T5, C, R any](f F) func(T0, T1, T2, T3, T4, T5) Reader[C, R] { + return G.From6[Reader[C, R]](f) +} + +// From7 converts a function with 8 parameters returning a [R] into a function with 7 parameters returning a [Reader[C, R]] +// The first parameter is considered to be the context [C] of the reader +func From7[F ~func(C, T0, T1, T2, T3, T4, T5, T6) R, T0, T1, T2, T3, T4, T5, T6, C, R any](f F) func(T0, T1, T2, T3, T4, T5, T6) Reader[C, R] { + return G.From7[Reader[C, R]](f) +} + +// From8 converts a function with 9 parameters returning a [R] into a function with 8 parameters returning a [Reader[C, R]] +// The first parameter is considered to be the context [C] of the reader +func From8[F ~func(C, T0, T1, T2, T3, T4, T5, T6, T7) R, T0, T1, T2, T3, T4, T5, T6, T7, C, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7) Reader[C, R] { + return G.From8[Reader[C, R]](f) +} + +// From9 converts a function with 10 parameters returning a [R] into a function with 9 parameters returning a [Reader[C, R]] +// The first parameter is considered to be the context [C] of the reader +func From9[F ~func(C, T0, T1, T2, T3, T4, T5, T6, T7, T8) R, T0, T1, T2, T3, T4, T5, T6, T7, T8, C, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7, T8) Reader[C, R] { + return G.From9[Reader[C, R]](f) +} + +// From10 converts a function with 11 parameters returning a [R] into a function with 10 parameters returning a [Reader[C, R]] +// The first parameter is considered to be the context [C] of the reader +func From10[F ~func(C, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9) R, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, C, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9) Reader[C, R] { + return G.From10[Reader[C, R]](f) +} diff --git a/v2/reader/generic/array.go b/v2/reader/generic/array.go new file mode 100644 index 0000000..c85315a --- /dev/null +++ b/v2/reader/generic/array.go @@ -0,0 +1,106 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +// Package generic provides generic array operations for custom Reader types. +package generic + +import ( + F "github.com/IBM/fp-go/v2/function" + RA "github.com/IBM/fp-go/v2/internal/array" +) + +// MonadTraverseArray transforms each element of an array using a function that returns a generic Reader, +// then collects the results into a single generic Reader containing an array. +// This is the monadic version that takes the array as the first parameter. +// +// This generic version works with custom reader types that match the pattern ~func(R) B. +// +// Type Parameters: +// - GB: The generic Reader type for individual elements (~func(R) B) +// - GBS: The generic Reader type for the result array (~func(R) BBS) +// - AAS: The input array type (~[]A) +// - BBS: The output array type (~[]B) +// - R: The environment/context type +// - A: The input element type +// - B: The output element type +func MonadTraverseArray[GB ~func(R) B, GBS ~func(R) BBS, AAS ~[]A, BBS ~[]B, R, A, B any](tas AAS, f func(A) GB) GBS { + return RA.MonadTraverse[AAS]( + Of[GBS, R, BBS], + Map[GBS, func(R) func(B) BBS, R, BBS, func(B) BBS], + Ap[GB, GBS, func(R) func(B) BBS, R, B, BBS], + tas, f, + ) +} + +// TraverseArray transforms each element of an array using a function that returns a generic Reader, +// then collects the results into a single generic Reader containing an array. +// +// This generic version works with custom reader types that match the pattern ~func(R) B. +// +// Type Parameters: +// - GB: The generic Reader type for individual elements (~func(R) B) +// - GBS: The generic Reader type for the result array (~func(R) BBS) +// - AAS: The input array type (~[]A) +// - BBS: The output array type (~[]B) +// - R: The environment/context type +// - A: The input element type +// - B: The output element type +func TraverseArray[GB ~func(R) B, GBS ~func(R) BBS, AAS ~[]A, BBS ~[]B, R, A, B any](f func(A) GB) func(AAS) GBS { + return RA.Traverse[AAS]( + Of[GBS, R, BBS], + Map[GBS, func(R) func(B) BBS, R, BBS, func(B) BBS], + Ap[GB, GBS, func(R) func(B) BBS, R, B, BBS], + f, + ) +} + +// TraverseArrayWithIndex transforms each element of an array using a function that takes +// both the index and the element, returning a generic Reader. The results are collected into +// a single generic Reader containing an array. +// +// This generic version works with custom reader types that match the pattern ~func(R) B. +// +// Type Parameters: +// - GB: The generic Reader type for individual elements (~func(R) B) +// - GBS: The generic Reader type for the result array (~func(R) BBS) +// - AAS: The input array type (~[]A) +// - BBS: The output array type (~[]B) +// - R: The environment/context type +// - A: The input element type +// - B: The output element type +func TraverseArrayWithIndex[GB ~func(R) B, GBS ~func(R) BBS, AAS ~[]A, BBS ~[]B, R, A, B any](f func(int, A) GB) func(AAS) GBS { + return RA.TraverseWithIndex[AAS]( + Of[GBS, R, BBS], + Map[GBS, func(R) func(B) BBS, R, BBS, func(B) BBS], + Ap[GB, GBS, func(R) func(B) BBS, R, B, BBS], + f, + ) +} + +// SequenceArray converts an array of generic Readers into a single generic Reader containing an array. +// All Readers in the input array share the same environment and are evaluated with it. +// +// This generic version works with custom reader types that match the pattern ~func(R) A. +// +// Type Parameters: +// - GA: The generic Reader type for individual elements (~func(R) A) +// - GAS: The generic Reader type for the result array (~func(R) AAS) +// - AAS: The array type (~[]A) +// - GAAS: The input array of Readers type (~[]GA) +// - R: The environment/context type +// - A: The element type +func SequenceArray[GA ~func(R) A, GAS ~func(R) AAS, AAS ~[]A, GAAS ~[]GA, R, A any](ma GAAS) GAS { + return MonadTraverseArray[GA, GAS](ma, F.Identity[GA]) +} diff --git a/v2/reader/generic/curry.go b/v2/reader/generic/curry.go new file mode 100644 index 0000000..a314992 --- /dev/null +++ b/v2/reader/generic/curry.go @@ -0,0 +1,157 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + F "github.com/IBM/fp-go/v2/function" +) + +// These functions curry a Go function with the context as the first parameter into a generic Reader +// with the context as the last parameter, which is equivalent to a function returning a Reader +// of that context. +// +// This follows the Go convention (https://pkg.go.dev/context) of putting the context as the +// first parameter, while Reader monad convention has the context as the last parameter. +// +// The generic versions work with custom reader types that match the pattern ~func(R) A. + +// Curry0 converts a function that takes a context and returns a value into a generic Reader. +// +// Type Parameters: +// - GA: The generic Reader type (~func(R) A) +// - R: The environment/context type +// - A: The result type +func Curry0[GA ~func(R) A, R, A any](f func(R) A) GA { + return MakeReader[GA](f) +} + +// Curry1 converts a function with context as first parameter into a curried function +// returning a generic Reader. The context parameter is moved to the end (Reader position). +// +// Type Parameters: +// - GA: The generic Reader type (~func(R) A) +// - R: The environment/context type +// - T1: The first parameter type +// - A: The result type +func Curry1[GA ~func(R) A, R, T1, A any](f func(R, T1) A) func(T1) GA { + return F.Curry1(From1[GA](f)) +} + +// Curry2 converts a function with context as first parameter and 2 other parameters +// into a curried function returning a generic Reader. +// +// Type Parameters: +// - GA: The generic Reader type (~func(R) A) +// - R: The environment/context type +// - T1, T2: The parameter types +// - A: The result type +func Curry2[GA ~func(R) A, R, T1, T2, A any](f func(R, T1, T2) A) func(T1) func(T2) GA { + return F.Curry2(From2[GA](f)) +} + +// Curry3 converts a function with context as first parameter and 3 other parameters +// into a curried function returning a generic Reader. +// +// Type Parameters: +// - GA: The generic Reader type (~func(R) A) +// - R: The environment/context type +// - T1, T2, T3: The parameter types +// - A: The result type +func Curry3[GA ~func(R) A, R, T1, T2, T3, A any](f func(R, T1, T2, T3) A) func(T1) func(T2) func(T3) GA { + return F.Curry3(From3[GA](f)) +} + +// Curry4 converts a function with context as first parameter and 4 other parameters +// into a curried function returning a generic Reader. +// +// Type Parameters: +// - GA: The generic Reader type (~func(R) A) +// - R: The environment/context type +// - T1, T2, T3, T4: The parameter types +// - A: The result type +func Curry4[GA ~func(R) A, R, T1, T2, T3, T4, A any](f func(R, T1, T2, T3, T4) A) func(T1) func(T2) func(T3) func(T4) GA { + return F.Curry4(From4[GA](f)) +} + +// Uncurry0 converts a generic Reader back into a regular function with context as first parameter. +// +// Type Parameters: +// - GA: The generic Reader type (~func(R) A) +// - R: The environment/context type +// - A: The result type +func Uncurry0[GA ~func(R) A, R, A any](f GA) func(R) A { + return f +} + +// Uncurry1 converts a curried function returning a generic Reader back into a regular function +// with context as first parameter. +// +// Type Parameters: +// - GA: The generic Reader type (~func(R) A) +// - R: The environment/context type +// - T1: The first parameter type +// - A: The result type +func Uncurry1[GA ~func(R) A, R, T1, A any](f func(T1) GA) func(R, T1) A { + uc := F.Uncurry1(f) + return func(r R, t1 T1) A { + return uc(t1)(r) + } +} + +// Uncurry2 converts a curried function with 2 parameters returning a generic Reader back into +// a regular function with context as first parameter. +// +// Type Parameters: +// - GA: The generic Reader type (~func(R) A) +// - R: The environment/context type +// - T1, T2: The parameter types +// - A: The result type +func Uncurry2[GA ~func(R) A, R, T1, T2, A any](f func(T1) func(T2) GA) func(R, T1, T2) A { + uc := F.Uncurry2(f) + return func(r R, t1 T1, t2 T2) A { + return uc(t1, t2)(r) + } +} + +// Uncurry3 converts a curried function with 3 parameters returning a generic Reader back into +// a regular function with context as first parameter. +// +// Type Parameters: +// - GA: The generic Reader type (~func(R) A) +// - R: The environment/context type +// - T1, T2, T3: The parameter types +// - A: The result type +func Uncurry3[GA ~func(R) A, R, T1, T2, T3, A any](f func(T1) func(T2) func(T3) GA) func(R, T1, T2, T3) A { + uc := F.Uncurry3(f) + return func(r R, t1 T1, t2 T2, t3 T3) A { + return uc(t1, t2, t3)(r) + } +} + +// Uncurry4 converts a curried function with 4 parameters returning a generic Reader back into +// a regular function with context as first parameter. +// +// Type Parameters: +// - GA: The generic Reader type (~func(R) A) +// - R: The environment/context type +// - T1, T2, T3, T4: The parameter types +// - A: The result type +func Uncurry4[GA ~func(R) A, R, T1, T2, T3, T4, A any](f func(T1) func(T2) func(T3) func(T4) GA) func(R, T1, T2, T3, T4) A { + uc := F.Uncurry4(f) + return func(r R, t1 T1, t2 T2, t3 T3, t4 T4) A { + return uc(t1, t2, t3, t4)(r) + } +} diff --git a/v2/reader/generic/gen.go b/v2/reader/generic/gen.go new file mode 100644 index 0000000..36f0f85 --- /dev/null +++ b/v2/reader/generic/gen.go @@ -0,0 +1,115 @@ +// Code generated by go generate; DO NOT EDIT. +// This file was generated by robots at +// 2025-03-09 23:53:11.926993 +0100 CET m=+0.002100401 +package generic + + +// From0 converts a function with 1 parameters returning a [R] into a function with 0 parameters returning a [GRA] +// The first parameter is considered to be the context [C]. +func From0[GRA ~func(C) R, F ~func(C) R, C, R any](f F) func() GRA { + return func() GRA { + return MakeReader[GRA](func(r C) R { + return f(r) + }) + } +} + +// From1 converts a function with 2 parameters returning a [R] into a function with 1 parameters returning a [GRA] +// The first parameter is considered to be the context [C]. +func From1[GRA ~func(C) R, F ~func(C, T0) R, T0, C, R any](f F) func(T0) GRA { + return func(t0 T0) GRA { + return MakeReader[GRA](func(r C) R { + return f(r, t0) + }) + } +} + +// From2 converts a function with 3 parameters returning a [R] into a function with 2 parameters returning a [GRA] +// The first parameter is considered to be the context [C]. +func From2[GRA ~func(C) R, F ~func(C, T0, T1) R, T0, T1, C, R any](f F) func(T0, T1) GRA { + return func(t0 T0, t1 T1) GRA { + return MakeReader[GRA](func(r C) R { + return f(r, t0, t1) + }) + } +} + +// From3 converts a function with 4 parameters returning a [R] into a function with 3 parameters returning a [GRA] +// The first parameter is considered to be the context [C]. +func From3[GRA ~func(C) R, F ~func(C, T0, T1, T2) R, T0, T1, T2, C, R any](f F) func(T0, T1, T2) GRA { + return func(t0 T0, t1 T1, t2 T2) GRA { + return MakeReader[GRA](func(r C) R { + return f(r, t0, t1, t2) + }) + } +} + +// From4 converts a function with 5 parameters returning a [R] into a function with 4 parameters returning a [GRA] +// The first parameter is considered to be the context [C]. +func From4[GRA ~func(C) R, F ~func(C, T0, T1, T2, T3) R, T0, T1, T2, T3, C, R any](f F) func(T0, T1, T2, T3) GRA { + return func(t0 T0, t1 T1, t2 T2, t3 T3) GRA { + return MakeReader[GRA](func(r C) R { + return f(r, t0, t1, t2, t3) + }) + } +} + +// From5 converts a function with 6 parameters returning a [R] into a function with 5 parameters returning a [GRA] +// The first parameter is considered to be the context [C]. +func From5[GRA ~func(C) R, F ~func(C, T0, T1, T2, T3, T4) R, T0, T1, T2, T3, T4, C, R any](f F) func(T0, T1, T2, T3, T4) GRA { + return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4) GRA { + return MakeReader[GRA](func(r C) R { + return f(r, t0, t1, t2, t3, t4) + }) + } +} + +// From6 converts a function with 7 parameters returning a [R] into a function with 6 parameters returning a [GRA] +// The first parameter is considered to be the context [C]. +func From6[GRA ~func(C) R, F ~func(C, T0, T1, T2, T3, T4, T5) R, T0, T1, T2, T3, T4, T5, C, R any](f F) func(T0, T1, T2, T3, T4, T5) GRA { + return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5) GRA { + return MakeReader[GRA](func(r C) R { + return f(r, t0, t1, t2, t3, t4, t5) + }) + } +} + +// From7 converts a function with 8 parameters returning a [R] into a function with 7 parameters returning a [GRA] +// The first parameter is considered to be the context [C]. +func From7[GRA ~func(C) R, F ~func(C, T0, T1, T2, T3, T4, T5, T6) R, T0, T1, T2, T3, T4, T5, T6, C, R any](f F) func(T0, T1, T2, T3, T4, T5, T6) GRA { + return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6) GRA { + return MakeReader[GRA](func(r C) R { + return f(r, t0, t1, t2, t3, t4, t5, t6) + }) + } +} + +// From8 converts a function with 9 parameters returning a [R] into a function with 8 parameters returning a [GRA] +// The first parameter is considered to be the context [C]. +func From8[GRA ~func(C) R, F ~func(C, T0, T1, T2, T3, T4, T5, T6, T7) R, T0, T1, T2, T3, T4, T5, T6, T7, C, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7) GRA { + return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7) GRA { + return MakeReader[GRA](func(r C) R { + return f(r, t0, t1, t2, t3, t4, t5, t6, t7) + }) + } +} + +// From9 converts a function with 10 parameters returning a [R] into a function with 9 parameters returning a [GRA] +// The first parameter is considered to be the context [C]. +func From9[GRA ~func(C) R, F ~func(C, T0, T1, T2, T3, T4, T5, T6, T7, T8) R, T0, T1, T2, T3, T4, T5, T6, T7, T8, C, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7, T8) GRA { + return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8) GRA { + return MakeReader[GRA](func(r C) R { + return f(r, t0, t1, t2, t3, t4, t5, t6, t7, t8) + }) + } +} + +// From10 converts a function with 11 parameters returning a [R] into a function with 10 parameters returning a [GRA] +// The first parameter is considered to be the context [C]. +func From10[GRA ~func(C) R, F ~func(C, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9) R, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, C, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9) GRA { + return func(t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9) GRA { + return MakeReader[GRA](func(r C) R { + return f(r, t0, t1, t2, t3, t4, t5, t6, t7, t8, t9) + }) + } +} diff --git a/v2/reader/generic/reader.go b/v2/reader/generic/reader.go new file mode 100644 index 0000000..fbc16e7 --- /dev/null +++ b/v2/reader/generic/reader.go @@ -0,0 +1,160 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +// Package generic provides generic implementations of Reader operations that work with +// custom reader types. These functions use Go's type constraints to work with any type +// that matches the Reader pattern (func(R) A). +// +// Most functions in this package are deprecated in favor of the non-generic versions +// in the parent reader package, which provide better type inference and simpler usage. +// +// Use this package when you need to work with custom reader types or when you need +// explicit control over type parameters. +package generic + +import ( + F "github.com/IBM/fp-go/v2/function" + FC "github.com/IBM/fp-go/v2/internal/functor" + T "github.com/IBM/fp-go/v2/tuple" +) + +// Reader[R, A] = func(R) A + +// MakeReader creates a reader, i.e. a method that accepts a context and that returns a value +// +// Deprecated: +func MakeReader[GA ~func(R) A, R, A any](r GA) GA { + return r +} + +// Ask reads the current context +// +// Deprecated: +func Ask[GR ~func(R) R, R any]() GR { + return MakeReader(F.Identity[R]) +} + +// Asks projects a value from the global context in a Reader +// +// Deprecated: +func Asks[GA ~func(R) A, R, A any](f GA) GA { + return MakeReader(f) +} + +// Deprecated: +func AsksReader[GA ~func(R) A, R, A any](f func(R) GA) GA { + return MakeReader(func(r R) A { + return f(r)(r) + }) +} + +// Deprecated: +func MonadMap[GA ~func(E) A, GB ~func(E) B, E, A, B any](fa GA, f func(A) B) GB { + return MakeReader(F.Flow2(fa, f)) +} + +// Map can be used to turn functions `func(A)B` into functions `(fa F[A])F[B]` whose argument and return types +// use the type constructor `F` to represent some computational context. +// +// Deprecated: +func Map[GA ~func(E) A, GB ~func(E) B, E, A, B any](f func(A) B) func(GA) GB { + return F.Bind2nd(MonadMap[GA, GB, E, A, B], f) +} + +// Deprecated: +func MonadAp[GA ~func(R) A, GB ~func(R) B, GAB ~func(R) func(A) B, R, A, B any](fab GAB, fa GA) GB { + return MakeReader(func(r R) B { + return fab(r)(fa(r)) + }) +} + +// Ap applies a function to an argument under a type constructor. +// +// Deprecated: +func Ap[GA ~func(R) A, GB ~func(R) B, GAB ~func(R) func(A) B, R, A, B any](fa GA) func(GAB) GB { + return F.Bind2nd(MonadAp[GA, GB, GAB, R, A, B], fa) +} + +// Deprecated: +func Of[GA ~func(R) A, R, A any](a A) GA { + return F.Constant1[R](a) +} + +// Deprecated: +func MonadChain[GA ~func(R) A, GB ~func(R) B, R, A, B any](ma GA, f func(A) GB) GB { + return MakeReader(func(r R) B { + return f(ma(r))(r) + }) +} + +// Chain composes computations in sequence, using the return value of one computation to determine the next computation. +// +// Deprecated: +func Chain[GA ~func(R) A, GB ~func(R) B, R, A, B any](f func(A) GB) func(GA) GB { + return F.Bind2nd(MonadChain[GA, GB, R, A, B], f) +} + +// Deprecated: +func Flatten[GA ~func(R) A, GGA ~func(R) GA, R, A any](mma GGA) GA { + return MonadChain(mma, F.Identity[GA]) +} + +// Deprecated: +func Compose[AB ~func(A) B, BC ~func(B) C, AC ~func(A) C, A, B, C any](ab AB) func(BC) AC { + return func(bc BC) AC { + return F.Flow2(ab, bc) + } +} + +// Deprecated: +func First[GAB ~func(A) B, GABC ~func(T.Tuple2[A, C]) T.Tuple2[B, C], A, B, C any](pab GAB) GABC { + return MakeReader(func(tac T.Tuple2[A, C]) T.Tuple2[B, C] { + return T.MakeTuple2(pab(tac.F1), tac.F2) + }) +} + +// Deprecated: +func Second[GBC ~func(B) C, GABC ~func(T.Tuple2[A, B]) T.Tuple2[A, C], A, B, C any](pbc GBC) GABC { + return MakeReader(func(tab T.Tuple2[A, B]) T.Tuple2[A, C] { + return T.MakeTuple2(tab.F1, pbc(tab.F2)) + }) +} + +// Deprecated: +func Promap[GA ~func(E) A, GB ~func(D) B, E, A, D, B any](f func(D) E, g func(A) B) func(GA) GB { + return func(fea GA) GB { + return MakeReader(F.Flow3(f, fea, g)) + } +} + +// Local changes the value of the local context during the execution of the action `ma` (similar to `Contravariant`'s +// `contramap`). +// +// Deprecated: +func Local[GA1 ~func(R1) A, GA2 ~func(R2) A, R2, R1, A any](f func(R2) R1) func(GA1) GA2 { + return func(r1 GA1) GA2 { + return F.Flow2(f, r1) + } +} + +// Deprecated: +func MonadFlap[GAB ~func(R) func(A) B, GB ~func(R) B, R, A, B any](fab GAB, a A) GB { + return FC.MonadFlap(MonadMap[GAB, GB], fab, a) +} + +// Deprecated: +func Flap[GAB ~func(R) func(A) B, GB ~func(R) B, R, A, B any](a A) func(GAB) GB { + return FC.Flap(Map[GAB, GB], a) +} diff --git a/v2/reader/generic/sequence.go b/v2/reader/generic/sequence.go new file mode 100644 index 0000000..b605fb0 --- /dev/null +++ b/v2/reader/generic/sequence.go @@ -0,0 +1,96 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + "github.com/IBM/fp-go/v2/internal/apply" + T "github.com/IBM/fp-go/v2/tuple" +) + +// SequenceT converts n inputs of higher kinded types into a higher kinded type of n strongly typed values, +// represented as a tuple. This generic version works with custom reader types that match the pattern ~func(R) A. +// +// This is useful for combining multiple independent generic Reader computations into a single +// generic Reader that produces a tuple of all results. + +// SequenceT1 combines 1 generic Reader into a generic Reader of a 1-tuple. +// +// Type Parameters: +// - GA: The generic Reader type for the input (~func(R) A) +// - GTA: The generic Reader type for the tuple result (~func(R) T.Tuple1[A]) +// - R: The environment/context type +// - A: The result type +func SequenceT1[GA ~func(R) A, GTA ~func(R) T.Tuple1[A], R, A any](a GA) GTA { + return apply.SequenceT1( + Map[GA, GTA, R, A, T.Tuple1[A]], + + a, + ) +} + +// SequenceT2 combines 2 generic Readers into a generic Reader of a 2-tuple. +// All Readers share the same environment and are evaluated with it. +// +// Type Parameters: +// - GA, GB: The generic Reader types for the inputs (~func(R) A, ~func(R) B) +// - GTAB: The generic Reader type for the tuple result (~func(R) T.Tuple2[A, B]) +// - R: The environment/context type +// - A, B: The result types +func SequenceT2[GA ~func(R) A, GB ~func(R) B, GTAB ~func(R) T.Tuple2[A, B], R, A, B any](a GA, b GB) GTAB { + return apply.SequenceT2( + Map[GA, func(R) func(B) T.Tuple2[A, B], R, A, func(B) T.Tuple2[A, B]], + Ap[GB, GTAB, func(R) func(B) T.Tuple2[A, B], R, B, T.Tuple2[A, B]], + + a, b, + ) +} + +// SequenceT3 combines 3 generic Readers into a generic Reader of a 3-tuple. +// All Readers share the same environment and are evaluated with it. +// +// Type Parameters: +// - GA, GB, GC: The generic Reader types for the inputs +// - GTABC: The generic Reader type for the tuple result (~func(R) T.Tuple3[A, B, C]) +// - R: The environment/context type +// - A, B, C: The result types +func SequenceT3[GA ~func(R) A, GB ~func(R) B, GC ~func(R) C, GTABC ~func(R) T.Tuple3[A, B, C], R, A, B, C any](a GA, b GB, c GC) GTABC { + return apply.SequenceT3( + Map[GA, func(R) func(B) func(C) T.Tuple3[A, B, C], R, A, func(B) func(C) T.Tuple3[A, B, C]], + Ap[GB, func(R) func(C) T.Tuple3[A, B, C], func(R) func(B) func(C) T.Tuple3[A, B, C], R, B, func(C) T.Tuple3[A, B, C]], + Ap[GC, GTABC, func(R) func(C) T.Tuple3[A, B, C], R, C, T.Tuple3[A, B, C]], + + a, b, c, + ) +} + +// SequenceT4 combines 4 generic Readers into a generic Reader of a 4-tuple. +// All Readers share the same environment and are evaluated with it. +// +// Type Parameters: +// - GA, GB, GC, GD: The generic Reader types for the inputs +// - GTABCD: The generic Reader type for the tuple result (~func(R) T.Tuple4[A, B, C, D]) +// - R: The environment/context type +// - A, B, C, D: The result types +func SequenceT4[GA ~func(R) A, GB ~func(R) B, GC ~func(R) C, GD ~func(R) D, GTABCD ~func(R) T.Tuple4[A, B, C, D], R, A, B, C, D any](a GA, b GB, c GC, d GD) GTABCD { + return apply.SequenceT4( + Map[GA, func(R) func(B) func(C) func(D) T.Tuple4[A, B, C, D], R, A, func(B) func(C) func(D) T.Tuple4[A, B, C, D]], + Ap[GB, func(R) func(C) func(D) T.Tuple4[A, B, C, D], func(R) func(B) func(C) func(D) T.Tuple4[A, B, C, D], R, B, func(C) func(D) T.Tuple4[A, B, C, D]], + Ap[GC, func(R) func(D) T.Tuple4[A, B, C, D], func(R) func(C) func(D) T.Tuple4[A, B, C, D], R, C, func(D) T.Tuple4[A, B, C, D]], + Ap[GD, GTABCD, func(R) func(D) T.Tuple4[A, B, C, D], R, D, T.Tuple4[A, B, C, D]], + + a, b, c, d, + ) +} diff --git a/v2/reader/reader.go b/v2/reader/reader.go new file mode 100644 index 0000000..f49cce1 --- /dev/null +++ b/v2/reader/reader.go @@ -0,0 +1,314 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package reader + +import ( + "github.com/IBM/fp-go/v2/function" + I "github.com/IBM/fp-go/v2/identity" + "github.com/IBM/fp-go/v2/internal/functor" + T "github.com/IBM/fp-go/v2/tuple" +) + +// Ask reads the current context and returns it as the result. +// This is the fundamental operation for accessing the environment. +// +// Example: +// +// type Config struct { Host string } +// r := reader.Ask[Config]() +// config := r(Config{Host: "localhost"}) // Returns the config itself +func Ask[R any]() Reader[R, R] { + return function.Identity[R] +} + +// Asks projects a value from the global context in a Reader. +// It's essentially an identity function that makes the intent clearer. +// +// Example: +// +// type Config struct { Port int } +// getPort := reader.Asks(func(c Config) int { return c.Port }) +// port := getPort(Config{Port: 8080}) // Returns 8080 +func Asks[R, A any](f Reader[R, A]) Reader[R, A] { + return f +} + +// AsksReader creates a Reader that depends on the environment to produce another Reader, +// then immediately executes that Reader with the same environment. +// +// This is useful when you need to dynamically choose a Reader based on the environment. +// +// Example: +// +// type Config struct { UseCache bool } +// r := reader.AsksReader(func(c Config) reader.Reader[Config, string] { +// if c.UseCache { +// return reader.Of[Config]("cached") +// } +// return reader.Of[Config]("fresh") +// }) +func AsksReader[R, A any](f func(R) Reader[R, A]) Reader[R, A] { + return func(r R) A { + return f(r)(r) + } +} + +// MonadMap transforms the result value of a Reader using the provided function. +// This is the monadic version that takes the Reader as the first parameter. +// +// Example: +// +// type Config struct { Port int } +// getPort := func(c Config) int { return c.Port } +// getPortStr := reader.MonadMap(getPort, strconv.Itoa) +// result := getPortStr(Config{Port: 8080}) // "8080" +func MonadMap[E, A, B any](fa Reader[E, A], f func(A) B) Reader[E, B] { + return function.Flow2(fa, f) +} + +// Map transforms the result value of a Reader using the provided function. +// This is the Functor operation that allows you to transform values inside the Reader context. +// +// Map can be used to turn functions `func(A)B` into functions `(fa F[A])F[B]` whose argument and return types +// use the type constructor `F` to represent some computational context. +// +// Example: +// +// type Config struct { Port int } +// getPort := reader.Asks(func(c Config) int { return c.Port }) +// getPortStr := reader.Map(strconv.Itoa)(getPort) +// result := getPortStr(Config{Port: 8080}) // "8080" +func Map[E, A, B any](f func(A) B) Operator[E, A, B] { + return function.Bind2nd(MonadMap[E, A, B], f) +} + +// MonadAp applies a Reader containing a function to a Reader containing a value. +// Both Readers share the same environment and are evaluated with it. +// This is the monadic version that takes both parameters. +// +// Example: +// +// type Config struct { X, Y int } +// add := func(x int) func(int) int { return func(y int) int { return x + y } } +// getX := func(c Config) func(int) int { return add(c.X) } +// getY := func(c Config) int { return c.Y } +// result := reader.MonadAp(getX, getY) +// sum := result(Config{X: 3, Y: 4}) // 7 +func MonadAp[B, R, A any](fab Reader[R, func(A) B], fa Reader[R, A]) Reader[R, B] { + return func(r R) B { + return fab(r)(fa(r)) + } +} + +// Ap applies a Reader containing a function to a Reader containing a value. +// This is the Applicative operation for combining independent computations. +// +// Example: +// +// type Config struct { X, Y int } +// add := func(x int) func(int) int { return func(y int) int { return x + y } } +// getX := reader.Map(add)(reader.Asks(func(c Config) int { return c.X })) +// getY := reader.Asks(func(c Config) int { return c.Y }) +// getSum := reader.Ap(getY)(getX) +// sum := getSum(Config{X: 3, Y: 4}) // 7 +func Ap[B, R, A any](fa Reader[R, A]) Operator[R, func(A) B, B] { + return function.Bind2nd(MonadAp[B, R, A], fa) +} + +// Of lifts a pure value into the Reader context. +// The resulting Reader ignores its environment and always returns the given value. +// This is the Pointed/Applicative pure operation. +// +// Example: +// +// type Config struct { Host string } +// r := reader.Of[Config]("constant value") +// result := r(Config{Host: "any"}) // "constant value" +func Of[R, A any](a A) Reader[R, A] { + return function.Constant1[R](a) +} + +// MonadChain sequences two Reader computations where the second depends on the result of the first. +// Both computations share the same environment. +// This is the monadic bind operation (flatMap). +// +// Example: +// +// type Config struct { UserId int } +// getUser := func(c Config) int { return c.UserId } +// getUserName := func(id int) reader.Reader[Config, string] { +// return func(c Config) string { return fmt.Sprintf("User%d", id) } +// } +// r := reader.MonadChain(getUser, getUserName) +// name := r(Config{UserId: 42}) // "User42" +func MonadChain[R, A, B any](ma Reader[R, A], f func(A) Reader[R, B]) Reader[R, B] { + return func(r R) B { + return f(ma(r))(r) + } +} + +// Chain sequences two Reader computations where the second depends on the result of the first. +// This is the Monad operation that enables dependent computations. +// +// Example: +// +// type Config struct { UserId int } +// getUser := reader.Asks(func(c Config) int { return c.UserId }) +// getUserName := func(id int) reader.Reader[Config, string] { +// return reader.Of[Config](fmt.Sprintf("User%d", id)) +// } +// r := reader.Chain(getUserName)(getUser) +// name := r(Config{UserId: 42}) // "User42" +func Chain[R, A, B any](f func(A) Reader[R, B]) Operator[R, A, B] { + return function.Bind2nd(MonadChain[R, A, B], f) +} + +// Flatten removes one level of Reader nesting. +// Converts Reader[R, Reader[R, A]] to Reader[R, A]. +// +// Example: +// +// type Config struct { Value int } +// nested := func(c Config) reader.Reader[Config, int] { +// return func(c2 Config) int { return c.Value + c2.Value } +// } +// flat := reader.Flatten(nested) +// result := flat(Config{Value: 5}) // 10 (5 + 5) +func Flatten[R, A any](mma func(R) Reader[R, A]) Reader[R, A] { + return MonadChain(mma, function.Identity[Reader[R, A]]) +} + +// Compose composes two Readers sequentially, where the output environment of the first +// becomes the input environment of the second. +// +// Example: +// +// type Config struct { Port int } +// type Env struct { Config Config } +// getConfig := func(e Env) Config { return e.Config } +// getPort := func(c Config) int { return c.Port } +// getPortFromEnv := reader.Compose(getConfig)(getPort) +func Compose[R, B, C any](ab Reader[R, B]) func(Reader[B, C]) Reader[R, C] { + return func(bc Reader[B, C]) Reader[R, C] { + return function.Flow2(ab, bc) + } +} + +// First applies a Reader to the first element of a tuple, leaving the second element unchanged. +// This is useful for working with paired data where only one element needs transformation. +// +// Example: +// +// double := func(x int) int { return x * 2 } +// r := reader.First[int, int, string](double) +// result := r(tuple.MakeTuple2(5, "hello")) // (10, "hello") +func First[A, B, C any](pab Reader[A, B]) Reader[T.Tuple2[A, C], T.Tuple2[B, C]] { + return func(tac T.Tuple2[A, C]) T.Tuple2[B, C] { + return T.MakeTuple2(pab(tac.F1), tac.F2) + } +} + +// Second applies a Reader to the second element of a tuple, leaving the first element unchanged. +// This is useful for working with paired data where only one element needs transformation. +// +// Example: +// +// double := func(x int) int { return x * 2 } +// r := reader.Second[string, int, int](double) +// result := r(tuple.MakeTuple2("hello", 5)) // ("hello", 10) +func Second[A, B, C any](pbc Reader[B, C]) Reader[T.Tuple2[A, B], T.Tuple2[A, C]] { + return func(tab T.Tuple2[A, B]) T.Tuple2[A, C] { + return T.MakeTuple2(tab.F1, pbc(tab.F2)) + } +} + +// Promap is the profunctor map operation that transforms both the input and output of a Reader. +// It applies f to the input (contravariantly) and g to the output (covariantly). +// +// Example: +// +// type Config struct { Port int } +// type Env struct { Config Config } +// getPort := func(c Config) int { return c.Port } +// extractConfig := func(e Env) Config { return e.Config } +// toString := func(i int) string { return strconv.Itoa(i) } +// r := reader.Promap(extractConfig, toString)(getPort) +// result := r(Env{Config: Config{Port: 8080}}) // "8080" +func Promap[E, A, D, B any](f func(D) E, g func(A) B) func(Reader[E, A]) Reader[D, B] { + return func(fea Reader[E, A]) Reader[D, B] { + return function.Flow3(f, fea, g) + } +} + +// Local changes the value of the local context during the execution of the action `ma`. +// This is similar to Contravariant's contramap and allows you to modify the environment +// before passing it to a Reader. +// +// Example: +// +// type DetailedConfig struct { Host string; Port int } +// type SimpleConfig struct { Host string } +// getHost := func(c SimpleConfig) string { return c.Host } +// simplify := func(d DetailedConfig) SimpleConfig { return SimpleConfig{Host: d.Host} } +// r := reader.Local(simplify)(getHost) +// result := r(DetailedConfig{Host: "localhost", Port: 8080}) // "localhost" +func Local[R2, R1, A any](f func(R2) R1) func(Reader[R1, A]) Reader[R2, A] { + return Compose[R2, R1, A](f) +} + +// Read applies a context to a Reader to obtain its value. +// This is the "run" operation that executes a Reader with a specific environment. +// +// Example: +// +// type Config struct { Port int } +// getPort := reader.Asks(func(c Config) int { return c.Port }) +// run := reader.Read(Config{Port: 8080}) +// port := run(getPort) // 8080 +func Read[E, A any](e E) func(Reader[E, A]) A { + return I.Ap[A](e) +} + +// MonadFlap is the monadic version of Flap. +// It takes a Reader containing a function and a value, and returns a Reader that applies the function to the value. +// +// Example: +// +// type Config struct { Multiplier int } +// getMultiplier := func(c Config) func(int) int { +// return func(x int) int { return x * c.Multiplier } +// } +// r := reader.MonadFlap(getMultiplier, 5) +// result := r(Config{Multiplier: 3}) // 15 +func MonadFlap[R, A, B any](fab Reader[R, func(A) B], a A) Reader[R, B] { + return functor.MonadFlap(MonadMap[R, func(A) B, B], fab, a) +} + +// Flap takes a value and returns a function that applies a Reader containing a function to that value. +// This is useful for partial application in the Reader context. +// +// Example: +// +// type Config struct { Multiplier int } +// getMultiplier := reader.Asks(func(c Config) func(int) int { +// return func(x int) int { return x * c.Multiplier } +// }) +// applyTo5 := reader.Flap[Config](5) +// r := applyTo5(getMultiplier) +// result := r(Config{Multiplier: 3}) // 15 +func Flap[R, A, B any](a A) Operator[R, func(A) B, B] { + return functor.Flap(Map[R, func(A) B, B], a) +} diff --git a/v2/reader/reader_test.go b/v2/reader/reader_test.go new file mode 100644 index 0000000..853a571 --- /dev/null +++ b/v2/reader/reader_test.go @@ -0,0 +1,202 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package reader + +import ( + "fmt" + "strconv" + "testing" + + F "github.com/IBM/fp-go/v2/function" + T "github.com/IBM/fp-go/v2/tuple" + "github.com/stretchr/testify/assert" + + "github.com/IBM/fp-go/v2/internal/utils" +) + +type Config struct { + Host string + Port int + Multiplier int + Prefix string +} + +func TestAsk(t *testing.T) { + config := Config{Host: "localhost", Port: 8080} + r := Ask[Config]() + result := r(config) + assert.Equal(t, config, result) +} + +func TestAsks(t *testing.T) { + config := Config{Port: 8080} + getPort := Asks(func(c Config) int { return c.Port }) + result := getPort(config) + assert.Equal(t, 8080, result) +} + +func TestAsksReader(t *testing.T) { + config := Config{Host: "localhost"} + r := AsksReader(func(c Config) Reader[Config, string] { + if c.Host == "localhost" { + return Of[Config]("local") + } + return Of[Config]("remote") + }) + result := r(config) + assert.Equal(t, "local", result) +} + +func TestMap(t *testing.T) { + assert.Equal(t, 2, F.Pipe1(Of[string](1), Map[string](utils.Double))("")) +} + +func TestMonadMap(t *testing.T) { + config := Config{Port: 8080} + getPort := func(c Config) int { return c.Port } + getPortStr := MonadMap(getPort, strconv.Itoa) + result := getPortStr(config) + assert.Equal(t, "8080", result) +} + +func TestAp(t *testing.T) { + assert.Equal(t, 2, F.Pipe1(Of[int](utils.Double), Ap[int, int, int](Of[int](1)))(0)) +} + +func TestMonadAp(t *testing.T) { + config := Config{Port: 8080, Multiplier: 2} + add := func(x int) func(int) int { return func(y int) int { return x + y } } + getAdder := func(c Config) func(int) int { return add(c.Port) } + getMultiplier := func(c Config) int { return c.Multiplier } + result := MonadAp(getAdder, getMultiplier)(config) + assert.Equal(t, 8082, result) +} + +func TestOf(t *testing.T) { + r := Of[Config]("constant") + result := r(Config{Host: "any"}) + assert.Equal(t, "constant", result) +} + +func TestChain(t *testing.T) { + config := Config{Port: 8080} + getPort := Asks(func(c Config) int { return c.Port }) + portToString := func(port int) Reader[Config, string] { + return Of[Config](fmt.Sprintf("Port: %d", port)) + } + r := Chain(portToString)(getPort) + result := r(config) + assert.Equal(t, "Port: 8080", result) +} + +func TestMonadChain(t *testing.T) { + config := Config{Port: 8080} + getPort := func(c Config) int { return c.Port } + portToString := func(port int) Reader[Config, string] { + return func(c Config) string { return fmt.Sprintf("Port: %d", port) } + } + r := MonadChain(getPort, portToString) + result := r(config) + assert.Equal(t, "Port: 8080", result) +} + +func TestFlatten(t *testing.T) { + config := Config{Multiplier: 5} + nested := func(c Config) Reader[Config, int] { + return func(c2 Config) int { return c.Multiplier + c2.Multiplier } + } + flat := Flatten(nested) + result := flat(config) + assert.Equal(t, 10, result) +} + +func TestCompose(t *testing.T) { + type Env struct{ Config Config } + env := Env{Config: Config{Port: 8080}} + getConfig := func(e Env) Config { return e.Config } + getPort := func(c Config) int { return c.Port } + getPortFromEnv := Compose[Env, Config, int](getConfig)(getPort) + result := getPortFromEnv(env) + assert.Equal(t, 8080, result) +} + +func TestFirst(t *testing.T) { + double := func(x int) int { return x * 2 } + r := First[int, int, string](double) + result := r(T.MakeTuple2(5, "hello")) + assert.Equal(t, T.MakeTuple2(10, "hello"), result) +} + +func TestSecond(t *testing.T) { + double := func(x int) int { return x * 2 } + r := Second[string, int, int](double) + result := r(T.MakeTuple2("hello", 5)) + assert.Equal(t, T.MakeTuple2("hello", 10), result) +} + +func TestPromap(t *testing.T) { + type Env struct{ Config Config } + env := Env{Config: Config{Port: 8080}} + getPort := func(c Config) int { return c.Port } + extractConfig := func(e Env) Config { return e.Config } + toString := func(i int) string { return strconv.Itoa(i) } + r := Promap(extractConfig, toString)(getPort) + result := r(env) + assert.Equal(t, "8080", result) +} + +func TestLocal(t *testing.T) { + type DetailedConfig struct { + Host string + Port int + } + type SimpleConfig struct{ Host string } + detailed := DetailedConfig{Host: "localhost", Port: 8080} + getHost := func(c SimpleConfig) string { return c.Host } + simplify := func(d DetailedConfig) SimpleConfig { return SimpleConfig{Host: d.Host} } + r := Local[DetailedConfig, SimpleConfig, string](simplify)(getHost) + result := r(detailed) + assert.Equal(t, "localhost", result) +} + +func TestRead(t *testing.T) { + config := Config{Port: 8080} + getPort := Asks(func(c Config) int { return c.Port }) + run := Read[Config, int](config) + port := run(getPort) + assert.Equal(t, 8080, port) +} + +func TestMonadFlap(t *testing.T) { + config := Config{Multiplier: 3} + getMultiplier := func(c Config) func(int) int { + return func(x int) int { return x * c.Multiplier } + } + r := MonadFlap(getMultiplier, 5) + result := r(config) + assert.Equal(t, 15, result) +} + +func TestFlap(t *testing.T) { + config := Config{Multiplier: 3} + getMultiplier := Asks(func(c Config) func(int) int { + return func(x int) int { return x * c.Multiplier } + }) + applyTo5 := Flap[Config, int, int](5) + r := applyTo5(getMultiplier) + result := r(config) + assert.Equal(t, 15, result) +} diff --git a/v2/reader/semigroup.go b/v2/reader/semigroup.go new file mode 100644 index 0000000..947d66d --- /dev/null +++ b/v2/reader/semigroup.go @@ -0,0 +1,85 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package reader + +import ( + M "github.com/IBM/fp-go/v2/monoid" + S "github.com/IBM/fp-go/v2/semigroup" +) + +// ApplySemigroup lifts a Semigroup[A] into a Semigroup[Reader[R, A]]. +// This allows you to combine two Readers that produce semigroup values by combining +// their results using the semigroup's concat operation. +// +// The _map and _ap parameters are the Map and Ap operations for the Reader type, +// typically obtained from the reader package. +// +// Example: +// +// type Config struct { Multiplier int } +// // Using the additive semigroup for integers +// intSemigroup := semigroup.MakeSemigroup(func(a, b int) int { return a + b }) +// readerSemigroup := reader.ApplySemigroup( +// reader.MonadMap[Config, int, func(int) int], +// reader.MonadAp[int, Config, int], +// intSemigroup, +// ) +// +// r1 := reader.Of[Config](5) +// r2 := reader.Of[Config](3) +// combined := readerSemigroup.Concat(r1, r2) +// result := combined(Config{Multiplier: 1}) // 8 +func ApplySemigroup[R, A any]( + _map func(func(R) A, func(A) func(A) A) func(R, func(A) A), + _ap func(func(R, func(A) A), func(R) A) func(R) A, + + s S.Semigroup[A], +) S.Semigroup[func(R) A] { + return S.ApplySemigroup(_map, _ap, s) +} + +// ApplicativeMonoid lifts a Monoid[A] into a Monoid[Reader[R, A]]. +// This allows you to combine Readers that produce monoid values, with an empty/identity Reader. +// +// The _of parameter is the Of operation (pure/return) for the Reader type. +// The _map and _ap parameters are the Map and Ap operations for the Reader type. +// +// Example: +// +// type Config struct { Prefix string } +// // Using the string concatenation monoid +// stringMonoid := monoid.MakeMonoid("", func(a, b string) string { return a + b }) +// readerMonoid := reader.ApplicativeMonoid( +// reader.Of[Config, string], +// reader.MonadMap[Config, string, func(string) string], +// reader.MonadAp[string, Config, string], +// stringMonoid, +// ) +// +// r1 := reader.Asks(func(c Config) string { return c.Prefix }) +// r2 := reader.Of[Config]("hello") +// combined := readerMonoid.Concat(r1, r2) +// result := combined(Config{Prefix: ">> "}) // ">> hello" +// empty := readerMonoid.Empty()(Config{Prefix: "any"}) // "" +func ApplicativeMonoid[R, A any]( + _of func(A) func(R) A, + _map func(func(R) A, func(A) func(A) A) func(R, func(A) A), + _ap func(func(R, func(A) A), func(R) A) func(R) A, + + m M.Monoid[A], +) M.Monoid[func(R) A] { + return M.ApplicativeMonoid(_of, _map, _ap, m) +} diff --git a/v2/reader/sequence.go b/v2/reader/sequence.go new file mode 100644 index 0000000..19bba4b --- /dev/null +++ b/v2/reader/sequence.go @@ -0,0 +1,84 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package reader + +import ( + G "github.com/IBM/fp-go/v2/reader/generic" + T "github.com/IBM/fp-go/v2/tuple" +) + +// SequenceT converts n inputs of higher kinded types into a higher kinded type of n strongly typed values, +// represented as a tuple. This is useful for combining multiple independent Reader computations into a single +// Reader that produces a tuple of all results. + +// SequenceT1 combines 1 Reader into a Reader of a 1-tuple. +// +// Example: +// +// type Config struct { Value int } +// r := reader.Asks(func(c Config) int { return c.Value }) +// result := reader.SequenceT1(r) +// tuple := result(Config{Value: 42}) // Tuple1{F1: 42} +func SequenceT1[R, A any](a Reader[R, A]) Reader[R, T.Tuple1[A]] { + return G.SequenceT1[Reader[R, A], Reader[R, T.Tuple1[A]]](a) +} + +// SequenceT2 combines 2 Readers into a Reader of a 2-tuple. +// All Readers share the same environment and are evaluated with it. +// +// Example: +// +// type Config struct { X, Y int } +// getX := reader.Asks(func(c Config) int { return c.X }) +// getY := reader.Asks(func(c Config) int { return c.Y }) +// result := reader.SequenceT2(getX, getY) +// tuple := result(Config{X: 10, Y: 20}) // Tuple2{F1: 10, F2: 20} +func SequenceT2[R, A, B any](a Reader[R, A], b Reader[R, B]) Reader[R, T.Tuple2[A, B]] { + return G.SequenceT2[Reader[R, A], Reader[R, B], Reader[R, T.Tuple2[A, B]]](a, b) +} + +// SequenceT3 combines 3 Readers into a Reader of a 3-tuple. +// All Readers share the same environment and are evaluated with it. +// +// Example: +// +// type Config struct { Host string; Port int; Secure bool } +// getHost := reader.Asks(func(c Config) string { return c.Host }) +// getPort := reader.Asks(func(c Config) int { return c.Port }) +// getSecure := reader.Asks(func(c Config) bool { return c.Secure }) +// result := reader.SequenceT3(getHost, getPort, getSecure) +// tuple := result(Config{Host: "localhost", Port: 8080, Secure: true}) +// // Tuple3{F1: "localhost", F2: 8080, F3: true} +func SequenceT3[R, A, B, C any](a Reader[R, A], b Reader[R, B], c Reader[R, C]) Reader[R, T.Tuple3[A, B, C]] { + return G.SequenceT3[Reader[R, A], Reader[R, B], Reader[R, C], Reader[R, T.Tuple3[A, B, C]]](a, b, c) +} + +// SequenceT4 combines 4 Readers into a Reader of a 4-tuple. +// All Readers share the same environment and are evaluated with it. +// +// Example: +// +// type Config struct { A, B, C, D int } +// getA := reader.Asks(func(c Config) int { return c.A }) +// getB := reader.Asks(func(c Config) int { return c.B }) +// getC := reader.Asks(func(c Config) int { return c.C }) +// getD := reader.Asks(func(c Config) int { return c.D }) +// result := reader.SequenceT4(getA, getB, getC, getD) +// tuple := result(Config{A: 1, B: 2, C: 3, D: 4}) +// // Tuple4{F1: 1, F2: 2, F3: 3, F4: 4} +func SequenceT4[R, A, B, C, D any](a Reader[R, A], b Reader[R, B], c Reader[R, C], d Reader[R, D]) Reader[R, T.Tuple4[A, B, C, D]] { + return G.SequenceT4[Reader[R, A], Reader[R, B], Reader[R, C], Reader[R, D], Reader[R, T.Tuple4[A, B, C, D]]](a, b, c, d) +} diff --git a/v2/reader/sequence_test.go b/v2/reader/sequence_test.go new file mode 100644 index 0000000..10ba0a1 --- /dev/null +++ b/v2/reader/sequence_test.go @@ -0,0 +1,67 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package reader + +import ( + "testing" + + T "github.com/IBM/fp-go/v2/tuple" + "github.com/stretchr/testify/assert" +) + +func TestSequenceT1(t *testing.T) { + config := Config{Port: 8080} + r := Asks(func(c Config) int { return c.Port }) + result := SequenceT1(r) + tuple := result(config) + assert.Equal(t, T.MakeTuple1(8080), tuple) +} + +func TestSequenceT2(t *testing.T) { + config := Config{Host: "localhost", Port: 8080} + getHost := Asks(func(c Config) string { return c.Host }) + getPort := Asks(func(c Config) int { return c.Port }) + result := SequenceT2(getHost, getPort) + tuple := result(config) + assert.Equal(t, T.MakeTuple2("localhost", 8080), tuple) +} + +func TestSequenceT3(t *testing.T) { + config := Config{Host: "localhost", Port: 8080, Multiplier: 2} + getHost := Asks(func(c Config) string { return c.Host }) + getPort := Asks(func(c Config) int { return c.Port }) + getMultiplier := Asks(func(c Config) int { return c.Multiplier }) + result := SequenceT3(getHost, getPort, getMultiplier) + tuple := result(config) + assert.Equal(t, T.MakeTuple3("localhost", 8080, 2), tuple) +} + +func TestSequenceT4(t *testing.T) { + config := Config{ + Host: "localhost", + Port: 8080, + Multiplier: 2, + Prefix: ">>", + } + getHost := Asks(func(c Config) string { return c.Host }) + getPort := Asks(func(c Config) int { return c.Port }) + getMultiplier := Asks(func(c Config) int { return c.Multiplier }) + getPrefix := Asks(func(c Config) string { return c.Prefix }) + result := SequenceT4(getHost, getPort, getMultiplier, getPrefix) + tuple := result(config) + expected := T.MakeTuple4("localhost", 8080, 2, ">>") + assert.Equal(t, expected, tuple) +} diff --git a/v2/reader/types.go b/v2/reader/types.go new file mode 100644 index 0000000..131f7d9 --- /dev/null +++ b/v2/reader/types.go @@ -0,0 +1,73 @@ +// Copyright (c) 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package reader + +type ( + // Reader represents a computation that depends on a shared environment of type R and produces a value of type A. + // + // The purpose of the Reader monad is to avoid threading arguments through multiple functions + // in order to only get them where they are needed. This enables dependency injection and + // configuration management in a functional style. + // + // Type Parameters: + // - R: The environment/context type (read-only, shared across computations) + // - A: The result type produced by the computation + // + // A Reader[R, A] is simply a function from R to A: func(R) A + // + // Example: + // + // type Config struct { + // DatabaseURL string + // APIKey string + // } + // + // // A Reader that extracts the database URL from config + // getDatabaseURL := func(c Config) string { return c.DatabaseURL } + // + // // A Reader that extracts the API key from config + // getAPIKey := func(c Config) string { return c.APIKey } + // + // // Use the readers with a config + // config := Config{DatabaseURL: "localhost:5432", APIKey: "secret"} + // dbURL := getDatabaseURL(config) // "localhost:5432" + // apiKey := getAPIKey(config) // "secret" + Reader[R, A any] = func(R) A + + // Operator represents a transformation from one Reader to another. + // It takes a Reader[R, A] and produces a Reader[R, B], where both readers + // share the same environment type R. + // + // This type is commonly used for operations like Map, Chain, and other + // transformations that convert readers while preserving the environment type. + // + // Type Parameters: + // - R: The shared environment/context type + // - A: The input Reader's result type + // - B: The output Reader's result type + // + // Example: + // + // type Config struct { Multiplier int } + // + // // An operator that transforms int readers to string readers + // intToString := reader.Map[Config, int, string](strconv.Itoa) + // + // getNumber := reader.Asks(func(c Config) int { return c.Multiplier }) + // getString := intToString(getNumber) + // result := getString(Config{Multiplier: 42}) // "42" + Operator[R, A, B any] = func(Reader[R, A]) Reader[R, B] +) diff --git a/v2/readereither/array.go b/v2/readereither/array.go new file mode 100644 index 0000000..c6c7b4f --- /dev/null +++ b/v2/readereither/array.go @@ -0,0 +1,35 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readereither + +import ( + G "github.com/IBM/fp-go/v2/readereither/generic" +) + +// TraverseArray transforms an array +func TraverseArray[E, L, A, B any](f func(A) ReaderEither[E, L, B]) func([]A) ReaderEither[E, L, []B] { + return G.TraverseArray[ReaderEither[E, L, B], ReaderEither[E, L, []B], []A](f) +} + +// TraverseArrayWithIndex transforms an array +func TraverseArrayWithIndex[E, L, A, B any](f func(int, A) ReaderEither[E, L, B]) func([]A) ReaderEither[E, L, []B] { + return G.TraverseArrayWithIndex[ReaderEither[E, L, B], ReaderEither[E, L, []B], []A](f) +} + +// SequenceArray converts a homogeneous sequence of either into an either of sequence +func SequenceArray[E, L, A any](ma []ReaderEither[E, L, A]) ReaderEither[E, L, []A] { + return G.SequenceArray[ReaderEither[E, L, A], ReaderEither[E, L, []A]](ma) +} diff --git a/v2/readereither/array_test.go b/v2/readereither/array_test.go new file mode 100644 index 0000000..396dc60 --- /dev/null +++ b/v2/readereither/array_test.go @@ -0,0 +1,41 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readereither + +import ( + "context" + "testing" + + A "github.com/IBM/fp-go/v2/array" + ET "github.com/IBM/fp-go/v2/either" + F "github.com/IBM/fp-go/v2/function" + "github.com/stretchr/testify/assert" +) + +func TestSequenceArray(t *testing.T) { + + n := 10 + + readers := A.MakeBy(n, Of[context.Context, error, int]) + exp := ET.Of[error](A.MakeBy(n, F.Identity[int])) + + g := F.Pipe1( + readers, + SequenceArray[context.Context, error, int], + ) + + assert.Equal(t, exp, g(context.Background())) +} diff --git a/v2/readereither/bind.go b/v2/readereither/bind.go new file mode 100644 index 0000000..35b7143 --- /dev/null +++ b/v2/readereither/bind.go @@ -0,0 +1,66 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readereither + +import ( + G "github.com/IBM/fp-go/v2/readereither/generic" +) + +// Bind creates an empty context of type [S] to be used with the [Bind] operation +func Do[R, E, S any]( + empty S, +) ReaderEither[R, E, S] { + return G.Do[ReaderEither[R, E, S], R, E, S](empty) +} + +// Bind attaches the result of a computation to a context [S1] to produce a context [S2] +func Bind[R, E, S1, S2, T any]( + setter func(T) func(S1) S2, + f func(S1) ReaderEither[R, E, T], +) func(ReaderEither[R, E, S1]) ReaderEither[R, E, S2] { + return G.Bind[ReaderEither[R, E, S1], ReaderEither[R, E, S2], ReaderEither[R, E, T], R, E, S1, S2, T](setter, f) +} + +// Let attaches the result of a computation to a context [S1] to produce a context [S2] +func Let[R, E, S1, S2, T any]( + setter func(T) func(S1) S2, + f func(S1) T, +) func(ReaderEither[R, E, S1]) ReaderEither[R, E, S2] { + return G.Let[ReaderEither[R, E, S1], ReaderEither[R, E, S2], R, E, S1, S2, T](setter, f) +} + +// LetTo attaches the a value to a context [S1] to produce a context [S2] +func LetTo[R, E, S1, S2, T any]( + setter func(T) func(S1) S2, + b T, +) func(ReaderEither[R, E, S1]) ReaderEither[R, E, S2] { + return G.LetTo[ReaderEither[R, E, S1], ReaderEither[R, E, S2], R, E, S1, S2, T](setter, b) +} + +// BindTo initializes a new state [S1] from a value [T] +func BindTo[R, E, S1, T any]( + setter func(T) S1, +) func(ReaderEither[R, E, T]) ReaderEither[R, E, S1] { + return G.BindTo[ReaderEither[R, E, S1], ReaderEither[R, E, T], R, E, S1, T](setter) +} + +// ApS attaches a value to a context [S1] to produce a context [S2] by considering the context and the value concurrently +func ApS[R, E, S1, S2, T any]( + setter func(T) func(S1) S2, + fa ReaderEither[R, E, T], +) func(ReaderEither[R, E, S1]) ReaderEither[R, E, S2] { + return G.ApS[ReaderEither[R, E, S1], ReaderEither[R, E, S2], ReaderEither[R, E, T], R, E, S1, S2, T](setter, fa) +} diff --git a/v2/readereither/bind_test.go b/v2/readereither/bind_test.go new file mode 100644 index 0000000..f7e4945 --- /dev/null +++ b/v2/readereither/bind_test.go @@ -0,0 +1,58 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readereither + +import ( + "context" + "testing" + + E "github.com/IBM/fp-go/v2/either" + F "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/internal/utils" + "github.com/stretchr/testify/assert" +) + +func getLastName(s utils.Initial) ReaderEither[context.Context, error, string] { + return Of[context.Context, error]("Doe") +} + +func getGivenName(s utils.WithLastName) ReaderEither[context.Context, error, string] { + return Of[context.Context, error]("John") +} + +func TestBind(t *testing.T) { + + res := F.Pipe3( + Do[context.Context, error](utils.Empty), + Bind(utils.SetLastName, getLastName), + Bind(utils.SetGivenName, getGivenName), + Map[context.Context, error](utils.GetFullName), + ) + + assert.Equal(t, res(context.Background()), E.Of[error]("John Doe")) +} + +func TestApS(t *testing.T) { + + res := F.Pipe3( + Do[context.Context, error](utils.Empty), + ApS(utils.SetLastName, Of[context.Context, error]("Doe")), + ApS(utils.SetGivenName, Of[context.Context, error]("John")), + Map[context.Context, error](utils.GetFullName), + ) + + assert.Equal(t, res(context.Background()), E.Of[error]("John Doe")) +} diff --git a/v2/readereither/curry.go b/v2/readereither/curry.go new file mode 100644 index 0000000..1bfa75f --- /dev/null +++ b/v2/readereither/curry.go @@ -0,0 +1,51 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readereither + +import ( + G "github.com/IBM/fp-go/v2/readereither/generic" +) + +// these functions curry a golang function with the context as the firsr parameter into a either reader with the context as the last parameter +// this goes back to the advice in https://pkg.go.dev/context to put the context as a first parameter as a convention + +func Curry0[R, A any](f func(R) (A, error)) ReaderEither[R, error, A] { + return G.Curry0[ReaderEither[R, error, A]](f) +} + +func Curry1[R, T1, A any](f func(R, T1) (A, error)) func(T1) ReaderEither[R, error, A] { + return G.Curry1[ReaderEither[R, error, A]](f) +} + +func Curry2[R, T1, T2, A any](f func(R, T1, T2) (A, error)) func(T1) func(T2) ReaderEither[R, error, A] { + return G.Curry2[ReaderEither[R, error, A]](f) +} + +func Curry3[R, T1, T2, T3, A any](f func(R, T1, T2, T3) (A, error)) func(T1) func(T2) func(T3) ReaderEither[R, error, A] { + return G.Curry3[ReaderEither[R, error, A]](f) +} + +func Uncurry1[R, T1, A any](f func(T1) ReaderEither[R, error, A]) func(R, T1) (A, error) { + return G.Uncurry1(f) +} + +func Uncurry2[R, T1, T2, A any](f func(T1) func(T2) ReaderEither[R, error, A]) func(R, T1, T2) (A, error) { + return G.Uncurry2(f) +} + +func Uncurry3[R, T1, T2, T3, A any](f func(T1) func(T2) func(T3) ReaderEither[R, error, A]) func(R, T1, T2, T3) (A, error) { + return G.Uncurry3(f) +} diff --git a/v2/readereither/from.go b/v2/readereither/from.go new file mode 100644 index 0000000..5ee8762 --- /dev/null +++ b/v2/readereither/from.go @@ -0,0 +1,39 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readereither + +import ( + G "github.com/IBM/fp-go/v2/readereither/generic" +) + +// these functions curry a golang function with the context as the firsr parameter into a either reader with the context as the last parameter +// this goes back to the advice in https://pkg.go.dev/context to put the context as a first parameter as a convention + +func From0[R, A any](f func(R) (A, error)) func() ReaderEither[R, error, A] { + return G.From0[ReaderEither[R, error, A]](f) +} + +func From1[R, T1, A any](f func(R, T1) (A, error)) func(T1) ReaderEither[R, error, A] { + return G.From1[ReaderEither[R, error, A]](f) +} + +func From2[R, T1, T2, A any](f func(R, T1, T2) (A, error)) func(T1, T2) ReaderEither[R, error, A] { + return G.From2[ReaderEither[R, error, A]](f) +} + +func From3[R, T1, T2, T3, A any](f func(R, T1, T2, T3) (A, error)) func(T1, T2, T3) ReaderEither[R, error, A] { + return G.From3[ReaderEither[R, error, A]](f) +} diff --git a/v2/readereither/generic/array.go b/v2/readereither/generic/array.go new file mode 100644 index 0000000..68794b1 --- /dev/null +++ b/v2/readereither/generic/array.go @@ -0,0 +1,60 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + ET "github.com/IBM/fp-go/v2/either" + F "github.com/IBM/fp-go/v2/function" + RA "github.com/IBM/fp-go/v2/internal/array" +) + +// MonadTraverseArray transforms an array +func MonadTraverseArray[GB ~func(E) ET.Either[L, B], GBS ~func(E) ET.Either[L, BBS], AAS ~[]A, BBS ~[]B, L, E, A, B any](ma AAS, f func(A) GB) GBS { + return RA.MonadTraverse[AAS]( + Of[GBS, L, E, BBS], + Map[GBS, func(E) ET.Either[L, func(B) BBS], L, E, BBS, func(B) BBS], + Ap[GB, GBS, func(E) ET.Either[L, func(B) BBS], L, E, B, BBS], + + ma, f, + ) +} + +// TraverseArray transforms an array +func TraverseArray[GB ~func(E) ET.Either[L, B], GBS ~func(E) ET.Either[L, BBS], AAS ~[]A, BBS ~[]B, L, E, A, B any](f func(A) GB) func(AAS) GBS { + return RA.Traverse[AAS]( + Of[GBS, L, E, BBS], + Map[GBS, func(E) ET.Either[L, func(B) BBS], L, E, BBS, func(B) BBS], + Ap[GB, GBS, func(E) ET.Either[L, func(B) BBS], L, E, B, BBS], + + f, + ) +} + +// TraverseArrayWithIndex transforms an array +func TraverseArrayWithIndex[GB ~func(E) ET.Either[L, B], GBS ~func(E) ET.Either[L, BBS], AAS ~[]A, BBS ~[]B, L, E, A, B any](f func(int, A) GB) func(AAS) GBS { + return RA.TraverseWithIndex[AAS]( + Of[GBS, L, E, BBS], + Map[GBS, func(E) ET.Either[L, func(B) BBS], L, E, BBS, func(B) BBS], + Ap[GB, GBS, func(E) ET.Either[L, func(B) BBS], L, E, B, BBS], + + f, + ) +} + +// SequenceArray converts a homogeneous sequence of either into an either of sequence +func SequenceArray[GA ~func(E) ET.Either[L, A], GAS ~func(E) ET.Either[L, AAS], AAS ~[]A, GAAS ~[]GA, L, E, A any](ma GAAS) GAS { + return MonadTraverseArray[GA, GAS](ma, F.Identity[GA]) +} diff --git a/v2/readereither/generic/bind.go b/v2/readereither/generic/bind.go new file mode 100644 index 0000000..55c7d05 --- /dev/null +++ b/v2/readereither/generic/bind.go @@ -0,0 +1,90 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + ET "github.com/IBM/fp-go/v2/either" + A "github.com/IBM/fp-go/v2/internal/apply" + C "github.com/IBM/fp-go/v2/internal/chain" + F "github.com/IBM/fp-go/v2/internal/functor" +) + +// Bind creates an empty context of type [S] to be used with the [Bind] operation +func Do[GS ~func(R) ET.Either[E, S], R, E, S any]( + empty S, +) GS { + return Of[GS, E, R, S](empty) +} + +// Bind attaches the result of a computation to a context [S1] to produce a context [S2] +func Bind[GS1 ~func(R) ET.Either[E, S1], GS2 ~func(R) ET.Either[E, S2], GT ~func(R) ET.Either[E, T], R, E, S1, S2, T any]( + setter func(T) func(S1) S2, + f func(S1) GT, +) func(GS1) GS2 { + return C.Bind( + Chain[GS1, GS2, E, R, S1, S2], + Map[GT, GS2, E, R, T, S2], + setter, + f, + ) +} + +// Let attaches the result of a computation to a context [S1] to produce a context [S2] +func Let[GS1 ~func(R) ET.Either[E, S1], GS2 ~func(R) ET.Either[E, S2], R, E, S1, S2, T any]( + key func(T) func(S1) S2, + f func(S1) T, +) func(GS1) GS2 { + return F.Let( + Map[GS1, GS2, E, R, S1, S2], + key, + f, + ) +} + +// LetTo attaches the a value to a context [S1] to produce a context [S2] +func LetTo[GS1 ~func(R) ET.Either[E, S1], GS2 ~func(R) ET.Either[E, S2], R, E, S1, S2, B any]( + key func(B) func(S1) S2, + b B, +) func(GS1) GS2 { + return F.LetTo( + Map[GS1, GS2, E, R, S1, S2], + key, + b, + ) +} + +// BindTo initializes a new state [S1] from a value [T] +func BindTo[GS1 ~func(R) ET.Either[E, S1], GT ~func(R) ET.Either[E, T], R, E, S1, T any]( + setter func(T) S1, +) func(GT) GS1 { + return C.BindTo( + Map[GT, GS1, E, R, T, S1], + setter, + ) +} + +// ApS attaches a value to a context [S1] to produce a context [S2] by considering the context and the value concurrently +func ApS[GS1 ~func(R) ET.Either[E, S1], GS2 ~func(R) ET.Either[E, S2], GT ~func(R) ET.Either[E, T], R, E, S1, S2, T any]( + setter func(T) func(S1) S2, + fa GT, +) func(GS1) GS2 { + return A.ApS( + Ap[GT, GS2, func(R) ET.Either[E, func(T) S2], E, R, T, S2], + Map[GS1, func(R) ET.Either[E, func(T) S2], E, R, S1, func(T) S2], + setter, + fa, + ) +} diff --git a/v2/readereither/generic/curry.go b/v2/readereither/generic/curry.go new file mode 100644 index 0000000..a084e00 --- /dev/null +++ b/v2/readereither/generic/curry.go @@ -0,0 +1,52 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + ET "github.com/IBM/fp-go/v2/either" + G "github.com/IBM/fp-go/v2/reader/generic" +) + +// these functions curry a golang function with the context as the firsr parameter into a either reader with the context as the last parameter +// this goes back to the advice in https://pkg.go.dev/context to put the context as a first parameter as a convention + +func Curry0[GEA ~func(R) ET.Either[error, A], R, A any](f func(R) (A, error)) GEA { + return G.Curry0[GEA](ET.Eitherize1(f)) +} + +func Curry1[GEA ~func(R) ET.Either[error, A], R, T1, A any](f func(R, T1) (A, error)) func(T1) GEA { + return G.Curry1[GEA](ET.Eitherize2(f)) +} + +func Curry2[GEA ~func(R) ET.Either[error, A], R, T1, T2, A any](f func(R, T1, T2) (A, error)) func(T1) func(T2) GEA { + return G.Curry2[GEA](ET.Eitherize3(f)) +} + +func Curry3[GEA ~func(R) ET.Either[error, A], R, T1, T2, T3, A any](f func(R, T1, T2, T3) (A, error)) func(T1) func(T2) func(T3) GEA { + return G.Curry3[GEA](ET.Eitherize4(f)) +} + +func Uncurry1[GEA ~func(R) ET.Either[error, A], R, T1, A any](f func(T1) GEA) func(R, T1) (A, error) { + return ET.Uneitherize2(G.Uncurry1(f)) +} + +func Uncurry2[GEA ~func(R) ET.Either[error, A], R, T1, T2, A any](f func(T1) func(T2) GEA) func(R, T1, T2) (A, error) { + return ET.Uneitherize3(G.Uncurry2(f)) +} + +func Uncurry3[GEA ~func(R) ET.Either[error, A], R, T1, T2, T3, A any](f func(T1) func(T2) func(T3) GEA) func(R, T1, T2, T3) (A, error) { + return ET.Uneitherize4(G.Uncurry3(f)) +} diff --git a/v2/readereither/generic/from.go b/v2/readereither/generic/from.go new file mode 100644 index 0000000..a646dd8 --- /dev/null +++ b/v2/readereither/generic/from.go @@ -0,0 +1,40 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + ET "github.com/IBM/fp-go/v2/either" + G "github.com/IBM/fp-go/v2/reader/generic" +) + +// these functions From a golang function with the context as the firsr parameter into a either reader with the context as the last parameter +// this goes back to the advice in https://pkg.go.dev/context to put the context as a first parameter as a convention + +func From0[GEA ~func(R) ET.Either[error, A], R, A any](f func(R) (A, error)) func() GEA { + return G.From0[GEA](ET.Eitherize1(f)) +} + +func From1[GEA ~func(R) ET.Either[error, A], R, T1, A any](f func(R, T1) (A, error)) func(T1) GEA { + return G.From1[GEA](ET.Eitherize2(f)) +} + +func From2[GEA ~func(R) ET.Either[error, A], R, T1, T2, A any](f func(R, T1, T2) (A, error)) func(T1, T2) GEA { + return G.From2[GEA](ET.Eitherize3(f)) +} + +func From3[GEA ~func(R) ET.Either[error, A], R, T1, T2, T3, A any](f func(R, T1, T2, T3) (A, error)) func(T1, T2, T3) GEA { + return G.From3[GEA](ET.Eitherize4(f)) +} diff --git a/v2/readereither/generic/reader.go b/v2/readereither/generic/reader.go new file mode 100644 index 0000000..384b1e6 --- /dev/null +++ b/v2/readereither/generic/reader.go @@ -0,0 +1,170 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + ET "github.com/IBM/fp-go/v2/either" + F "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/internal/eithert" + FE "github.com/IBM/fp-go/v2/internal/fromeither" + FR "github.com/IBM/fp-go/v2/internal/fromreader" + FC "github.com/IBM/fp-go/v2/internal/functor" + "github.com/IBM/fp-go/v2/internal/readert" + O "github.com/IBM/fp-go/v2/option" + R "github.com/IBM/fp-go/v2/reader/generic" +) + +func MakeReaderEither[GEA ~func(E) ET.Either[L, A], L, E, A any](f func(E) ET.Either[L, A]) GEA { + return f +} + +func FromEither[GEA ~func(E) ET.Either[L, A], L, E, A any](e ET.Either[L, A]) GEA { + return R.Of[GEA](e) +} + +func RightReader[GA ~func(E) A, GEA ~func(E) ET.Either[L, A], L, E, A any](r GA) GEA { + return eithert.RightF(R.MonadMap[GA, GEA, E, A, ET.Either[L, A]], r) +} + +func LeftReader[GL ~func(E) L, GEA ~func(E) ET.Either[L, A], L, E, A any](l GL) GEA { + return eithert.LeftF(R.MonadMap[GL, GEA, E, L, ET.Either[L, A]], l) +} + +func Left[GEA ~func(E) ET.Either[L, A], L, E, A any](l L) GEA { + return eithert.Left(R.Of[GEA, E, ET.Either[L, A]], l) +} + +func Right[GEA ~func(E) ET.Either[L, A], L, E, A any](r A) GEA { + return eithert.Right(R.Of[GEA, E, ET.Either[L, A]], r) +} + +func FromReader[GA ~func(E) A, GEA ~func(E) ET.Either[L, A], L, E, A any](r GA) GEA { + return RightReader[GA, GEA](r) +} + +func MonadMap[GEA ~func(E) ET.Either[L, A], GEB ~func(E) ET.Either[L, B], L, E, A, B any](fa GEA, f func(A) B) GEB { + return readert.MonadMap[GEA, GEB](ET.MonadMap[L, A, B], fa, f) +} + +func Map[GEA ~func(E) ET.Either[L, A], GEB ~func(E) ET.Either[L, B], L, E, A, B any](f func(A) B) func(GEA) GEB { + return readert.Map[GEA, GEB](ET.Map[L, A, B], f) +} + +func MonadChain[GEA ~func(E) ET.Either[L, A], GEB ~func(E) ET.Either[L, B], L, E, A, B any](ma GEA, f func(A) GEB) GEB { + return readert.MonadChain(ET.MonadChain[L, A, B], ma, f) +} + +func Chain[GEA ~func(E) ET.Either[L, A], GEB ~func(E) ET.Either[L, B], L, E, A, B any](f func(A) GEB) func(GEA) GEB { + return F.Bind2nd(MonadChain[GEA, GEB, L, E, A, B], f) +} + +func Of[GEA ~func(E) ET.Either[L, A], L, E, A any](a A) GEA { + return readert.MonadOf[GEA](ET.Of[L, A], a) +} + +func MonadAp[GEA ~func(E) ET.Either[L, A], GEB ~func(E) ET.Either[L, B], GEFAB ~func(E) ET.Either[L, func(A) B], L, E, A, B any](fab GEFAB, fa GEA) GEB { + return readert.MonadAp[GEA, GEB, GEFAB, E, A](ET.MonadAp[B, L, A], fab, fa) +} + +func Ap[GEA ~func(E) ET.Either[L, A], GEB ~func(E) ET.Either[L, B], GEFAB ~func(E) ET.Either[L, func(A) B], L, E, A, B any](fa GEA) func(GEFAB) GEB { + return F.Bind2nd(MonadAp[GEA, GEB, GEFAB, L, E, A, B], fa) +} + +func FromPredicate[GEA ~func(E) ET.Either[L, A], L, E, A any](pred func(A) bool, onFalse func(A) L) func(A) GEA { + return FE.FromPredicate(FromEither[GEA, L, E, A], pred, onFalse) +} + +func Fold[GEA ~func(E) ET.Either[L, A], GB ~func(E) B, E, L, A, B any](onLeft func(L) GB, onRight func(A) GB) func(GEA) GB { + return eithert.MatchE(R.MonadChain[GEA, GB, E, ET.Either[L, A], B], onLeft, onRight) +} + +func GetOrElse[GEA ~func(E) ET.Either[L, A], GA ~func(E) A, E, L, A any](onLeft func(L) GA) func(GEA) GA { + return eithert.GetOrElse(R.MonadChain[GEA, GA, E, ET.Either[L, A], A], R.Of[GA, E, A], onLeft) +} + +func OrElse[GEA1 ~func(E) ET.Either[L1, A], GEA2 ~func(E) ET.Either[L2, A], E, L1, A, L2 any](onLeft func(L1) GEA2) func(GEA1) GEA2 { + return eithert.OrElse(R.MonadChain[GEA1, GEA2, E, ET.Either[L1, A], ET.Either[L2, A]], R.Of[GEA2, E, ET.Either[L2, A]], onLeft) +} + +func OrLeft[GEA1 ~func(E) ET.Either[L1, A], GEA2 ~func(E) ET.Either[L2, A], GE2 ~func(E) L2, L1, E, L2, A any](onLeft func(L1) GE2) func(GEA1) GEA2 { + return eithert.OrLeft( + R.MonadChain[GEA1, GEA2, E, ET.Either[L1, A], ET.Either[L2, A]], + R.MonadMap[GE2, GEA2, E, L2, ET.Either[L2, A]], + R.Of[GEA2, E, ET.Either[L2, A]], + onLeft, + ) +} + +func Ask[GEE ~func(E) ET.Either[L, E], E, L any]() GEE { + return FR.Ask(FromReader[func(E) E, GEE, L, E, E])() +} + +func Asks[GA ~func(E) A, GEA ~func(E) ET.Either[L, A], E, L, A any](r GA) GEA { + return FR.Asks(FromReader[GA, GEA, L, E, A])(r) +} + +func MonadChainEitherK[GEA ~func(E) ET.Either[L, A], GEB ~func(E) ET.Either[L, B], L, E, A, B any](ma GEA, f func(A) ET.Either[L, B]) GEB { + return FE.MonadChainEitherK( + MonadChain[GEA, GEB, L, E, A, B], + FromEither[GEB, L, E, B], + ma, + f, + ) +} + +func ChainEitherK[GEA ~func(E) ET.Either[L, A], GEB ~func(E) ET.Either[L, B], L, E, A, B any](f func(A) ET.Either[L, B]) func(ma GEA) GEB { + return F.Bind2nd(MonadChainEitherK[GEA, GEB, L, E, A, B], f) +} + +func ChainOptionK[GEA ~func(E) ET.Either[L, A], GEB ~func(E) ET.Either[L, B], L, E, A, B any](onNone func() L) func(func(A) O.Option[B]) func(GEA) GEB { + return FE.ChainOptionK(MonadChain[GEA, GEB, L, E, A, B], FromEither[GEB, L, E, B], onNone) +} + +func Flatten[GEA ~func(E) ET.Either[L, A], GGA ~func(E) ET.Either[L, GEA], L, E, A any](mma GGA) GEA { + return MonadChain(mma, F.Identity[GEA]) +} + +func MonadBiMap[GA ~func(E) ET.Either[E1, A], GB ~func(E) ET.Either[E2, B], E, E1, E2, A, B any](fa GA, f func(E1) E2, g func(A) B) GB { + return eithert.MonadBiMap(R.MonadMap[GA, GB, E, ET.Either[E1, A], ET.Either[E2, B]], fa, f, g) +} + +// BiMap maps a pair of functions over the two type arguments of the bifunctor. +func BiMap[GA ~func(E) ET.Either[E1, A], GB ~func(E) ET.Either[E2, B], E, E1, E2, A, B any](f func(E1) E2, g func(A) B) func(GA) GB { + return eithert.BiMap(R.Map[GA, GB, E, ET.Either[E1, A], ET.Either[E2, B]], f, g) +} + +// Local changes the value of the local context during the execution of the action `ma` (similar to `Contravariant`'s +// `contramap`). +func Local[GA1 ~func(R1) ET.Either[E, A], GA2 ~func(R2) ET.Either[E, A], R2, R1, E, A any](f func(R2) R1) func(GA1) GA2 { + return R.Local[GA1, GA2](f) +} + +func MonadFlap[GEFAB ~func(E) ET.Either[L, func(A) B], GEB ~func(E) ET.Either[L, B], L, E, A, B any](fab GEFAB, a A) GEB { + return FC.MonadFlap(MonadMap[GEFAB, GEB], fab, a) +} + +func Flap[GEFAB ~func(E) ET.Either[L, func(A) B], GEB ~func(E) ET.Either[L, B], L, E, A, B any](a A) func(GEFAB) GEB { + return FC.Flap(Map[GEFAB, GEB], a) +} + +func MonadMapLeft[GA1 ~func(C) ET.Either[E1, A], GA2 ~func(C) ET.Either[E2, A], C, E1, E2, A any](fa GA1, f func(E1) E2) GA2 { + return eithert.MonadMapLeft(R.MonadMap[GA1, GA2, C, ET.Either[E1, A], ET.Either[E2, A]], fa, f) +} + +// MapLeft applies a mapping function to the error channel +func MapLeft[GA1 ~func(C) ET.Either[E1, A], GA2 ~func(C) ET.Either[E2, A], C, E1, E2, A any](f func(E1) E2) func(GA1) GA2 { + return F.Bind2nd(MonadMapLeft[GA1, GA2, C, E1, E2, A], f) +} diff --git a/v2/readereither/generic/sequence.go b/v2/readereither/generic/sequence.go new file mode 100644 index 0000000..3fab57c --- /dev/null +++ b/v2/readereither/generic/sequence.go @@ -0,0 +1,80 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + ET "github.com/IBM/fp-go/v2/either" + "github.com/IBM/fp-go/v2/internal/apply" + T "github.com/IBM/fp-go/v2/tuple" +) + +// SequenceT converts n inputs of higher kinded types into a higher kinded types of n strongly typed values, represented as a tuple + +func SequenceT1[ + GA ~func(E) ET.Either[L, A], + GTA ~func(E) ET.Either[L, T.Tuple1[A]], + L, E, A any](a GA) GTA { + return apply.SequenceT1( + Map[GA, GTA, L, E, A, T.Tuple1[A]], + + a, + ) +} + +func SequenceT2[ + GA ~func(E) ET.Either[L, A], + GB ~func(E) ET.Either[L, B], + GTAB ~func(E) ET.Either[L, T.Tuple2[A, B]], + L, E, A, B any](a GA, b GB) GTAB { + return apply.SequenceT2( + Map[GA, func(E) ET.Either[L, func(B) T.Tuple2[A, B]], L, E, A, func(B) T.Tuple2[A, B]], + Ap[GB, GTAB, func(E) ET.Either[L, func(B) T.Tuple2[A, B]], L, E, B, T.Tuple2[A, B]], + + a, b, + ) +} + +func SequenceT3[ + GA ~func(E) ET.Either[L, A], + GB ~func(E) ET.Either[L, B], + GC ~func(E) ET.Either[L, C], + GTABC ~func(E) ET.Either[L, T.Tuple3[A, B, C]], + L, E, A, B, C any](a GA, b GB, c GC) GTABC { + return apply.SequenceT3( + Map[GA, func(E) ET.Either[L, func(B) func(C) T.Tuple3[A, B, C]], L, E, A, func(B) func(C) T.Tuple3[A, B, C]], + Ap[GB, func(E) ET.Either[L, func(C) T.Tuple3[A, B, C]], func(E) ET.Either[L, func(B) func(C) T.Tuple3[A, B, C]], L, E, B, func(C) T.Tuple3[A, B, C]], + Ap[GC, GTABC, func(E) ET.Either[L, func(C) T.Tuple3[A, B, C]], L, E, C, T.Tuple3[A, B, C]], + + a, b, c, + ) +} + +func SequenceT4[ + GA ~func(E) ET.Either[L, A], + GB ~func(E) ET.Either[L, B], + GC ~func(E) ET.Either[L, C], + GD ~func(E) ET.Either[L, D], + GTABCD ~func(E) ET.Either[L, T.Tuple4[A, B, C, D]], + L, E, A, B, C, D any](a GA, b GB, c GC, d GD) GTABCD { + return apply.SequenceT4( + Map[GA, func(E) ET.Either[L, func(B) func(C) func(D) T.Tuple4[A, B, C, D]], L, E, A, func(B) func(C) func(D) T.Tuple4[A, B, C, D]], + Ap[GB, func(E) ET.Either[L, func(C) func(D) T.Tuple4[A, B, C, D]], func(E) ET.Either[L, func(B) func(C) func(D) T.Tuple4[A, B, C, D]], L, E, B, func(C) func(D) T.Tuple4[A, B, C, D]], + Ap[GC, func(E) ET.Either[L, func(D) T.Tuple4[A, B, C, D]], func(E) ET.Either[L, func(C) func(D) T.Tuple4[A, B, C, D]], L, E, C, func(D) T.Tuple4[A, B, C, D]], + Ap[GD, GTABCD, func(E) ET.Either[L, func(D) T.Tuple4[A, B, C, D]], L, E, D, T.Tuple4[A, B, C, D]], + + a, b, c, d, + ) +} diff --git a/v2/readereither/reader.go b/v2/readereither/reader.go new file mode 100644 index 0000000..1c1ba71 --- /dev/null +++ b/v2/readereither/reader.go @@ -0,0 +1,174 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readereither + +import ( + ET "github.com/IBM/fp-go/v2/either" + "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/internal/eithert" + "github.com/IBM/fp-go/v2/internal/fromeither" + "github.com/IBM/fp-go/v2/internal/fromreader" + "github.com/IBM/fp-go/v2/internal/functor" + "github.com/IBM/fp-go/v2/internal/readert" + "github.com/IBM/fp-go/v2/reader" +) + +func FromEither[E, L, A any](e Either[L, A]) ReaderEither[E, L, A] { + return reader.Of[E](e) +} + +func RightReader[L, E, A any](r Reader[E, A]) ReaderEither[E, L, A] { + return eithert.RightF(reader.MonadMap[E, A, Either[L, A]], r) +} + +func LeftReader[A, E, L any](l Reader[E, L]) ReaderEither[E, L, A] { + return eithert.LeftF(reader.MonadMap[E, L, Either[L, A]], l) +} + +func Left[E, A, L any](l L) ReaderEither[E, L, A] { + return eithert.Left(reader.Of[E, Either[L, A]], l) +} + +func Right[E, L, A any](r A) ReaderEither[E, L, A] { + return eithert.Right(reader.Of[E, Either[L, A]], r) +} + +func FromReader[E, L, A any](r Reader[E, A]) ReaderEither[E, L, A] { + return RightReader[L](r) +} + +func MonadMap[E, L, A, B any](fa ReaderEither[E, L, A], f func(A) B) ReaderEither[E, L, B] { + return readert.MonadMap[ReaderEither[E, L, A], ReaderEither[E, L, B]](ET.MonadMap[L, A, B], fa, f) +} + +func Map[E, L, A, B any](f func(A) B) func(ReaderEither[E, L, A]) ReaderEither[E, L, B] { + return readert.Map[ReaderEither[E, L, A], ReaderEither[E, L, B]](ET.Map[L, A, B], f) +} + +func MonadChain[E, L, A, B any](ma ReaderEither[E, L, A], f func(A) ReaderEither[E, L, B]) ReaderEither[E, L, B] { + return readert.MonadChain[ReaderEither[E, L, A], ReaderEither[E, L, B]](ET.MonadChain[L, A, B], ma, f) +} + +func Chain[E, L, A, B any](f func(A) ReaderEither[E, L, B]) func(ReaderEither[E, L, A]) ReaderEither[E, L, B] { + return readert.Chain[ReaderEither[E, L, A], ReaderEither[E, L, B]](ET.Chain[L, A, B], f) +} + +func Of[E, L, A any](a A) ReaderEither[E, L, A] { + return readert.MonadOf[ReaderEither[E, L, A]](ET.Of[L, A], a) +} + +func MonadAp[E, L, A, B any](fab ReaderEither[E, L, func(A) B], fa ReaderEither[E, L, A]) ReaderEither[E, L, B] { + return readert.MonadAp[ReaderEither[E, L, A], ReaderEither[E, L, B], ReaderEither[E, L, func(A) B], E, A](ET.MonadAp[B, L, A], fab, fa) +} + +func Ap[B, E, L, A any](fa ReaderEither[E, L, A]) func(ReaderEither[E, L, func(A) B]) ReaderEither[E, L, B] { + return readert.Ap[ReaderEither[E, L, A], ReaderEither[E, L, B], ReaderEither[E, L, func(A) B], E, A](ET.Ap[B, L, A], fa) +} + +func FromPredicate[E, L, A any](pred func(A) bool, onFalse func(A) L) func(A) ReaderEither[E, L, A] { + return fromeither.FromPredicate(FromEither[E, L, A], pred, onFalse) +} + +func Fold[E, L, A, B any](onLeft func(L) Reader[E, B], onRight func(A) Reader[E, B]) func(ReaderEither[E, L, A]) Reader[E, B] { + return eithert.MatchE(reader.MonadChain[E, Either[L, A], B], onLeft, onRight) +} + +func GetOrElse[E, L, A any](onLeft func(L) Reader[E, A]) func(ReaderEither[E, L, A]) Reader[E, A] { + return eithert.GetOrElse(reader.MonadChain[E, Either[L, A], A], reader.Of[E, A], onLeft) +} + +func OrElse[E, L1, A, L2 any](onLeft func(L1) ReaderEither[E, L2, A]) func(ReaderEither[E, L1, A]) ReaderEither[E, L2, A] { + return eithert.OrElse(reader.MonadChain[E, Either[L1, A], Either[L2, A]], reader.Of[E, Either[L2, A]], onLeft) +} + +func OrLeft[A, L1, E, L2 any](onLeft func(L1) Reader[E, L2]) func(ReaderEither[E, L1, A]) ReaderEither[E, L2, A] { + return eithert.OrLeft( + reader.MonadChain[E, Either[L1, A], Either[L2, A]], + reader.MonadMap[E, L2, Either[L2, A]], + reader.Of[E, Either[L2, A]], + onLeft, + ) +} + +func Ask[E, L any]() ReaderEither[E, L, E] { + return fromreader.Ask(FromReader[E, L, E])() +} + +func Asks[L, E, A any](r Reader[E, A]) ReaderEither[E, L, A] { + return fromreader.Asks(FromReader[E, L, A])(r) +} + +func MonadChainEitherK[E, L, A, B any](ma ReaderEither[E, L, A], f func(A) Either[L, B]) ReaderEither[E, L, B] { + return fromeither.MonadChainEitherK( + MonadChain[E, L, A, B], + FromEither[E, L, B], + ma, + f, + ) +} + +func ChainEitherK[E, L, A, B any](f func(A) Either[L, B]) func(ma ReaderEither[E, L, A]) ReaderEither[E, L, B] { + return fromeither.ChainEitherK( + Chain[E, L, A, B], + FromEither[E, L, B], + f, + ) +} + +func ChainOptionK[E, A, B, L any](onNone func() L) func(func(A) Option[B]) func(ReaderEither[E, L, A]) ReaderEither[E, L, B] { + return fromeither.ChainOptionK(MonadChain[E, L, A, B], FromEither[E, L, B], onNone) +} + +func Flatten[E, L, A any](mma ReaderEither[E, L, ReaderEither[E, L, A]]) ReaderEither[E, L, A] { + return MonadChain(mma, function.Identity[ReaderEither[E, L, A]]) +} + +func MonadBiMap[E, E1, E2, A, B any](fa ReaderEither[E, E1, A], f func(E1) E2, g func(A) B) ReaderEither[E, E2, B] { + return eithert.MonadBiMap(reader.MonadMap[E, Either[E1, A], Either[E2, B]], fa, f, g) +} + +// BiMap maps a pair of functions over the two type arguments of the bifunctor. +func BiMap[E, E1, E2, A, B any](f func(E1) E2, g func(A) B) func(ReaderEither[E, E1, A]) ReaderEither[E, E2, B] { + return eithert.BiMap(reader.Map[E, Either[E1, A], Either[E2, B]], f, g) +} + +// Local changes the value of the local context during the execution of the action `ma` (similar to `Contravariant`'s +// `contramap`). +func Local[E, A, R2, R1 any](f func(R2) R1) func(ReaderEither[R1, E, A]) ReaderEither[R2, E, A] { + return reader.Local[R2, R1, Either[E, A]](f) +} + +// Read applies a context to a reader to obtain its value +func Read[E1, A, E any](e E) func(ReaderEither[E, E1, A]) Either[E1, A] { + return reader.Read[E, Either[E1, A]](e) +} + +func MonadFlap[L, E, A, B any](fab ReaderEither[L, E, func(A) B], a A) ReaderEither[L, E, B] { + return functor.MonadFlap(MonadMap[L, E, func(A) B, B], fab, a) +} + +func Flap[L, E, B, A any](a A) func(ReaderEither[L, E, func(A) B]) ReaderEither[L, E, B] { + return functor.Flap(Map[L, E, func(A) B, B], a) +} + +func MonadMapLeft[C, E1, E2, A any](fa ReaderEither[C, E1, A], f func(E1) E2) ReaderEither[C, E2, A] { + return eithert.MonadMapLeft(reader.MonadMap[C, Either[E1, A], Either[E2, A]], fa, f) +} + +// MapLeft applies a mapping function to the error channel +func MapLeft[C, E1, E2, A any](f func(E1) E2) func(ReaderEither[C, E1, A]) ReaderEither[C, E2, A] { + return eithert.MapLeft(reader.Map[C, Either[E1, A], Either[E2, A]], f) +} diff --git a/v2/readereither/reader_test.go b/v2/readereither/reader_test.go new file mode 100644 index 0000000..f743ffd --- /dev/null +++ b/v2/readereither/reader_test.go @@ -0,0 +1,59 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readereither + +import ( + "testing" + + ET "github.com/IBM/fp-go/v2/either" + F "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/internal/utils" + "github.com/stretchr/testify/assert" +) + +type MyContext string + +const defaultContext MyContext = "default" + +func TestMap(t *testing.T) { + + g := F.Pipe1( + Of[MyContext, error](1), + Map[MyContext, error](utils.Double), + ) + + assert.Equal(t, ET.Of[error](2), g(defaultContext)) + +} + +func TestAp(t *testing.T) { + g := F.Pipe1( + Of[MyContext, error](utils.Double), + Ap[int](Of[MyContext, error](1)), + ) + assert.Equal(t, ET.Of[error](2), g(defaultContext)) + +} + +func TestFlatten(t *testing.T) { + + g := F.Pipe1( + Of[MyContext, string](Of[MyContext, string]("a")), + Flatten[MyContext, string, string], + ) + + assert.Equal(t, ET.Of[string]("a"), g(defaultContext)) +} diff --git a/v2/readereither/sequence.go b/v2/readereither/sequence.go new file mode 100644 index 0000000..834f7b5 --- /dev/null +++ b/v2/readereither/sequence.go @@ -0,0 +1,69 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readereither + +import ( + G "github.com/IBM/fp-go/v2/readereither/generic" + T "github.com/IBM/fp-go/v2/tuple" +) + +// SequenceT converts n inputs of higher kinded types into a higher kinded types of n strongly typed values, represented as a tuple + +func SequenceT1[L, E, A any](a ReaderEither[E, L, A]) ReaderEither[E, L, T.Tuple1[A]] { + return G.SequenceT1[ + ReaderEither[E, L, A], + ReaderEither[E, L, T.Tuple1[A]], + ](a) +} + +func SequenceT2[L, E, A, B any]( + a ReaderEither[E, L, A], + b ReaderEither[E, L, B], +) ReaderEither[E, L, T.Tuple2[A, B]] { + return G.SequenceT2[ + ReaderEither[E, L, A], + ReaderEither[E, L, B], + ReaderEither[E, L, T.Tuple2[A, B]], + ](a, b) +} + +func SequenceT3[L, E, A, B, C any]( + a ReaderEither[E, L, A], + b ReaderEither[E, L, B], + c ReaderEither[E, L, C], +) ReaderEither[E, L, T.Tuple3[A, B, C]] { + return G.SequenceT3[ + ReaderEither[E, L, A], + ReaderEither[E, L, B], + ReaderEither[E, L, C], + ReaderEither[E, L, T.Tuple3[A, B, C]], + ](a, b, c) +} + +func SequenceT4[L, E, A, B, C, D any]( + a ReaderEither[E, L, A], + b ReaderEither[E, L, B], + c ReaderEither[E, L, C], + d ReaderEither[E, L, D], +) ReaderEither[E, L, T.Tuple4[A, B, C, D]] { + return G.SequenceT4[ + ReaderEither[E, L, A], + ReaderEither[E, L, B], + ReaderEither[E, L, C], + ReaderEither[E, L, D], + ReaderEither[E, L, T.Tuple4[A, B, C, D]], + ](a, b, c, d) +} diff --git a/v2/readereither/sequence_test.go b/v2/readereither/sequence_test.go new file mode 100644 index 0000000..1601f9a --- /dev/null +++ b/v2/readereither/sequence_test.go @@ -0,0 +1,92 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readereither + +import ( + "fmt" + "testing" + + E "github.com/IBM/fp-go/v2/either" + T "github.com/IBM/fp-go/v2/tuple" + "github.com/stretchr/testify/assert" +) + +var ( + errFoo = fmt.Errorf("error") +) + +func TestSequenceT1(t *testing.T) { + + t1 := Of[MyContext, error]("s1") + e1 := Left[MyContext, string](errFoo) + + res1 := SequenceT1(t1) + assert.Equal(t, E.Of[error](T.MakeTuple1("s1")), res1(defaultContext)) + + res2 := SequenceT1(e1) + assert.Equal(t, E.Left[T.Tuple1[string]](errFoo), res2(defaultContext)) +} + +func TestSequenceT2(t *testing.T) { + + t1 := Of[MyContext, error]("s1") + e1 := Left[MyContext, string](errFoo) + t2 := Of[MyContext, error](2) + e2 := Left[MyContext, int](errFoo) + + res1 := SequenceT2(t1, t2) + assert.Equal(t, E.Of[error](T.MakeTuple2("s1", 2)), res1(defaultContext)) + + res2 := SequenceT2(e1, t2) + assert.Equal(t, E.Left[T.Tuple2[string, int]](errFoo), res2(defaultContext)) + + res3 := SequenceT2(t1, e2) + assert.Equal(t, E.Left[T.Tuple2[string, int]](errFoo), res3(defaultContext)) +} + +func TestSequenceT3(t *testing.T) { + + t1 := Of[MyContext, error]("s1") + e1 := Left[MyContext, string](errFoo) + t2 := Of[MyContext, error](2) + e2 := Left[MyContext, int](errFoo) + t3 := Of[MyContext, error](true) + e3 := Left[MyContext, bool](errFoo) + + res1 := SequenceT3(t1, t2, t3) + assert.Equal(t, E.Of[error](T.MakeTuple3("s1", 2, true)), res1(defaultContext)) + + res2 := SequenceT3(e1, t2, t3) + assert.Equal(t, E.Left[T.Tuple3[string, int, bool]](errFoo), res2(defaultContext)) + + res3 := SequenceT3(t1, e2, t3) + assert.Equal(t, E.Left[T.Tuple3[string, int, bool]](errFoo), res3(defaultContext)) + + res4 := SequenceT3(t1, t2, e3) + assert.Equal(t, E.Left[T.Tuple3[string, int, bool]](errFoo), res4(defaultContext)) +} + +func TestSequenceT4(t *testing.T) { + + t1 := Of[MyContext, error]("s1") + t2 := Of[MyContext, error](2) + t3 := Of[MyContext, error](true) + t4 := Of[MyContext, error](1.0) + + res := SequenceT4(t1, t2, t3, t4) + + assert.Equal(t, E.Of[error](T.MakeTuple4("s1", 2, true, 1.0)), res(defaultContext)) +} diff --git a/v2/readereither/types.go b/v2/readereither/types.go new file mode 100644 index 0000000..6f06338 --- /dev/null +++ b/v2/readereither/types.go @@ -0,0 +1,29 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readereither + +import ( + "github.com/IBM/fp-go/v2/either" + "github.com/IBM/fp-go/v2/option" + "github.com/IBM/fp-go/v2/reader" +) + +type ( + Option[A any] = option.Option[A] + Either[E, A any] = either.Either[E, A] + Reader[R, A any] = reader.Reader[R, A] + ReaderEither[R, E, A any] = Reader[R, Either[E, A]] +) diff --git a/v2/readerio/ap.go b/v2/readerio/ap.go new file mode 100644 index 0000000..0b8f52a --- /dev/null +++ b/v2/readerio/ap.go @@ -0,0 +1,40 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readerio + +import ( + G "github.com/IBM/fp-go/v2/readerio/generic" +) + +// MonadApFirst combines two effectful actions, keeping only the result of the first. +func MonadApFirst[A, R, B any](first ReaderIO[R, A], second ReaderIO[R, B]) ReaderIO[R, A] { + return G.MonadApFirst[ReaderIO[R, A], ReaderIO[R, B], ReaderIO[R, func(B) A]](first, second) +} + +// ApFirst combines two effectful actions, keeping only the result of the first. +func ApFirst[A, R, B any](second ReaderIO[R, B]) func(ReaderIO[R, A]) ReaderIO[R, A] { + return G.ApFirst[ReaderIO[R, A], ReaderIO[R, B], ReaderIO[R, func(B) A]](second) +} + +// MonadApSecond combines two effectful actions, keeping only the result of the second. +func MonadApSecond[A, R, B any](first ReaderIO[R, A], second ReaderIO[R, B]) ReaderIO[R, B] { + return G.MonadApSecond[ReaderIO[R, A], ReaderIO[R, B], ReaderIO[R, func(B) B]](first, second) +} + +// ApSecond combines two effectful actions, keeping only the result of the second. +func ApSecond[A, R, B any](second ReaderIO[R, B]) func(ReaderIO[R, A]) ReaderIO[R, B] { + return G.ApSecond[ReaderIO[R, A], ReaderIO[R, B], ReaderIO[R, func(B) B]](second) +} diff --git a/v2/readerio/array.go b/v2/readerio/array.go new file mode 100644 index 0000000..bde0c66 --- /dev/null +++ b/v2/readerio/array.go @@ -0,0 +1,35 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readerio + +import ( + G "github.com/IBM/fp-go/v2/readerio/generic" +) + +// TraverseArray transforms an array +func TraverseArray[R, A, B any](f func(A) ReaderIO[R, B]) func([]A) ReaderIO[R, []B] { + return G.TraverseArray[ReaderIO[R, B], ReaderIO[R, []B], IO[B], IO[[]B], []A](f) +} + +// TraverseArrayWithIndex transforms an array +func TraverseArrayWithIndex[R, A, B any](f func(int, A) ReaderIO[R, B]) func([]A) ReaderIO[R, []B] { + return G.TraverseArrayWithIndex[ReaderIO[R, B], ReaderIO[R, []B], IO[B], IO[[]B], []A](f) +} + +// SequenceArray converts a homogeneous sequence of Readers into a Reader of sequence +func SequenceArray[R, A any](ma []ReaderIO[R, A]) ReaderIO[R, []A] { + return G.SequenceArray[ReaderIO[R, A], ReaderIO[R, []A]](ma) +} diff --git a/v2/readerio/array_test.go b/v2/readerio/array_test.go new file mode 100644 index 0000000..62f1ef7 --- /dev/null +++ b/v2/readerio/array_test.go @@ -0,0 +1,34 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readerio + +import ( + "context" + "testing" + + A "github.com/IBM/fp-go/v2/array" + F "github.com/IBM/fp-go/v2/function" + "github.com/stretchr/testify/assert" +) + +func TestTraverseArray(t *testing.T) { + f := TraverseArray(func(a string) ReaderIO[context.Context, string] { + return Of[context.Context](a + a) + }) + ctx := context.Background() + assert.Equal(t, A.Empty[string](), F.Pipe1(A.Empty[string](), f)(ctx)()) + assert.Equal(t, []string{"aa", "bb"}, F.Pipe1([]string{"a", "b"}, f)(ctx)()) +} diff --git a/v2/readerio/bind.go b/v2/readerio/bind.go new file mode 100644 index 0000000..85b920c --- /dev/null +++ b/v2/readerio/bind.go @@ -0,0 +1,89 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readerio + +import ( + "github.com/IBM/fp-go/v2/internal/apply" + "github.com/IBM/fp-go/v2/internal/chain" + "github.com/IBM/fp-go/v2/internal/functor" +) + +// Bind creates an empty context of type [S] to be used with the [Bind] operation +func Do[R, S any]( + empty S, +) ReaderIO[R, S] { + return Of[R](empty) +} + +// Bind attaches the result of a computation to a context [S1] to produce a context [S2] +func Bind[R, S1, S2, T any]( + setter func(T) func(S1) S2, + f func(S1) ReaderIO[R, T], +) func(ReaderIO[R, S1]) ReaderIO[R, S2] { + return chain.Bind( + Chain[R, S1, S2], + Map[R, T, S2], + setter, + f, + ) +} + +// Let attaches the result of a computation to a context [S1] to produce a context [S2] +func Let[R, S1, S2, T any]( + setter func(T) func(S1) S2, + f func(S1) T, +) func(ReaderIO[R, S1]) ReaderIO[R, S2] { + return functor.Let( + Map[R, S1, S2], + setter, + f, + ) +} + +// LetTo attaches the a value to a context [S1] to produce a context [S2] +func LetTo[R, S1, S2, T any]( + setter func(T) func(S1) S2, + b T, +) func(ReaderIO[R, S1]) ReaderIO[R, S2] { + return functor.LetTo( + Map[R, S1, S2], + setter, + b, + ) +} + +// BindTo initializes a new state [S1] from a value [T] +func BindTo[R, S1, T any]( + setter func(T) S1, +) func(ReaderIO[R, T]) ReaderIO[R, S1] { + return chain.BindTo( + Map[R, T, S1], + setter, + ) +} + +// ApS attaches a value to a context [S1] to produce a context [S2] by considering the context and the value concurrently +func ApS[R, S1, S2, T any]( + setter func(T) func(S1) S2, + fa ReaderIO[R, T], +) func(ReaderIO[R, S1]) ReaderIO[R, S2] { + return apply.ApS( + Ap[S2, R, T], + Map[R, S1, func(T) S2], + setter, + fa, + ) +} diff --git a/v2/readerio/bind_test.go b/v2/readerio/bind_test.go new file mode 100644 index 0000000..bd01630 --- /dev/null +++ b/v2/readerio/bind_test.go @@ -0,0 +1,57 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readerio + +import ( + "context" + "testing" + + F "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/internal/utils" + "github.com/stretchr/testify/assert" +) + +func getLastName(s utils.Initial) ReaderIO[context.Context, string] { + return Of[context.Context]("Doe") +} + +func getGivenName(s utils.WithLastName) ReaderIO[context.Context, string] { + return Of[context.Context]("John") +} + +func TestBind(t *testing.T) { + + res := F.Pipe3( + Do[context.Context](utils.Empty), + Bind(utils.SetLastName, getLastName), + Bind(utils.SetGivenName, getGivenName), + Map[context.Context](utils.GetFullName), + ) + + assert.Equal(t, res(context.Background())(), "John Doe") +} + +func TestApS(t *testing.T) { + + res := F.Pipe3( + Do[context.Context](utils.Empty), + ApS(utils.SetLastName, Of[context.Context]("Doe")), + ApS(utils.SetGivenName, Of[context.Context]("John")), + Map[context.Context](utils.GetFullName), + ) + + assert.Equal(t, res(context.Background())(), "John Doe") +} diff --git a/v2/readerio/eq.go b/v2/readerio/eq.go new file mode 100644 index 0000000..81fd144 --- /dev/null +++ b/v2/readerio/eq.go @@ -0,0 +1,26 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readerio + +import ( + EQ "github.com/IBM/fp-go/v2/eq" + G "github.com/IBM/fp-go/v2/readerio/generic" +) + +// Eq implements the equals predicate for values contained in the IO monad +func Eq[R, A any](e EQ.Eq[A]) func(r R) EQ.Eq[ReaderIO[R, A]] { + return G.Eq[ReaderIO[R, A]](e) +} diff --git a/v2/readerio/from.go b/v2/readerio/from.go new file mode 100644 index 0000000..314537c --- /dev/null +++ b/v2/readerio/from.go @@ -0,0 +1,39 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readerio + +import ( + "github.com/IBM/fp-go/v2/reader" +) + +// these functions From a golang function with the context as the firsr parameter into a either reader with the context as the last parameter +// this goes back to the advice in https://pkg.go.dev/context to put the context as a first parameter as a convention + +func From0[F ~func(R) IO[A], R, A any](f func(R) IO[A]) func() ReaderIO[R, A] { + return reader.From0(f) +} + +func From1[F ~func(R, T1) IO[A], R, T1, A any](f func(R, T1) IO[A]) func(T1) ReaderIO[R, A] { + return reader.From1(f) +} + +func From2[F ~func(R, T1, T2) IO[A], R, T1, T2, A any](f func(R, T1, T2) IO[A]) func(T1, T2) ReaderIO[R, A] { + return reader.From2(f) +} + +func From3[F ~func(R, T1, T2, T3) IO[A], R, T1, T2, T3, A any](f func(R, T1, T2, T3) IO[A]) func(T1, T2, T3) ReaderIO[R, A] { + return reader.From3(f) +} diff --git a/v2/readerio/generic/ap.go b/v2/readerio/generic/ap.go new file mode 100644 index 0000000..249e070 --- /dev/null +++ b/v2/readerio/generic/ap.go @@ -0,0 +1,62 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + G "github.com/IBM/fp-go/v2/internal/apply" +) + +// MonadApFirst combines two effectful actions, keeping only the result of the first. +func MonadApFirst[GRA ~func(R) GA, GRB ~func(R) GB, GRBA ~func(R) GBA, GA ~func() A, GB ~func() B, GBA ~func() func(B) A, R, A, B any](first GRA, second GRB) GRA { + return G.MonadApFirst( + MonadAp[GRB, GRA, GRBA, GB, GA, GBA, R, B, A], + MonadMap[GRA, GRBA, GA, GBA, R, A, func(B) A], + + first, + second, + ) +} + +// ApFirst combines two effectful actions, keeping only the result of the first. +func ApFirst[GRA ~func(R) GA, GRB ~func(R) GB, GRBA ~func(R) GBA, GA ~func() A, GB ~func() B, GBA ~func() func(B) A, R, A, B any](second GRB) func(GRA) GRA { + return G.ApFirst( + MonadAp[GRB, GRA, GRBA, GB, GA, GBA, R, B, A], + MonadMap[GRA, GRBA, GA, GBA, R, A, func(B) A], + + second, + ) +} + +// MonadApSecond combines two effectful actions, keeping only the result of the second. +func MonadApSecond[GRA ~func(R) GA, GRB ~func(R) GB, GRBB ~func(R) GBB, GA ~func() A, GB ~func() B, GBB ~func() func(B) B, R, A, B any](first GRA, second GRB) GRB { + return G.MonadApSecond( + MonadAp[GRB, GRB, GRBB, GB, GB, GBB, R, B, B], + MonadMap[GRA, GRBB, GA, GBB, R, A, func(B) B], + + first, + second, + ) +} + +// ApSecond combines two effectful actions, keeping only the result of the second. +func ApSecond[GRA ~func(R) GA, GRB ~func(R) GB, GRBB ~func(R) GBB, GA ~func() A, GB ~func() B, GBB ~func() func(B) B, R, A, B any](second GRB) func(GRA) GRB { + return G.ApSecond( + MonadAp[GRB, GRB, GRBB, GB, GB, GBB, R, B, B], + MonadMap[GRA, GRBB, GA, GBB, R, A, func(B) B], + + second, + ) +} diff --git a/v2/readerio/generic/array.go b/v2/readerio/generic/array.go new file mode 100644 index 0000000..c625b6f --- /dev/null +++ b/v2/readerio/generic/array.go @@ -0,0 +1,59 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + F "github.com/IBM/fp-go/v2/function" + RA "github.com/IBM/fp-go/v2/internal/array" +) + +// MonadTraverseArray transforms an array +func MonadTraverseArray[GB ~func(E) GIOB, GBS ~func(E) GIOBS, GIOB ~func() B, GIOBS ~func() BBS, AAS ~[]A, BBS ~[]B, E, A, B any](ma AAS, f func(A) GB) GBS { + return RA.MonadTraverse[AAS]( + Of[GBS, GIOBS, E, BBS], + Map[GBS, func(E) func() func(B) BBS, GIOBS, func() func(B) BBS, E, BBS, func(B) BBS], + Ap[GB, GBS, func(E) func() func(B) BBS, GIOB, GIOBS, func() func(B) BBS, E, B, BBS], + + ma, f, + ) +} + +// TraverseArray transforms an array +func TraverseArray[GB ~func(E) GIOB, GBS ~func(E) GIOBS, GIOB ~func() B, GIOBS ~func() BBS, AAS ~[]A, BBS ~[]B, E, A, B any](f func(A) GB) func(AAS) GBS { + return RA.Traverse[AAS]( + Of[GBS, GIOBS, E, BBS], + Map[GBS, func(E) func() func(B) BBS, GIOBS, func() func(B) BBS, E, BBS, func(B) BBS], + Ap[GB, GBS, func(E) func() func(B) BBS, GIOB, GIOBS, func() func(B) BBS, E, B, BBS], + + f, + ) +} + +// TraverseArrayWithIndex transforms an array +func TraverseArrayWithIndex[GB ~func(E) GIOB, GBS ~func(E) GIOBS, GIOB ~func() B, GIOBS ~func() BBS, AAS ~[]A, BBS ~[]B, E, A, B any](f func(int, A) GB) func(AAS) GBS { + return RA.TraverseWithIndex[AAS]( + Of[GBS, GIOBS, E, BBS], + Map[GBS, func(E) func() func(B) BBS, GIOBS, func() func(B) BBS, E, BBS, func(B) BBS], + Ap[GB, GBS, func(E) func() func(B) BBS, GIOB, GIOBS, func() func(B) BBS, E, B, BBS], + + f, + ) +} + +// SequenceArray converts a homogeneous sequence of either into an either of sequence +func SequenceArray[GA ~func(E) GIOA, GAS ~func(E) GIOAS, GIOA ~func() A, GIOAS ~func() AAS, AAS ~[]A, GAAS ~[]GA, E, A any](ma GAAS) GAS { + return MonadTraverseArray[GA, GAS](ma, F.Identity[GA]) +} diff --git a/v2/readerio/generic/eq.go b/v2/readerio/generic/eq.go new file mode 100644 index 0000000..6475934 --- /dev/null +++ b/v2/readerio/generic/eq.go @@ -0,0 +1,37 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + EQ "github.com/IBM/fp-go/v2/eq" + G "github.com/IBM/fp-go/v2/internal/eq" +) + +// Eq implements the equals predicate for values contained in the IO monad +func Eq[GEA ~func(R) GIOA, GIOA ~func() A, R, A any](e EQ.Eq[A]) func(r R) EQ.Eq[GEA] { + // comparator for the monad + eq := G.Eq( + MonadMap[GEA, func(R) func() func(A) bool, GIOA, func() func(A) bool, R, A, func(A) bool], + MonadAp[GEA, func(R) func() bool, func(R) func() func(A) bool], + e, + ) + // eagerly execute + return func(ctx R) EQ.Eq[GEA] { + return EQ.FromEquals(func(l, r GEA) bool { + return eq(l, r)(ctx)() + }) + } +} diff --git a/v2/readerio/generic/reader.go b/v2/readerio/generic/reader.go new file mode 100644 index 0000000..325a893 --- /dev/null +++ b/v2/readerio/generic/reader.go @@ -0,0 +1,166 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + "sync" + + F "github.com/IBM/fp-go/v2/function" + FIO "github.com/IBM/fp-go/v2/internal/fromio" + FR "github.com/IBM/fp-go/v2/internal/fromreader" + FC "github.com/IBM/fp-go/v2/internal/functor" + "github.com/IBM/fp-go/v2/internal/readert" + IO "github.com/IBM/fp-go/v2/io/generic" + R "github.com/IBM/fp-go/v2/reader/generic" +) + +func FromIO[GEA ~func(E) GIOA, GIOA ~func() A, E, A any](t GIOA) GEA { + return R.Of[GEA, E](t) +} + +func FromReader[GA ~func(E) A, GEA ~func(E) GIOA, GIOA ~func() A, E, A any](r GA) GEA { + return readert.MonadFromReader[GA, GEA](IO.Of[GIOA, A], r) +} + +func MonadMap[GEA ~func(E) GIOA, GEB ~func(E) GIOB, GIOA ~func() A, GIOB ~func() B, E, A, B any](fa GEA, f func(A) B) GEB { + return readert.MonadMap[GEA, GEB](IO.MonadMap[GIOA, GIOB, A, B], fa, f) +} + +func Map[GEA ~func(E) GIOA, GEB ~func(E) GIOB, GIOA ~func() A, GIOB ~func() B, E, A, B any](f func(A) B) func(GEA) GEB { + return readert.Map[GEA, GEB](IO.Map[GIOA, GIOB, A, B], f) +} + +func MonadChain[GEA ~func(E) GIOA, GEB ~func(E) GIOB, GIOA ~func() A, GIOB ~func() B, E, A, B any](ma GEA, f func(A) GEB) GEB { + return readert.MonadChain(IO.MonadChain[GIOA, GIOB, A, B], ma, f) +} + +func Chain[GEA ~func(E) GIOA, GEB ~func(E) GIOB, GIOA ~func() A, GIOB ~func() B, E, A, B any](f func(A) GEB) func(GEA) GEB { + return F.Bind2nd(MonadChain[GEA, GEB, GIOA, GIOB, E, A, B], f) +} + +func Of[GEA ~func(E) GIOA, GIOA ~func() A, E, A any](a A) GEA { + return readert.MonadOf[GEA](IO.Of[GIOA, A], a) +} + +func MonadAp[GEA ~func(E) GIOA, GEB ~func(E) GIOB, GEFAB ~func(E) GIOFAB, GIOA ~func() A, GIOB ~func() B, GIOFAB ~func() func(A) B, E, A, B any](fab GEFAB, fa GEA) GEB { + return readert.MonadAp[GEA, GEB, GEFAB, E, A](IO.MonadAp[GIOA, GIOB, GIOFAB, A, B], fab, fa) +} + +func Ap[GEA ~func(E) GIOA, GEB ~func(E) GIOB, GEFAB ~func(E) GIOFAB, GIOA ~func() A, GIOB ~func() B, GIOFAB ~func() func(A) B, E, A, B any](fa GEA) func(GEFAB) GEB { + return F.Bind2nd(MonadAp[GEA, GEB, GEFAB, GIOA, GIOB, GIOFAB, E, A, B], fa) +} + +func MonadApSeq[GEA ~func(E) GIOA, GEB ~func(E) GIOB, GEFAB ~func(E) GIOFAB, GIOA ~func() A, GIOB ~func() B, GIOFAB ~func() func(A) B, E, A, B any](fab GEFAB, fa GEA) GEB { + return readert.MonadAp[GEA, GEB, GEFAB, E, A](IO.MonadApSeq[GIOA, GIOB, GIOFAB, A, B], fab, fa) +} + +func ApSeq[GEA ~func(E) GIOA, GEB ~func(E) GIOB, GEFAB ~func(E) GIOFAB, GIOA ~func() A, GIOB ~func() B, GIOFAB ~func() func(A) B, E, A, B any](fa GEA) func(GEFAB) GEB { + return F.Bind2nd(MonadApSeq[GEA, GEB, GEFAB, GIOA, GIOB, GIOFAB, E, A, B], fa) +} + +func MonadApPar[GEA ~func(E) GIOA, GEB ~func(E) GIOB, GEFAB ~func(E) GIOFAB, GIOA ~func() A, GIOB ~func() B, GIOFAB ~func() func(A) B, E, A, B any](fab GEFAB, fa GEA) GEB { + return readert.MonadAp[GEA, GEB, GEFAB, E, A](IO.MonadApPar[GIOA, GIOB, GIOFAB, A, B], fab, fa) +} + +func ApPar[GEA ~func(E) GIOA, GEB ~func(E) GIOB, GEFAB ~func(E) GIOFAB, GIOA ~func() A, GIOB ~func() B, GIOFAB ~func() func(A) B, E, A, B any](fa GEA) func(GEFAB) GEB { + return F.Bind2nd(MonadApPar[GEA, GEB, GEFAB, GIOA, GIOB, GIOFAB, E, A, B], fa) +} + +func Ask[GEE ~func(E) GIOE, GIOE ~func() E, E any]() GEE { + return FR.Ask(FromReader[func(E) E, GEE, GIOE, E, E])() +} + +func Asks[GA ~func(E) A, GEA ~func(E) GIOA, GIOA ~func() A, E, A any](r GA) GEA { + return FR.Asks(FromReader[GA, GEA, GIOA, E, A])(r) +} + +func MonadChainIOK[GEA ~func(E) GIOA, GEB ~func(E) GIOB, GIOA ~func() A, GIOB ~func() B, E, A, B any](ma GEA, f func(A) GIOB) GEB { + return FIO.MonadChainIOK( + MonadChain[GEA, GEB], + FromIO[GEB], + ma, f, + ) +} + +func ChainIOK[GEA ~func(E) GIOA, GEB ~func(E) GIOB, GIOA ~func() A, GIOB ~func() B, E, A, B any](f func(A) GIOB) func(GEA) GEB { + return FIO.ChainIOK( + Chain[GEA, GEB], + FromIO[GEB], + f, + ) +} + +func MonadChainFirstIOK[GEA ~func(E) GIOA, GEB ~func(E) GIOB, GIOA ~func() A, GIOB ~func() B, E, A, B any](ma GEA, f func(A) GIOB) GEA { + return FIO.MonadChainFirstIOK( + MonadChain[GEA, GEA], + MonadMap[GEB, GEA], + FromIO[GEB], + ma, f, + ) +} + +func ChainFirstIOK[GEA ~func(E) GIOA, GEB ~func(E) GIOB, GIOA ~func() A, GIOB ~func() B, E, A, B any](f func(A) GIOB) func(GEA) GEA { + return FIO.ChainFirstIOK( + Chain[GEA, GEA], + Map[GEB, GEA], + FromIO[GEB], + f, + ) +} + +// Defer creates an IO by creating a brand new IO via a generator function, each time +func Defer[GEA ~func(E) GA, GA ~func() A, E, A any](gen func() GEA) GEA { + return func(e E) GA { + return func() A { + return gen()(e)() + } + } +} + +// Memoize computes the value of the provided reader monad lazily but exactly once +// The context used to compute the value is the context of the first call, so do not use this +// method if the value has a functional dependency on the content of the context +func Memoize[GEA ~func(E) GA, GA ~func() A, E, A any](rdr GEA) GEA { + // synchronization primitives + var once sync.Once + var result A + // callback + gen := func(e E) func() { + return func() { + result = rdr(e)() + } + } + // returns our memoized wrapper + return func(e E) GA { + io := gen(e) + return func() A { + once.Do(io) + return result + } + } +} + +func Flatten[GEA ~func(R) GIOA, GGEA ~func(R) GIOEA, GIOA ~func() A, GIOEA ~func() GEA, R, A any](mma GGEA) GEA { + return MonadChain(mma, F.Identity[GEA]) +} + +func MonadFlap[GEFAB ~func(E) GIOFAB, GEB ~func(E) GIOB, GIOFAB ~func() func(A) B, GIOB ~func() B, E, A, B any](fab GEFAB, a A) GEB { + return FC.MonadFlap(MonadMap[GEFAB, GEB], fab, a) +} + +func Flap[GEFAB ~func(E) GIOFAB, GEB ~func(E) GIOB, GIOFAB ~func() func(A) B, GIOB ~func() B, E, A, B any](a A) func(GEFAB) GEB { + return FC.Flap(Map[GEFAB, GEB], a) +} diff --git a/v2/readerio/generic/sequence.go b/v2/readerio/generic/sequence.go new file mode 100644 index 0000000..c05c6cf --- /dev/null +++ b/v2/readerio/generic/sequence.go @@ -0,0 +1,93 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + "github.com/IBM/fp-go/v2/internal/apply" + T "github.com/IBM/fp-go/v2/tuple" +) + +// SequenceT converts n inputs of higher kinded types into a higher kinded types of n strongly typed values, represented as a tuple + +func SequenceT1[ + GA ~func(E) GIOA, + GTA ~func(E) GIOTA, + GIOA ~func() A, + GIOTA ~func() T.Tuple1[A], + E, A any](a GA) GTA { + return apply.SequenceT1( + Map[GA, GTA, GIOA, GIOTA, E, A, T.Tuple1[A]], + + a, + ) +} + +func SequenceT2[ + GA ~func(E) GIOA, + GB ~func(E) GIOB, + GTAB ~func(E) GIOTAB, + GIOA ~func() A, + GIOB ~func() B, + GIOTAB ~func() T.Tuple2[A, B], + E, A, B any](a GA, b GB) GTAB { + return apply.SequenceT2( + Map[GA, func(E) func() func(B) T.Tuple2[A, B], GIOA, func() func(B) T.Tuple2[A, B], E, A, func(B) T.Tuple2[A, B]], + Ap[GB, GTAB, func(E) func() func(B) T.Tuple2[A, B], GIOB, GIOTAB, func() func(B) T.Tuple2[A, B], E, B, T.Tuple2[A, B]], + + a, b, + ) +} + +func SequenceT3[ + GA ~func(E) GIOA, + GB ~func(E) GIOB, + GC ~func(E) GIOC, + GTABC ~func(E) GIOTABC, + GIOA ~func() A, + GIOB ~func() B, + GIOC ~func() C, + GIOTABC ~func() T.Tuple3[A, B, C], + E, A, B, C any](a GA, b GB, c GC) GTABC { + return apply.SequenceT3( + Map[GA, func(E) func() func(B) func(C) T.Tuple3[A, B, C], GIOA, func() func(B) func(C) T.Tuple3[A, B, C], E, A, func(B) func(C) T.Tuple3[A, B, C]], + Ap[GB, func(E) func() func(C) T.Tuple3[A, B, C], func(E) func() func(B) func(C) T.Tuple3[A, B, C], GIOB, func() func(C) T.Tuple3[A, B, C], func() func(B) func(C) T.Tuple3[A, B, C], E, B, func(C) T.Tuple3[A, B, C]], + Ap[GC, GTABC, func(E) func() func(C) T.Tuple3[A, B, C], GIOC, GIOTABC, func() func(C) T.Tuple3[A, B, C], E, C, T.Tuple3[A, B, C]], + + a, b, c, + ) +} + +func SequenceT4[ + GA ~func(E) GIOA, + GB ~func(E) GIOB, + GC ~func(E) GIOC, + GD ~func(E) GIOD, + GTABCD ~func(E) GIOTABCD, + GIOA ~func() A, + GIOB ~func() B, + GIOC ~func() C, + GIOD ~func() D, + GIOTABCD ~func() T.Tuple4[A, B, C, D], + E, A, B, C, D any](a GA, b GB, c GC, d GD) GTABCD { + return apply.SequenceT4( + Map[GA, func(E) func() func(B) func(C) func(D) T.Tuple4[A, B, C, D], GIOA, func() func(B) func(C) func(D) T.Tuple4[A, B, C, D], E, A, func(B) func(C) func(D) T.Tuple4[A, B, C, D]], + Ap[GB, func(E) func() func(C) func(D) T.Tuple4[A, B, C, D], func(E) func() func(B) func(C) func(D) T.Tuple4[A, B, C, D], GIOB, func() func(C) func(D) T.Tuple4[A, B, C, D], func() func(B) func(C) func(D) T.Tuple4[A, B, C, D], E, B, func(C) func(D) T.Tuple4[A, B, C, D]], + Ap[GC, func(E) func() func(D) T.Tuple4[A, B, C, D], func(E) func() func(C) func(D) T.Tuple4[A, B, C, D], GIOC, func() func(D) T.Tuple4[A, B, C, D], func() func(C) func(D) T.Tuple4[A, B, C, D], E, C, func(D) T.Tuple4[A, B, C, D]], + Ap[GD, GTABCD, func(E) func() func(D) T.Tuple4[A, B, C, D], GIOD, GIOTABCD, func() func(D) T.Tuple4[A, B, C, D], E, D, T.Tuple4[A, B, C, D]], + + a, b, c, d, + ) +} diff --git a/v2/readerio/reader.go b/v2/readerio/reader.go new file mode 100644 index 0000000..05f8b9e --- /dev/null +++ b/v2/readerio/reader.go @@ -0,0 +1,141 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readerio + +import ( + "sync" + + "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/internal/fromio" + "github.com/IBM/fp-go/v2/internal/fromreader" + "github.com/IBM/fp-go/v2/internal/functor" + "github.com/IBM/fp-go/v2/internal/readert" + "github.com/IBM/fp-go/v2/io" + "github.com/IBM/fp-go/v2/reader" +) + +// FromIO converts an [IO] to a [ReaderIO] +func FromIO[R, A any](t IO[A]) ReaderIO[R, A] { + return reader.Of[R](t) +} + +func FromReader[R, A any](r Reader[R, A]) ReaderIO[R, A] { + return readert.MonadFromReader[Reader[R, A], ReaderIO[R, A]](io.Of[A], r) +} + +func MonadMap[R, A, B any](fa ReaderIO[R, A], f func(A) B) ReaderIO[R, B] { + return readert.MonadMap[ReaderIO[R, A], ReaderIO[R, B]](io.MonadMap[A, B], fa, f) +} + +func Map[R, A, B any](f func(A) B) Operator[R, A, B] { + return readert.Map[ReaderIO[R, A], ReaderIO[R, B]](io.Map[A, B], f) +} + +func MonadChain[R, A, B any](ma ReaderIO[R, A], f func(A) ReaderIO[R, B]) ReaderIO[R, B] { + return readert.MonadChain(io.MonadChain[A, B], ma, f) +} + +func Chain[R, A, B any](f func(A) ReaderIO[R, B]) Operator[R, A, B] { + return readert.Chain[ReaderIO[R, A]](io.Chain[A, B], f) +} + +func Of[R, A any](a A) ReaderIO[R, A] { + return readert.MonadOf[ReaderIO[R, A]](io.Of[A], a) +} + +func MonadAp[B, R, A any](fab ReaderIO[R, func(A) B], fa ReaderIO[R, A]) ReaderIO[R, B] { + return readert.MonadAp[ReaderIO[R, A], ReaderIO[R, B], ReaderIO[R, func(A) B], R, A](io.MonadAp[A, B], fab, fa) +} + +func MonadApSeq[B, R, A any](fab ReaderIO[R, func(A) B], fa ReaderIO[R, A]) ReaderIO[R, B] { + return readert.MonadAp[ReaderIO[R, A], ReaderIO[R, B], ReaderIO[R, func(A) B], R, A](io.MonadApSeq[A, B], fab, fa) +} + +func MonadApPar[B, R, A any](fab ReaderIO[R, func(A) B], fa ReaderIO[R, A]) ReaderIO[R, B] { + return readert.MonadAp[ReaderIO[R, A], ReaderIO[R, B], ReaderIO[R, func(A) B], R, A](io.MonadApPar[A, B], fab, fa) +} + +func Ap[B, R, A any](fa ReaderIO[R, A]) Operator[R, func(A) B, B] { + return function.Bind2nd(MonadAp[B, R, A], fa) +} + +func Ask[R any]() ReaderIO[R, R] { + return fromreader.Ask(FromReader[R, R])() +} + +func Asks[R, A any](r Reader[R, A]) ReaderIO[R, A] { + return fromreader.Asks(FromReader[R, A])(r) +} + +func MonadChainIOK[R, A, B any](ma ReaderIO[R, A], f func(A) IO[B]) ReaderIO[R, B] { + return fromio.MonadChainIOK( + MonadChain[R, A, B], + FromIO[R, B], + ma, f, + ) +} + +func ChainIOK[R, A, B any](f func(A) IO[B]) Operator[R, A, B] { + return fromio.ChainIOK( + Chain[R, A, B], + FromIO[R, B], + f, + ) +} + +// Defer creates an IO by creating a brand new IO via a generator function, each time +func Defer[R, A any](gen func() ReaderIO[R, A]) ReaderIO[R, A] { + return func(r R) IO[A] { + return func() A { + return gen()(r)() + } + } +} + +// Memoize computes the value of the provided [ReaderIO] monad lazily but exactly once +// The context used to compute the value is the context of the first call, so do not use this +// method if the value has a functional dependency on the content of the context +func Memoize[R, A any](rdr ReaderIO[R, A]) ReaderIO[R, A] { + // synchronization primitives + var once sync.Once + var result A + // callback + gen := func(r R) func() { + return func() { + result = rdr(r)() + } + } + // returns our memoized wrapper + return func(r R) IO[A] { + io := gen(r) + return func() A { + once.Do(io) + return result + } + } +} + +func Flatten[R, A any](mma ReaderIO[R, ReaderIO[R, A]]) ReaderIO[R, A] { + return MonadChain(mma, function.Identity[ReaderIO[R, A]]) +} + +func MonadFlap[R, A, B any](fab ReaderIO[R, func(A) B], a A) ReaderIO[R, B] { + return functor.MonadFlap(MonadMap[R, func(A) B, B], fab, a) +} + +func Flap[R, A, B any](a A) Operator[R, func(A) B, B] { + return functor.Flap(Map[R, func(A) B, B], a) +} diff --git a/v2/readerio/reader_test.go b/v2/readerio/reader_test.go new file mode 100644 index 0000000..12166d6 --- /dev/null +++ b/v2/readerio/reader_test.go @@ -0,0 +1,44 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readerio + +import ( + "context" + "testing" + + F "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/internal/utils" + "github.com/stretchr/testify/assert" +) + +func TestMap(t *testing.T) { + + g := F.Pipe1( + Of[context.Context](1), + Map[context.Context](utils.Double), + ) + + assert.Equal(t, 2, g(context.Background())()) +} + +func TestAp(t *testing.T) { + g := F.Pipe1( + Of[context.Context](utils.Double), + Ap[int](Of[context.Context](1)), + ) + + assert.Equal(t, 2, g(context.Background())()) +} diff --git a/v2/readerio/sequence.go b/v2/readerio/sequence.go new file mode 100644 index 0000000..6c585c2 --- /dev/null +++ b/v2/readerio/sequence.go @@ -0,0 +1,57 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readerio + +import ( + G "github.com/IBM/fp-go/v2/readerio/generic" + T "github.com/IBM/fp-go/v2/tuple" +) + +// SequenceT converts n inputs of higher kinded types into a higher kinded types of n strongly typed values, represented as a tuple + +func SequenceT1[R, A any](a ReaderIO[R, A]) ReaderIO[R, T.Tuple1[A]] { + return G.SequenceT1[ + ReaderIO[R, A], + ReaderIO[R, T.Tuple1[A]], + ](a) +} + +func SequenceT2[R, A, B any](a ReaderIO[R, A], b ReaderIO[R, B]) ReaderIO[R, T.Tuple2[A, B]] { + return G.SequenceT2[ + ReaderIO[R, A], + ReaderIO[R, B], + ReaderIO[R, T.Tuple2[A, B]], + ](a, b) +} + +func SequenceT3[R, A, B, C any](a ReaderIO[R, A], b ReaderIO[R, B], c ReaderIO[R, C]) ReaderIO[R, T.Tuple3[A, B, C]] { + return G.SequenceT3[ + ReaderIO[R, A], + ReaderIO[R, B], + ReaderIO[R, C], + ReaderIO[R, T.Tuple3[A, B, C]], + ](a, b, c) +} + +func SequenceT4[R, A, B, C, D any](a ReaderIO[R, A], b ReaderIO[R, B], c ReaderIO[R, C], d ReaderIO[R, D]) ReaderIO[R, T.Tuple4[A, B, C, D]] { + return G.SequenceT4[ + ReaderIO[R, A], + ReaderIO[R, B], + ReaderIO[R, C], + ReaderIO[R, D], + ReaderIO[R, T.Tuple4[A, B, C, D]], + ](a, b, c, d) +} diff --git a/v2/readerio/sync.go b/v2/readerio/sync.go new file mode 100644 index 0000000..df98874 --- /dev/null +++ b/v2/readerio/sync.go @@ -0,0 +1,34 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readerio + +import ( + "context" + + "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/io" +) + +// WithLock executes the provided IO operation in the scope of a lock +func WithLock[R, A any](lock func() context.CancelFunc) Operator[R, A, A] { + l := io.WithLock[A](lock) + return func(fa ReaderIO[R, A]) ReaderIO[R, A] { + return function.Flow2( + fa, + l, + ) + } +} diff --git a/v2/readerio/types.go b/v2/readerio/types.go new file mode 100644 index 0000000..0808c1b --- /dev/null +++ b/v2/readerio/types.go @@ -0,0 +1,29 @@ +// Copyright (c) 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readerio + +import ( + "github.com/IBM/fp-go/v2/io" + "github.com/IBM/fp-go/v2/reader" +) + +type ( + IO[A any] = io.IO[A] + Reader[R, A any] = reader.Reader[R, A] + ReaderIO[R, A any] = Reader[R, IO[A]] + + Operator[R, A, B any] = Reader[ReaderIO[R, A], ReaderIO[R, B]] +) diff --git a/v2/readerioeither/array_test.go b/v2/readerioeither/array_test.go new file mode 100644 index 0000000..6fedf52 --- /dev/null +++ b/v2/readerioeither/array_test.go @@ -0,0 +1,71 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readerioeither + +import ( + "context" + "fmt" + "testing" + + A "github.com/IBM/fp-go/v2/array" + "github.com/IBM/fp-go/v2/either" + F "github.com/IBM/fp-go/v2/function" + TST "github.com/IBM/fp-go/v2/internal/testing" + "github.com/stretchr/testify/assert" +) + +func TestTraverseArray(t *testing.T) { + f := TraverseArray(func(a string) ReaderIOEither[context.Context, string, string] { + if len(a) > 0 { + return Right[context.Context, string](a + a) + } + return Left[context.Context, string, string]("e") + }) + ctx := context.Background() + assert.Equal(t, either.Right[string](A.Empty[string]()), F.Pipe1(A.Empty[string](), f)(ctx)()) + assert.Equal(t, either.Right[string]([]string{"aa", "bb"}), F.Pipe1([]string{"a", "b"}, f)(ctx)()) + assert.Equal(t, either.Left[[]string]("e"), F.Pipe1([]string{"a", ""}, f)(ctx)()) +} + +func TestSequenceArray(t *testing.T) { + + s := TST.SequenceArrayTest( + FromStrictEquals[context.Context, error, bool]()(context.Background()), + Pointed[context.Context, error, string](), + Pointed[context.Context, error, bool](), + Functor[context.Context, error, []string, bool](), + SequenceArray[context.Context, error, string], + ) + + for i := 0; i < 10; i++ { + t.Run(fmt.Sprintf("TestSequenceArray %d", i), s(i)) + } +} + +func TestSequenceArrayError(t *testing.T) { + + s := TST.SequenceArrayErrorTest( + FromStrictEquals[context.Context, error, bool]()(context.Background()), + Left[context.Context, string, error], + Left[context.Context, bool, error], + Pointed[context.Context, error, string](), + Pointed[context.Context, error, bool](), + Functor[context.Context, error, []string, bool](), + SequenceArray[context.Context, error, string], + ) + // run across four bits + s(4)(t) +} diff --git a/v2/readerioeither/bind.go b/v2/readerioeither/bind.go new file mode 100644 index 0000000..6dcdfdb --- /dev/null +++ b/v2/readerioeither/bind.go @@ -0,0 +1,67 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readerioeither + +import ( + IOE "github.com/IBM/fp-go/v2/ioeither" + G "github.com/IBM/fp-go/v2/readerioeither/generic" +) + +// Bind creates an empty context of type [S] to be used with the [Bind] operation +func Do[R, E, S any]( + empty S, +) ReaderIOEither[R, E, S] { + return G.Do[ReaderIOEither[R, E, S], IOE.IOEither[E, S], R, E, S](empty) +} + +// Bind attaches the result of a computation to a context [S1] to produce a context [S2] +func Bind[R, E, S1, S2, T any]( + setter func(T) func(S1) S2, + f func(S1) ReaderIOEither[R, E, T], +) Operator[R, E, S1, S2] { + return G.Bind[ReaderIOEither[R, E, S1], ReaderIOEither[R, E, S2], ReaderIOEither[R, E, T], IOE.IOEither[E, S1], IOE.IOEither[E, S2], IOE.IOEither[E, T], R, E, S1, S2, T](setter, f) +} + +// Let attaches the result of a computation to a context [S1] to produce a context [S2] +func Let[R, E, S1, S2, T any]( + setter func(T) func(S1) S2, + f func(S1) T, +) Operator[R, E, S1, S2] { + return G.Let[ReaderIOEither[R, E, S1], ReaderIOEither[R, E, S2], IOE.IOEither[E, S1], IOE.IOEither[E, S2], R, E, S1, S2, T](setter, f) +} + +// LetTo attaches the a value to a context [S1] to produce a context [S2] +func LetTo[R, E, S1, S2, T any]( + setter func(T) func(S1) S2, + b T, +) Operator[R, E, S1, S2] { + return G.LetTo[ReaderIOEither[R, E, S1], ReaderIOEither[R, E, S2], IOE.IOEither[E, S1], IOE.IOEither[E, S2], R, E, S1, S2, T](setter, b) +} + +// BindTo initializes a new state [S1] from a value [T] +func BindTo[R, E, S1, T any]( + setter func(T) S1, +) Operator[R, E, T, S1] { + return G.BindTo[ReaderIOEither[R, E, S1], ReaderIOEither[R, E, T], IOE.IOEither[E, S1], IOE.IOEither[E, T], R, E, S1, T](setter) +} + +// ApS attaches a value to a context [S1] to produce a context [S2] by considering the context and the value concurrently +func ApS[R, E, S1, S2, T any]( + setter func(T) func(S1) S2, + fa ReaderIOEither[R, E, T], +) Operator[R, E, S1, S2] { + return G.ApS[ReaderIOEither[R, E, func(T) S2], ReaderIOEither[R, E, S1], ReaderIOEither[R, E, S2], ReaderIOEither[R, E, T], IOE.IOEither[E, func(T) S2], IOE.IOEither[E, S1], IOE.IOEither[E, S2], IOE.IOEither[E, T], R, E, S1, S2, T](setter, fa) +} diff --git a/v2/readerioeither/bind_test.go b/v2/readerioeither/bind_test.go new file mode 100644 index 0000000..d2fafe4 --- /dev/null +++ b/v2/readerioeither/bind_test.go @@ -0,0 +1,58 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readerioeither + +import ( + "context" + "testing" + + E "github.com/IBM/fp-go/v2/either" + F "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/internal/utils" + "github.com/stretchr/testify/assert" +) + +func getLastName(s utils.Initial) ReaderIOEither[context.Context, error, string] { + return Of[context.Context, error]("Doe") +} + +func getGivenName(s utils.WithLastName) ReaderIOEither[context.Context, error, string] { + return Of[context.Context, error]("John") +} + +func TestBind(t *testing.T) { + + res := F.Pipe3( + Do[context.Context, error](utils.Empty), + Bind(utils.SetLastName, getLastName), + Bind(utils.SetGivenName, getGivenName), + Map[context.Context, error](utils.GetFullName), + ) + + assert.Equal(t, res(context.Background())(), E.Of[error]("John Doe")) +} + +func TestApS(t *testing.T) { + + res := F.Pipe3( + Do[context.Context, error](utils.Empty), + ApS(utils.SetLastName, Of[context.Context, error]("Doe")), + ApS(utils.SetGivenName, Of[context.Context, error]("John")), + Map[context.Context, error](utils.GetFullName), + ) + + assert.Equal(t, res(context.Background())(), E.Of[error]("John Doe")) +} diff --git a/v2/readerioeither/bracket.go b/v2/readerioeither/bracket.go new file mode 100644 index 0000000..5517269 --- /dev/null +++ b/v2/readerioeither/bracket.go @@ -0,0 +1,33 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readerioeither + +import ( + "github.com/IBM/fp-go/v2/either" + G "github.com/IBM/fp-go/v2/readerioeither/generic" +) + +// Bracket makes sure that a resource is cleaned up in the event of an error. The release action is called regardless of +// whether the body action returns and error or not. +func Bracket[ + R, E, A, B, ANY any]( + + acquire ReaderIOEither[R, E, A], + use func(A) ReaderIOEither[R, E, B], + release func(A, either.Either[E, B]) ReaderIOEither[R, E, ANY], +) ReaderIOEither[R, E, B] { + return G.Bracket(acquire, use, release) +} diff --git a/v2/readerioeither/coverage.out b/v2/readerioeither/coverage.out new file mode 100644 index 0000000..0a62019 --- /dev/null +++ b/v2/readerioeither/coverage.out @@ -0,0 +1,127 @@ +mode: set +github.com/IBM/fp-go/v2/readerioeither/bind.go:26.27,28.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/bind.go:34.26,36.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/bind.go:42.26,44.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/bind.go:50.26,52.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/bind.go:57.25,59.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/bind.go:65.26,67.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/bracket.go:31.27,33.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/eq.go:25.92,27.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/eq.go:30.88,32.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/gen.go:14.92,16.2 1 0 +github.com/IBM/fp-go/v2/readerioeither/gen.go:20.90,22.2 1 0 +github.com/IBM/fp-go/v2/readerioeither/gen.go:26.92,28.2 1 0 +github.com/IBM/fp-go/v2/readerioeither/gen.go:32.102,34.2 1 0 +github.com/IBM/fp-go/v2/readerioeither/gen.go:38.100,40.2 1 0 +github.com/IBM/fp-go/v2/readerioeither/gen.go:44.102,46.2 1 0 +github.com/IBM/fp-go/v2/readerioeither/gen.go:50.114,52.2 1 0 +github.com/IBM/fp-go/v2/readerioeither/gen.go:56.112,58.2 1 0 +github.com/IBM/fp-go/v2/readerioeither/gen.go:62.114,64.2 1 0 +github.com/IBM/fp-go/v2/readerioeither/gen.go:68.126,70.2 1 0 +github.com/IBM/fp-go/v2/readerioeither/gen.go:74.124,76.2 1 0 +github.com/IBM/fp-go/v2/readerioeither/gen.go:80.126,82.2 1 0 +github.com/IBM/fp-go/v2/readerioeither/gen.go:86.138,88.2 1 0 +github.com/IBM/fp-go/v2/readerioeither/gen.go:92.136,94.2 1 0 +github.com/IBM/fp-go/v2/readerioeither/gen.go:98.138,100.2 1 0 +github.com/IBM/fp-go/v2/readerioeither/gen.go:104.150,106.2 1 0 +github.com/IBM/fp-go/v2/readerioeither/gen.go:110.148,112.2 1 0 +github.com/IBM/fp-go/v2/readerioeither/gen.go:116.150,118.2 1 0 +github.com/IBM/fp-go/v2/readerioeither/gen.go:122.162,124.2 1 0 +github.com/IBM/fp-go/v2/readerioeither/gen.go:128.160,130.2 1 0 +github.com/IBM/fp-go/v2/readerioeither/gen.go:134.162,136.2 1 0 +github.com/IBM/fp-go/v2/readerioeither/gen.go:140.174,142.2 1 0 +github.com/IBM/fp-go/v2/readerioeither/gen.go:146.172,148.2 1 0 +github.com/IBM/fp-go/v2/readerioeither/gen.go:152.174,154.2 1 0 +github.com/IBM/fp-go/v2/readerioeither/gen.go:158.186,160.2 1 0 +github.com/IBM/fp-go/v2/readerioeither/gen.go:164.184,166.2 1 0 +github.com/IBM/fp-go/v2/readerioeither/gen.go:170.186,172.2 1 0 +github.com/IBM/fp-go/v2/readerioeither/gen.go:176.198,178.2 1 0 +github.com/IBM/fp-go/v2/readerioeither/gen.go:182.196,184.2 1 0 +github.com/IBM/fp-go/v2/readerioeither/gen.go:188.198,190.2 1 0 +github.com/IBM/fp-go/v2/readerioeither/gen.go:194.211,196.2 1 0 +github.com/IBM/fp-go/v2/readerioeither/gen.go:200.209,202.2 1 0 +github.com/IBM/fp-go/v2/readerioeither/gen.go:206.211,208.2 1 0 +github.com/IBM/fp-go/v2/readerioeither/monad.go:26.73,28.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/monad.go:31.104,33.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/monad.go:36.131,38.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:40.92,46.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:50.90,52.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:55.76,60.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:63.75,68.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:73.96,75.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:79.60,81.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:85.90,87.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:91.54,93.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:98.120,104.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:108.125,114.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:118.123,125.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:129.87,135.2 1 0 +github.com/IBM/fp-go/v2/readerioeither/reader.go:139.128,147.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:151.92,158.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:162.116,169.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:173.80,179.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:183.124,190.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:194.88,200.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:204.108,211.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:215.72,221.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:225.113,233.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:237.77,244.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:248.99,254.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:258.119,265.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:268.122,275.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:278.122,285.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:289.119,291.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:295.84,300.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:304.89,309.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:312.54,314.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:317.53,319.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:323.59,325.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:329.51,331.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:335.102,337.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:341.77,343.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:346.72,348.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:351.71,353.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:357.71,359.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:362.64,364.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:367.63,369.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:373.63,375.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:379.79,381.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:385.89,387.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:391.46,393.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:397.64,399.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:403.89,405.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:409.103,411.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:415.135,417.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:421.105,423.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:427.129,429.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:433.120,440.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:444.120,449.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:453.117,455.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:459.77,461.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:465.86,467.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:471.104,472.34 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:472.34,474.3 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:479.123,487.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:491.84,498.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:503.68,505.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:509.98,511.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:515.94,517.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:520.106,522.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:526.103,528.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/reader.go:533.101,535.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/resource.go:21.181,22.73 1 1 +github.com/IBM/fp-go/v2/readerioeither/resource.go:22.73,23.44 1 1 +github.com/IBM/fp-go/v2/readerioeither/resource.go:23.44,27.41 1 1 +github.com/IBM/fp-go/v2/readerioeither/resource.go:27.41,29.6 1 1 +github.com/IBM/fp-go/v2/readerioeither/resource.go:30.40,32.5 1 1 +github.com/IBM/fp-go/v2/readerioeither/sequence.go:25.91,30.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/sequence.go:32.124,38.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/sequence.go:40.157,47.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/sequence.go:49.190,57.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/sync.go:26.81,28.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/traverse.go:23.107,25.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/traverse.go:28.121,30.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/traverse.go:33.89,35.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/traverse.go:38.134,40.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/traverse.go:43.146,45.2 1 1 +github.com/IBM/fp-go/v2/readerioeither/traverse.go:48.116,50.2 1 1 diff --git a/v2/readerioeither/doc.go b/v2/readerioeither/doc.go new file mode 100644 index 0000000..e5ff01a --- /dev/null +++ b/v2/readerioeither/doc.go @@ -0,0 +1,100 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +// Package readerioeither provides a functional programming abstraction that combines +// three powerful concepts: Reader, IO, and Either monads. +// +// # ReaderIOEither +// +// ReaderIOEither[R, E, A] represents a computation that: +// - Depends on some context/environment of type R (Reader) +// - Performs side effects (IO) +// - Can fail with an error of type E or succeed with a value of type A (Either) +// +// This is particularly useful for: +// - Dependency injection patterns +// - Error handling in effectful computations +// - Composing operations that need access to shared configuration or context +// +// # Core Operations +// +// Construction: +// - Of/Right: Create a successful computation +// - Left/ThrowError: Create a failed computation +// - FromEither: Lift an Either into ReaderIOEither +// - FromIO: Lift an IO into ReaderIOEither +// - FromReader: Lift a Reader into ReaderIOEither +// - FromIOEither: Lift an IOEither into ReaderIOEither +// - TryCatch: Wrap error-returning functions +// +// Transformation: +// - Map: Transform the success value +// - MapLeft: Transform the error value +// - BiMap: Transform both success and error values +// - Chain/Bind: Sequence dependent computations +// - Flatten: Flatten nested ReaderIOEither +// +// Combination: +// - Ap: Apply a function in a context to a value in a context +// - SequenceArray: Convert array of ReaderIOEither to ReaderIOEither of array +// - TraverseArray: Map and sequence in one operation +// +// Error Handling: +// - Fold: Handle both success and error cases +// - GetOrElse: Provide a default value on error +// - OrElse: Try an alternative computation on error +// - Alt: Choose the first successful computation +// +// Context Access: +// - Ask: Get the current context +// - Asks: Get a value derived from the context +// - Local: Run a computation with a modified context +// +// Resource Management: +// - Bracket: Ensure resource cleanup +// - WithResource: Manage resource lifecycle +// +// # Example Usage +// +// type Config struct { +// BaseURL string +// Timeout time.Duration +// } +// +// // A computation that depends on Config, performs IO, and can fail +// func fetchUser(id int) readerioeither.ReaderIOEither[Config, error, User] { +// return func(cfg Config) ioeither.IOEither[error, User] { +// return func() either.Either[error, User] { +// // Use cfg.BaseURL and cfg.Timeout to fetch user +// // Return either.Right(user) or either.Left(err) +// } +// } +// } +// +// // Compose operations +// result := function.Pipe2( +// fetchUser(123), +// readerioeither.Map[Config, error](func(u User) string { return u.Name }), +// readerioeither.Chain[Config, error](func(name string) readerioeither.ReaderIOEither[Config, error, string] { +// return readerioeither.Of[Config, error]("Hello, " + name) +// }), +// ) +// +// // Execute with config +// config := Config{BaseURL: "https://api.example.com", Timeout: 30 * time.Second} +// outcome := result(config)() // Returns either.Either[error, string] +package readerioeither + +//go:generate go run .. readerioeither --count 10 --filename gen.go diff --git a/v2/readerioeither/eq.go b/v2/readerioeither/eq.go new file mode 100644 index 0000000..f33da98 --- /dev/null +++ b/v2/readerioeither/eq.go @@ -0,0 +1,32 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readerioeither + +import ( + "github.com/IBM/fp-go/v2/either" + EQ "github.com/IBM/fp-go/v2/eq" + "github.com/IBM/fp-go/v2/readerio" +) + +// Eq implements the equals predicate for values contained in the IOEither monad +func Eq[R, E, A any](eq EQ.Eq[either.Either[E, A]]) func(R) EQ.Eq[ReaderIOEither[R, E, A]] { + return readerio.Eq[R](eq) +} + +// FromStrictEquals constructs an [EQ.Eq] from the canonical comparison function +func FromStrictEquals[R any, E, A comparable]() func(R) EQ.Eq[ReaderIOEither[R, E, A]] { + return Eq[R](either.FromStrictEquals[E, A]()) +} diff --git a/v2/readerioeither/gen.go b/v2/readerioeither/gen.go new file mode 100644 index 0000000..47f1212 --- /dev/null +++ b/v2/readerioeither/gen.go @@ -0,0 +1,208 @@ +// Code generated by go generate; DO NOT EDIT. +// This file was generated by robots at +// 2025-03-09 23:53:13.769132 +0100 CET m=+0.013697001 + +package readerioeither + + +import ( + G "github.com/IBM/fp-go/v2/readerioeither/generic" +) + +// From0 converts a function with 1 parameters returning a tuple into a function with 0 parameters returning a [ReaderIOEither[R]] +// The first parameter is considered to be the context [C]. +func From0[F ~func(C) func() (R, error), C, R any](f F) func() ReaderIOEither[C, error, R] { + return G.From0[ReaderIOEither[C, error, R]](f) +} + +// Eitherize0 converts a function with 1 parameters returning a tuple into a function with 0 parameters returning a [ReaderIOEither[C, error, R]] +// The first parameter is considered to be the context [C]. +func Eitherize0[F ~func(C) (R, error), C, R any](f F) func() ReaderIOEither[C, error, R] { + return G.Eitherize0[ReaderIOEither[C, error, R]](f) +} + +// Uneitherize0 converts a function with 1 parameters returning a [ReaderIOEither[C, error, R]] into a function with 0 parameters returning a tuple. +// The first parameter is considered to be the context [C]. +func Uneitherize0[F ~func() ReaderIOEither[C, error, R], C, R any](f F) func(C) (R, error) { + return G.Uneitherize0[ReaderIOEither[C, error, R], func(C)(R, error)](f) +} + +// From1 converts a function with 2 parameters returning a tuple into a function with 1 parameters returning a [ReaderIOEither[R]] +// The first parameter is considered to be the context [C]. +func From1[F ~func(C, T0) func() (R, error), T0, C, R any](f F) func(T0) ReaderIOEither[C, error, R] { + return G.From1[ReaderIOEither[C, error, R]](f) +} + +// Eitherize1 converts a function with 2 parameters returning a tuple into a function with 1 parameters returning a [ReaderIOEither[C, error, R]] +// The first parameter is considered to be the context [C]. +func Eitherize1[F ~func(C, T0) (R, error), T0, C, R any](f F) func(T0) ReaderIOEither[C, error, R] { + return G.Eitherize1[ReaderIOEither[C, error, R]](f) +} + +// Uneitherize1 converts a function with 2 parameters returning a [ReaderIOEither[C, error, R]] into a function with 1 parameters returning a tuple. +// The first parameter is considered to be the context [C]. +func Uneitherize1[F ~func(T0) ReaderIOEither[C, error, R], T0, C, R any](f F) func(C, T0) (R, error) { + return G.Uneitherize1[ReaderIOEither[C, error, R], func(C, T0)(R, error)](f) +} + +// From2 converts a function with 3 parameters returning a tuple into a function with 2 parameters returning a [ReaderIOEither[R]] +// The first parameter is considered to be the context [C]. +func From2[F ~func(C, T0, T1) func() (R, error), T0, T1, C, R any](f F) func(T0, T1) ReaderIOEither[C, error, R] { + return G.From2[ReaderIOEither[C, error, R]](f) +} + +// Eitherize2 converts a function with 3 parameters returning a tuple into a function with 2 parameters returning a [ReaderIOEither[C, error, R]] +// The first parameter is considered to be the context [C]. +func Eitherize2[F ~func(C, T0, T1) (R, error), T0, T1, C, R any](f F) func(T0, T1) ReaderIOEither[C, error, R] { + return G.Eitherize2[ReaderIOEither[C, error, R]](f) +} + +// Uneitherize2 converts a function with 3 parameters returning a [ReaderIOEither[C, error, R]] into a function with 2 parameters returning a tuple. +// The first parameter is considered to be the context [C]. +func Uneitherize2[F ~func(T0, T1) ReaderIOEither[C, error, R], T0, T1, C, R any](f F) func(C, T0, T1) (R, error) { + return G.Uneitherize2[ReaderIOEither[C, error, R], func(C, T0, T1)(R, error)](f) +} + +// From3 converts a function with 4 parameters returning a tuple into a function with 3 parameters returning a [ReaderIOEither[R]] +// The first parameter is considered to be the context [C]. +func From3[F ~func(C, T0, T1, T2) func() (R, error), T0, T1, T2, C, R any](f F) func(T0, T1, T2) ReaderIOEither[C, error, R] { + return G.From3[ReaderIOEither[C, error, R]](f) +} + +// Eitherize3 converts a function with 4 parameters returning a tuple into a function with 3 parameters returning a [ReaderIOEither[C, error, R]] +// The first parameter is considered to be the context [C]. +func Eitherize3[F ~func(C, T0, T1, T2) (R, error), T0, T1, T2, C, R any](f F) func(T0, T1, T2) ReaderIOEither[C, error, R] { + return G.Eitherize3[ReaderIOEither[C, error, R]](f) +} + +// Uneitherize3 converts a function with 4 parameters returning a [ReaderIOEither[C, error, R]] into a function with 3 parameters returning a tuple. +// The first parameter is considered to be the context [C]. +func Uneitherize3[F ~func(T0, T1, T2) ReaderIOEither[C, error, R], T0, T1, T2, C, R any](f F) func(C, T0, T1, T2) (R, error) { + return G.Uneitherize3[ReaderIOEither[C, error, R], func(C, T0, T1, T2)(R, error)](f) +} + +// From4 converts a function with 5 parameters returning a tuple into a function with 4 parameters returning a [ReaderIOEither[R]] +// The first parameter is considered to be the context [C]. +func From4[F ~func(C, T0, T1, T2, T3) func() (R, error), T0, T1, T2, T3, C, R any](f F) func(T0, T1, T2, T3) ReaderIOEither[C, error, R] { + return G.From4[ReaderIOEither[C, error, R]](f) +} + +// Eitherize4 converts a function with 5 parameters returning a tuple into a function with 4 parameters returning a [ReaderIOEither[C, error, R]] +// The first parameter is considered to be the context [C]. +func Eitherize4[F ~func(C, T0, T1, T2, T3) (R, error), T0, T1, T2, T3, C, R any](f F) func(T0, T1, T2, T3) ReaderIOEither[C, error, R] { + return G.Eitherize4[ReaderIOEither[C, error, R]](f) +} + +// Uneitherize4 converts a function with 5 parameters returning a [ReaderIOEither[C, error, R]] into a function with 4 parameters returning a tuple. +// The first parameter is considered to be the context [C]. +func Uneitherize4[F ~func(T0, T1, T2, T3) ReaderIOEither[C, error, R], T0, T1, T2, T3, C, R any](f F) func(C, T0, T1, T2, T3) (R, error) { + return G.Uneitherize4[ReaderIOEither[C, error, R], func(C, T0, T1, T2, T3)(R, error)](f) +} + +// From5 converts a function with 6 parameters returning a tuple into a function with 5 parameters returning a [ReaderIOEither[R]] +// The first parameter is considered to be the context [C]. +func From5[F ~func(C, T0, T1, T2, T3, T4) func() (R, error), T0, T1, T2, T3, T4, C, R any](f F) func(T0, T1, T2, T3, T4) ReaderIOEither[C, error, R] { + return G.From5[ReaderIOEither[C, error, R]](f) +} + +// Eitherize5 converts a function with 6 parameters returning a tuple into a function with 5 parameters returning a [ReaderIOEither[C, error, R]] +// The first parameter is considered to be the context [C]. +func Eitherize5[F ~func(C, T0, T1, T2, T3, T4) (R, error), T0, T1, T2, T3, T4, C, R any](f F) func(T0, T1, T2, T3, T4) ReaderIOEither[C, error, R] { + return G.Eitherize5[ReaderIOEither[C, error, R]](f) +} + +// Uneitherize5 converts a function with 6 parameters returning a [ReaderIOEither[C, error, R]] into a function with 5 parameters returning a tuple. +// The first parameter is considered to be the context [C]. +func Uneitherize5[F ~func(T0, T1, T2, T3, T4) ReaderIOEither[C, error, R], T0, T1, T2, T3, T4, C, R any](f F) func(C, T0, T1, T2, T3, T4) (R, error) { + return G.Uneitherize5[ReaderIOEither[C, error, R], func(C, T0, T1, T2, T3, T4)(R, error)](f) +} + +// From6 converts a function with 7 parameters returning a tuple into a function with 6 parameters returning a [ReaderIOEither[R]] +// The first parameter is considered to be the context [C]. +func From6[F ~func(C, T0, T1, T2, T3, T4, T5) func() (R, error), T0, T1, T2, T3, T4, T5, C, R any](f F) func(T0, T1, T2, T3, T4, T5) ReaderIOEither[C, error, R] { + return G.From6[ReaderIOEither[C, error, R]](f) +} + +// Eitherize6 converts a function with 7 parameters returning a tuple into a function with 6 parameters returning a [ReaderIOEither[C, error, R]] +// The first parameter is considered to be the context [C]. +func Eitherize6[F ~func(C, T0, T1, T2, T3, T4, T5) (R, error), T0, T1, T2, T3, T4, T5, C, R any](f F) func(T0, T1, T2, T3, T4, T5) ReaderIOEither[C, error, R] { + return G.Eitherize6[ReaderIOEither[C, error, R]](f) +} + +// Uneitherize6 converts a function with 7 parameters returning a [ReaderIOEither[C, error, R]] into a function with 6 parameters returning a tuple. +// The first parameter is considered to be the context [C]. +func Uneitherize6[F ~func(T0, T1, T2, T3, T4, T5) ReaderIOEither[C, error, R], T0, T1, T2, T3, T4, T5, C, R any](f F) func(C, T0, T1, T2, T3, T4, T5) (R, error) { + return G.Uneitherize6[ReaderIOEither[C, error, R], func(C, T0, T1, T2, T3, T4, T5)(R, error)](f) +} + +// From7 converts a function with 8 parameters returning a tuple into a function with 7 parameters returning a [ReaderIOEither[R]] +// The first parameter is considered to be the context [C]. +func From7[F ~func(C, T0, T1, T2, T3, T4, T5, T6) func() (R, error), T0, T1, T2, T3, T4, T5, T6, C, R any](f F) func(T0, T1, T2, T3, T4, T5, T6) ReaderIOEither[C, error, R] { + return G.From7[ReaderIOEither[C, error, R]](f) +} + +// Eitherize7 converts a function with 8 parameters returning a tuple into a function with 7 parameters returning a [ReaderIOEither[C, error, R]] +// The first parameter is considered to be the context [C]. +func Eitherize7[F ~func(C, T0, T1, T2, T3, T4, T5, T6) (R, error), T0, T1, T2, T3, T4, T5, T6, C, R any](f F) func(T0, T1, T2, T3, T4, T5, T6) ReaderIOEither[C, error, R] { + return G.Eitherize7[ReaderIOEither[C, error, R]](f) +} + +// Uneitherize7 converts a function with 8 parameters returning a [ReaderIOEither[C, error, R]] into a function with 7 parameters returning a tuple. +// The first parameter is considered to be the context [C]. +func Uneitherize7[F ~func(T0, T1, T2, T3, T4, T5, T6) ReaderIOEither[C, error, R], T0, T1, T2, T3, T4, T5, T6, C, R any](f F) func(C, T0, T1, T2, T3, T4, T5, T6) (R, error) { + return G.Uneitherize7[ReaderIOEither[C, error, R], func(C, T0, T1, T2, T3, T4, T5, T6)(R, error)](f) +} + +// From8 converts a function with 9 parameters returning a tuple into a function with 8 parameters returning a [ReaderIOEither[R]] +// The first parameter is considered to be the context [C]. +func From8[F ~func(C, T0, T1, T2, T3, T4, T5, T6, T7) func() (R, error), T0, T1, T2, T3, T4, T5, T6, T7, C, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7) ReaderIOEither[C, error, R] { + return G.From8[ReaderIOEither[C, error, R]](f) +} + +// Eitherize8 converts a function with 9 parameters returning a tuple into a function with 8 parameters returning a [ReaderIOEither[C, error, R]] +// The first parameter is considered to be the context [C]. +func Eitherize8[F ~func(C, T0, T1, T2, T3, T4, T5, T6, T7) (R, error), T0, T1, T2, T3, T4, T5, T6, T7, C, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7) ReaderIOEither[C, error, R] { + return G.Eitherize8[ReaderIOEither[C, error, R]](f) +} + +// Uneitherize8 converts a function with 9 parameters returning a [ReaderIOEither[C, error, R]] into a function with 8 parameters returning a tuple. +// The first parameter is considered to be the context [C]. +func Uneitherize8[F ~func(T0, T1, T2, T3, T4, T5, T6, T7) ReaderIOEither[C, error, R], T0, T1, T2, T3, T4, T5, T6, T7, C, R any](f F) func(C, T0, T1, T2, T3, T4, T5, T6, T7) (R, error) { + return G.Uneitherize8[ReaderIOEither[C, error, R], func(C, T0, T1, T2, T3, T4, T5, T6, T7)(R, error)](f) +} + +// From9 converts a function with 10 parameters returning a tuple into a function with 9 parameters returning a [ReaderIOEither[R]] +// The first parameter is considered to be the context [C]. +func From9[F ~func(C, T0, T1, T2, T3, T4, T5, T6, T7, T8) func() (R, error), T0, T1, T2, T3, T4, T5, T6, T7, T8, C, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7, T8) ReaderIOEither[C, error, R] { + return G.From9[ReaderIOEither[C, error, R]](f) +} + +// Eitherize9 converts a function with 10 parameters returning a tuple into a function with 9 parameters returning a [ReaderIOEither[C, error, R]] +// The first parameter is considered to be the context [C]. +func Eitherize9[F ~func(C, T0, T1, T2, T3, T4, T5, T6, T7, T8) (R, error), T0, T1, T2, T3, T4, T5, T6, T7, T8, C, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7, T8) ReaderIOEither[C, error, R] { + return G.Eitherize9[ReaderIOEither[C, error, R]](f) +} + +// Uneitherize9 converts a function with 10 parameters returning a [ReaderIOEither[C, error, R]] into a function with 9 parameters returning a tuple. +// The first parameter is considered to be the context [C]. +func Uneitherize9[F ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8) ReaderIOEither[C, error, R], T0, T1, T2, T3, T4, T5, T6, T7, T8, C, R any](f F) func(C, T0, T1, T2, T3, T4, T5, T6, T7, T8) (R, error) { + return G.Uneitherize9[ReaderIOEither[C, error, R], func(C, T0, T1, T2, T3, T4, T5, T6, T7, T8)(R, error)](f) +} + +// From10 converts a function with 11 parameters returning a tuple into a function with 10 parameters returning a [ReaderIOEither[R]] +// The first parameter is considered to be the context [C]. +func From10[F ~func(C, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9) func() (R, error), T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, C, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9) ReaderIOEither[C, error, R] { + return G.From10[ReaderIOEither[C, error, R]](f) +} + +// Eitherize10 converts a function with 11 parameters returning a tuple into a function with 10 parameters returning a [ReaderIOEither[C, error, R]] +// The first parameter is considered to be the context [C]. +func Eitherize10[F ~func(C, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9) (R, error), T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, C, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9) ReaderIOEither[C, error, R] { + return G.Eitherize10[ReaderIOEither[C, error, R]](f) +} + +// Uneitherize10 converts a function with 11 parameters returning a [ReaderIOEither[C, error, R]] into a function with 10 parameters returning a tuple. +// The first parameter is considered to be the context [C]. +func Uneitherize10[F ~func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9) ReaderIOEither[C, error, R], T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, C, R any](f F) func(C, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9) (R, error) { + return G.Uneitherize10[ReaderIOEither[C, error, R], func(C, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9)(R, error)](f) +} diff --git a/v2/readerioeither/gen_test.go b/v2/readerioeither/gen_test.go new file mode 100644 index 0000000..2e0d2d1 --- /dev/null +++ b/v2/readerioeither/gen_test.go @@ -0,0 +1,164 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readerioeither + +import ( + "context" + "errors" + "testing" + + E "github.com/IBM/fp-go/v2/either" + "github.com/stretchr/testify/assert" +) + +func TestEitherize0(t *testing.T) { + f := func(ctx context.Context) (int, error) { + return ctx.Value("key").(int), nil + } + + result := Eitherize0(f)() + ctx := context.WithValue(context.Background(), "key", 42) + assert.Equal(t, E.Right[error](42), result(ctx)()) +} + +func TestEitherize1(t *testing.T) { + f := func(ctx context.Context, x int) (int, error) { + return x * 2, nil + } + + result := Eitherize1(f)(5) + ctx := context.Background() + assert.Equal(t, E.Right[error](10), result(ctx)()) +} + +func TestEitherize2(t *testing.T) { + f := func(ctx context.Context, x, y int) (int, error) { + return x + y, nil + } + + result := Eitherize2(f)(5, 3) + ctx := context.Background() + assert.Equal(t, E.Right[error](8), result(ctx)()) +} + +func TestEitherize3(t *testing.T) { + f := func(ctx context.Context, x, y, z int) (int, error) { + return x + y + z, nil + } + + result := Eitherize3(f)(5, 3, 2) + ctx := context.Background() + assert.Equal(t, E.Right[error](10), result(ctx)()) +} + +func TestUneitherize0(t *testing.T) { + f := func() ReaderIOEither[context.Context, error, int] { + return Of[context.Context, error](42) + } + + result := Uneitherize0(f) + ctx := context.Background() + val, err := result(ctx) + assert.NoError(t, err) + assert.Equal(t, 42, val) +} + +func TestUneitherize1(t *testing.T) { + f := func(x int) ReaderIOEither[context.Context, error, int] { + return Of[context.Context, error](x * 2) + } + + result := Uneitherize1(f) + ctx := context.Background() + val, err := result(ctx, 5) + assert.NoError(t, err) + assert.Equal(t, 10, val) +} + +func TestUneitherize2(t *testing.T) { + f := func(x, y int) ReaderIOEither[context.Context, error, int] { + return Of[context.Context, error](x + y) + } + + result := Uneitherize2(f) + ctx := context.Background() + val, err := result(ctx, 5, 3) + assert.NoError(t, err) + assert.Equal(t, 8, val) +} + +func TestFrom0(t *testing.T) { + f := func(ctx context.Context) func() (int, error) { + return func() (int, error) { + return 42, nil + } + } + + result := From0(f)() + ctx := context.Background() + assert.Equal(t, E.Right[error](42), result(ctx)()) +} + +func TestFrom1(t *testing.T) { + f := func(ctx context.Context, x int) func() (int, error) { + return func() (int, error) { + return x * 2, nil + } + } + + result := From1(f)(5) + ctx := context.Background() + assert.Equal(t, E.Right[error](10), result(ctx)()) +} + +func TestFrom2(t *testing.T) { + f := func(ctx context.Context, x, y int) func() (int, error) { + return func() (int, error) { + return x + y, nil + } + } + + result := From2(f)(5, 3) + ctx := context.Background() + assert.Equal(t, E.Right[error](8), result(ctx)()) +} + +func TestEitherizeWithError(t *testing.T) { + f := func(ctx context.Context, x int) (int, error) { + if x < 0 { + return 0, errors.New("negative value") + } + return x * 2, nil + } + + result := Eitherize1(f)(-5) + ctx := context.Background() + assert.True(t, E.IsLeft(result(ctx)())) +} + +func TestUneitherizeWithError(t *testing.T) { + f := func(x int) ReaderIOEither[context.Context, error, int] { + if x < 0 { + return Left[context.Context, int](errors.New("negative value")) + } + return Of[context.Context, error](x * 2) + } + + result := Uneitherize1(f) + ctx := context.Background() + _, err := result(ctx, -5) + assert.Error(t, err) +} diff --git a/v2/readerioeither/generic/bind.go b/v2/readerioeither/generic/bind.go new file mode 100644 index 0000000..812871d --- /dev/null +++ b/v2/readerioeither/generic/bind.go @@ -0,0 +1,90 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + "github.com/IBM/fp-go/v2/either" + A "github.com/IBM/fp-go/v2/internal/apply" + C "github.com/IBM/fp-go/v2/internal/chain" + F "github.com/IBM/fp-go/v2/internal/functor" +) + +// Bind creates an empty context of type [S] to be used with the [Bind] operation +func Do[GRS ~func(R) GS, GS ~func() either.Either[E, S], R, E, S any]( + empty S, +) GRS { + return Of[GRS, GS, R, E, S](empty) +} + +// Bind attaches the result of a computation to a context [S1] to produce a context [S2] +func Bind[GRS1 ~func(R) GS1, GRS2 ~func(R) GS2, GRT ~func(R) GT, GS1 ~func() either.Either[E, S1], GS2 ~func() either.Either[E, S2], GT ~func() either.Either[E, T], R, E, S1, S2, T any]( + setter func(T) func(S1) S2, + f func(S1) GRT, +) func(GRS1) GRS2 { + return C.Bind( + Chain[GRS1, GRS2, GS1, GS2, R, E, S1, S2], + Map[GRT, GRS2, GT, GS2, R, E, T, S2], + setter, + f, + ) +} + +// Let attaches the result of a computation to a context [S1] to produce a context [S2] +func Let[GRS1 ~func(R) GS1, GRS2 ~func(R) GS2, GS1 ~func() either.Either[E, S1], GS2 ~func() either.Either[E, S2], R, E, S1, S2, T any]( + key func(T) func(S1) S2, + f func(S1) T, +) func(GRS1) GRS2 { + return F.Let( + Map[GRS1, GRS2, GS1, GS2, R, E, S1, S2], + key, + f, + ) +} + +// LetTo attaches the a value to a context [S1] to produce a context [S2] +func LetTo[GRS1 ~func(R) GS1, GRS2 ~func(R) GS2, GS1 ~func() either.Either[E, S1], GS2 ~func() either.Either[E, S2], R, E, S1, S2, B any]( + key func(B) func(S1) S2, + b B, +) func(GRS1) GRS2 { + return F.LetTo( + Map[GRS1, GRS2, GS1, GS2, R, E, S1, S2], + key, + b, + ) +} + +// BindTo initializes a new state [S1] from a value [T] +func BindTo[GRS1 ~func(R) GS1, GRT ~func(R) GT, GS1 ~func() either.Either[E, S1], GT ~func() either.Either[E, T], R, E, S1, T any]( + setter func(T) S1, +) func(GRT) GRS1 { + return C.BindTo( + Map[GRT, GRS1, GT, GS1, R, E, T, S1], + setter, + ) +} + +// ApS attaches a value to a context [S1] to produce a context [S2] by considering the context and the value concurrently +func ApS[GRTS1 ~func(R) GTS1, GRS1 ~func(R) GS1, GRS2 ~func(R) GS2, GRT ~func(R) GT, GTS1 ~func() either.Either[E, func(T) S2], GS1 ~func() either.Either[E, S1], GS2 ~func() either.Either[E, S2], GT ~func() either.Either[E, T], R, E, S1, S2, T any]( + setter func(T) func(S1) S2, + fa GRT, +) func(GRS1) GRS2 { + return A.ApS( + Ap[GRT, GRS2, GRTS1, GT, GS2, GTS1, R, E, T, S2], + Map[GRS1, GRTS1, GS1, GTS1, R, E, S1, func(T) S2], + setter, + fa, + ) +} diff --git a/v2/readerioeither/generic/bracket.go b/v2/readerioeither/generic/bracket.go new file mode 100644 index 0000000..e03633a --- /dev/null +++ b/v2/readerioeither/generic/bracket.go @@ -0,0 +1,51 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + "github.com/IBM/fp-go/v2/either" + G "github.com/IBM/fp-go/v2/internal/bracket" + I "github.com/IBM/fp-go/v2/readerio/generic" +) + +// Bracket makes sure that a resource is cleaned up in the event of an error. The release action is called regardless of +// whether the body action returns and error or not. +func Bracket[ + GA ~func(R) TA, + GB ~func(R) TB, + GANY ~func(R) TANY, + + TA ~func() either.Either[E, A], + TB ~func() either.Either[E, B], + TANY ~func() either.Either[E, ANY], + + R, E, A, B, ANY any]( + + acquire GA, + use func(A) GB, + release func(A, either.Either[E, B]) GANY, +) GB { + return G.Bracket[GA, GB, GANY, either.Either[E, B], A, B]( + I.Of[GB, TB, R, either.Either[E, B]], + MonadChain[GA, GB, TA, TB, R, E, A, B], + I.MonadChain[GB, GB, TB, TB, R, either.Either[E, B], either.Either[E, B]], + MonadChain[GANY, GB, TANY, TB, R, E, ANY, B], + + acquire, + use, + release, + ) +} diff --git a/v2/readerioeither/generic/gen.go b/v2/readerioeither/generic/gen.go new file mode 100644 index 0000000..76be986 --- /dev/null +++ b/v2/readerioeither/generic/gen.go @@ -0,0 +1,285 @@ +// Code generated by go generate; DO NOT EDIT. +// This file was generated by robots at +// 2025-03-09 23:53:13.781616 +0100 CET m=+0.026181001 +package generic + + +import ( + E "github.com/IBM/fp-go/v2/either" + RD "github.com/IBM/fp-go/v2/reader/generic" +) + +// From0 converts a function with 1 parameters returning a tuple into a function with 0 parameters returning a [GRA] +// The first parameter is considerd to be the context [C]. +func From0[GRA ~func(C) GIOA, F ~func(C) func() (R, error), GIOA ~func() E.Either[error, R], C, R any](f F) func() GRA { + return RD.From0[GRA](func(r C) GIOA { + return E.Eitherize0(f(r)) + }) +} + +// Eitherize0 converts a function with 0 parameters returning a tuple into a function with 0 parameters returning a [GRA] +// The first parameter is considered to be the context [C]. +func Eitherize0[GRA ~func(C) GIOA, F ~func(C) (R, error), GIOA ~func() E.Either[error, R], C, R any](f F) func() GRA { + return From0[GRA](func(r C) func() (R, error) { + return func() (R, error) { + return f(r) + }}) +} + +// Uneitherize0 converts a function with 0 parameters returning a [GRA] into a function with 0 parameters returning a tuple. +// The first parameter is considered to be the context [C]. +func Uneitherize0[GRA ~func(C) GIOA, F ~func(C) (R, error), GIOA ~func() E.Either[error, R], C, R any](f func() GRA) F { + return func(c C) (R, error) { + return E.UnwrapError(f()(c)()) + } +} + +// From1 converts a function with 2 parameters returning a tuple into a function with 1 parameters returning a [GRA] +// The first parameter is considerd to be the context [C]. +func From1[GRA ~func(C) GIOA, F ~func(C, T0) func() (R, error), GIOA ~func() E.Either[error, R], T0, C, R any](f F) func(T0) GRA { + return RD.From1[GRA](func(r C, t0 T0) GIOA { + return E.Eitherize0(f(r, t0)) + }) +} + +// Eitherize1 converts a function with 1 parameters returning a tuple into a function with 1 parameters returning a [GRA] +// The first parameter is considered to be the context [C]. +func Eitherize1[GRA ~func(C) GIOA, F ~func(C, T0) (R, error), GIOA ~func() E.Either[error, R], T0, C, R any](f F) func(T0) GRA { + return From1[GRA](func(r C, t0 T0) func() (R, error) { + return func() (R, error) { + return f(r, t0) + }}) +} + +// Uneitherize1 converts a function with 1 parameters returning a [GRA] into a function with 1 parameters returning a tuple. +// The first parameter is considered to be the context [C]. +func Uneitherize1[GRA ~func(C) GIOA, F ~func(C, T0) (R, error), GIOA ~func() E.Either[error, R], T0, C, R any](f func(T0) GRA) F { + return func(c C, t0 T0) (R, error) { + return E.UnwrapError(f(t0)(c)()) + } +} + +// From2 converts a function with 3 parameters returning a tuple into a function with 2 parameters returning a [GRA] +// The first parameter is considerd to be the context [C]. +func From2[GRA ~func(C) GIOA, F ~func(C, T0, T1) func() (R, error), GIOA ~func() E.Either[error, R], T0, T1, C, R any](f F) func(T0, T1) GRA { + return RD.From2[GRA](func(r C, t0 T0, t1 T1) GIOA { + return E.Eitherize0(f(r, t0, t1)) + }) +} + +// Eitherize2 converts a function with 2 parameters returning a tuple into a function with 2 parameters returning a [GRA] +// The first parameter is considered to be the context [C]. +func Eitherize2[GRA ~func(C) GIOA, F ~func(C, T0, T1) (R, error), GIOA ~func() E.Either[error, R], T0, T1, C, R any](f F) func(T0, T1) GRA { + return From2[GRA](func(r C, t0 T0, t1 T1) func() (R, error) { + return func() (R, error) { + return f(r, t0, t1) + }}) +} + +// Uneitherize2 converts a function with 2 parameters returning a [GRA] into a function with 2 parameters returning a tuple. +// The first parameter is considered to be the context [C]. +func Uneitherize2[GRA ~func(C) GIOA, F ~func(C, T0, T1) (R, error), GIOA ~func() E.Either[error, R], T0, T1, C, R any](f func(T0, T1) GRA) F { + return func(c C, t0 T0, t1 T1) (R, error) { + return E.UnwrapError(f(t0, t1)(c)()) + } +} + +// From3 converts a function with 4 parameters returning a tuple into a function with 3 parameters returning a [GRA] +// The first parameter is considerd to be the context [C]. +func From3[GRA ~func(C) GIOA, F ~func(C, T0, T1, T2) func() (R, error), GIOA ~func() E.Either[error, R], T0, T1, T2, C, R any](f F) func(T0, T1, T2) GRA { + return RD.From3[GRA](func(r C, t0 T0, t1 T1, t2 T2) GIOA { + return E.Eitherize0(f(r, t0, t1, t2)) + }) +} + +// Eitherize3 converts a function with 3 parameters returning a tuple into a function with 3 parameters returning a [GRA] +// The first parameter is considered to be the context [C]. +func Eitherize3[GRA ~func(C) GIOA, F ~func(C, T0, T1, T2) (R, error), GIOA ~func() E.Either[error, R], T0, T1, T2, C, R any](f F) func(T0, T1, T2) GRA { + return From3[GRA](func(r C, t0 T0, t1 T1, t2 T2) func() (R, error) { + return func() (R, error) { + return f(r, t0, t1, t2) + }}) +} + +// Uneitherize3 converts a function with 3 parameters returning a [GRA] into a function with 3 parameters returning a tuple. +// The first parameter is considered to be the context [C]. +func Uneitherize3[GRA ~func(C) GIOA, F ~func(C, T0, T1, T2) (R, error), GIOA ~func() E.Either[error, R], T0, T1, T2, C, R any](f func(T0, T1, T2) GRA) F { + return func(c C, t0 T0, t1 T1, t2 T2) (R, error) { + return E.UnwrapError(f(t0, t1, t2)(c)()) + } +} + +// From4 converts a function with 5 parameters returning a tuple into a function with 4 parameters returning a [GRA] +// The first parameter is considerd to be the context [C]. +func From4[GRA ~func(C) GIOA, F ~func(C, T0, T1, T2, T3) func() (R, error), GIOA ~func() E.Either[error, R], T0, T1, T2, T3, C, R any](f F) func(T0, T1, T2, T3) GRA { + return RD.From4[GRA](func(r C, t0 T0, t1 T1, t2 T2, t3 T3) GIOA { + return E.Eitherize0(f(r, t0, t1, t2, t3)) + }) +} + +// Eitherize4 converts a function with 4 parameters returning a tuple into a function with 4 parameters returning a [GRA] +// The first parameter is considered to be the context [C]. +func Eitherize4[GRA ~func(C) GIOA, F ~func(C, T0, T1, T2, T3) (R, error), GIOA ~func() E.Either[error, R], T0, T1, T2, T3, C, R any](f F) func(T0, T1, T2, T3) GRA { + return From4[GRA](func(r C, t0 T0, t1 T1, t2 T2, t3 T3) func() (R, error) { + return func() (R, error) { + return f(r, t0, t1, t2, t3) + }}) +} + +// Uneitherize4 converts a function with 4 parameters returning a [GRA] into a function with 4 parameters returning a tuple. +// The first parameter is considered to be the context [C]. +func Uneitherize4[GRA ~func(C) GIOA, F ~func(C, T0, T1, T2, T3) (R, error), GIOA ~func() E.Either[error, R], T0, T1, T2, T3, C, R any](f func(T0, T1, T2, T3) GRA) F { + return func(c C, t0 T0, t1 T1, t2 T2, t3 T3) (R, error) { + return E.UnwrapError(f(t0, t1, t2, t3)(c)()) + } +} + +// From5 converts a function with 6 parameters returning a tuple into a function with 5 parameters returning a [GRA] +// The first parameter is considerd to be the context [C]. +func From5[GRA ~func(C) GIOA, F ~func(C, T0, T1, T2, T3, T4) func() (R, error), GIOA ~func() E.Either[error, R], T0, T1, T2, T3, T4, C, R any](f F) func(T0, T1, T2, T3, T4) GRA { + return RD.From5[GRA](func(r C, t0 T0, t1 T1, t2 T2, t3 T3, t4 T4) GIOA { + return E.Eitherize0(f(r, t0, t1, t2, t3, t4)) + }) +} + +// Eitherize5 converts a function with 5 parameters returning a tuple into a function with 5 parameters returning a [GRA] +// The first parameter is considered to be the context [C]. +func Eitherize5[GRA ~func(C) GIOA, F ~func(C, T0, T1, T2, T3, T4) (R, error), GIOA ~func() E.Either[error, R], T0, T1, T2, T3, T4, C, R any](f F) func(T0, T1, T2, T3, T4) GRA { + return From5[GRA](func(r C, t0 T0, t1 T1, t2 T2, t3 T3, t4 T4) func() (R, error) { + return func() (R, error) { + return f(r, t0, t1, t2, t3, t4) + }}) +} + +// Uneitherize5 converts a function with 5 parameters returning a [GRA] into a function with 5 parameters returning a tuple. +// The first parameter is considered to be the context [C]. +func Uneitherize5[GRA ~func(C) GIOA, F ~func(C, T0, T1, T2, T3, T4) (R, error), GIOA ~func() E.Either[error, R], T0, T1, T2, T3, T4, C, R any](f func(T0, T1, T2, T3, T4) GRA) F { + return func(c C, t0 T0, t1 T1, t2 T2, t3 T3, t4 T4) (R, error) { + return E.UnwrapError(f(t0, t1, t2, t3, t4)(c)()) + } +} + +// From6 converts a function with 7 parameters returning a tuple into a function with 6 parameters returning a [GRA] +// The first parameter is considerd to be the context [C]. +func From6[GRA ~func(C) GIOA, F ~func(C, T0, T1, T2, T3, T4, T5) func() (R, error), GIOA ~func() E.Either[error, R], T0, T1, T2, T3, T4, T5, C, R any](f F) func(T0, T1, T2, T3, T4, T5) GRA { + return RD.From6[GRA](func(r C, t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5) GIOA { + return E.Eitherize0(f(r, t0, t1, t2, t3, t4, t5)) + }) +} + +// Eitherize6 converts a function with 6 parameters returning a tuple into a function with 6 parameters returning a [GRA] +// The first parameter is considered to be the context [C]. +func Eitherize6[GRA ~func(C) GIOA, F ~func(C, T0, T1, T2, T3, T4, T5) (R, error), GIOA ~func() E.Either[error, R], T0, T1, T2, T3, T4, T5, C, R any](f F) func(T0, T1, T2, T3, T4, T5) GRA { + return From6[GRA](func(r C, t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5) func() (R, error) { + return func() (R, error) { + return f(r, t0, t1, t2, t3, t4, t5) + }}) +} + +// Uneitherize6 converts a function with 6 parameters returning a [GRA] into a function with 6 parameters returning a tuple. +// The first parameter is considered to be the context [C]. +func Uneitherize6[GRA ~func(C) GIOA, F ~func(C, T0, T1, T2, T3, T4, T5) (R, error), GIOA ~func() E.Either[error, R], T0, T1, T2, T3, T4, T5, C, R any](f func(T0, T1, T2, T3, T4, T5) GRA) F { + return func(c C, t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5) (R, error) { + return E.UnwrapError(f(t0, t1, t2, t3, t4, t5)(c)()) + } +} + +// From7 converts a function with 8 parameters returning a tuple into a function with 7 parameters returning a [GRA] +// The first parameter is considerd to be the context [C]. +func From7[GRA ~func(C) GIOA, F ~func(C, T0, T1, T2, T3, T4, T5, T6) func() (R, error), GIOA ~func() E.Either[error, R], T0, T1, T2, T3, T4, T5, T6, C, R any](f F) func(T0, T1, T2, T3, T4, T5, T6) GRA { + return RD.From7[GRA](func(r C, t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6) GIOA { + return E.Eitherize0(f(r, t0, t1, t2, t3, t4, t5, t6)) + }) +} + +// Eitherize7 converts a function with 7 parameters returning a tuple into a function with 7 parameters returning a [GRA] +// The first parameter is considered to be the context [C]. +func Eitherize7[GRA ~func(C) GIOA, F ~func(C, T0, T1, T2, T3, T4, T5, T6) (R, error), GIOA ~func() E.Either[error, R], T0, T1, T2, T3, T4, T5, T6, C, R any](f F) func(T0, T1, T2, T3, T4, T5, T6) GRA { + return From7[GRA](func(r C, t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6) func() (R, error) { + return func() (R, error) { + return f(r, t0, t1, t2, t3, t4, t5, t6) + }}) +} + +// Uneitherize7 converts a function with 7 parameters returning a [GRA] into a function with 7 parameters returning a tuple. +// The first parameter is considered to be the context [C]. +func Uneitherize7[GRA ~func(C) GIOA, F ~func(C, T0, T1, T2, T3, T4, T5, T6) (R, error), GIOA ~func() E.Either[error, R], T0, T1, T2, T3, T4, T5, T6, C, R any](f func(T0, T1, T2, T3, T4, T5, T6) GRA) F { + return func(c C, t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6) (R, error) { + return E.UnwrapError(f(t0, t1, t2, t3, t4, t5, t6)(c)()) + } +} + +// From8 converts a function with 9 parameters returning a tuple into a function with 8 parameters returning a [GRA] +// The first parameter is considerd to be the context [C]. +func From8[GRA ~func(C) GIOA, F ~func(C, T0, T1, T2, T3, T4, T5, T6, T7) func() (R, error), GIOA ~func() E.Either[error, R], T0, T1, T2, T3, T4, T5, T6, T7, C, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7) GRA { + return RD.From8[GRA](func(r C, t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7) GIOA { + return E.Eitherize0(f(r, t0, t1, t2, t3, t4, t5, t6, t7)) + }) +} + +// Eitherize8 converts a function with 8 parameters returning a tuple into a function with 8 parameters returning a [GRA] +// The first parameter is considered to be the context [C]. +func Eitherize8[GRA ~func(C) GIOA, F ~func(C, T0, T1, T2, T3, T4, T5, T6, T7) (R, error), GIOA ~func() E.Either[error, R], T0, T1, T2, T3, T4, T5, T6, T7, C, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7) GRA { + return From8[GRA](func(r C, t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7) func() (R, error) { + return func() (R, error) { + return f(r, t0, t1, t2, t3, t4, t5, t6, t7) + }}) +} + +// Uneitherize8 converts a function with 8 parameters returning a [GRA] into a function with 8 parameters returning a tuple. +// The first parameter is considered to be the context [C]. +func Uneitherize8[GRA ~func(C) GIOA, F ~func(C, T0, T1, T2, T3, T4, T5, T6, T7) (R, error), GIOA ~func() E.Either[error, R], T0, T1, T2, T3, T4, T5, T6, T7, C, R any](f func(T0, T1, T2, T3, T4, T5, T6, T7) GRA) F { + return func(c C, t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7) (R, error) { + return E.UnwrapError(f(t0, t1, t2, t3, t4, t5, t6, t7)(c)()) + } +} + +// From9 converts a function with 10 parameters returning a tuple into a function with 9 parameters returning a [GRA] +// The first parameter is considerd to be the context [C]. +func From9[GRA ~func(C) GIOA, F ~func(C, T0, T1, T2, T3, T4, T5, T6, T7, T8) func() (R, error), GIOA ~func() E.Either[error, R], T0, T1, T2, T3, T4, T5, T6, T7, T8, C, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7, T8) GRA { + return RD.From9[GRA](func(r C, t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8) GIOA { + return E.Eitherize0(f(r, t0, t1, t2, t3, t4, t5, t6, t7, t8)) + }) +} + +// Eitherize9 converts a function with 9 parameters returning a tuple into a function with 9 parameters returning a [GRA] +// The first parameter is considered to be the context [C]. +func Eitherize9[GRA ~func(C) GIOA, F ~func(C, T0, T1, T2, T3, T4, T5, T6, T7, T8) (R, error), GIOA ~func() E.Either[error, R], T0, T1, T2, T3, T4, T5, T6, T7, T8, C, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7, T8) GRA { + return From9[GRA](func(r C, t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8) func() (R, error) { + return func() (R, error) { + return f(r, t0, t1, t2, t3, t4, t5, t6, t7, t8) + }}) +} + +// Uneitherize9 converts a function with 9 parameters returning a [GRA] into a function with 9 parameters returning a tuple. +// The first parameter is considered to be the context [C]. +func Uneitherize9[GRA ~func(C) GIOA, F ~func(C, T0, T1, T2, T3, T4, T5, T6, T7, T8) (R, error), GIOA ~func() E.Either[error, R], T0, T1, T2, T3, T4, T5, T6, T7, T8, C, R any](f func(T0, T1, T2, T3, T4, T5, T6, T7, T8) GRA) F { + return func(c C, t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8) (R, error) { + return E.UnwrapError(f(t0, t1, t2, t3, t4, t5, t6, t7, t8)(c)()) + } +} + +// From10 converts a function with 11 parameters returning a tuple into a function with 10 parameters returning a [GRA] +// The first parameter is considerd to be the context [C]. +func From10[GRA ~func(C) GIOA, F ~func(C, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9) func() (R, error), GIOA ~func() E.Either[error, R], T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, C, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9) GRA { + return RD.From10[GRA](func(r C, t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9) GIOA { + return E.Eitherize0(f(r, t0, t1, t2, t3, t4, t5, t6, t7, t8, t9)) + }) +} + +// Eitherize10 converts a function with 10 parameters returning a tuple into a function with 10 parameters returning a [GRA] +// The first parameter is considered to be the context [C]. +func Eitherize10[GRA ~func(C) GIOA, F ~func(C, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9) (R, error), GIOA ~func() E.Either[error, R], T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, C, R any](f F) func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9) GRA { + return From10[GRA](func(r C, t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9) func() (R, error) { + return func() (R, error) { + return f(r, t0, t1, t2, t3, t4, t5, t6, t7, t8, t9) + }}) +} + +// Uneitherize10 converts a function with 10 parameters returning a [GRA] into a function with 10 parameters returning a tuple. +// The first parameter is considered to be the context [C]. +func Uneitherize10[GRA ~func(C) GIOA, F ~func(C, T0, T1, T2, T3, T4, T5, T6, T7, T8, T9) (R, error), GIOA ~func() E.Either[error, R], T0, T1, T2, T3, T4, T5, T6, T7, T8, T9, C, R any](f func(T0, T1, T2, T3, T4, T5, T6, T7, T8, T9) GRA) F { + return func(c C, t0 T0, t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9) (R, error) { + return E.UnwrapError(f(t0, t1, t2, t3, t4, t5, t6, t7, t8, t9)(c)()) + } +} diff --git a/v2/readerioeither/generic/monad.go b/v2/readerioeither/generic/monad.go new file mode 100644 index 0000000..8e86a4b --- /dev/null +++ b/v2/readerioeither/generic/monad.go @@ -0,0 +1,68 @@ +// Copyright (c) 2024 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + "github.com/IBM/fp-go/v2/either" + "github.com/IBM/fp-go/v2/internal/functor" + "github.com/IBM/fp-go/v2/internal/monad" + "github.com/IBM/fp-go/v2/internal/pointed" +) + +type readerIOEitherPointed[R, E, A any, GRA ~func(R) GIOA, GIOA ~func() either.Either[E, A]] struct{} + +type readerIOEitherMonad[R, E, A, B any, GRA ~func(R) GIOA, GRB ~func(R) GIOB, GRAB ~func(R) GIOAB, GIOA ~func() either.Either[E, A], GIOB ~func() either.Either[E, B], GIOAB ~func() either.Either[E, func(A) B]] struct{} + +type readerIOEitherFunctor[R, E, A, B any, GRA ~func(R) GIOA, GRB ~func(R) GIOB, GIOA ~func() either.Either[E, A], GIOB ~func() either.Either[E, B]] struct{} + +func (o *readerIOEitherPointed[R, E, A, GRA, GIOA]) Of(a A) GRA { + return Of[GRA, GIOA, R, E, A](a) +} + +func (o *readerIOEitherMonad[R, E, A, B, GRA, GRB, GRAB, GIOA, GIOB, GIOAB]) Of(a A) GRA { + return Of[GRA, GIOA, R, E, A](a) +} + +func (o *readerIOEitherMonad[R, E, A, B, GRA, GRB, GRAB, GIOA, GIOB, GIOAB]) Map(f func(A) B) func(GRA) GRB { + return Map[GRA, GRB, GIOA, GIOB, R, E, A, B](f) +} + +func (o *readerIOEitherMonad[R, E, A, B, GRA, GRB, GRAB, GIOA, GIOB, GIOAB]) Chain(f func(A) GRB) func(GRA) GRB { + return Chain[GRA, GRB, GIOA, GIOB, R, E, A, B](f) +} + +func (o *readerIOEitherMonad[R, E, A, B, GRA, GRB, GRAB, GIOA, GIOB, GIOAB]) Ap(fa GRA) func(GRAB) GRB { + return Ap[GRA, GRB, GRAB, GIOA, GIOB, GIOAB, R, E, A, B](fa) +} + +func (o *readerIOEitherFunctor[R, E, A, B, GRA, GRB, GIOA, GIOB]) Map(f func(A) B) func(GRA) GRB { + return Map[GRA, GRB, GIOA, GIOB, R, E, A, B](f) +} + +// Pointed implements the pointed operations for [ReaderIOEither] +func Pointed[R, E, A any, GRA ~func(R) GIOA, GIOA ~func() either.Either[E, A]]() pointed.Pointed[A, GRA] { + return &readerIOEitherPointed[R, E, A, GRA, GIOA]{} +} + +// Functor implements the monadic operations for [ReaderIOEither] +func Functor[R, E, A, B any, GRA ~func(R) GIOA, GRB ~func(R) GIOB, GIOA ~func() either.Either[E, A], GIOB ~func() either.Either[E, B]]() functor.Functor[A, B, GRA, GRB] { + return &readerIOEitherFunctor[R, E, A, B, GRA, GRB, GIOA, GIOB]{} +} + +// Monad implements the monadic operations for [ReaderIOEither] +func Monad[R, E, A, B any, GRA ~func(R) GIOA, GRB ~func(R) GIOB, GRAB ~func(R) GIOAB, GIOA ~func() either.Either[E, A], GIOB ~func() either.Either[E, B], GIOAB ~func() either.Either[E, func(A) B]]() monad.Monad[A, B, GRA, GRB, GRAB] { + return &readerIOEitherMonad[R, E, A, B, GRA, GRB, GRAB, GIOA, GIOB, GIOAB]{} +} diff --git a/v2/readerioeither/generic/reader.go b/v2/readerioeither/generic/reader.go new file mode 100644 index 0000000..4c3b5ed --- /dev/null +++ b/v2/readerioeither/generic/reader.go @@ -0,0 +1,532 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + "github.com/IBM/fp-go/v2/either" + F "github.com/IBM/fp-go/v2/function" + C "github.com/IBM/fp-go/v2/internal/chain" + "github.com/IBM/fp-go/v2/internal/eithert" + FE "github.com/IBM/fp-go/v2/internal/fromeither" + FIO "github.com/IBM/fp-go/v2/internal/fromio" + FIOE "github.com/IBM/fp-go/v2/internal/fromioeither" + FR "github.com/IBM/fp-go/v2/internal/fromreader" + FC "github.com/IBM/fp-go/v2/internal/functor" + IOE "github.com/IBM/fp-go/v2/ioeither/generic" + O "github.com/IBM/fp-go/v2/option" + RD "github.com/IBM/fp-go/v2/reader/generic" + G "github.com/IBM/fp-go/v2/readerio/generic" +) + +// MakeReader constructs an instance of a reader +// Deprecated: +func MakeReader[GEA ~func(R) GIOA, GIOA ~func() either.Either[E, A], R, E, A any](f func(R) GIOA) GEA { + return f +} + +// Deprecated: +func MonadAlt[LAZY ~func() GEA, GEA ~func(R) GIOA, GIOA ~func() either.Either[E, A], R, E, A any](first GEA, second LAZY) GEA { + return eithert.MonadAlt( + G.Of[GEA], + G.MonadChain[GEA, GEA], + + first, + second, + ) +} + +// Deprecated: +func Alt[LAZY ~func() GEA, GEA ~func(R) GIOA, GIOA ~func() either.Either[E, A], R, E, A any](second LAZY) func(GEA) GEA { + return F.Bind2nd(MonadAlt[LAZY], second) +} + +// Deprecated: +func MonadMap[GEA ~func(R) GIOA, GEB ~func(R) GIOB, GIOA ~func() either.Either[E, A], GIOB ~func() either.Either[E, B], R, E, A, B any](fa GEA, f func(A) B) GEB { + return eithert.MonadMap(G.MonadMap[GEA, GEB, GIOA, GIOB, R, either.Either[E, A], either.Either[E, B]], fa, f) +} + +// Deprecated: +func Map[GEA ~func(R) GIOA, GEB ~func(R) GIOB, GIOA ~func() either.Either[E, A], GIOB ~func() either.Either[E, B], R, E, A, B any](f func(A) B) func(GEA) GEB { + return F.Bind2nd(MonadMap[GEA, GEB, GIOA, GIOB, R, E, A, B], f) +} + +// Deprecated: +func MonadMapTo[GEA ~func(R) GIOA, GEB ~func(R) GIOB, GIOA ~func() either.Either[E, A], GIOB ~func() either.Either[E, B], R, E, A, B any](fa GEA, b B) GEB { + return MonadMap[GEA, GEB](fa, F.Constant1[A](b)) +} + +// Deprecated: +func MapTo[GEA ~func(R) GIOA, GEB ~func(R) GIOB, GIOA ~func() either.Either[E, A], GIOB ~func() either.Either[E, B], R, E, A, B any](b B) func(GEA) GEB { + return Map[GEA, GEB](F.Constant1[A](b)) +} + +// Deprecated: +func MonadChain[GEA ~func(R) GIOA, GEB ~func(R) GIOB, GIOA ~func() either.Either[E, A], GIOB ~func() either.Either[E, B], R, E, A, B any](fa GEA, f func(A) GEB) GEB { + return eithert.MonadChain( + G.MonadChain[GEA, GEB, GIOA, GIOB, R, either.Either[E, A], either.Either[E, B]], + G.Of[GEB, GIOB, R, either.Either[E, B]], + fa, + f) +} + +// Deprecated: +func Chain[GEA ~func(R) GIOA, GEB ~func(R) GIOB, GIOA ~func() either.Either[E, A], GIOB ~func() either.Either[E, B], R, E, A, B any](f func(A) GEB) func(fa GEA) GEB { + return F.Bind2nd(MonadChain[GEA, GEB, GIOA, GIOB, R, E, A, B], f) +} + +// Deprecated: +func MonadChainFirst[GEA ~func(R) GIOA, GEB ~func(R) GIOB, GIOA ~func() either.Either[E, A], GIOB ~func() either.Either[E, B], R, E, A, B any](fa GEA, f func(A) GEB) GEA { + return C.MonadChainFirst( + MonadChain[GEA, GEA, GIOA, GIOA, R, E, A, A], + MonadMap[GEB, GEA, GIOB, GIOA, R, E, B, A], + fa, + f) +} + +// Deprecated: +func ChainFirst[GEA ~func(R) GIOA, GEB ~func(R) GIOB, GIOA ~func() either.Either[E, A], GIOB ~func() either.Either[E, B], R, E, A, B any](f func(A) GEB) func(fa GEA) GEA { + return F.Bind2nd(MonadChainFirst[GEA, GEB, GIOA, GIOB, R, E, A, B], f) +} + +// Deprecated: +func MonadChainEitherK[GEA ~func(R) GIOA, GEB ~func(R) GIOB, GIOA ~func() either.Either[E, A], GIOB ~func() either.Either[E, B], R, E, A, B any](ma GEA, f func(A) either.Either[E, B]) GEB { + return FE.MonadChainEitherK( + MonadChain[GEA, GEB, GIOA, GIOB, R, E, A, B], + FromEither[GEB, GIOB, R, E, B], + ma, + f, + ) +} + +// Deprecated: +func ChainEitherK[GEA ~func(R) GIOA, GEB ~func(R) GIOB, GIOA ~func() either.Either[E, A], GIOB ~func() either.Either[E, B], R, E, A, B any](f func(A) either.Either[E, B]) func(ma GEA) GEB { + return F.Bind2nd(MonadChainEitherK[GEA, GEB, GIOA, GIOB, R, E, A, B], f) +} + +// Deprecated: +func MonadChainFirstEitherK[GEA ~func(R) GIOA, GIOA ~func() either.Either[E, A], R, E, A, B any](ma GEA, f func(A) either.Either[E, B]) GEA { + return FE.MonadChainFirstEitherK( + MonadChain[GEA, GEA, GIOA, GIOA, R, E, A, A], + MonadMap[func(R) func() either.Either[E, B], GEA, func() either.Either[E, B], GIOA, R, E, B, A], + FromEither[func(R) func() either.Either[E, B], func() either.Either[E, B], R, E, B], + ma, + f, + ) +} + +// Deprecated: +func ChainFirstEitherK[GEA ~func(R) GIOA, GIOA ~func() either.Either[E, A], R, E, A, B any](f func(A) either.Either[E, B]) func(ma GEA) GEA { + return F.Bind2nd(MonadChainFirstEitherK[GEA, GIOA, R, E, A, B], f) +} + +// Deprecated: +func MonadChainFirstIOK[GEA ~func(R) GIOA, GIOA ~func() either.Either[E, A], GIO ~func() B, R, E, A, B any](ma GEA, f func(A) GIO) GEA { + return FIO.MonadChainFirstIOK( + MonadChain[GEA, GEA, GIOA, GIOA, R, E, A, A], + MonadMap[func(R) func() either.Either[E, B], GEA, func() either.Either[E, B], GIOA, R, E, B, A], + FromIO[func(R) func() either.Either[E, B], func() either.Either[E, B], GIO, R, E, B], + ma, + f, + ) +} + +// Deprecated: +func ChainFirstIOK[GEA ~func(R) GIOA, GIOA ~func() either.Either[E, A], GIO ~func() B, R, E, A, B any](f func(A) GIO) func(GEA) GEA { + return F.Bind2nd(MonadChainFirstIOK[GEA, GIOA, GIO, R, E, A, B], f) +} + +// Deprecated: +func MonadChainReaderK[GEA ~func(R) GIOA, GEB ~func(R) GIOB, GIOA ~func() either.Either[E, A], GIOB ~func() either.Either[E, B], GB ~func(R) B, R, E, A, B any](ma GEA, f func(A) GB) GEB { + return FR.MonadChainReaderK( + MonadChain[GEA, GEB, GIOA, GIOB, R, E, A, B], + FromReader[GB, GEB, GIOB, R, E, B], + ma, + f, + ) +} + +// Deprecated: +func ChainReaderK[GEA ~func(R) GIOA, GEB ~func(R) GIOB, GIOA ~func() either.Either[E, A], GIOB ~func() either.Either[E, B], GB ~func(R) B, R, E, A, B any](f func(A) GB) func(GEA) GEB { + return FR.ChainReaderK( + MonadChain[GEA, GEB, GIOA, GIOB, R, E, A, B], + FromReader[GB, GEB, GIOB, R, E, B], + f, + ) +} + +// Deprecated: +func MonadChainReaderIOK[GEA ~func(R) GIOEA, GEB ~func(R) GIOEB, GIOEA ~func() either.Either[E, A], GIOEB ~func() either.Either[E, B], GIOB ~func() B, GB ~func(R) GIOB, R, E, A, B any](ma GEA, f func(A) GB) GEB { + return FR.MonadChainReaderK( + MonadChain[GEA, GEB, GIOEA, GIOEB, R, E, A, B], + RightReaderIO[GEB, GIOEB, GB, GIOB, R, E, B], + ma, + f, + ) +} + +// Deprecated: +func ChainReaderIOK[GEA ~func(R) GIOEA, GEB ~func(R) GIOEB, GIOEA ~func() either.Either[E, A], GIOEB ~func() either.Either[E, B], GIOB ~func() B, GB ~func(R) GIOB, R, E, A, B any](f func(A) GB) func(GEA) GEB { + return FR.ChainReaderK( + MonadChain[GEA, GEB, GIOEA, GIOEB, R, E, A, B], + RightReaderIO[GEB, GIOEB, GB, GIOB, R, E, B], + f, + ) +} + +// Deprecated: +func MonadChainIOEitherK[GEA ~func(R) GIOA, GEB ~func(R) GIOB, GIOA ~func() either.Either[E, A], GIOB ~func() either.Either[E, B], R, E, A, B any](ma GEA, f func(A) GIOB) GEB { + return FIOE.MonadChainIOEitherK( + MonadChain[GEA, GEB, GIOA, GIOB, R, E, A, B], + FromIOEither[GEB, GIOB, R, E, B], + ma, + f, + ) +} + +// Deprecated: +func ChainIOEitherK[GEA ~func(R) GIOA, GEB ~func(R) GIOB, GIOA ~func() either.Either[E, A], GIOB ~func() either.Either[E, B], R, E, A, B any](f func(A) GIOB) func(GEA) GEB { + return F.Bind2nd(MonadChainIOEitherK[GEA, GEB, GIOA, GIOB, R, E, A, B], f) +} + +// Deprecated: +func MonadChainIOK[GEA ~func(R) GIOA, GEB ~func(R) GIOB, GIOA ~func() either.Either[E, A], GIOB ~func() either.Either[E, B], GIO ~func() B, R, E, A, B any](ma GEA, f func(A) GIO) GEB { + return FIO.MonadChainIOK( + MonadChain[GEA, GEB, GIOA, GIOB, R, E, A, B], + FromIO[GEB, GIOB, GIO, R, E, B], + ma, + f, + ) +} + +// Deprecated: +func ChainIOK[GEA ~func(R) GIOA, GEB ~func(R) GIOB, GIOA ~func() either.Either[E, A], GIOB ~func() either.Either[E, B], GIO ~func() B, R, E, A, B any](f func(A) GIO) func(GEA) GEB { + return F.Bind2nd(MonadChainIOK[GEA, GEB, GIOA, GIOB, GIO, R, E, A, B], f) +} + +// Deprecated: +func ChainOptionK[GEA ~func(R) GIOA, GEB ~func(R) GIOB, GIOA ~func() either.Either[E, A], GIOB ~func() either.Either[E, B], R, E, A, B any](onNone func() E) func(func(A) O.Option[B]) func(GEA) GEB { + return FE.ChainOptionK(MonadChain[GEA, GEB, GIOA, GIOB, R, E, A, B], FromEither[GEB, GIOB, R, E, B], onNone) +} + +// Deprecated: +func MonadAp[ + GEA ~func(R) GIOA, + GEB ~func(R) GIOB, + GEFAB ~func(R) GIOFAB, + GIOA ~func() either.Either[E, A], + GIOB ~func() either.Either[E, B], + GIOFAB ~func() either.Either[E, func(A) B], + R, E, A, B any](fab GEFAB, fa GEA) GEB { + + return eithert.MonadAp( + G.MonadAp[GEA, GEB, func(R) func() func(either.Either[E, A]) either.Either[E, B], GIOA, GIOB, func() func(either.Either[E, A]) either.Either[E, B], R, either.Either[E, A], either.Either[E, B]], + G.MonadMap[GEFAB, func(R) func() func(either.Either[E, A]) either.Either[E, B], GIOFAB, func() func(either.Either[E, A]) either.Either[E, B], R, either.Either[E, func(A) B], func(either.Either[E, A]) either.Either[E, B]], + fab, + fa, + ) +} + +// Deprecated: +func Ap[ + GEA ~func(R) GIOA, + GEB ~func(R) GIOB, + GEFAB ~func(R) GIOFAB, + GIOA ~func() either.Either[E, A], + GIOB ~func() either.Either[E, B], + GIOFAB ~func() either.Either[E, func(A) B], + R, E, A, B any](fa GEA) func(fab GEFAB) GEB { + return F.Bind2nd(MonadAp[GEA, GEB, GEFAB, GIOA, GIOB, GIOFAB, R, E, A, B], fa) +} + +// Deprecated: +func MonadApSeq[ + GEA ~func(R) GIOA, + GEB ~func(R) GIOB, + GEFAB ~func(R) GIOFAB, + GIOA ~func() either.Either[E, A], + GIOB ~func() either.Either[E, B], + GIOFAB ~func() either.Either[E, func(A) B], + R, E, A, B any](fab GEFAB, fa GEA) GEB { + + return eithert.MonadAp( + G.MonadApSeq[GEA, GEB, func(R) func() func(either.Either[E, A]) either.Either[E, B], GIOA, GIOB, func() func(either.Either[E, A]) either.Either[E, B], R, either.Either[E, A], either.Either[E, B]], + G.MonadMap[GEFAB, func(R) func() func(either.Either[E, A]) either.Either[E, B], GIOFAB, func() func(either.Either[E, A]) either.Either[E, B], R, either.Either[E, func(A) B], func(either.Either[E, A]) either.Either[E, B]], + fab, + fa, + ) +} + +// Deprecated: +func ApSeq[ + GEA ~func(R) GIOA, + GEB ~func(R) GIOB, + GEFAB ~func(R) GIOFAB, + GIOA ~func() either.Either[E, A], + GIOB ~func() either.Either[E, B], + GIOFAB ~func() either.Either[E, func(A) B], + R, E, A, B any](fa GEA) func(fab GEFAB) GEB { + return F.Bind2nd(MonadApSeq[GEA, GEB, GEFAB, GIOA, GIOB, GIOFAB, R, E, A, B], fa) +} + +// Deprecated: +func MonadApPar[ + GEA ~func(R) GIOA, + GEB ~func(R) GIOB, + GEFAB ~func(R) GIOFAB, + GIOA ~func() either.Either[E, A], + GIOB ~func() either.Either[E, B], + GIOFAB ~func() either.Either[E, func(A) B], + R, E, A, B any](fab GEFAB, fa GEA) GEB { + + return eithert.MonadAp( + G.MonadApPar[GEA, GEB, func(R) func() func(either.Either[E, A]) either.Either[E, B], GIOA, GIOB, func() func(either.Either[E, A]) either.Either[E, B], R, either.Either[E, A], either.Either[E, B]], + G.MonadMap[GEFAB, func(R) func() func(either.Either[E, A]) either.Either[E, B], GIOFAB, func() func(either.Either[E, A]) either.Either[E, B], R, either.Either[E, func(A) B], func(either.Either[E, A]) either.Either[E, B]], + fab, + fa, + ) +} + +// Deprecated: +func ApPar[ + GEA ~func(R) GIOA, + GEB ~func(R) GIOB, + GEFAB ~func(R) GIOFAB, + GIOA ~func() either.Either[E, A], + GIOB ~func() either.Either[E, B], + GIOFAB ~func() either.Either[E, func(A) B], + R, E, A, B any](fa GEA) func(fab GEFAB) GEB { + return F.Bind2nd(MonadApPar[GEA, GEB, GEFAB, GIOA, GIOB, GIOFAB, R, E, A, B], fa) +} + +// Deprecated: +func Right[GEA ~func(R) GIOA, GIOA ~func() either.Either[E, A], R, E, A any](a A) GEA { + return eithert.Right(G.Of[GEA, GIOA, R, either.Either[E, A]], a) +} + +// Deprecated: +func Left[GEA ~func(R) GIOA, GIOA ~func() either.Either[E, A], R, E, A any](e E) GEA { + return eithert.Left(G.Of[GEA, GIOA, R, either.Either[E, A]], e) +} + +// Deprecated: +func ThrowError[GEA ~func(R) GIOA, GIOA ~func() either.Either[E, A], R, E, A any](e E) GEA { + return Left[GEA](e) +} + +// Of returns a Reader with a fixed value +// Deprecated: +func Of[GEA ~func(R) GIOA, GIOA ~func() either.Either[E, A], R, E, A any](a A) GEA { + return Right[GEA](a) +} + +// Deprecated: +func Flatten[GEA ~func(R) GIOA, GGEA ~func(R) GIOEA, GIOA ~func() either.Either[E, A], GIOEA ~func() either.Either[E, GEA], R, E, A any](mma GGEA) GEA { + return MonadChain(mma, F.Identity[GEA]) +} + +// Deprecated: +func FromIOEither[GEA ~func(R) GIOA, GIOA ~func() either.Either[E, A], R, E, A any](t GIOA) GEA { + return RD.Of[GEA](t) +} + +// Deprecated: +func FromEither[GEA ~func(R) GIOA, GIOA ~func() either.Either[E, A], R, E, A any](t either.Either[E, A]) GEA { + return G.Of[GEA](t) +} + +// Deprecated: +func RightReader[GA ~func(R) A, GEA ~func(R) GIOA, GIOA ~func() either.Either[E, A], R, E, A any](ma GA) GEA { + return F.Flow2(ma, IOE.Right[GIOA, E, A]) +} + +// Deprecated: +func LeftReader[GE ~func(R) E, GEA ~func(R) GIOA, GIOA ~func() either.Either[E, A], R, E, A any](ma GE) GEA { + return F.Flow2(ma, IOE.Left[GIOA, E, A]) +} + +// Deprecated: +func FromReader[GA ~func(R) A, GEA ~func(R) GIOA, GIOA ~func() either.Either[E, A], R, E, A any](ma GA) GEA { + return RightReader[GA, GEA](ma) +} + +// Deprecated: +func MonadFromReaderIO[GEA ~func(R) GIOA, GIOA ~func() either.Either[E, A], GRIO ~func(R) GIO, GIO ~func() A, R, E, A any](a A, f func(A) GRIO) GEA { + return F.Pipe2( + a, + f, + RightReaderIO[GEA, GIOA, GRIO, GIO, R, E, A], + ) +} + +// Deprecated: +func FromReaderIO[GEA ~func(R) GIOA, GIOA ~func() either.Either[E, A], GRIO ~func(R) GIO, GIO ~func() A, R, E, A any](f func(A) GRIO) func(A) GEA { + return F.Bind2nd(MonadFromReaderIO[GEA, GIOA, GRIO, GIO, R, E, A], f) +} + +// Deprecated: +func RightReaderIO[GEA ~func(R) GIOA, GIOA ~func() either.Either[E, A], GRIO ~func(R) GIO, GIO ~func() A, R, E, A any](ma GRIO) GEA { + return eithert.RightF( + G.MonadMap[GRIO, GEA, GIO, GIOA, R, A, either.Either[E, A]], + ma, + ) +} + +// Deprecated: +func LeftReaderIO[GEA ~func(R) GIOA, GIOA ~func() either.Either[E, A], GRIO ~func(R) GIO, GIO ~func() E, R, E, A any](me GRIO) GEA { + return eithert.LeftF( + G.MonadMap[GRIO, GEA, GIO, GIOA, R, E, either.Either[E, A]], + me, + ) +} + +// Deprecated: +func RightIO[GEA ~func(R) GIOA, GIOA ~func() either.Either[E, A], GR ~func() A, R, E, A any](ma GR) GEA { + return F.Pipe2(ma, IOE.RightIO[GIOA, GR, E, A], FromIOEither[GEA, GIOA, R, E, A]) +} + +// Deprecated: +func LeftIO[GEA ~func(R) GIOA, GIOA ~func() either.Either[E, A], GR ~func() E, R, E, A any](ma GR) GEA { + return F.Pipe2(ma, IOE.LeftIO[GIOA, GR, E, A], FromIOEither[GEA, GIOA, R, E, A]) +} + +// Deprecated: +func FromIO[GEA ~func(R) GIOA, GIOA ~func() either.Either[E, A], GR ~func() A, R, E, A any](ma GR) GEA { + return RightIO[GEA](ma) +} + +// Deprecated: +func FromReaderEither[GA ~func(R) either.Either[E, A], GEA ~func(R) GIOA, GIOA ~func() either.Either[E, A], R, E, A any](ma GA) GEA { + return F.Flow2(ma, IOE.FromEither[GIOA, E, A]) +} + +// Deprecated: +func Ask[GER ~func(R) GIOR, GIOR ~func() either.Either[E, R], R, E any]() GER { + return FR.Ask(FromReader[func(R) R, GER, GIOR, R, E, R])() +} + +// Deprecated: +func Asks[GA ~func(R) A, GEA ~func(R) GIOA, GIOA ~func() either.Either[E, A], R, E, A any](r GA) GEA { + return FR.Asks(FromReader[GA, GEA, GIOA, R, E, A])(r) +} + +// Deprecated: +func FromOption[GEA ~func(R) GIOA, GIOA ~func() either.Either[E, A], R, E, A any](onNone func() E) func(O.Option[A]) GEA { + return FE.FromOption(FromEither[GEA, GIOA, R, E, A], onNone) +} + +// Deprecated: +func FromPredicate[GEA ~func(R) GIOA, GIOA ~func() either.Either[E, A], R, E, A any](pred func(A) bool, onFalse func(A) E) func(A) GEA { + return FE.FromPredicate(FromEither[GEA, GIOA, R, E, A], pred, onFalse) +} + +// Deprecated: +func Fold[GB ~func(R) GIOB, GEA ~func(R) GIOA, GIOB ~func() B, GIOA ~func() either.Either[E, A], R, E, A, B any](onLeft func(E) GB, onRight func(A) GB) func(GEA) GB { + return eithert.MatchE(G.MonadChain[GEA, GB, GIOA, GIOB, R, either.Either[E, A], B], onLeft, onRight) +} + +// Deprecated: +func GetOrElse[GA ~func(R) GIOB, GEA ~func(R) GIOA, GIOB ~func() A, GIOA ~func() either.Either[E, A], R, E, A any](onLeft func(E) GA) func(GEA) GA { + return eithert.GetOrElse(G.MonadChain[GEA, GA, GIOA, GIOB, R, either.Either[E, A], A], G.Of[GA, GIOB, R, A], onLeft) +} + +// Deprecated: +func OrElse[GEA1 ~func(R) GIOA1, GEA2 ~func(R) GIOA2, GIOA1 ~func() either.Either[E1, A], GIOA2 ~func() either.Either[E2, A], R, E1, A, E2 any](onLeft func(E1) GEA2) func(GEA1) GEA2 { + return eithert.OrElse(G.MonadChain[GEA1, GEA2, GIOA1, GIOA2, R, either.Either[E1, A], either.Either[E2, A]], G.Of[GEA2, GIOA2, R, either.Either[E2, A]], onLeft) +} + +// Deprecated: +func OrLeft[GEA1 ~func(R) GIOA1, GE2 ~func(R) GIOE2, GEA2 ~func(R) GIOA2, GIOA1 ~func() either.Either[E1, A], GIOE2 ~func() E2, GIOA2 ~func() either.Either[E2, A], E1, R, E2, A any](onLeft func(E1) GE2) func(GEA1) GEA2 { + return eithert.OrLeft( + G.MonadChain[GEA1, GEA2, GIOA1, GIOA2, R, either.Either[E1, A], either.Either[E2, A]], + G.MonadMap[GE2, GEA2, GIOE2, GIOA2, R, E2, either.Either[E2, A]], + G.Of[GEA2, GIOA2, R, either.Either[E2, A]], + onLeft, + ) +} + +// Deprecated: +func MonadBiMap[GA ~func(R) GE1A, GB ~func(R) GE2B, GE1A ~func() either.Either[E1, A], GE2B ~func() either.Either[E2, B], R, E1, E2, A, B any](fa GA, f func(E1) E2, g func(A) B) GB { + return eithert.MonadBiMap(G.MonadMap[GA, GB, GE1A, GE2B, R, either.Either[E1, A], either.Either[E2, B]], fa, f, g) +} + +// BiMap maps a pair of functions over the two type arguments of the bifunctor. +// Deprecated: +func BiMap[GA ~func(R) GE1A, GB ~func(R) GE2B, GE1A ~func() either.Either[E1, A], GE2B ~func() either.Either[E2, B], R, E1, E2, A, B any](f func(E1) E2, g func(A) B) func(GA) GB { + return eithert.BiMap(G.Map[GA, GB, GE1A, GE2B, R, either.Either[E1, A], either.Either[E2, B]], f, g) +} + +// Swap changes the order of type parameters +// Deprecated: +func Swap[GREA ~func(R) GEA, GRAE ~func(R) GAE, GEA ~func() either.Either[E, A], GAE ~func() either.Either[A, E], R, E, A any](val GREA) GRAE { + return RD.MonadMap[GREA, GRAE, R, GEA, GAE](val, IOE.Swap[GEA, GAE]) +} + +// Defer creates an IO by creating a brand new IO via a generator function, each time +// Deprecated: +func Defer[GEA ~func(R) GA, GA ~func() either.Either[E, A], R, E, A any](gen func() GEA) GEA { + return G.Defer[GEA](gen) +} + +// TryCatch wraps a reader returning a tuple as an error into ReaderIOEither +// Deprecated: +func TryCatch[GEA ~func(R) GA, GA ~func() either.Either[E, A], R, E, A any](f func(R) func() (A, error), onThrow func(error) E) GEA { + return func(r R) GA { + return IOE.TryCatch[GA](f(r), onThrow) + } +} + +// Memoize computes the value of the provided monad lazily but exactly once +// The context used to compute the value is the context of the first call, so do not use this +// method if the value has a functional dependency on the content of the context +// Deprecated: +func Memoize[ + GEA ~func(R) GIOA, GIOA ~func() either.Either[E, A], R, E, A any](rdr GEA) GEA { + return G.Memoize[GEA](rdr) +} + +// Deprecated: +func MonadFlap[GREAB ~func(R) GEAB, GREB ~func(R) GEB, GEAB ~func() either.Either[E, func(A) B], GEB ~func() either.Either[E, B], R, E, B, A any](fab GREAB, a A) GREB { + return FC.MonadFlap(MonadMap[GREAB, GREB], fab, a) +} + +// Deprecated: +func Flap[GREAB ~func(R) GEAB, GREB ~func(R) GEB, GEAB ~func() either.Either[E, func(A) B], GEB ~func() either.Either[E, B], R, E, B, A any](a A) func(GREAB) GREB { + return FC.Flap(Map[GREAB, GREB], a) +} + +// Deprecated: +func MonadMapLeft[GREA1 ~func(R) GEA1, GREA2 ~func(R) GEA2, GEA1 ~func() either.Either[E1, A], GEA2 ~func() either.Either[E2, A], R, E1, E2, A any](fa GREA1, f func(E1) E2) GREA2 { + return eithert.MonadMapLeft(G.MonadMap[GREA1, GREA2], fa, f) +} + +// MapLeft applies a mapping function to the error channel +// Deprecated: +func MapLeft[GREA1 ~func(R) GEA1, GREA2 ~func(R) GEA2, GEA1 ~func() either.Either[E1, A], GEA2 ~func() either.Either[E2, A], R, E1, E2, A any](f func(E1) E2) func(GREA1) GREA2 { + return F.Bind2nd(MonadMapLeft[GREA1, GREA2], f) +} + +// Local changes the value of the local context during the execution of the action `ma` (similar to `Contravariant`'s +// `contramap`). +// Deprecated: +func Local[ + GEA1 ~func(R1) GIOA, + GEA2 ~func(R2) GIOA, + + GIOA ~func() either.Either[E, A], + R1, R2, E, A any, +](f func(R2) R1) func(GEA1) GEA2 { + return RD.Local[GEA1, GEA2](f) +} diff --git a/v2/readerioeither/generic/sequence.go b/v2/readerioeither/generic/sequence.go new file mode 100644 index 0000000..33fdfe4 --- /dev/null +++ b/v2/readerioeither/generic/sequence.go @@ -0,0 +1,94 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + "github.com/IBM/fp-go/v2/either" + "github.com/IBM/fp-go/v2/internal/apply" + T "github.com/IBM/fp-go/v2/tuple" +) + +// SequenceT converts n inputs of higher kinded types into a higher kinded types of n strongly typed values, represented as a tuple + +func SequenceT1[ + GA ~func(E) GIOA, + GTA ~func(E) GIOTA, + GIOA ~func() either.Either[L, A], + GIOTA ~func() either.Either[L, T.Tuple1[A]], + E, L, A any](a GA) GTA { + return apply.SequenceT1( + Map[GA, GTA, GIOA, GIOTA, E, L, A, T.Tuple1[A]], + + a, + ) +} + +func SequenceT2[ + GA ~func(E) GIOA, + GB ~func(E) GIOB, + GTAB ~func(E) GIOTAB, + GIOA ~func() either.Either[L, A], + GIOB ~func() either.Either[L, B], + GIOTAB ~func() either.Either[L, T.Tuple2[A, B]], + E, L, A, B any](a GA, b GB) GTAB { + return apply.SequenceT2( + Map[GA, func(E) func() either.Either[L, func(B) T.Tuple2[A, B]], GIOA, func() either.Either[L, func(B) T.Tuple2[A, B]], E, L, A, func(B) T.Tuple2[A, B]], + Ap[GB, GTAB, func(E) func() either.Either[L, func(B) T.Tuple2[A, B]], GIOB, GIOTAB, func() either.Either[L, func(B) T.Tuple2[A, B]], E, L, B, T.Tuple2[A, B]], + + a, b, + ) +} + +func SequenceT3[ + GA ~func(E) GIOA, + GB ~func(E) GIOB, + GC ~func(E) GIOC, + GTABC ~func(E) GIOTABC, + GIOA ~func() either.Either[L, A], + GIOB ~func() either.Either[L, B], + GIOC ~func() either.Either[L, C], + GIOTABC ~func() either.Either[L, T.Tuple3[A, B, C]], + E, L, A, B, C any](a GA, b GB, c GC) GTABC { + return apply.SequenceT3( + Map[GA, func(E) func() either.Either[L, func(B) func(C) T.Tuple3[A, B, C]], GIOA, func() either.Either[L, func(B) func(C) T.Tuple3[A, B, C]], E, L, A, func(B) func(C) T.Tuple3[A, B, C]], + Ap[GB, func(E) func() either.Either[L, func(C) T.Tuple3[A, B, C]], func(E) func() either.Either[L, func(B) func(C) T.Tuple3[A, B, C]], GIOB, func() either.Either[L, func(C) T.Tuple3[A, B, C]], func() either.Either[L, func(B) func(C) T.Tuple3[A, B, C]], E, L, B, func(C) T.Tuple3[A, B, C]], + Ap[GC, GTABC, func(E) func() either.Either[L, func(C) T.Tuple3[A, B, C]], GIOC, GIOTABC, func() either.Either[L, func(C) T.Tuple3[A, B, C]], E, L, C, T.Tuple3[A, B, C]], + + a, b, c, + ) +} + +func SequenceT4[ + GA ~func(E) GIOA, + GB ~func(E) GIOB, + GC ~func(E) GIOC, + GD ~func(E) GIOD, + GTABCD ~func(E) GIOTABCD, + GIOA ~func() either.Either[L, A], + GIOB ~func() either.Either[L, B], + GIOC ~func() either.Either[L, C], + GIOD ~func() either.Either[L, D], + GIOTABCD ~func() either.Either[L, T.Tuple4[A, B, C, D]], + E, L, A, B, C, D any](a GA, b GB, c GC, d GD) GTABCD { + return apply.SequenceT4( + Map[GA, func(E) func() either.Either[L, func(B) func(C) func(D) T.Tuple4[A, B, C, D]], GIOA, func() either.Either[L, func(B) func(C) func(D) T.Tuple4[A, B, C, D]], E, L, A, func(B) func(C) func(D) T.Tuple4[A, B, C, D]], + Ap[GB, func(E) func() either.Either[L, func(C) func(D) T.Tuple4[A, B, C, D]], func(E) func() either.Either[L, func(B) func(C) func(D) T.Tuple4[A, B, C, D]], GIOB, func() either.Either[L, func(C) func(D) T.Tuple4[A, B, C, D]], func() either.Either[L, func(B) func(C) func(D) T.Tuple4[A, B, C, D]], E, L, B, func(C) func(D) T.Tuple4[A, B, C, D]], + Ap[GC, func(E) func() either.Either[L, func(D) T.Tuple4[A, B, C, D]], func(E) func() either.Either[L, func(C) func(D) T.Tuple4[A, B, C, D]], GIOC, func() either.Either[L, func(D) T.Tuple4[A, B, C, D]], func() either.Either[L, func(C) func(D) T.Tuple4[A, B, C, D]], E, L, C, func(D) T.Tuple4[A, B, C, D]], + Ap[GD, GTABCD, func(E) func() either.Either[L, func(D) T.Tuple4[A, B, C, D]], GIOD, GIOTABCD, func() either.Either[L, func(D) T.Tuple4[A, B, C, D]], E, L, D, T.Tuple4[A, B, C, D]], + + a, b, c, d, + ) +} diff --git a/v2/readerioeither/generic/traverse.go b/v2/readerioeither/generic/traverse.go new file mode 100644 index 0000000..702432d --- /dev/null +++ b/v2/readerioeither/generic/traverse.go @@ -0,0 +1,100 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + "github.com/IBM/fp-go/v2/either" + F "github.com/IBM/fp-go/v2/function" + RA "github.com/IBM/fp-go/v2/internal/array" + RR "github.com/IBM/fp-go/v2/internal/record" +) + +// MonadTraverseArray transforms an array +func MonadTraverseArray[GB ~func(E) GIOB, GBS ~func(E) GIOBS, GIOB ~func() either.Either[L, B], GIOBS ~func() either.Either[L, BBS], AAS ~[]A, BBS ~[]B, E, L, A, B any](ma AAS, f func(A) GB) GBS { + return RA.MonadTraverse[AAS]( + Of[GBS, GIOBS, E, L, BBS], + Map[GBS, func(E) func() either.Either[L, func(B) BBS], GIOBS, func() either.Either[L, func(B) BBS], E, L, BBS, func(B) BBS], + Ap[GB, GBS, func(E) func() either.Either[L, func(B) BBS], GIOB, GIOBS, func() either.Either[L, func(B) BBS], E, L, B, BBS], + + ma, f, + ) +} + +// TraverseArray transforms an array +func TraverseArray[GB ~func(E) GIOB, GBS ~func(E) GIOBS, GIOB ~func() either.Either[L, B], GIOBS ~func() either.Either[L, BBS], AAS ~[]A, BBS ~[]B, E, L, A, B any](f func(A) GB) func(AAS) GBS { + return RA.Traverse[AAS]( + Of[GBS, GIOBS, E, L, BBS], + Map[GBS, func(E) func() either.Either[L, func(B) BBS], GIOBS, func() either.Either[L, func(B) BBS], E, L, BBS, func(B) BBS], + Ap[GB, GBS, func(E) func() either.Either[L, func(B) BBS], GIOB, GIOBS, func() either.Either[L, func(B) BBS], E, L, B, BBS], + + f, + ) +} + +// TraverseArrayWithIndex transforms an array +func TraverseArrayWithIndex[GB ~func(E) GIOB, GBS ~func(E) GIOBS, GIOB ~func() either.Either[L, B], GIOBS ~func() either.Either[L, BBS], AAS ~[]A, BBS ~[]B, E, L, A, B any](f func(int, A) GB) func(AAS) GBS { + return RA.TraverseWithIndex[AAS]( + Of[GBS, GIOBS, E, L, BBS], + Map[GBS, func(E) func() either.Either[L, func(B) BBS], GIOBS, func() either.Either[L, func(B) BBS], E, L, BBS, func(B) BBS], + Ap[GB, GBS, func(E) func() either.Either[L, func(B) BBS], GIOB, GIOBS, func() either.Either[L, func(B) BBS], E, L, B, BBS], + + f, + ) +} + +// SequenceArray converts a homogeneous sequence of either into an either of sequence +func SequenceArray[GA ~func(E) GIOA, GAS ~func(E) GIOAS, GIOA ~func() either.Either[L, A], GIOAS ~func() either.Either[L, AAS], AAS ~[]A, GAAS ~[]GA, E, L, A any](ma GAAS) GAS { + return MonadTraverseArray[GA, GAS](ma, F.Identity[GA]) +} + +// MonadTraverseRecord transforms an array +func MonadTraverseRecord[GB ~func(C) GIOB, GBS ~func(C) GIOBS, GIOB ~func() either.Either[E, B], GIOBS ~func() either.Either[E, BBS], AAS ~map[K]A, BBS ~map[K]B, K comparable, C, E, A, B any](tas AAS, f func(A) GB) GBS { + return RR.MonadTraverse[AAS]( + Of[GBS, GIOBS, C, E, BBS], + Map[GBS, func(C) func() either.Either[E, func(B) BBS], GIOBS, func() either.Either[E, func(B) BBS], C, E, BBS, func(B) BBS], + Ap[GB, GBS, func(C) func() either.Either[E, func(B) BBS], GIOB, GIOBS, func() either.Either[E, func(B) BBS], C, E, B, BBS], + + tas, + f, + ) +} + +// TraverseRecord transforms a record +func TraverseRecord[GB ~func(C) GIOB, GBS ~func(C) GIOBS, GIOB ~func() either.Either[E, B], GIOBS ~func() either.Either[E, BBS], AAS ~map[K]A, BBS ~map[K]B, K comparable, C, E, A, B any](f func(A) GB) func(AAS) GBS { + return RR.Traverse[AAS]( + Of[GBS, GIOBS, C, E, BBS], + Map[GBS, func(C) func() either.Either[E, func(B) BBS], GIOBS, func() either.Either[E, func(B) BBS], C, E, BBS, func(B) BBS], + Ap[GB, GBS, func(C) func() either.Either[E, func(B) BBS], GIOB, GIOBS, func() either.Either[E, func(B) BBS], C, E, B, BBS], + + f, + ) +} + +// TraverseRecordWithIndex transforms a record +func TraverseRecordWithIndex[GB ~func(C) GIOB, GBS ~func(C) GIOBS, GIOB ~func() either.Either[E, B], GIOBS ~func() either.Either[E, BBS], AAS ~map[K]A, BBS ~map[K]B, K comparable, C, E, A, B any](f func(K, A) GB) func(AAS) GBS { + return RR.TraverseWithIndex[AAS]( + Of[GBS, GIOBS, C, E, BBS], + Map[GBS, func(C) func() either.Either[E, func(B) BBS], GIOBS, func() either.Either[E, func(B) BBS], C, E, BBS, func(B) BBS], + Ap[GB, GBS, func(C) func() either.Either[E, func(B) BBS], GIOB, GIOBS, func() either.Either[E, func(B) BBS], C, E, B, BBS], + + f, + ) +} + +// SequenceRecord converts a homogeneous sequence of either into an either of sequence +func SequenceRecord[GA ~func(C) GIOA, GAS ~func(C) GIOAS, GIOA ~func() either.Either[E, A], GIOAS ~func() either.Either[E, AAS], AAS ~map[K]A, GAAS ~map[K]GA, K comparable, C, E, A any](tas GAAS) GAS { + return MonadTraverseRecord[GA, GAS](tas, F.Identity[GA]) +} diff --git a/v2/readerioeither/monad.go b/v2/readerioeither/monad.go new file mode 100644 index 0000000..18911d2 --- /dev/null +++ b/v2/readerioeither/monad.go @@ -0,0 +1,38 @@ +// Copyright (c) 2024 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readerioeither + +import ( + "github.com/IBM/fp-go/v2/internal/functor" + "github.com/IBM/fp-go/v2/internal/monad" + "github.com/IBM/fp-go/v2/internal/pointed" + G "github.com/IBM/fp-go/v2/readerioeither/generic" +) + +// Pointed returns the pointed operations for [ReaderIOEither] +func Pointed[R, E, A any]() pointed.Pointed[A, ReaderIOEither[R, E, A]] { + return G.Pointed[R, E, A, ReaderIOEither[R, E, A]]() +} + +// Functor returns the functor operations for [ReaderIOEither] +func Functor[R, E, A, B any]() functor.Functor[A, B, ReaderIOEither[R, E, A], ReaderIOEither[R, E, B]] { + return G.Functor[R, E, A, B, ReaderIOEither[R, E, A], ReaderIOEither[R, E, B]]() +} + +// Monad returns the monadic operations for [ReaderIOEither] +func Monad[R, E, A, B any]() monad.Monad[A, B, ReaderIOEither[R, E, A], ReaderIOEither[R, E, B], ReaderIOEither[R, E, func(A) B]] { + return G.Monad[R, E, A, B, ReaderIOEither[R, E, A], ReaderIOEither[R, E, B], ReaderIOEither[R, E, func(A) B]]() +} diff --git a/v2/readerioeither/reader.go b/v2/readerioeither/reader.go new file mode 100644 index 0000000..41c5437 --- /dev/null +++ b/v2/readerioeither/reader.go @@ -0,0 +1,535 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readerioeither + +import ( + "github.com/IBM/fp-go/v2/either" + "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/internal/chain" + "github.com/IBM/fp-go/v2/internal/eithert" + "github.com/IBM/fp-go/v2/internal/fromeither" + "github.com/IBM/fp-go/v2/internal/fromio" + "github.com/IBM/fp-go/v2/internal/fromioeither" + "github.com/IBM/fp-go/v2/internal/fromreader" + "github.com/IBM/fp-go/v2/internal/functor" + "github.com/IBM/fp-go/v2/io" + "github.com/IBM/fp-go/v2/ioeither" + IOE "github.com/IBM/fp-go/v2/ioeither" + L "github.com/IBM/fp-go/v2/lazy" + O "github.com/IBM/fp-go/v2/option" + "github.com/IBM/fp-go/v2/reader" + RE "github.com/IBM/fp-go/v2/readereither" + "github.com/IBM/fp-go/v2/readerio" +) + +// MonadFromReaderIO creates a ReaderIOEither from a value and a function that produces a ReaderIO. +// The ReaderIO result is lifted into the Right side of the Either. +func MonadFromReaderIO[R, E, A any](a A, f func(A) ReaderIO[R, A]) ReaderIOEither[R, E, A] { + return function.Pipe2( + a, + f, + RightReaderIO[R, E, A], + ) +} + +// FromReaderIO creates a function that lifts a ReaderIO-producing function into ReaderIOEither. +// The ReaderIO result is placed in the Right side of the Either. +func FromReaderIO[R, E, A any](f func(A) ReaderIO[R, A]) func(A) ReaderIOEither[R, E, A] { + return function.Bind2nd(MonadFromReaderIO[R, E, A], f) +} + +// RightReaderIO lifts a ReaderIO into a ReaderIOEither, placing the result in the Right side. +func RightReaderIO[R, E, A any](ma ReaderIO[R, A]) ReaderIOEither[R, E, A] { + return eithert.RightF( + readerio.MonadMap[R, A, either.Either[E, A]], + ma, + ) +} + +// LeftReaderIO lifts a ReaderIO into a ReaderIOEither, placing the result in the Left (error) side. +func LeftReaderIO[A, R, E any](me ReaderIO[R, E]) ReaderIOEither[R, E, A] { + return eithert.LeftF( + readerio.MonadMap[R, E, either.Either[E, A]], + me, + ) +} + +// MonadMap applies a function to the value inside a ReaderIOEither context. +// If the computation is successful (Right), the function is applied to the value. +// If it's an error (Left), the error is propagated unchanged. +func MonadMap[R, E, A, B any](fa ReaderIOEither[R, E, A], f func(A) B) ReaderIOEither[R, E, B] { + return eithert.MonadMap(readerio.MonadMap[R, either.Either[E, A], either.Either[E, B]], fa, f) +} + +// Map returns a function that applies a transformation to the success value of a ReaderIOEither. +// This is the curried version of MonadMap, useful for function composition. +func Map[R, E, A, B any](f func(A) B) Operator[R, E, A, B] { + return eithert.Map(readerio.Map[R, either.Either[E, A], either.Either[E, B]], f) +} + +// MonadMapTo replaces the success value with a constant value. +// Useful when you want to discard the result but keep the effect. +func MonadMapTo[R, E, A, B any](fa ReaderIOEither[R, E, A], b B) ReaderIOEither[R, E, B] { + return MonadMap(fa, function.Constant1[A](b)) +} + +// MapTo returns a function that replaces the success value with a constant. +// This is the curried version of MonadMapTo. +func MapTo[R, E, A, B any](b B) Operator[R, E, A, B] { + return Map[R, E](function.Constant1[A](b)) +} + +// MonadChain sequences two computations where the second depends on the result of the first. +// This is the fundamental operation for composing dependent effectful computations. +// If the first computation fails, the second is not executed. +func MonadChain[R, E, A, B any](fa ReaderIOEither[R, E, A], f func(A) ReaderIOEither[R, E, B]) ReaderIOEither[R, E, B] { + return eithert.MonadChain( + readerio.MonadChain[R, either.Either[E, A], either.Either[E, B]], + readerio.Of[R, either.Either[E, B]], + fa, + f) +} + +// MonadChainFirst sequences two computations but keeps the result of the first. +// Useful for performing side effects while preserving the original value. +func MonadChainFirst[R, E, A, B any](fa ReaderIOEither[R, E, A], f func(A) ReaderIOEither[R, E, B]) ReaderIOEither[R, E, A] { + return chain.MonadChainFirst( + MonadChain[R, E, A, A], + MonadMap[R, E, B, A], + fa, + f) +} + +// MonadChainEitherK chains a computation that returns an Either into a ReaderIOEither. +// The Either is automatically lifted into the ReaderIOEither context. +func MonadChainEitherK[R, E, A, B any](ma ReaderIOEither[R, E, A], f func(A) either.Either[E, B]) ReaderIOEither[R, E, B] { + return fromeither.MonadChainEitherK( + MonadChain[R, E, A, B], + FromEither[R, E, B], + ma, + f, + ) +} + +// ChainEitherK returns a function that chains an Either-returning function into ReaderIOEither. +// This is the curried version of MonadChainEitherK. +func ChainEitherK[R, E, A, B any](f func(A) either.Either[E, B]) Operator[R, E, A, B] { + return fromeither.ChainEitherK( + Chain[R, E, A, B], + FromEither[R, E, B], + f, + ) +} + +// MonadChainFirstEitherK chains an Either-returning computation but keeps the original value. +// Useful for validation or side effects that return Either. +func MonadChainFirstEitherK[R, E, A, B any](ma ReaderIOEither[R, E, A], f func(A) either.Either[E, B]) ReaderIOEither[R, E, A] { + return fromeither.MonadChainFirstEitherK( + MonadChain[R, E, A, A], + MonadMap[R, E, B, A], + FromEither[R, E, B], + ma, + f, + ) +} + +// ChainFirstEitherK returns a function that chains an Either computation while preserving the original value. +// This is the curried version of MonadChainFirstEitherK. +func ChainFirstEitherK[R, E, A, B any](f func(A) either.Either[E, B]) Operator[R, E, A, A] { + return fromeither.ChainFirstEitherK( + Chain[R, E, A, A], + Map[R, E, B, A], + FromEither[R, E, B], + f, + ) +} + +// MonadChainReaderK chains a Reader-returning computation into a ReaderIOEither. +// The Reader is automatically lifted into the ReaderIOEither context. +func MonadChainReaderK[R, E, A, B any](ma ReaderIOEither[R, E, A], f func(A) Reader[R, B]) ReaderIOEither[R, E, B] { + return fromreader.MonadChainReaderK( + MonadChain[R, E, A, B], + FromReader[E, R, B], + ma, + f, + ) +} + +// ChainReaderK returns a function that chains a Reader-returning function into ReaderIOEither. +// This is the curried version of MonadChainReaderK. +func ChainReaderK[E, R, A, B any](f func(A) Reader[R, B]) Operator[R, E, A, B] { + return fromreader.ChainReaderK( + MonadChain[R, E, A, B], + FromReader[E, R, B], + f, + ) +} + +// MonadChainIOEitherK chains an IOEither-returning computation into a ReaderIOEither. +// The IOEither is automatically lifted into the ReaderIOEither context. +func MonadChainIOEitherK[R, E, A, B any](ma ReaderIOEither[R, E, A], f func(A) IOE.IOEither[E, B]) ReaderIOEither[R, E, B] { + return fromioeither.MonadChainIOEitherK( + MonadChain[R, E, A, B], + FromIOEither[R, E, B], + ma, + f, + ) +} + +// ChainIOEitherK returns a function that chains an IOEither-returning function into ReaderIOEither. +// This is the curried version of MonadChainIOEitherK. +func ChainIOEitherK[R, E, A, B any](f func(A) IOE.IOEither[E, B]) Operator[R, E, A, B] { + return fromioeither.ChainIOEitherK( + Chain[R, E, A, B], + FromIOEither[R, E, B], + f, + ) +} + +// MonadChainIOK chains an IO-returning computation into a ReaderIOEither. +// The IO is automatically lifted into the ReaderIOEither context (always succeeds). +func MonadChainIOK[R, E, A, B any](ma ReaderIOEither[R, E, A], f func(A) io.IO[B]) ReaderIOEither[R, E, B] { + return fromio.MonadChainIOK( + MonadChain[R, E, A, B], + FromIO[R, E, B], + ma, + f, + ) +} + +// ChainIOK returns a function that chains an IO-returning function into ReaderIOEither. +// This is the curried version of MonadChainIOK. +func ChainIOK[R, E, A, B any](f func(A) io.IO[B]) Operator[R, E, A, B] { + return fromio.ChainIOK( + Chain[R, E, A, B], + FromIO[R, E, B], + f, + ) +} + +// MonadChainFirstIOK chains an IO computation but keeps the original value. +// Useful for performing IO side effects while preserving the original value. +func MonadChainFirstIOK[R, E, A, B any](ma ReaderIOEither[R, E, A], f func(A) io.IO[B]) ReaderIOEither[R, E, A] { + return fromio.MonadChainFirstIOK( + MonadChain[R, E, A, A], + MonadMap[R, E, B, A], + FromIO[R, E, B], + ma, + f, + ) +} + +// ChainFirstIOK returns a function that chains an IO computation while preserving the original value. +// This is the curried version of MonadChainFirstIOK. +func ChainFirstIOK[R, E, A, B any](f func(A) io.IO[B]) Operator[R, E, A, A] { + return fromio.ChainFirstIOK( + Chain[R, E, A, A], + Map[R, E, B, A], + FromIO[R, E, B], + f, + ) +} + +// ChainOptionK returns a function that chains an Option-returning function into ReaderIOEither. +// If the Option is None, the provided error function is called to produce the error value. +func ChainOptionK[R, A, B, E any](onNone func() E) func(func(A) O.Option[B]) Operator[R, E, A, B] { + return fromeither.ChainOptionK( + MonadChain[R, E, A, B], + FromEither[R, E, B], + onNone, + ) +} + +// MonadAp applies a function wrapped in a context to a value wrapped in a context. +// Both computations are executed (default behavior may be sequential or parallel depending on implementation). +func MonadAp[R, E, A, B any](fab ReaderIOEither[R, E, func(A) B], fa ReaderIOEither[R, E, A]) ReaderIOEither[R, E, B] { + return eithert.MonadAp( + readerio.MonadAp[Either[E, B], R, Either[E, A]], + readerio.MonadMap[R, Either[E, func(A) B], func(Either[E, A]) Either[E, B]], + fab, + fa, + ) +} + +// MonadApSeq applies a function in a context to a value in a context, executing them sequentially. +func MonadApSeq[R, E, A, B any](fab ReaderIOEither[R, E, func(A) B], fa ReaderIOEither[R, E, A]) ReaderIOEither[R, E, B] { + return eithert.MonadAp( + readerio.MonadApSeq[Either[E, B], R, Either[E, A]], + readerio.MonadMap[R, Either[E, func(A) B], func(Either[E, A]) Either[E, B]], + fab, + fa, + ) +} + +// MonadApPar applies a function in a context to a value in a context, executing them in parallel. +func MonadApPar[R, E, A, B any](fab ReaderIOEither[R, E, func(A) B], fa ReaderIOEither[R, E, A]) ReaderIOEither[R, E, B] { + return eithert.MonadAp( + readerio.MonadApPar[Either[E, B], R, Either[E, A]], + readerio.MonadMap[R, Either[E, func(A) B], func(Either[E, A]) Either[E, B]], + fab, + fa, + ) +} + +// Ap returns a function that applies a function in a context to a value in a context. +// This is the curried version of MonadAp. +func Ap[B, R, E, A any](fa ReaderIOEither[R, E, A]) func(fab ReaderIOEither[R, E, func(A) B]) ReaderIOEither[R, E, B] { + return function.Bind2nd(MonadAp[R, E, A, B], fa) +} + +// Chain returns a function that sequences computations where the second depends on the first. +// This is the curried version of MonadChain. +func Chain[R, E, A, B any](f func(A) ReaderIOEither[R, E, B]) Operator[R, E, A, B] { + return eithert.Chain( + readerio.Chain[R, either.Either[E, A], either.Either[E, B]], + readerio.Of[R, either.Either[E, B]], + f) +} + +// ChainFirst returns a function that sequences computations but keeps the first result. +// This is the curried version of MonadChainFirst. +func ChainFirst[R, E, A, B any](f func(A) ReaderIOEither[R, E, B]) Operator[R, E, A, A] { + return chain.ChainFirst( + Chain[R, E, A, A], + Map[R, E, B, A], + f) +} + +// Right creates a successful ReaderIOEither with the given value. +func Right[R, E, A any](a A) ReaderIOEither[R, E, A] { + return eithert.Right(readerio.Of[R, Either[E, A]], a) +} + +// Left creates a failed ReaderIOEither with the given error. +func Left[R, A, E any](e E) ReaderIOEither[R, E, A] { + return eithert.Left(readerio.Of[R, Either[E, A]], e) +} + +// ThrowError creates a failed ReaderIOEither with the given error. +// This is an alias for Left, following the naming convention from other functional libraries. +func ThrowError[R, A, E any](e E) ReaderIOEither[R, E, A] { + return Left[R, A](e) +} + +// Of creates a successful ReaderIOEither with the given value. +// This is the pointed functor operation, lifting a pure value into the ReaderIOEither context. +func Of[R, E, A any](a A) ReaderIOEither[R, E, A] { + return Right[R, E](a) +} + +// Flatten removes one level of nesting from a nested ReaderIOEither. +// Converts ReaderIOEither[R, E, ReaderIOEither[R, E, A]] to ReaderIOEither[R, E, A]. +func Flatten[R, E, A any](mma ReaderIOEither[R, E, ReaderIOEither[R, E, A]]) ReaderIOEither[R, E, A] { + return MonadChain(mma, function.Identity[ReaderIOEither[R, E, A]]) +} + +// FromEither lifts an Either into a ReaderIOEither context. +// The Either value is independent of any context or IO effects. +func FromEither[R, E, A any](t either.Either[E, A]) ReaderIOEither[R, E, A] { + return readerio.Of[R](t) +} + +// RightReader lifts a Reader into a ReaderIOEither, placing the result in the Right side. +func RightReader[E, R, A any](ma Reader[R, A]) ReaderIOEither[R, E, A] { + return function.Flow2(ma, ioeither.Right[E, A]) +} + +// LeftReader lifts a Reader into a ReaderIOEither, placing the result in the Left (error) side. +func LeftReader[A, R, E any](ma Reader[R, E]) ReaderIOEither[R, E, A] { + return function.Flow2(ma, ioeither.Left[A, E]) +} + +// FromReader lifts a Reader into a ReaderIOEither context. +// The Reader result is placed in the Right side (success). +func FromReader[E, R, A any](ma Reader[R, A]) ReaderIOEither[R, E, A] { + return RightReader[E](ma) +} + +// RightIO lifts an IO into a ReaderIOEither, placing the result in the Right side. +func RightIO[R, E, A any](ma io.IO[A]) ReaderIOEither[R, E, A] { + return function.Pipe2(ma, ioeither.RightIO[E, A], FromIOEither[R, E, A]) +} + +// LeftIO lifts an IO into a ReaderIOEither, placing the result in the Left (error) side. +func LeftIO[R, A, E any](ma io.IO[E]) ReaderIOEither[R, E, A] { + return function.Pipe2(ma, ioeither.LeftIO[A, E], FromIOEither[R, E, A]) +} + +// FromIO lifts an IO into a ReaderIOEither context. +// The IO result is placed in the Right side (success). +func FromIO[R, E, A any](ma io.IO[A]) ReaderIOEither[R, E, A] { + return RightIO[R, E](ma) +} + +// FromIOEither lifts an IOEither into a ReaderIOEither context. +// The computation becomes independent of any reader context. +func FromIOEither[R, E, A any](ma IOE.IOEither[E, A]) ReaderIOEither[R, E, A] { + return reader.Of[R](ma) +} + +// FromReaderEither lifts a ReaderEither into a ReaderIOEither context. +// The Either result is lifted into an IO effect. +func FromReaderEither[R, E, A any](ma RE.ReaderEither[R, E, A]) ReaderIOEither[R, E, A] { + return function.Flow2(ma, ioeither.FromEither[E, A]) +} + +// Ask returns a ReaderIOEither that retrieves the current context. +// Useful for accessing configuration or dependencies. +func Ask[R, E any]() ReaderIOEither[R, E, R] { + return fromreader.Ask(FromReader[E, R, R])() +} + +// Asks returns a ReaderIOEither that retrieves a value derived from the context. +// This is useful for extracting specific fields from a configuration object. +func Asks[E, R, A any](r Reader[R, A]) ReaderIOEither[R, E, A] { + return fromreader.Asks(FromReader[E, R, A])(r) +} + +// FromOption converts an Option to a ReaderIOEither. +// If the Option is None, the provided function is called to produce the error. +func FromOption[R, A, E any](onNone func() E) func(O.Option[A]) ReaderIOEither[R, E, A] { + return fromeither.FromOption(FromEither[R, E, A], onNone) +} + +// FromPredicate creates a ReaderIOEither from a predicate. +// If the predicate returns false, the onFalse function is called to produce the error. +func FromPredicate[R, E, A any](pred func(A) bool, onFalse func(A) E) func(A) ReaderIOEither[R, E, A] { + return fromeither.FromPredicate(FromEither[R, E, A], pred, onFalse) +} + +// Fold handles both success and error cases, producing a ReaderIO. +// This is useful for converting a ReaderIOEither into a ReaderIO by handling all cases. +func Fold[R, E, A, B any](onLeft func(E) ReaderIO[R, B], onRight func(A) ReaderIO[R, B]) func(ReaderIOEither[R, E, A]) ReaderIO[R, B] { + return eithert.MatchE(readerio.MonadChain[R, either.Either[E, A], B], onLeft, onRight) +} + +// GetOrElse provides a default value in case of error. +// The default is computed lazily via a ReaderIO. +func GetOrElse[R, E, A any](onLeft func(E) ReaderIO[R, A]) func(ReaderIOEither[R, E, A]) ReaderIO[R, A] { + return eithert.GetOrElse(readerio.MonadChain[R, either.Either[E, A], A], readerio.Of[R, A], onLeft) +} + +// OrElse tries an alternative computation if the first one fails. +// The alternative can produce a different error type. +func OrElse[R, E1, A, E2 any](onLeft func(E1) ReaderIOEither[R, E2, A]) func(ReaderIOEither[R, E1, A]) ReaderIOEither[R, E2, A] { + return eithert.OrElse(readerio.MonadChain[R, either.Either[E1, A], either.Either[E2, A]], readerio.Of[R, either.Either[E2, A]], onLeft) +} + +// OrLeft transforms the error using a ReaderIO if the computation fails. +// The success value is preserved unchanged. +func OrLeft[A, E1, R, E2 any](onLeft func(E1) ReaderIO[R, E2]) func(ReaderIOEither[R, E1, A]) ReaderIOEither[R, E2, A] { + return eithert.OrLeft( + readerio.MonadChain[R, either.Either[E1, A], either.Either[E2, A]], + readerio.MonadMap[R, E2, either.Either[E2, A]], + readerio.Of[R, either.Either[E2, A]], + onLeft, + ) +} + +// MonadBiMap applies two functions: one to the error, one to the success value. +// This allows transforming both channels simultaneously. +func MonadBiMap[R, E1, E2, A, B any](fa ReaderIOEither[R, E1, A], f func(E1) E2, g func(A) B) ReaderIOEither[R, E2, B] { + return eithert.MonadBiMap( + readerio.MonadMap[R, either.Either[E1, A], either.Either[E2, B]], + fa, f, g, + ) +} + +// BiMap returns a function that maps over both the error and success channels. +// This is the curried version of MonadBiMap. +func BiMap[R, E1, E2, A, B any](f func(E1) E2, g func(A) B) func(ReaderIOEither[R, E1, A]) ReaderIOEither[R, E2, B] { + return eithert.BiMap(readerio.Map[R, either.Either[E1, A], either.Either[E2, B]], f, g) +} + +// Swap exchanges the error and success types. +// Left becomes Right and Right becomes Left. +func Swap[R, E, A any](val ReaderIOEither[R, E, A]) ReaderIOEither[R, A, E] { + return reader.MonadMap(val, ioeither.Swap[E, A]) +} + +// Defer creates a ReaderIOEither lazily via a generator function. +// The generator is called each time the ReaderIOEither is executed. +func Defer[R, E, A any](gen L.Lazy[ReaderIOEither[R, E, A]]) ReaderIOEither[R, E, A] { + return readerio.Defer(gen) +} + +// TryCatch wraps a function that returns (value, error) into a ReaderIOEither. +// The onThrow function converts the error into the desired error type. +func TryCatch[R, E, A any](f func(R) func() (A, error), onThrow func(error) E) ReaderIOEither[R, E, A] { + return func(r R) IOEither[E, A] { + return ioeither.TryCatch(f(r), onThrow) + } +} + +// MonadAlt tries the first computation, and if it fails, tries the second. +// This implements the Alternative pattern for error recovery. +func MonadAlt[R, E, A any](first ReaderIOEither[R, E, A], second L.Lazy[ReaderIOEither[R, E, A]]) ReaderIOEither[R, E, A] { + return eithert.MonadAlt( + readerio.Of[R, Either[E, A]], + readerio.MonadChain[R, Either[E, A], Either[E, A]], + + first, + second, + ) +} + +// Alt returns a function that tries an alternative computation if the first fails. +// This is the curried version of MonadAlt. +func Alt[R, E, A any](second L.Lazy[ReaderIOEither[R, E, A]]) Operator[R, E, A, A] { + return eithert.Alt( + readerio.Of[R, Either[E, A]], + readerio.MonadChain[R, Either[E, A], Either[E, A]], + + second, + ) +} + +// Memoize computes the value of the ReaderIOEither lazily but exactly once. +// The context used is from the first call. Do not use if the value depends on the context. +func Memoize[ + R, E, A any](rdr ReaderIOEither[R, E, A]) ReaderIOEither[R, E, A] { + return readerio.Memoize(rdr) +} + +// MonadFlap applies a value to a function wrapped in a context. +// This is the reverse of Ap - the value is fixed and the function varies. +func MonadFlap[R, E, B, A any](fab ReaderIOEither[R, E, func(A) B], a A) ReaderIOEither[R, E, B] { + return functor.MonadFlap(MonadMap[R, E, func(A) B, B], fab, a) +} + +// Flap returns a function that applies a fixed value to a function in a context. +// This is the curried version of MonadFlap. +func Flap[R, E, B, A any](a A) func(ReaderIOEither[R, E, func(A) B]) ReaderIOEither[R, E, B] { + return functor.Flap(Map[R, E, func(A) B, B], a) +} + +// MonadMapLeft applies a function to the error value, leaving success unchanged. +func MonadMapLeft[R, E1, E2, A any](fa ReaderIOEither[R, E1, A], f func(E1) E2) ReaderIOEither[R, E2, A] { + return eithert.MonadMapLeft(readerio.MonadMap[R, Either[E1, A], Either[E2, A]], fa, f) +} + +// MapLeft returns a function that transforms the error channel. +// This is the curried version of MonadMapLeft. +func MapLeft[R, A, E1, E2 any](f func(E1) E2) func(ReaderIOEither[R, E1, A]) ReaderIOEither[R, E2, A] { + return eithert.MapLeft(readerio.Map[R, Either[E1, A], Either[E2, A]], f) +} + +// Local runs a computation with a modified context. +// The function f transforms the context before passing it to the computation. +// This is similar to Contravariant's contramap operation. +func Local[R1, R2, E, A any](f func(R2) R1) func(ReaderIOEither[R1, E, A]) ReaderIOEither[R2, E, A] { + return reader.Local[R2, R1, IOEither[E, A]](f) +} diff --git a/v2/readerioeither/reader_test.go b/v2/readerioeither/reader_test.go new file mode 100644 index 0000000..2126f43 --- /dev/null +++ b/v2/readerioeither/reader_test.go @@ -0,0 +1,79 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readerioeither + +import ( + "context" + "fmt" + "testing" + + E "github.com/IBM/fp-go/v2/either" + F "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/internal/utils" + R "github.com/IBM/fp-go/v2/reader" + "github.com/IBM/fp-go/v2/readerio" + "github.com/stretchr/testify/assert" +) + +func TestMap(t *testing.T) { + + g := F.Pipe1( + Of[context.Context, error](1), + Map[context.Context, error](utils.Double), + ) + + assert.Equal(t, E.Of[error](2), g(context.Background())()) +} + +func TestOrLeft(t *testing.T) { + f := OrLeft[int](func(s string) readerio.ReaderIO[context.Context, string] { + return readerio.Of[context.Context](s + "!") + }) + + g1 := F.Pipe1( + Right[context.Context, string](1), + f, + ) + + g2 := F.Pipe1( + Left[context.Context, int]("a"), + f, + ) + + assert.Equal(t, E.Of[string](1), g1(context.Background())()) + assert.Equal(t, E.Left[int]("a!"), g2(context.Background())()) +} + +func TestAp(t *testing.T) { + g := F.Pipe1( + Right[context.Context, error](utils.Double), + Ap[int](Right[context.Context, error](1)), + ) + + assert.Equal(t, E.Right[error](2), g(context.Background())()) +} + +func TestChainReaderK(t *testing.T) { + + g := F.Pipe1( + Of[context.Context, error](1), + ChainReaderK[error](func(v int) R.Reader[context.Context, string] { + return R.Of[context.Context](fmt.Sprintf("%d", v)) + }), + ) + + assert.Equal(t, E.Right[error]("1"), g(context.Background())()) +} diff --git a/v2/readerioeither/readerioeither_test.go b/v2/readerioeither/readerioeither_test.go new file mode 100644 index 0000000..830b791 --- /dev/null +++ b/v2/readerioeither/readerioeither_test.go @@ -0,0 +1,830 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readerioeither + +import ( + "context" + "errors" + "fmt" + "testing" + + E "github.com/IBM/fp-go/v2/either" + F "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/io" + IOE "github.com/IBM/fp-go/v2/ioeither" + O "github.com/IBM/fp-go/v2/option" + R "github.com/IBM/fp-go/v2/reader" + RE "github.com/IBM/fp-go/v2/readereither" + RIO "github.com/IBM/fp-go/v2/readerio" + "github.com/stretchr/testify/assert" +) + +type testContext struct { + value int +} + +func TestMonadMap(t *testing.T) { + ctx := testContext{value: 10} + result := MonadMap(Of[testContext, error](5), func(x int) int { return x * 2 }) + assert.Equal(t, E.Right[error](10), result(ctx)()) +} + +func TestMonadMapTo(t *testing.T) { + ctx := testContext{value: 10} + result := MonadMapTo(Of[testContext, error](5), 42) + assert.Equal(t, E.Right[error](42), result(ctx)()) +} + +func TestMapTo(t *testing.T) { + ctx := testContext{value: 10} + result := F.Pipe1(Of[testContext, error](5), MapTo[testContext, error, int](42)) + assert.Equal(t, E.Right[error](42), result(ctx)()) +} + +func TestMonadChainFirst(t *testing.T) { + ctx := testContext{value: 10} + result := MonadChainFirst( + Of[testContext, error](5), + func(x int) ReaderIOEither[testContext, error, string] { + return Of[testContext, error](fmt.Sprintf("%d", x)) + }, + ) + assert.Equal(t, E.Right[error](5), result(ctx)()) +} + +func TestChainFirst(t *testing.T) { + ctx := testContext{value: 10} + result := F.Pipe1( + Of[testContext, error](5), + ChainFirst[testContext, error](func(x int) ReaderIOEither[testContext, error, string] { + return Of[testContext, error](fmt.Sprintf("%d", x)) + }), + ) + assert.Equal(t, E.Right[error](5), result(ctx)()) +} + +func TestMonadChainEitherK(t *testing.T) { + ctx := testContext{value: 10} + result := MonadChainEitherK( + Of[testContext, error](5), + func(x int) E.Either[error, int] { + return E.Right[error](x * 2) + }, + ) + assert.Equal(t, E.Right[error](10), result(ctx)()) +} + +func TestMonadChainFirstEitherK(t *testing.T) { + ctx := testContext{value: 10} + result := MonadChainFirstEitherK( + Of[testContext, error](5), + func(x int) E.Either[error, string] { + return E.Right[error](fmt.Sprintf("%d", x)) + }, + ) + assert.Equal(t, E.Right[error](5), result(ctx)()) +} + +func TestChainFirstEitherK(t *testing.T) { + ctx := testContext{value: 10} + result := F.Pipe1( + Of[testContext, error](5), + ChainFirstEitherK[testContext, error](func(x int) E.Either[error, string] { + return E.Right[error](fmt.Sprintf("%d", x)) + }), + ) + assert.Equal(t, E.Right[error](5), result(ctx)()) +} + +func TestMonadChainReaderK(t *testing.T) { + ctx := testContext{value: 10} + result := MonadChainReaderK( + Of[testContext, error](5), + func(x int) R.Reader[testContext, int] { + return func(c testContext) int { return x + c.value } + }, + ) + assert.Equal(t, E.Right[error](15), result(ctx)()) +} + +func TestMonadChainIOEitherK(t *testing.T) { + ctx := testContext{value: 10} + result := MonadChainIOEitherK( + Of[testContext, error](5), + func(x int) IOE.IOEither[error, int] { + return IOE.Right[error](x * 2) + }, + ) + assert.Equal(t, E.Right[error](10), result(ctx)()) +} + +func TestChainIOEitherK(t *testing.T) { + ctx := testContext{value: 10} + result := F.Pipe1( + Of[testContext, error](5), + ChainIOEitherK[testContext, error](func(x int) IOE.IOEither[error, int] { + return IOE.Right[error](x * 2) + }), + ) + assert.Equal(t, E.Right[error](10), result(ctx)()) +} + +func TestMonadChainIOK(t *testing.T) { + ctx := testContext{value: 10} + result := MonadChainIOK( + Of[testContext, error](5), + func(x int) io.IO[int] { + return func() int { return x * 2 } + }, + ) + assert.Equal(t, E.Right[error](10), result(ctx)()) +} + +func TestChainIOK(t *testing.T) { + ctx := testContext{value: 10} + result := F.Pipe1( + Of[testContext, error](5), + ChainIOK[testContext, error](func(x int) io.IO[int] { + return func() int { return x * 2 } + }), + ) + assert.Equal(t, E.Right[error](10), result(ctx)()) +} + +func TestMonadChainFirstIOK(t *testing.T) { + ctx := testContext{value: 10} + result := MonadChainFirstIOK( + Of[testContext, error](5), + func(x int) io.IO[string] { + return func() string { return fmt.Sprintf("%d", x) } + }, + ) + assert.Equal(t, E.Right[error](5), result(ctx)()) +} + +func TestChainFirstIOK(t *testing.T) { + ctx := testContext{value: 10} + result := F.Pipe1( + Of[testContext, error](5), + ChainFirstIOK[testContext, error](func(x int) io.IO[string] { + return func() string { return fmt.Sprintf("%d", x) } + }), + ) + assert.Equal(t, E.Right[error](5), result(ctx)()) +} + +func TestChainOptionK(t *testing.T) { + ctx := testContext{value: 10} + + // Test with Some + resultSome := F.Pipe1( + Of[testContext, error](5), + ChainOptionK[testContext, int, int, error](func() error { + return errors.New("none") + })(func(x int) O.Option[int] { + return O.Some(x * 2) + }), + ) + assert.Equal(t, E.Right[error](10), resultSome(ctx)()) + + // Test with None + resultNone := F.Pipe1( + Of[testContext, error](5), + ChainOptionK[testContext, int, int, error](func() error { + return errors.New("none") + })(func(x int) O.Option[int] { + return O.None[int]() + }), + ) + assert.True(t, E.IsLeft(resultNone(ctx)())) +} + +func TestMonadApSeq(t *testing.T) { + ctx := testContext{value: 10} + fab := Of[testContext, error](func(x int) int { return x * 2 }) + fa := Of[testContext, error](5) + result := MonadApSeq(fab, fa) + assert.Equal(t, E.Right[error](10), result(ctx)()) +} + +func TestMonadApPar(t *testing.T) { + ctx := testContext{value: 10} + fab := Of[testContext, error](func(x int) int { return x * 2 }) + fa := Of[testContext, error](5) + result := MonadApPar(fab, fa) + assert.Equal(t, E.Right[error](10), result(ctx)()) +} + +func TestChain(t *testing.T) { + ctx := testContext{value: 10} + result := F.Pipe1( + Of[testContext, error](5), + Chain[testContext, error](func(x int) ReaderIOEither[testContext, error, int] { + return Of[testContext, error](x * 2) + }), + ) + assert.Equal(t, E.Right[error](10), result(ctx)()) +} + +func TestThrowError(t *testing.T) { + ctx := testContext{value: 10} + result := ThrowError[testContext, int](errors.New("test error")) + assert.True(t, E.IsLeft(result(ctx)())) +} + +func TestFlatten(t *testing.T) { + ctx := testContext{value: 10} + nested := Of[testContext, error](Of[testContext, error](5)) + result := Flatten(nested) + assert.Equal(t, E.Right[error](5), result(ctx)()) +} + +func TestFromEither(t *testing.T) { + ctx := testContext{value: 10} + result := FromEither[testContext](E.Right[error](5)) + assert.Equal(t, E.Right[error](5), result(ctx)()) +} + +func TestRightReader(t *testing.T) { + ctx := testContext{value: 10} + reader := func(c testContext) int { return c.value } + result := RightReader[error](reader) + assert.Equal(t, E.Right[error](10), result(ctx)()) +} + +func TestLeftReader(t *testing.T) { + ctx := testContext{value: 10} + reader := func(c testContext) error { return errors.New("test") } + result := LeftReader[int](reader) + assert.True(t, E.IsLeft(result(ctx)())) +} + +func TestRightIO(t *testing.T) { + ctx := testContext{value: 10} + ioVal := func() int { return 42 } + result := RightIO[testContext, error](ioVal) + assert.Equal(t, E.Right[error](42), result(ctx)()) +} + +func TestLeftIO(t *testing.T) { + ctx := testContext{value: 10} + ioVal := func() error { return errors.New("test") } + result := LeftIO[testContext, int](ioVal) + assert.True(t, E.IsLeft(result(ctx)())) +} + +func TestFromIO(t *testing.T) { + ctx := testContext{value: 10} + ioVal := func() int { return 42 } + result := FromIO[testContext, error](ioVal) + assert.Equal(t, E.Right[error](42), result(ctx)()) +} + +func TestFromIOEither(t *testing.T) { + ctx := testContext{value: 10} + ioe := IOE.Right[error](42) + result := FromIOEither[testContext](ioe) + assert.Equal(t, E.Right[error](42), result(ctx)()) +} + +func TestFromReaderEither(t *testing.T) { + ctx := testContext{value: 10} + re := RE.Of[testContext, error](42) + result := FromReaderEither(re) + assert.Equal(t, E.Right[error](42), result(ctx)()) +} + +func TestAsk(t *testing.T) { + ctx := testContext{value: 10} + result := Ask[testContext, error]() + assert.Equal(t, E.Right[error](ctx), result(ctx)()) +} + +func TestAsks(t *testing.T) { + ctx := testContext{value: 10} + result := Asks[error](func(c testContext) int { return c.value }) + assert.Equal(t, E.Right[error](10), result(ctx)()) +} + +func TestFromOption(t *testing.T) { + ctx := testContext{value: 10} + + // Test with Some + resultSome := FromOption[testContext, int](func() error { + return errors.New("none") + })(O.Some(42)) + assert.Equal(t, E.Right[error](42), resultSome(ctx)()) + + // Test with None + resultNone := FromOption[testContext, int](func() error { + return errors.New("none") + })(O.None[int]()) + assert.True(t, E.IsLeft(resultNone(ctx)())) +} + +func TestFromPredicate(t *testing.T) { + ctx := testContext{value: 10} + + // Test predicate true + resultTrue := FromPredicate[testContext, error]( + func(x int) bool { return x > 0 }, + func(x int) error { return errors.New("negative") }, + )(5) + assert.Equal(t, E.Right[error](5), resultTrue(ctx)()) + + // Test predicate false + resultFalse := FromPredicate[testContext, error]( + func(x int) bool { return x > 0 }, + func(x int) error { return errors.New("negative") }, + )(-5) + assert.True(t, E.IsLeft(resultFalse(ctx)())) +} + +func TestFold(t *testing.T) { + ctx := testContext{value: 10} + + // Test Right case + resultRight := Fold[testContext, error, int, string]( + func(e error) RIO.ReaderIO[testContext, string] { + return RIO.Of[testContext]("error: " + e.Error()) + }, + func(x int) RIO.ReaderIO[testContext, string] { + return RIO.Of[testContext](fmt.Sprintf("value: %d", x)) + }, + )(Of[testContext, error](42)) + assert.Equal(t, "value: 42", resultRight(ctx)()) + + // Test Left case + resultLeft := Fold[testContext, error, int, string]( + func(e error) RIO.ReaderIO[testContext, string] { + return RIO.Of[testContext]("error: " + e.Error()) + }, + func(x int) RIO.ReaderIO[testContext, string] { + return RIO.Of[testContext](fmt.Sprintf("value: %d", x)) + }, + )(Left[testContext, int](errors.New("test"))) + assert.Equal(t, "error: test", resultLeft(ctx)()) +} + +func TestGetOrElse(t *testing.T) { + ctx := testContext{value: 10} + + // Test Right case + resultRight := GetOrElse[testContext, error](func(e error) RIO.ReaderIO[testContext, int] { + return RIO.Of[testContext](0) + })(Of[testContext, error](42)) + assert.Equal(t, 42, resultRight(ctx)()) + + // Test Left case + resultLeft := GetOrElse[testContext, error](func(e error) RIO.ReaderIO[testContext, int] { + return RIO.Of[testContext](0) + })(Left[testContext, int](errors.New("test"))) + assert.Equal(t, 0, resultLeft(ctx)()) +} + +func TestOrElse(t *testing.T) { + ctx := testContext{value: 10} + + // Test Right case + resultRight := OrElse[testContext, error, int, string](func(e error) ReaderIOEither[testContext, string, int] { + return Left[testContext, int]("alternative") + })(Of[testContext, error](42)) + assert.Equal(t, E.Right[string](42), resultRight(ctx)()) + + // Test Left case + resultLeft := OrElse[testContext, error, int, string](func(e error) ReaderIOEither[testContext, string, int] { + return Of[testContext, string](99) + })(Left[testContext, int](errors.New("test"))) + assert.Equal(t, E.Right[string](99), resultLeft(ctx)()) +} + +func TestMonadBiMap(t *testing.T) { + ctx := testContext{value: 10} + + // Test Right case + resultRight := MonadBiMap( + Of[testContext, error](5), + func(e error) string { return e.Error() }, + func(x int) string { return fmt.Sprintf("%d", x) }, + ) + assert.Equal(t, E.Right[string]("5"), resultRight(ctx)()) + + // Test Left case + resultLeft := MonadBiMap( + Left[testContext, int](errors.New("test")), + func(e error) string { return e.Error() }, + func(x int) string { return fmt.Sprintf("%d", x) }, + ) + assert.Equal(t, E.Left[string]("test"), resultLeft(ctx)()) +} + +func TestBiMap(t *testing.T) { + ctx := testContext{value: 10} + result := F.Pipe1( + Of[testContext, error](5), + BiMap[testContext, error, string]( + func(e error) string { return e.Error() }, + func(x int) string { return fmt.Sprintf("%d", x) }, + ), + ) + assert.Equal(t, E.Right[string]("5"), result(ctx)()) +} + +func TestSwap(t *testing.T) { + ctx := testContext{value: 10} + + // Test Right becomes Left + resultRight := Swap(Of[testContext, error](5)) + res := resultRight(ctx)() + assert.True(t, E.IsLeft(res)) + + // Test Left becomes Right + resultLeft := Swap(Left[testContext, int](errors.New("test"))) + assert.True(t, E.IsRight(resultLeft(ctx)())) +} + +func TestDefer(t *testing.T) { + ctx := testContext{value: 10} + callCount := 0 + result := Defer(func() ReaderIOEither[testContext, error, int] { + callCount++ + return Of[testContext, error](42) + }) + + // First call + assert.Equal(t, E.Right[error](42), result(ctx)()) + assert.Equal(t, 1, callCount) + + // Second call + assert.Equal(t, E.Right[error](42), result(ctx)()) + assert.Equal(t, 2, callCount) +} + +func TestTryCatch(t *testing.T) { + ctx := testContext{value: 10} + + // Test success + resultSuccess := TryCatch( + func(c testContext) func() (int, error) { + return func() (int, error) { return c.value * 2, nil } + }, + func(err error) error { return err }, + ) + assert.Equal(t, E.Right[error](20), resultSuccess(ctx)()) + + // Test error + resultError := TryCatch( + func(c testContext) func() (int, error) { + return func() (int, error) { return 0, errors.New("test error") } + }, + func(err error) error { return err }, + ) + assert.True(t, E.IsLeft(resultError(ctx)())) +} + +func TestMonadAlt(t *testing.T) { + ctx := testContext{value: 10} + + // Test first succeeds + resultFirst := MonadAlt( + Of[testContext, error](42), + func() ReaderIOEither[testContext, error, int] { + return Of[testContext, error](99) + }, + ) + assert.Equal(t, E.Right[error](42), resultFirst(ctx)()) + + // Test first fails, second succeeds + resultSecond := MonadAlt( + Left[testContext, int](errors.New("first")), + func() ReaderIOEither[testContext, error, int] { + return Of[testContext, error](99) + }, + ) + assert.Equal(t, E.Right[error](99), resultSecond(ctx)()) +} + +func TestAlt(t *testing.T) { + ctx := testContext{value: 10} + result := F.Pipe1( + Left[testContext, int](errors.New("first")), + Alt[testContext, error](func() ReaderIOEither[testContext, error, int] { + return Of[testContext, error](99) + }), + ) + assert.Equal(t, E.Right[error](99), result(ctx)()) +} + +func TestMemoize(t *testing.T) { + ctx := testContext{value: 10} + callCount := 0 + result := Memoize(func(c testContext) IOE.IOEither[error, int] { + return func() E.Either[error, int] { + callCount++ + return E.Right[error](c.value * 2) + } + }) + + // First call + assert.Equal(t, E.Right[error](20), result(ctx)()) + assert.Equal(t, 1, callCount) + + // Second call should use memoized value + assert.Equal(t, E.Right[error](20), result(ctx)()) + assert.Equal(t, 1, callCount) +} + +func TestMonadFlap(t *testing.T) { + ctx := testContext{value: 10} + fab := Of[testContext, error](func(x int) int { return x * 2 }) + result := MonadFlap(fab, 5) + assert.Equal(t, E.Right[error](10), result(ctx)()) +} + +func TestFlap(t *testing.T) { + ctx := testContext{value: 10} + result := F.Pipe1( + Of[testContext, error](func(x int) int { return x * 2 }), + Flap[testContext, error, int](5), + ) + assert.Equal(t, E.Right[error](10), result(ctx)()) +} + +func TestMonadMapLeft(t *testing.T) { + ctx := testContext{value: 10} + result := MonadMapLeft( + Left[testContext, int](errors.New("test")), + func(e error) string { return e.Error() + "!" }, + ) + res := result(ctx)() + assert.True(t, E.IsLeft(res)) + // Verify the error was transformed + E.Fold( + func(s string) int { + assert.Equal(t, "test!", s) + return 0 + }, + func(i int) int { return i }, + )(res) +} + +func TestMapLeft(t *testing.T) { + ctx := testContext{value: 10} + result := F.Pipe1( + Left[testContext, int](errors.New("test")), + MapLeft[testContext, int](func(e error) string { return e.Error() + "!" }), + ) + res := result(ctx)() + assert.True(t, E.IsLeft(res)) + // Verify the error was transformed + E.Fold( + func(s string) int { + assert.Equal(t, "test!", s) + return 0 + }, + func(i int) int { return i }, + )(res) +} + +func TestLocal(t *testing.T) { + ctx2 := testContext{value: 20} + + rdr := Asks[error](func(c testContext) int { return c.value }) + result := Local[testContext, testContext, error, int](func(c testContext) testContext { + return testContext{value: c.value * 2} + })(rdr) + + assert.Equal(t, E.Right[error](40), result(ctx2)()) +} + +func TestMonadFromReaderIO(t *testing.T) { + ctx := testContext{value: 10} + result := MonadFromReaderIO[testContext, error]( + 5, + func(x int) RIO.ReaderIO[testContext, int] { + return func(c testContext) io.IO[int] { + return func() int { return x + c.value } + } + }, + ) + assert.Equal(t, E.Right[error](15), result(ctx)()) +} + +func TestFromReaderIO(t *testing.T) { + ctx := testContext{value: 10} + result := FromReaderIO[testContext, error](func(x int) RIO.ReaderIO[testContext, int] { + return func(c testContext) io.IO[int] { + return func() int { return x + c.value } + } + })(5) + assert.Equal(t, E.Right[error](15), result(ctx)()) +} + +func TestRightReaderIO(t *testing.T) { + ctx := testContext{value: 10} + rio := func(c testContext) io.IO[int] { + return func() int { return c.value * 2 } + } + result := RightReaderIO[testContext, error](rio) + assert.Equal(t, E.Right[error](20), result(ctx)()) +} + +func TestLeftReaderIO(t *testing.T) { + ctx := testContext{value: 10} + rio := func(c testContext) io.IO[error] { + return func() error { return errors.New("test") } + } + result := LeftReaderIO[int](rio) + assert.True(t, E.IsLeft(result(ctx)())) +} + +func TestLet(t *testing.T) { + type State struct { + a int + b string + } + + ctx := context.Background() + result := F.Pipe2( + Do[context.Context, error](State{}), + Let[context.Context, error](func(b string) func(State) State { + return func(s State) State { return State{a: s.a, b: b} } + }, func(s State) string { return "test" }), + Map[context.Context, error](func(s State) string { return s.b }), + ) + + assert.Equal(t, E.Right[error]("test"), result(ctx)()) +} + +func TestLetTo(t *testing.T) { + type State struct { + a int + b string + } + + ctx := context.Background() + result := F.Pipe2( + Do[context.Context, error](State{}), + LetTo[context.Context, error](func(b string) func(State) State { + return func(s State) State { return State{a: s.a, b: b} } + }, "constant"), + Map[context.Context, error](func(s State) string { return s.b }), + ) + + assert.Equal(t, E.Right[error]("constant"), result(ctx)()) +} + +func TestBindTo(t *testing.T) { + type State struct { + value int + } + + ctx := context.Background() + result := F.Pipe2( + Of[context.Context, error](42), + BindTo[context.Context, error](func(v int) State { return State{value: v} }), + Map[context.Context, error](func(s State) int { return s.value }), + ) + + assert.Equal(t, E.Right[error](42), result(ctx)()) +} + +func TestBracket(t *testing.T) { + ctx := testContext{value: 10} + released := false + + result := Bracket( + Of[testContext, error](42), + func(x int) ReaderIOEither[testContext, error, string] { + return Of[testContext, error](fmt.Sprintf("%d", x)) + }, + func(x int, result E.Either[error, string]) ReaderIOEither[testContext, error, int] { + released = true + return Of[testContext, error](0) + }, + ) + + assert.Equal(t, E.Right[error]("42"), result(ctx)()) + assert.True(t, released) +} + +func TestWithResource(t *testing.T) { + ctx := testContext{value: 10} + released := false + + result := WithResource[string, testContext, error, int, int]( + Of[testContext, error](42), + func(x int) ReaderIOEither[testContext, error, int] { + released = true + return Of[testContext, error](0) + }, + )(func(x int) ReaderIOEither[testContext, error, string] { + return Of[testContext, error](fmt.Sprintf("%d", x)) + }) + + assert.Equal(t, E.Right[error]("42"), result(ctx)()) + assert.True(t, released) +} + +func TestMonad(t *testing.T) { + m := Monad[testContext, error, int, string]() + assert.NotNil(t, m) +} + +func TestTraverseArrayWithIndex(t *testing.T) { + ctx := testContext{value: 10} + result := TraverseArrayWithIndex[testContext, error](func(i int, x int) ReaderIOEither[testContext, error, int] { + return Of[testContext, error](x + i) + })([]int{1, 2, 3}) + + assert.Equal(t, E.Right[error]([]int{1, 3, 5}), result(ctx)()) +} + +func TestTraverseRecord(t *testing.T) { + ctx := testContext{value: 10} + result := TraverseRecord[testContext, string, error](func(x int) ReaderIOEither[testContext, error, int] { + return Of[testContext, error](x * 2) + })(map[string]int{"a": 1, "b": 2}) + + expected := map[string]int{"a": 2, "b": 4} + assert.Equal(t, E.Right[error](expected), result(ctx)()) +} + +func TestTraverseRecordWithIndex(t *testing.T) { + ctx := testContext{value: 10} + result := TraverseRecordWithIndex[testContext, string, error](func(k string, x int) ReaderIOEither[testContext, error, string] { + return Of[testContext, error](fmt.Sprintf("%s:%d", k, x)) + })(map[string]int{"a": 1, "b": 2}) + + res := result(ctx)() + assert.True(t, E.IsRight(res)) +} + +func TestSequenceRecord(t *testing.T) { + ctx := testContext{value: 10} + result := SequenceRecord[testContext, string, error](map[string]ReaderIOEither[testContext, error, int]{ + "a": Of[testContext, error](1), + "b": Of[testContext, error](2), + }) + + expected := map[string]int{"a": 1, "b": 2} + assert.Equal(t, E.Right[error](expected), result(ctx)()) +} + +func TestSequenceT1(t *testing.T) { + ctx := testContext{value: 10} + result := SequenceT1(Of[testContext, error](42)) + res := result(ctx)() + assert.True(t, E.IsRight(res)) +} + +func TestSequenceT3(t *testing.T) { + ctx := testContext{value: 10} + result := SequenceT3( + Of[testContext, error](1), + Of[testContext, error]("a"), + Of[testContext, error](true), + ) + res := result(ctx)() + assert.True(t, E.IsRight(res)) +} + +func TestSequenceT4(t *testing.T) { + ctx := testContext{value: 10} + result := SequenceT4( + Of[testContext, error](1), + Of[testContext, error]("a"), + Of[testContext, error](true), + Of[testContext, error](3.14), + ) + res := result(ctx)() + assert.True(t, E.IsRight(res)) +} + +func TestWithLock(t *testing.T) { + ctx := testContext{value: 10} + unlocked := false + + result := F.Pipe1( + Of[testContext, error](42), + WithLock[testContext, error, int](func() context.CancelFunc { + return func() { unlocked = true } + }), + ) + + assert.Equal(t, E.Right[error](42), result(ctx)()) + assert.True(t, unlocked) +} diff --git a/v2/readerioeither/resource.go b/v2/readerioeither/resource.go new file mode 100644 index 0000000..525958d --- /dev/null +++ b/v2/readerioeither/resource.go @@ -0,0 +1,68 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readerioeither + +import "github.com/IBM/fp-go/v2/ioeither" + +// WithResource constructs a function that creates a resource, operates on it, and then releases the resource. +// This ensures proper resource cleanup even in the presence of errors, following the Resource Acquisition Is Initialization (RAII) pattern. +// +// The resource lifecycle is: +// 1. onCreate: Acquires the resource +// 2. use: Operates on the resource (provided as argument to the returned function) +// 3. onRelease: Releases the resource (called regardless of success or failure) +// +// Type parameters: +// - A: The type of the result produced by using the resource +// - L: The context type +// - E: The error type +// - R: The resource type +// - ANY: The type returned by the release function (typically ignored) +// +// Parameters: +// - onCreate: A computation that acquires the resource +// - onRelease: A function that releases the resource, called with the resource and executed regardless of errors +// +// Returns: +// +// A function that takes a resource-using function and returns a ReaderIOEither that manages the resource lifecycle +// +// Example: +// +// withFile := WithResource( +// openFile("data.txt"), +// func(f *File) ReaderIOEither[Config, error, int] { +// return closeFile(f) +// }, +// ) +// result := withFile(func(f *File) ReaderIOEither[Config, error, string] { +// return readContent(f) +// }) +func WithResource[A, L, E, R, ANY any](onCreate ReaderIOEither[L, E, R], onRelease func(R) ReaderIOEither[L, E, ANY]) func(func(R) ReaderIOEither[L, E, A]) ReaderIOEither[L, E, A] { + return func(f func(R) ReaderIOEither[L, E, A]) ReaderIOEither[L, E, A] { + return func(l L) ioeither.IOEither[E, A] { + // dispatch to the generic implementation + return ioeither.WithResource[A]( + onCreate(l), + func(r R) ioeither.IOEither[E, ANY] { + return onRelease(r)(l) + }, + )(func(r R) ioeither.IOEither[E, A] { + return f(r)(l) + }) + } + } +} diff --git a/v2/readerioeither/sequence.go b/v2/readerioeither/sequence.go new file mode 100644 index 0000000..091280f --- /dev/null +++ b/v2/readerioeither/sequence.go @@ -0,0 +1,103 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readerioeither + +import ( + G "github.com/IBM/fp-go/v2/readerioeither/generic" + T "github.com/IBM/fp-go/v2/tuple" +) + +// SequenceT1 converts a single ReaderIOEither into a ReaderIOEither of a 1-tuple. +// This is useful for uniformly handling computations with different arities. +// +// If the input computation fails, the result will be a Left with the error. +// If it succeeds, the result will be a Right with a tuple containing the value. +// +// Example: +// +// result := SequenceT1(Of[Config, error](42)) +// // result(cfg)() returns Right(Tuple1{42}) +func SequenceT1[R, E, A any](a ReaderIOEither[R, E, A]) ReaderIOEither[R, E, T.Tuple1[A]] { + return G.SequenceT1[ + ReaderIOEither[R, E, A], + ReaderIOEither[R, E, T.Tuple1[A]], + ](a) +} + +// SequenceT2 combines two ReaderIOEither computations into a single ReaderIOEither of a 2-tuple. +// Both computations are executed, and if both succeed, their results are combined into a tuple. +// If either fails, the result is a Left with the first error encountered. +// +// This is useful for running multiple independent computations and collecting their results. +// +// Example: +// +// result := SequenceT2( +// fetchUser(123), +// fetchProfile(123), +// ) +// // result(cfg)() returns Right(Tuple2{user, profile}) or Left(error) +func SequenceT2[R, E, A, B any](a ReaderIOEither[R, E, A], b ReaderIOEither[R, E, B]) ReaderIOEither[R, E, T.Tuple2[A, B]] { + return G.SequenceT2[ + ReaderIOEither[R, E, A], + ReaderIOEither[R, E, B], + ReaderIOEither[R, E, T.Tuple2[A, B]], + ](a, b) +} + +// SequenceT3 combines three ReaderIOEither computations into a single ReaderIOEither of a 3-tuple. +// All three computations are executed, and if all succeed, their results are combined into a tuple. +// If any fails, the result is a Left with the first error encountered. +// +// Example: +// +// result := SequenceT3( +// fetchUser(123), +// fetchProfile(123), +// fetchSettings(123), +// ) +// // result(cfg)() returns Right(Tuple3{user, profile, settings}) or Left(error) +func SequenceT3[R, E, A, B, C any](a ReaderIOEither[R, E, A], b ReaderIOEither[R, E, B], c ReaderIOEither[R, E, C]) ReaderIOEither[R, E, T.Tuple3[A, B, C]] { + return G.SequenceT3[ + ReaderIOEither[R, E, A], + ReaderIOEither[R, E, B], + ReaderIOEither[R, E, C], + ReaderIOEither[R, E, T.Tuple3[A, B, C]], + ](a, b, c) +} + +// SequenceT4 combines four ReaderIOEither computations into a single ReaderIOEither of a 4-tuple. +// All four computations are executed, and if all succeed, their results are combined into a tuple. +// If any fails, the result is a Left with the first error encountered. +// +// Example: +// +// result := SequenceT4( +// fetchUser(123), +// fetchProfile(123), +// fetchSettings(123), +// fetchPreferences(123), +// ) +// // result(cfg)() returns Right(Tuple4{user, profile, settings, prefs}) or Left(error) +func SequenceT4[R, E, A, B, C, D any](a ReaderIOEither[R, E, A], b ReaderIOEither[R, E, B], c ReaderIOEither[R, E, C], d ReaderIOEither[R, E, D]) ReaderIOEither[R, E, T.Tuple4[A, B, C, D]] { + return G.SequenceT4[ + ReaderIOEither[R, E, A], + ReaderIOEither[R, E, B], + ReaderIOEither[R, E, C], + ReaderIOEither[R, E, D], + ReaderIOEither[R, E, T.Tuple4[A, B, C, D]], + ](a, b, c, d) +} diff --git a/v2/readerioeither/sequence_test.go b/v2/readerioeither/sequence_test.go new file mode 100644 index 0000000..dbf3107 --- /dev/null +++ b/v2/readerioeither/sequence_test.go @@ -0,0 +1,38 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readerioeither + +import ( + "context" + "testing" + + "github.com/IBM/fp-go/v2/either" + T "github.com/IBM/fp-go/v2/tuple" + "github.com/stretchr/testify/assert" +) + +func TestSequence2(t *testing.T) { + // two readers of heterogeneous types + first := Of[context.Context, error]("a") + second := Of[context.Context, error](1) + + // compose + s2 := SequenceT2[context.Context, error, string, int] + res := s2(first, second) + + ctx := context.Background() + assert.Equal(t, either.Right[error](T.MakeTuple2("a", 1)), res(ctx)()) +} diff --git a/v2/readerioeither/sync.go b/v2/readerioeither/sync.go new file mode 100644 index 0000000..9227237 --- /dev/null +++ b/v2/readerioeither/sync.go @@ -0,0 +1,56 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readerioeither + +import ( + "context" + + "github.com/IBM/fp-go/v2/either" + "github.com/IBM/fp-go/v2/readerio" +) + +// WithLock executes a ReaderIOEither operation within the scope of a lock. +// The lock is acquired before the operation executes and released after it completes, +// regardless of whether the operation succeeds or fails. +// +// This is useful for ensuring thread-safe access to shared resources or for +// implementing critical sections in concurrent code. +// +// Type parameters: +// - R: The context type +// - E: The error type +// - A: The value type +// +// Parameters: +// - lock: A function that acquires a lock and returns a CancelFunc to release it +// +// Returns: +// +// An Operator that wraps the computation with lock acquisition and release +// +// Example: +// +// var mu sync.Mutex +// safeFetch := F.Pipe1( +// fetchData(), +// WithLock[Config, error, Data](func() context.CancelFunc { +// mu.Lock() +// return func() { mu.Unlock() } +// }), +// ) +func WithLock[R, E, A any](lock func() context.CancelFunc) Operator[R, E, A, A] { + return readerio.WithLock[R, either.Either[E, A]](lock) +} diff --git a/v2/readerioeither/traverse.go b/v2/readerioeither/traverse.go new file mode 100644 index 0000000..1f4e095 --- /dev/null +++ b/v2/readerioeither/traverse.go @@ -0,0 +1,194 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readerioeither + +import ( + G "github.com/IBM/fp-go/v2/readerioeither/generic" +) + +// TraverseArray transforms each element of an array using a function that returns a ReaderIOEither, +// then collects the results into a single ReaderIOEither containing an array. +// +// If any transformation fails, the entire operation fails with the first error encountered. +// All transformations are executed sequentially. +// +// Type parameters: +// - R: The context type +// - E: The error type +// - A: The input element type +// - B: The output element type +// +// Parameters: +// - f: A function that transforms each element into a ReaderIOEither +// +// Returns: +// +// A function that takes an array and returns a ReaderIOEither of an array +// +// Example: +// +// fetchUsers := TraverseArray(func(id int) ReaderIOEither[Config, error, User] { +// return fetchUser(id) +// }) +// result := fetchUsers([]int{1, 2, 3}) +// // result(cfg)() returns Right([user1, user2, user3]) or Left(error) +func TraverseArray[R, E, A, B any](f func(A) ReaderIOEither[R, E, B]) func([]A) ReaderIOEither[R, E, []B] { + return G.TraverseArray[ReaderIOEither[R, E, B], ReaderIOEither[R, E, []B], IOEither[E, B], IOEither[E, []B], []A](f) +} + +// TraverseArrayWithIndex is like TraverseArray but the transformation function also receives the index. +// +// This is useful when the transformation depends on the element's position in the array. +// +// Type parameters: +// - R: The context type +// - E: The error type +// - A: The input element type +// - B: The output element type +// +// Parameters: +// - f: A function that transforms each element and its index into a ReaderIOEither +// +// Returns: +// +// A function that takes an array and returns a ReaderIOEither of an array +// +// Example: +// +// processWithIndex := TraverseArrayWithIndex(func(i int, val string) ReaderIOEither[Config, error, string] { +// return Of[Config, error](fmt.Sprintf("%d: %s", i, val)) +// }) +func TraverseArrayWithIndex[R, E, A, B any](f func(int, A) ReaderIOEither[R, E, B]) func([]A) ReaderIOEither[R, E, []B] { + return G.TraverseArrayWithIndex[ReaderIOEither[R, E, B], ReaderIOEither[R, E, []B], IOEither[E, B], IOEither[E, []B], []A](f) +} + +// SequenceArray converts an array of ReaderIOEither into a ReaderIOEither of an array. +// +// This is useful when you have multiple independent computations and want to execute them all +// and collect their results. If any computation fails, the entire operation fails with the first error. +// +// Type parameters: +// - R: The context type +// - E: The error type +// - A: The element type +// +// Parameters: +// - ma: An array of ReaderIOEither computations +// +// Returns: +// +// A ReaderIOEither that produces an array of results +// +// Example: +// +// computations := []ReaderIOEither[Config, error, int]{ +// fetchCount("users"), +// fetchCount("posts"), +// fetchCount("comments"), +// } +// result := SequenceArray(computations) +// // result(cfg)() returns Right([userCount, postCount, commentCount]) or Left(error) +func SequenceArray[R, E, A any](ma []ReaderIOEither[R, E, A]) ReaderIOEither[R, E, []A] { + return G.SequenceArray[ReaderIOEither[R, E, A], ReaderIOEither[R, E, []A]](ma) +} + +// TraverseRecord transforms each value in a map using a function that returns a ReaderIOEither, +// then collects the results into a single ReaderIOEither containing a map. +// +// If any transformation fails, the entire operation fails with the first error encountered. +// The keys are preserved in the output map. +// +// Type parameters: +// - R: The context type +// - K: The key type (must be comparable) +// - E: The error type +// - A: The input value type +// - B: The output value type +// +// Parameters: +// - f: A function that transforms each value into a ReaderIOEither +// +// Returns: +// +// A function that takes a map and returns a ReaderIOEither of a map +// +// Example: +// +// enrichUsers := TraverseRecord(func(user User) ReaderIOEither[Config, error, EnrichedUser] { +// return enrichUser(user) +// }) +// result := enrichUsers(map[string]User{"alice": user1, "bob": user2}) +func TraverseRecord[R any, K comparable, E, A, B any](f func(A) ReaderIOEither[R, E, B]) func(map[K]A) ReaderIOEither[R, E, map[K]B] { + return G.TraverseRecord[ReaderIOEither[R, E, B], ReaderIOEither[R, E, map[K]B], IOEither[E, B], IOEither[E, map[K]B], map[K]A](f) +} + +// TraverseRecordWithIndex is like TraverseRecord but the transformation function also receives the key. +// +// This is useful when the transformation depends on the key associated with each value. +// +// Type parameters: +// - R: The context type +// - K: The key type (must be comparable) +// - E: The error type +// - A: The input value type +// - B: The output value type +// +// Parameters: +// - f: A function that transforms each key-value pair into a ReaderIOEither +// +// Returns: +// +// A function that takes a map and returns a ReaderIOEither of a map +// +// Example: +// +// processWithKey := TraverseRecordWithIndex(func(key string, val int) ReaderIOEither[Config, error, string] { +// return Of[Config, error](fmt.Sprintf("%s: %d", key, val)) +// }) +func TraverseRecordWithIndex[R any, K comparable, E, A, B any](f func(K, A) ReaderIOEither[R, E, B]) func(map[K]A) ReaderIOEither[R, E, map[K]B] { + return G.TraverseRecordWithIndex[ReaderIOEither[R, E, B], ReaderIOEither[R, E, map[K]B], IOEither[E, B], IOEither[E, map[K]B], map[K]A](f) +} + +// SequenceRecord converts a map of ReaderIOEither into a ReaderIOEither of a map. +// +// This is useful when you have multiple independent computations keyed by some identifier +// and want to execute them all and collect their results. If any computation fails, +// the entire operation fails with the first error. +// +// Type parameters: +// - R: The context type +// - K: The key type (must be comparable) +// - E: The error type +// - A: The value type +// +// Parameters: +// - ma: A map of ReaderIOEither computations +// +// Returns: +// +// A ReaderIOEither that produces a map of results +// +// Example: +// +// computations := map[string]ReaderIOEither[Config, error, int]{ +// "users": fetchCount("users"), +// "posts": fetchCount("posts"), +// } +// result := SequenceRecord(computations) +// // result(cfg)() returns Right(map[string]int{"users": 100, "posts": 50}) or Left(error) +func SequenceRecord[R any, K comparable, E, A any](ma map[K]ReaderIOEither[R, E, A]) ReaderIOEither[R, E, map[K]A] { + return G.SequenceRecord[ReaderIOEither[R, E, A], ReaderIOEither[R, E, map[K]A]](ma) +} diff --git a/v2/readerioeither/types.go b/v2/readerioeither/types.go new file mode 100644 index 0000000..c87a7e7 --- /dev/null +++ b/v2/readerioeither/types.go @@ -0,0 +1,82 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readerioeither + +import ( + "github.com/IBM/fp-go/v2/either" + "github.com/IBM/fp-go/v2/ioeither" + "github.com/IBM/fp-go/v2/reader" + "github.com/IBM/fp-go/v2/readerio" +) + +type ( + // Either represents a value of one of two possible types (a disjoint union). + // An instance of Either is either Left (representing an error) or Right (representing a success). + Either[E, A any] = either.Either[E, A] + + // Reader represents a computation that depends on some context/environment of type R + // and produces a value of type A. It's useful for dependency injection patterns. + Reader[R, A any] = reader.Reader[R, A] + + // ReaderIO represents a computation that depends on some context R and performs + // side effects to produce a value of type A. + ReaderIO[R, A any] = readerio.ReaderIO[R, A] + + // IOEither represents a computation that performs side effects and can either + // fail with an error of type E or succeed with a value of type A. + IOEither[E, A any] = ioeither.IOEither[E, A] + + // ReaderIOEither represents a computation that: + // - Depends on some context/environment of type R (Reader) + // - Performs side effects (IO) + // - Can fail with an error of type E or succeed with a value of type A (Either) + // + // It combines three powerful functional programming concepts: + // 1. Reader monad for dependency injection + // 2. IO monad for side effects + // 3. Either monad for error handling + // + // Type parameters: + // - R: The type of the context/environment (e.g., configuration, dependencies) + // - E: The type of errors that can occur + // - A: The type of the success value + // + // Example: + // type Config struct { BaseURL string } + // func fetchUser(id int) ReaderIOEither[Config, error, User] { + // return func(cfg Config) IOEither[error, User] { + // return func() Either[error, User] { + // // Use cfg.BaseURL to fetch user + // // Return either.Right(user) or either.Left(err) + // } + // } + // } + ReaderIOEither[R, E, A any] = Reader[R, IOEither[E, A]] + + // Operator represents a transformation from one ReaderIOEither to another. + // It's a Reader that takes a ReaderIOEither[R, E, A] and produces a ReaderIOEither[R, E, B]. + // This type is commonly used for composing operations in a point-free style. + // + // Type parameters: + // - R: The context type + // - E: The error type + // - A: The input value type + // - B: The output value type + // + // Example: + // var doubleOp Operator[Config, error, int, int] = Map(func(x int) int { return x * 2 }) + Operator[R, E, A, B any] = reader.Reader[ReaderIOEither[R, E, A], ReaderIOEither[R, E, B]] +) diff --git a/v2/record/bind.go b/v2/record/bind.go new file mode 100644 index 0000000..6255185 --- /dev/null +++ b/v2/record/bind.go @@ -0,0 +1,57 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package record + +import ( + Mo "github.com/IBM/fp-go/v2/monoid" + G "github.com/IBM/fp-go/v2/record/generic" +) + +// Bind creates an empty context of type [S] to be used with the [Bind] operation +func Do[K comparable, S any]() map[K]S { + return G.Do[map[K]S, K, S]() +} + +// Bind attaches the result of a computation to a context [S1] to produce a context [S2] +func Bind[S1, T any, K comparable, S2 any](m Mo.Monoid[map[K]S2]) func(setter func(T) func(S1) S2, f func(S1) map[K]T) func(map[K]S1) map[K]S2 { + return G.Bind[map[K]S1, map[K]S2, map[K]T, K, S1, S2, T](m) +} + +// Let attaches the result of a computation to a context [S1] to produce a context [S2] +func Let[S1, T any, K comparable, S2 any]( + setter func(T) func(S1) S2, + f func(S1) T, +) func(map[K]S1) map[K]S2 { + return G.Let[map[K]S1, map[K]S2, K, S1, S2, T](setter, f) +} + +// LetTo attaches the a value to a context [S1] to produce a context [S2] +func LetTo[S1, T any, K comparable, S2 any]( + setter func(T) func(S1) S2, + b T, +) func(map[K]S1) map[K]S2 { + return G.LetTo[map[K]S1, map[K]S2, K, S1, S2, T](setter, b) +} + +// BindTo initializes a new state [S1] from a value [T] +func BindTo[S1, T any, K comparable](setter func(T) S1) func(map[K]T) map[K]S1 { + return G.BindTo[map[K]S1, map[K]T, K, S1, T](setter) +} + +// ApS attaches a value to a context [S1] to produce a context [S2] by considering the context and the value concurrently +func ApS[S1, T any, K comparable, S2 any](m Mo.Monoid[map[K]S2]) func(setter func(T) func(S1) S2, fa map[K]T) func(map[K]S1) map[K]S2 { + return G.ApS[map[K]S1, map[K]S2, map[K]T, K, S1, S2, T](m) +} diff --git a/v2/record/doc.go b/v2/record/doc.go new file mode 100644 index 0000000..7871634 --- /dev/null +++ b/v2/record/doc.go @@ -0,0 +1,17 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +// Package record contains monadic operations for maps as well as a rich set of utility functions +package record diff --git a/v2/record/eq.go b/v2/record/eq.go new file mode 100644 index 0000000..b57d021 --- /dev/null +++ b/v2/record/eq.go @@ -0,0 +1,30 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package record + +import ( + E "github.com/IBM/fp-go/v2/eq" + G "github.com/IBM/fp-go/v2/record/generic" +) + +func Eq[K comparable, V any](e E.Eq[V]) E.Eq[map[K]V] { + return G.Eq[map[K]V, K, V](e) +} + +// FromStrictEquals constructs an [EQ.Eq] from the canonical comparison function +func FromStrictEquals[K, V comparable]() E.Eq[map[K]V] { + return G.FromStrictEquals[map[K]V]() +} diff --git a/v2/record/eq_test.go b/v2/record/eq_test.go new file mode 100644 index 0000000..787fb7a --- /dev/null +++ b/v2/record/eq_test.go @@ -0,0 +1,48 @@ +// Copyright (c) 2024 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package record + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestFromStrictEquals(t *testing.T) { + m1 := map[string]string{ + "a": "A", + "b": "B", + } + m2 := map[string]string{ + "a": "A", + "b": "C", + } + m3 := map[string]string{ + "a": "A", + "b": "B", + } + m4 := map[string]string{ + "a": "A", + "b": "B", + "c": "C", + } + + e := FromStrictEquals[string, string]() + assert.True(t, e.Equals(m1, m1)) + assert.True(t, e.Equals(m1, m3)) + assert.False(t, e.Equals(m1, m2)) + assert.False(t, e.Equals(m1, m4)) +} diff --git a/v2/record/generic/bind.go b/v2/record/generic/bind.go new file mode 100644 index 0000000..29fe63b --- /dev/null +++ b/v2/record/generic/bind.go @@ -0,0 +1,86 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + A "github.com/IBM/fp-go/v2/internal/apply" + C "github.com/IBM/fp-go/v2/internal/chain" + F "github.com/IBM/fp-go/v2/internal/functor" + Mo "github.com/IBM/fp-go/v2/monoid" +) + +// Bind creates an empty context of type [S] to be used with the [Bind] operation +func Do[GS ~map[K]S, K comparable, S any]() GS { + return Empty[GS, K, S]() +} + +// Bind attaches the result of a computation to a context [S1] to produce a context [S2] +func Bind[GS1 ~map[K]S1, GS2 ~map[K]S2, GT ~map[K]T, K comparable, S1, S2, T any](m Mo.Monoid[GS2]) func(setter func(T) func(S1) S2, f func(S1) GT) func(GS1) GS2 { + c := Chain[GS1, GS2, K, S1, S2](m) + return func(setter func(T) func(S1) S2, f func(S1) GT) func(GS1) GS2 { + return C.Bind( + c, + Map[GT, GS2, K, T, S2], + setter, + f, + ) + } +} + +// Let attaches the result of a computation to a context [S1] to produce a context [S2] +func Let[GS1 ~map[K]S1, GS2 ~map[K]S2, K comparable, S1, S2, T any]( + key func(T) func(S1) S2, + f func(S1) T, +) func(GS1) GS2 { + return F.Let( + Map[GS1, GS2, K, S1, S2], + key, + f, + ) +} + +// LetTo attaches the a value to a context [S1] to produce a context [S2] +func LetTo[GS1 ~map[K]S1, GS2 ~map[K]S2, K comparable, S1, S2, B any]( + key func(B) func(S1) S2, + b B, +) func(GS1) GS2 { + return F.LetTo( + Map[GS1, GS2, K, S1, S2], + key, + b, + ) +} + +// BindTo initializes a new state [S1] from a value [T] +func BindTo[GS1 ~map[K]S1, GT ~map[K]T, K comparable, S1, T any](setter func(T) S1) func(GT) GS1 { + return C.BindTo( + Map[GT, GS1, K, T, S1], + setter, + ) +} + +// ApS attaches a value to a context [S1] to produce a context [S2] by considering the context and the value concurrently +func ApS[GS1 ~map[K]S1, GS2 ~map[K]S2, GT ~map[K]T, K comparable, S1, S2, T any](m Mo.Monoid[GS2]) func(setter func(T) func(S1) S2, fa GT) func(GS1) GS2 { + a := Ap[GS2, map[K]func(T) S2, GT, K, S2, T](m) + return func(setter func(T) func(S1) S2, fa GT) func(GS1) GS2 { + return A.ApS( + a, + Map[GS1, map[K]func(T) S2, K, S1, func(T) S2], + setter, + fa, + ) + } +} diff --git a/v2/record/generic/eq.go b/v2/record/generic/eq.go new file mode 100644 index 0000000..e0adf54 --- /dev/null +++ b/v2/record/generic/eq.go @@ -0,0 +1,44 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + E "github.com/IBM/fp-go/v2/eq" +) + +func equals[M ~map[K]V, K comparable, V any](left, right M, eq func(V, V) bool) bool { + if len(left) != len(right) { + return false + } + for k, v1 := range left { + if v2, ok := right[k]; !ok || !eq(v1, v2) { + return false + } + } + return true +} + +func Eq[M ~map[K]V, K comparable, V any](e E.Eq[V]) E.Eq[M] { + eq := e.Equals + return E.FromEquals(func(left, right M) bool { + return equals(left, right, eq) + }) +} + +// FromStrictEquals constructs an [EQ.Eq] from the canonical comparison function +func FromStrictEquals[M ~map[K]V, K, V comparable]() E.Eq[M] { + return Eq[M](E.FromStrictEquals[V]()) +} diff --git a/v2/record/generic/monoid.go b/v2/record/generic/monoid.go new file mode 100644 index 0000000..84e2ecd --- /dev/null +++ b/v2/record/generic/monoid.go @@ -0,0 +1,43 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + F "github.com/IBM/fp-go/v2/function" + M "github.com/IBM/fp-go/v2/monoid" + S "github.com/IBM/fp-go/v2/semigroup" +) + +func UnionMonoid[N ~map[K]V, K comparable, V any](s S.Semigroup[V]) M.Monoid[N] { + return M.MakeMonoid( + UnionSemigroup[N](s).Concat, + Empty[N](), + ) +} + +func UnionLastMonoid[N ~map[K]V, K comparable, V any]() M.Monoid[N] { + return M.MakeMonoid( + unionLast[N], + Empty[N](), + ) +} + +func UnionFirstMonoid[N ~map[K]V, K comparable, V any]() M.Monoid[N] { + return M.MakeMonoid( + F.Swap(unionLast[N]), + Empty[N](), + ) +} diff --git a/v2/record/generic/record.go b/v2/record/generic/record.go new file mode 100644 index 0000000..993ae99 --- /dev/null +++ b/v2/record/generic/record.go @@ -0,0 +1,611 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + "sort" + + F "github.com/IBM/fp-go/v2/function" + RAG "github.com/IBM/fp-go/v2/internal/array" + FC "github.com/IBM/fp-go/v2/internal/functor" + G "github.com/IBM/fp-go/v2/internal/record" + Mg "github.com/IBM/fp-go/v2/magma" + Mo "github.com/IBM/fp-go/v2/monoid" + O "github.com/IBM/fp-go/v2/option" + "github.com/IBM/fp-go/v2/ord" + T "github.com/IBM/fp-go/v2/tuple" +) + +func IsEmpty[M ~map[K]V, K comparable, V any](r M) bool { + return len(r) == 0 +} + +func IsNonEmpty[M ~map[K]V, K comparable, V any](r M) bool { + return len(r) > 0 +} + +func Keys[M ~map[K]V, GK ~[]K, K comparable, V any](r M) GK { + return collect[M, GK](r, F.First[K, V]) +} + +func Values[M ~map[K]V, GV ~[]V, K comparable, V any](r M) GV { + return collect[M, GV](r, F.Second[K, V]) +} + +func KeysOrd[M ~map[K]V, GK ~[]K, K comparable, V any](o ord.Ord[K]) func(r M) GK { + return func(r M) GK { + return collectOrd[M, GK](o, r, F.First[K, V]) + } +} + +func ValuesOrd[M ~map[K]V, GV ~[]V, K comparable, V any](o ord.Ord[K]) func(r M) GV { + return func(r M) GV { + return collectOrd[M, GV](o, r, F.Second[K, V]) + } +} + +func collectOrd[M ~map[K]V, GR ~[]R, K comparable, V, R any](o ord.Ord[K], r M, f func(K, V) R) GR { + // create the entries + entries := toEntriesOrd[M, []T.Tuple2[K, V]](o, r) + // collect this array + ft := T.Tupled2(f) + count := len(entries) + result := make(GR, count) + for i := count - 1; i >= 0; i-- { + result[i] = ft(entries[i]) + } + // done + return result +} + +func reduceOrd[M ~map[K]V, K comparable, V, R any](o ord.Ord[K], r M, f func(K, R, V) R, initial R) R { + // create the entries + entries := toEntriesOrd[M, []T.Tuple2[K, V]](o, r) + // collect this array + current := initial + count := len(entries) + for i := 0; i < count; i++ { + t := entries[i] + current = f(T.First(t), current, T.Second(t)) + } + // done + return current +} + +func collect[M ~map[K]V, GR ~[]R, K comparable, V, R any](r M, f func(K, V) R) GR { + count := len(r) + result := make(GR, count) + idx := 0 + for k, v := range r { + result[idx] = f(k, v) + idx++ + } + return result +} + +func Collect[M ~map[K]V, GR ~[]R, K comparable, V, R any](f func(K, V) R) func(M) GR { + return F.Bind2nd(collect[M, GR, K, V, R], f) +} + +func CollectOrd[M ~map[K]V, GR ~[]R, K comparable, V, R any](o ord.Ord[K]) func(f func(K, V) R) func(M) GR { + return func(f func(K, V) R) func(M) GR { + return func(r M) GR { + return collectOrd[M, GR](o, r, f) + } + } +} + +func Reduce[M ~map[K]V, K comparable, V, R any](f func(R, V) R, initial R) func(M) R { + return func(r M) R { + return G.Reduce(r, f, initial) + } +} + +func ReduceWithIndex[M ~map[K]V, K comparable, V, R any](f func(K, R, V) R, initial R) func(M) R { + return func(r M) R { + return G.ReduceWithIndex(r, f, initial) + } +} + +func ReduceRef[M ~map[K]V, K comparable, V, R any](f func(R, *V) R, initial R) func(M) R { + return func(r M) R { + return G.ReduceRef(r, f, initial) + } +} + +func ReduceRefWithIndex[M ~map[K]V, K comparable, V, R any](f func(K, R, *V) R, initial R) func(M) R { + return func(r M) R { + return G.ReduceRefWithIndex(r, f, initial) + } +} + +func MonadAp[BS ~map[K]B, ABS ~map[K]func(A) B, AS ~map[K]A, K comparable, B, A any](m Mo.Monoid[BS], fab ABS, fa AS) BS { + return MonadChain(m, fab, F.Bind1st(MonadMap[AS, BS, K, A, B], fa)) +} + +func Ap[BS ~map[K]B, ABS ~map[K]func(A) B, AS ~map[K]A, K comparable, B, A any](m Mo.Monoid[BS]) func(fa AS) func(ABS) BS { + return func(ma AS) func(ABS) BS { + return func(abs ABS) BS { + return MonadAp(m, abs, ma) + } + } +} + +func MonadMap[M ~map[K]V, N ~map[K]R, K comparable, V, R any](r M, f func(V) R) N { + return MonadMapWithIndex[M, N](r, F.Ignore1of2[K](f)) +} + +func MonadChainWithIndex[M ~map[K]V1, N ~map[K]V2, K comparable, V1, V2 any](m Mo.Monoid[N], r M, f func(K, V1) N) N { + return G.ReduceWithIndex(r, func(k K, dst N, b V1) N { + return m.Concat(dst, f(k, b)) + }, m.Empty()) +} + +func MonadChain[M ~map[K]V1, N ~map[K]V2, K comparable, V1, V2 any](m Mo.Monoid[N], r M, f func(V1) N) N { + return G.Reduce(r, func(dst N, b V1) N { + return m.Concat(dst, f(b)) + }, m.Empty()) +} + +func ChainWithIndex[M ~map[K]V1, N ~map[K]V2, K comparable, V1, V2 any](m Mo.Monoid[N]) func(func(K, V1) N) func(M) N { + return func(f func(K, V1) N) func(M) N { + return func(ma M) N { + return MonadChainWithIndex(m, ma, f) + } + } +} + +func Chain[M ~map[K]V1, N ~map[K]V2, K comparable, V1, V2 any](m Mo.Monoid[N]) func(func(V1) N) func(M) N { + return func(f func(V1) N) func(M) N { + return func(ma M) N { + return MonadChain(m, ma, f) + } + } +} + +func MonadMapWithIndex[M ~map[K]V, N ~map[K]R, K comparable, V, R any](r M, f func(K, V) R) N { + return G.ReduceWithIndex(r, func(k K, dst N, v V) N { + return upsertAtReadWrite(dst, k, f(k, v)) + }, make(N, len(r))) +} + +func MonadMapRefWithIndex[M ~map[K]V, N ~map[K]R, K comparable, V, R any](r M, f func(K, *V) R) N { + return G.ReduceRefWithIndex(r, func(k K, dst N, v *V) N { + return upsertAtReadWrite(dst, k, f(k, v)) + }, make(N, len(r))) +} + +func MonadMapRef[M ~map[K]V, N ~map[K]R, K comparable, V, R any](r M, f func(*V) R) N { + return MonadMapRefWithIndex[M, N](r, F.Ignore1of2[K](f)) +} + +func Map[M ~map[K]V, N ~map[K]R, K comparable, V, R any](f func(V) R) func(M) N { + return F.Bind2nd(MonadMap[M, N, K, V, R], f) +} + +func MapRef[M ~map[K]V, N ~map[K]R, K comparable, V, R any](f func(*V) R) func(M) N { + return F.Bind2nd(MonadMapRef[M, N, K, V, R], f) +} + +func MapWithIndex[M ~map[K]V, N ~map[K]R, K comparable, V, R any](f func(K, V) R) func(M) N { + return F.Bind2nd(MonadMapWithIndex[M, N, K, V, R], f) +} + +func MapRefWithIndex[M ~map[K]V, N ~map[K]R, K comparable, V, R any](f func(K, *V) R) func(M) N { + return F.Bind2nd(MonadMapRefWithIndex[M, N, K, V, R], f) +} + +func MonadLookup[M ~map[K]V, K comparable, V any](m M, k K) O.Option[V] { + if val, ok := m[k]; ok { + return O.Some(val) + } + return O.None[V]() +} + +func Lookup[M ~map[K]V, K comparable, V any](k K) func(M) O.Option[V] { + n := O.None[V]() + return func(m M) O.Option[V] { + if val, ok := m[k]; ok { + return O.Some(val) + } + return n + } +} + +func Has[M ~map[K]V, K comparable, V any](k K, r M) bool { + _, ok := r[k] + return ok +} + +func union[M ~map[K]V, K comparable, V any](m Mg.Magma[V], left M, right M) M { + lenLeft := len(left) + + if lenLeft == 0 { + return right + } + + lenRight := len(right) + if lenRight == 0 { + return left + } + + result := make(M, lenLeft+lenRight) + + for k, v := range left { + if val, ok := right[k]; ok { + result[k] = m.Concat(v, val) + } else { + result[k] = v + } + } + + for k, v := range right { + if _, ok := left[k]; !ok { + result[k] = v + } + } + + return result +} + +func unionLast[M ~map[K]V, K comparable, V any](left M, right M) M { + lenLeft := len(left) + + if lenLeft == 0 { + return right + } + + lenRight := len(right) + if lenRight == 0 { + return left + } + + result := make(M, lenLeft+lenRight) + + for k, v := range left { + result[k] = v + } + + for k, v := range right { + result[k] = v + } + + return result +} + +func Union[M ~map[K]V, K comparable, V any](m Mg.Magma[V]) func(M) func(M) M { + return func(right M) func(M) M { + return func(left M) M { + return union(m, left, right) + } + } +} + +func UnionLast[M ~map[K]V, K comparable, V any](right M) func(M) M { + return func(left M) M { + return unionLast(left, right) + } +} + +func Merge[M ~map[K]V, K comparable, V any](right M) func(M) M { + return UnionLast(right) +} + +func UnionFirst[M ~map[K]V, K comparable, V any](right M) func(M) M { + return func(left M) M { + return unionLast(right, left) + } +} + +func Empty[M ~map[K]V, K comparable, V any]() M { + return make(M) +} + +func Size[M ~map[K]V, K comparable, V any](r M) int { + return len(r) +} + +func ToArray[M ~map[K]V, GT ~[]T.Tuple2[K, V], K comparable, V any](r M) GT { + return collect[M, GT](r, T.MakeTuple2[K, V]) +} + +func toEntriesOrd[M ~map[K]V, GT ~[]T.Tuple2[K, V], K comparable, V any](o ord.Ord[K], r M) GT { + // total number of elements + count := len(r) + // produce an array that we can sort by key + entries := make(GT, count) + idx := 0 + for k, v := range r { + entries[idx] = T.MakeTuple2(k, v) + idx++ + } + sort.Slice(entries, func(i, j int) bool { + return o.Compare(T.First(entries[i]), T.First(entries[j])) < 0 + }) + // final entries + return entries +} + +func ToEntriesOrd[M ~map[K]V, GT ~[]T.Tuple2[K, V], K comparable, V any](o ord.Ord[K]) func(r M) GT { + return F.Bind1st(toEntriesOrd[M, GT, K, V], o) +} + +func ToEntries[M ~map[K]V, GT ~[]T.Tuple2[K, V], K comparable, V any](r M) GT { + return ToArray[M, GT](r) +} + +// FromFoldableMap uses the reduce method for a higher kinded type to transform +// its values into a tuple. The key and value are then used to populate the map. Duplicate +// values are resolved via the provided [Mg.Magma] +func FromFoldableMap[ + FCT ~func(A) T.Tuple2[K, V], + HKTA any, + FOLDABLE ~func(func(M, A) M, M) func(HKTA) M, + M ~map[K]V, + A any, + K comparable, + V any](m Mg.Magma[V], fld FOLDABLE) func(f FCT) func(fa HKTA) M { + return func(f FCT) func(fa HKTA) M { + return fld(func(dst M, a A) M { + if IsEmpty(dst) { + dst = make(M) + } + e := f(a) + k := T.First(e) + old, ok := dst[k] + if ok { + dst[k] = m.Concat(old, T.Second(e)) + } else { + dst[k] = T.Second(e) + } + return dst + }, Empty[M]()) + } +} + +func FromFoldable[ + HKTA any, + FOLDABLE ~func(func(M, T.Tuple2[K, V]) M, M) func(HKTA) M, + M ~map[K]V, + K comparable, + V any](m Mg.Magma[V], red FOLDABLE) func(fa HKTA) M { + return FromFoldableMap[func(T.Tuple2[K, V]) T.Tuple2[K, V], HKTA, FOLDABLE](m, red)(F.Identity[T.Tuple2[K, V]]) +} + +func FromArrayMap[ + FCT ~func(A) T.Tuple2[K, V], + GA ~[]A, + M ~map[K]V, + A any, + K comparable, + V any](m Mg.Magma[V]) func(f FCT) func(fa GA) M { + return FromFoldableMap[FCT](m, F.Bind23of3(RAG.Reduce[GA, A, M])) +} + +func FromArray[ + GA ~[]T.Tuple2[K, V], + M ~map[K]V, + K comparable, + V any](m Mg.Magma[V]) func(fa GA) M { + return FromFoldable[GA](m, F.Bind23of3(RAG.Reduce[GA, T.Tuple2[K, V], M])) +} + +func FromEntries[M ~map[K]V, GT ~[]T.Tuple2[K, V], K comparable, V any](fa GT) M { + m := make(M) + for _, t := range fa { + upsertAtReadWrite(m, t.F1, t.F2) + } + return m +} + +func duplicate[M ~map[K]V, K comparable, V any](r M) M { + return MonadMap[M, M](r, F.Identity[V]) +} + +func upsertAt[M ~map[K]V, K comparable, V any](r M, k K, v V) M { + dup := duplicate(r) + dup[k] = v + return dup +} + +func deleteAt[M ~map[K]V, K comparable, V any](r M, k K) M { + dup := duplicate(r) + delete(dup, k) + return dup +} + +func upsertAtReadWrite[M ~map[K]V, K comparable, V any](r M, k K, v V) M { + r[k] = v + return r +} + +func UpsertAt[M ~map[K]V, K comparable, V any](k K, v V) func(M) M { + return func(ma M) M { + return upsertAt(ma, k, v) + } +} + +func DeleteAt[M ~map[K]V, K comparable, V any](k K) func(M) M { + return F.Bind2nd(deleteAt[M, K, V], k) +} + +func Singleton[M ~map[K]V, K comparable, V any](k K, v V) M { + return M{k: v} +} + +func filterMapWithIndex[M ~map[K]V1, N ~map[K]V2, K comparable, V1, V2 any](fa M, f func(K, V1) O.Option[V2]) N { + return G.ReduceWithIndex(fa, func(key K, n N, value V1) N { + return O.MonadFold(f(key, value), F.Constant(n), func(v V2) N { + return upsertAtReadWrite(n, key, v) + }) + }, make(N)) +} + +func filterWithIndex[M ~map[K]V, K comparable, V any](fa M, f func(K, V) bool) M { + return filterMapWithIndex[M, M](fa, func(k K, v V) O.Option[V] { + if f(k, v) { + return O.Of(v) + } + return O.None[V]() + }) +} + +func filter[M ~map[K]V, K comparable, V any](fa M, f func(K) bool) M { + return filterWithIndex(fa, F.Ignore2of2[V](f)) +} + +// Filter creates a new map with only the elements that match the predicate +func Filter[M ~map[K]V, K comparable, V any](f func(K) bool) func(M) M { + return F.Bind2nd(filter[M, K, V], f) +} + +// FilterWithIndex creates a new map with only the elements that match the predicate +func FilterWithIndex[M ~map[K]V, K comparable, V any](f func(K, V) bool) func(M) M { + return F.Bind2nd(filterWithIndex[M, K, V], f) +} + +// FilterMapWithIndex creates a new map with only the elements for which the transformation function creates a Some +func FilterMapWithIndex[M ~map[K]V1, N ~map[K]V2, K comparable, V1, V2 any](f func(K, V1) O.Option[V2]) func(M) N { + return F.Bind2nd(filterMapWithIndex[M, N, K, V1, V2], f) +} + +// FilterMap creates a new map with only the elements for which the transformation function creates a Some +func FilterMap[M ~map[K]V1, N ~map[K]V2, K comparable, V1, V2 any](f func(V1) O.Option[V2]) func(M) N { + return F.Bind2nd(filterMapWithIndex[M, N, K, V1, V2], F.Ignore1of2[K](f)) +} + +// Flatten converts a nested map into a regular map +func Flatten[M ~map[K]N, N ~map[K]V, K comparable, V any](m Mo.Monoid[N]) func(M) N { + return Chain[M, N](m)(F.Identity[N]) +} + +// FilterChainWithIndex creates a new map with only the elements for which the transformation function creates a Some +func FilterChainWithIndex[M ~map[K]V1, N ~map[K]V2, K comparable, V1, V2 any](m Mo.Monoid[N]) func(func(K, V1) O.Option[N]) func(M) N { + flatten := Flatten[map[K]N, N](m) + return func(f func(K, V1) O.Option[N]) func(M) N { + return F.Flow2( + FilterMapWithIndex[M, map[K]N](f), + flatten, + ) + } +} + +// FilterChain creates a new map with only the elements for which the transformation function creates a Some +func FilterChain[M ~map[K]V1, N ~map[K]V2, K comparable, V1, V2 any](m Mo.Monoid[N]) func(func(V1) O.Option[N]) func(M) N { + flatten := Flatten[map[K]N, N](m) + return func(f func(V1) O.Option[N]) func(M) N { + return F.Flow2( + FilterMap[M, map[K]N](f), + flatten, + ) + } +} + +// IsNil checks if the map is set to nil +func IsNil[M ~map[K]V, K comparable, V any](m M) bool { + return m == nil +} + +// IsNonNil checks if the map is set to nil +func IsNonNil[M ~map[K]V, K comparable, V any](m M) bool { + return m != nil +} + +// ConstNil return a nil map +func ConstNil[M ~map[K]V, K comparable, V any]() M { + return (M)(nil) +} + +func FoldMap[AS ~map[K]A, K comparable, A, B any](m Mo.Monoid[B]) func(func(A) B) func(AS) B { + return func(f func(A) B) func(AS) B { + return Reduce[AS](func(cur B, a A) B { + return m.Concat(cur, f(a)) + }, m.Empty()) + } +} + +func Fold[AS ~map[K]A, K comparable, A any](m Mo.Monoid[A]) func(AS) A { + return Reduce[AS](m.Concat, m.Empty()) +} + +func FoldMapWithIndex[AS ~map[K]A, K comparable, A, B any](m Mo.Monoid[B]) func(func(K, A) B) func(AS) B { + return func(f func(K, A) B) func(AS) B { + return ReduceWithIndex[AS](func(k K, cur B, a A) B { + return m.Concat(cur, f(k, a)) + }, m.Empty()) + } +} + +func ReduceOrdWithIndex[M ~map[K]V, K comparable, V, R any](o ord.Ord[K]) func(func(K, R, V) R, R) func(M) R { + return func(f func(K, R, V) R, initial R) func(M) R { + return func(m M) R { + return reduceOrd(o, m, f, initial) + } + } +} + +func ReduceOrd[M ~map[K]V, K comparable, V, R any](o ord.Ord[K]) func(func(R, V) R, R) func(M) R { + ro := ReduceOrdWithIndex[M, K, V, R](o) + return func(f func(R, V) R, initial R) func(M) R { + return ro(F.Ignore1of3[K](f), initial) + } +} + +func FoldMapOrd[AS ~map[K]A, K comparable, A, B any](o ord.Ord[K]) func(m Mo.Monoid[B]) func(func(A) B) func(AS) B { + red := ReduceOrd[AS, K, A, B](o) + return func(m Mo.Monoid[B]) func(func(A) B) func(AS) B { + return func(f func(A) B) func(AS) B { + return red(func(cur B, a A) B { + return m.Concat(cur, f(a)) + }, m.Empty()) + } + } +} + +func FoldOrd[AS ~map[K]A, K comparable, A any](o ord.Ord[K]) func(m Mo.Monoid[A]) func(AS) A { + red := ReduceOrd[AS, K, A, A](o) + return func(m Mo.Monoid[A]) func(AS) A { + return red(m.Concat, m.Empty()) + } +} + +func FoldMapOrdWithIndex[AS ~map[K]A, K comparable, A, B any](o ord.Ord[K]) func(m Mo.Monoid[B]) func(func(K, A) B) func(AS) B { + red := ReduceOrdWithIndex[AS, K, A, B](o) + return func(m Mo.Monoid[B]) func(func(K, A) B) func(AS) B { + return func(f func(K, A) B) func(AS) B { + return red(func(k K, cur B, a A) B { + return m.Concat(cur, f(k, a)) + }, m.Empty()) + } + } +} + +func MonadFlap[GFAB ~map[K]func(A) B, GB ~map[K]B, K comparable, A, B any](fab GFAB, a A) GB { + return FC.MonadFlap(MonadMap[GFAB, GB], fab, a) +} + +func Flap[GFAB ~map[K]func(A) B, GB ~map[K]B, K comparable, A, B any](a A) func(GFAB) GB { + return FC.Flap(Map[GFAB, GB], a) +} + +func Copy[M ~map[K]V, K comparable, V any](m M) M { + return duplicate(m) +} + +func Clone[M ~map[K]V, K comparable, V any](f func(V) V) func(m M) M { + // impementation assumes that map does not optimize for the empty map + return Map[M, M](f) +} diff --git a/v2/record/generic/semigroup.go b/v2/record/generic/semigroup.go new file mode 100644 index 0000000..c64cea2 --- /dev/null +++ b/v2/record/generic/semigroup.go @@ -0,0 +1,38 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + S "github.com/IBM/fp-go/v2/semigroup" +) + +func UnionSemigroup[N ~map[K]V, K comparable, V any](s S.Semigroup[V]) S.Semigroup[N] { + return S.MakeSemigroup(func(first N, second N) N { + return union[N, K, V](S.ToMagma(s), first, second) + }) +} + +func UnionLastSemigroup[N ~map[K]V, K comparable, V any]() S.Semigroup[N] { + return S.MakeSemigroup(func(first N, second N) N { + return unionLast[N, K, V](first, second) + }) +} + +func UnionFirstSemigroup[N ~map[K]V, K comparable, V any]() S.Semigroup[N] { + return S.MakeSemigroup(func(first N, second N) N { + return unionLast[N, K, V](second, first) + }) +} diff --git a/v2/record/monoid.go b/v2/record/monoid.go new file mode 100644 index 0000000..554ab94 --- /dev/null +++ b/v2/record/monoid.go @@ -0,0 +1,42 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package record + +import ( + M "github.com/IBM/fp-go/v2/monoid" + G "github.com/IBM/fp-go/v2/record/generic" + S "github.com/IBM/fp-go/v2/semigroup" +) + +// UnionMonoid computes the union of two maps of the same type +func UnionMonoid[K comparable, V any](s S.Semigroup[V]) M.Monoid[map[K]V] { + return G.UnionMonoid[map[K]V](s) +} + +// UnionLastMonoid computes the union of two maps of the same type giving the last map precedence +func UnionLastMonoid[K comparable, V any]() M.Monoid[map[K]V] { + return G.UnionLastMonoid[map[K]V]() +} + +// UnionFirstMonoid computes the union of two maps of the same type giving the first map precedence +func UnionFirstMonoid[K comparable, V any]() M.Monoid[map[K]V] { + return G.UnionFirstMonoid[map[K]V]() +} + +// MergeMonoid computes the union of two maps of the same type giving the last map precedence +func MergeMonoid[K comparable, V any]() M.Monoid[map[K]V] { + return G.UnionLastMonoid[map[K]V]() +} diff --git a/v2/record/monoid_test.go b/v2/record/monoid_test.go new file mode 100644 index 0000000..4c888e5 --- /dev/null +++ b/v2/record/monoid_test.go @@ -0,0 +1,122 @@ +// Copyright (c) 2023 IBM Corp. +// All rights reserved. +// +// 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. + +package record + +import ( + "testing" + + S "github.com/IBM/fp-go/v2/string" + "github.com/stretchr/testify/assert" +) + +func TestUnionMonoid(t *testing.T) { + m := UnionMonoid[string](S.Semigroup()) + + e := Empty[string, string]() + + x := map[string]string{ + "a": "a1", + "b": "b1", + "c": "c1", + } + + y := map[string]string{ + "b": "b2", + "c": "c2", + "d": "d2", + } + + res := map[string]string{ + "a": "a1", + "b": "b1b2", + "c": "c1c2", + "d": "d2", + } + + assert.Equal(t, x, m.Concat(x, m.Empty())) + assert.Equal(t, x, m.Concat(m.Empty(), x)) + + assert.Equal(t, x, m.Concat(x, e)) + assert.Equal(t, x, m.Concat(e, x)) + + assert.Equal(t, res, m.Concat(x, y)) +} + +func TestUnionFirstMonoid(t *testing.T) { + m := UnionFirstMonoid[string, string]() + + e := Empty[string, string]() + + x := map[string]string{ + "a": "a1", + "b": "b1", + "c": "c1", + } + + y := map[string]string{ + "b": "b2", + "c": "c2", + "d": "d2", + } + + res := map[string]string{ + "a": "a1", + "b": "b1", + "c": "c1", + "d": "d2", + } + + assert.Equal(t, x, m.Concat(x, m.Empty())) + assert.Equal(t, x, m.Concat(m.Empty(), x)) + + assert.Equal(t, x, m.Concat(x, e)) + assert.Equal(t, x, m.Concat(e, x)) + + assert.Equal(t, res, m.Concat(x, y)) +} + +func TestUnionLastMonoid(t *testing.T) { + m := UnionLastMonoid[string, string]() + + e := Empty[string, string]() + + x := map[string]string{ + "a": "a1", + "b": "b1", + "c": "c1", + } + + y := map[string]string{ + "b": "b2", + "c": "c2", + "d": "d2", + } + + res := map[string]string{ + "a": "a1", + "b": "b2", + "c": "c2", + "d": "d2", + } + + assert.Equal(t, x, m.Concat(x, m.Empty())) + assert.Equal(t, x, m.Concat(m.Empty(), x)) + + assert.Equal(t, x, m.Concat(x, e)) + assert.Equal(t, x, m.Concat(e, x)) + + assert.Equal(t, res, m.Concat(x, y)) +} diff --git a/v2/record/record.go b/v2/record/record.go new file mode 100644 index 0000000..860a797 --- /dev/null +++ b/v2/record/record.go @@ -0,0 +1,343 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package record + +import ( + EM "github.com/IBM/fp-go/v2/endomorphism" + Mg "github.com/IBM/fp-go/v2/magma" + Mo "github.com/IBM/fp-go/v2/monoid" + O "github.com/IBM/fp-go/v2/option" + "github.com/IBM/fp-go/v2/ord" + G "github.com/IBM/fp-go/v2/record/generic" + T "github.com/IBM/fp-go/v2/tuple" +) + +// IsEmpty tests if a map is empty +func IsEmpty[K comparable, V any](r map[K]V) bool { + return G.IsEmpty(r) +} + +// IsNonEmpty tests if a map is not empty +func IsNonEmpty[K comparable, V any](r map[K]V) bool { + return G.IsNonEmpty(r) +} + +// Keys returns the key in a map +func Keys[K comparable, V any](r map[K]V) []K { + return G.Keys[map[K]V, []K](r) +} + +// Values returns the values in a map +func Values[K comparable, V any](r map[K]V) []V { + return G.Values[map[K]V, []V](r) +} + +// Collect applies a collector function to the key value pairs in a map and returns the result as an array +func Collect[K comparable, V, R any](f func(K, V) R) func(map[K]V) []R { + return G.Collect[map[K]V, []R](f) +} + +// CollectOrd applies a collector function to the key value pairs in a map and returns the result as an array +func CollectOrd[V, R any, K comparable](o ord.Ord[K]) func(func(K, V) R) func(map[K]V) []R { + return G.CollectOrd[map[K]V, []R](o) +} + +func Reduce[K comparable, V, R any](f func(R, V) R, initial R) func(map[K]V) R { + return G.Reduce[map[K]V](f, initial) +} + +func ReduceWithIndex[K comparable, V, R any](f func(K, R, V) R, initial R) func(map[K]V) R { + return G.ReduceWithIndex[map[K]V](f, initial) +} + +func ReduceRef[K comparable, V, R any](f func(R, *V) R, initial R) func(map[K]V) R { + return G.ReduceRef[map[K]V](f, initial) +} + +func ReduceRefWithIndex[K comparable, V, R any](f func(K, R, *V) R, initial R) func(map[K]V) R { + return G.ReduceRefWithIndex[map[K]V](f, initial) +} + +func MonadMap[K comparable, V, R any](r map[K]V, f func(V) R) map[K]R { + return G.MonadMap[map[K]V, map[K]R](r, f) +} + +func MonadMapWithIndex[K comparable, V, R any](r map[K]V, f func(K, V) R) map[K]R { + return G.MonadMapWithIndex[map[K]V, map[K]R](r, f) +} + +func MonadMapRefWithIndex[K comparable, V, R any](r map[K]V, f func(K, *V) R) map[K]R { + return G.MonadMapRefWithIndex[map[K]V, map[K]R](r, f) +} + +func MonadMapRef[K comparable, V, R any](r map[K]V, f func(*V) R) map[K]R { + return G.MonadMapRef[map[K]V, map[K]R](r, f) +} + +func Map[K comparable, V, R any](f func(V) R) func(map[K]V) map[K]R { + return G.Map[map[K]V, map[K]R](f) +} + +func MapRef[K comparable, V, R any](f func(*V) R) func(map[K]V) map[K]R { + return G.MapRef[map[K]V, map[K]R](f) +} + +func MapWithIndex[K comparable, V, R any](f func(K, V) R) func(map[K]V) map[K]R { + return G.MapWithIndex[map[K]V, map[K]R](f) +} + +func MapRefWithIndex[K comparable, V, R any](f func(K, *V) R) func(map[K]V) map[K]R { + return G.MapRefWithIndex[map[K]V, map[K]R](f) +} + +// Lookup returns the entry for a key in a map if it exists +func Lookup[V any, K comparable](k K) func(map[K]V) O.Option[V] { + return G.Lookup[map[K]V](k) +} + +// MonadLookup returns the entry for a key in a map if it exists +func MonadLookup[V any, K comparable](m map[K]V, k K) O.Option[V] { + return G.MonadLookup[map[K]V](m, k) +} + +// Has tests if a key is contained in a map +func Has[K comparable, V any](k K, r map[K]V) bool { + return G.Has(k, r) +} + +func Union[K comparable, V any](m Mg.Magma[V]) func(map[K]V) func(map[K]V) map[K]V { + return G.Union[map[K]V](m) +} + +// Merge combines two maps giving the values in the right one precedence. Also refer to [MergeMonoid] +func Merge[K comparable, V any](right map[K]V) func(map[K]V) map[K]V { + return G.Merge[map[K]V](right) +} + +// Empty creates an empty map +func Empty[K comparable, V any]() map[K]V { + return G.Empty[map[K]V]() +} + +// Size returns the number of elements in a map +func Size[K comparable, V any](r map[K]V) int { + return G.Size(r) +} + +func ToArray[K comparable, V any](r map[K]V) []T.Tuple2[K, V] { + return G.ToArray[map[K]V, []T.Tuple2[K, V]](r) +} + +func ToEntries[K comparable, V any](r map[K]V) []T.Tuple2[K, V] { + return G.ToEntries[map[K]V, []T.Tuple2[K, V]](r) +} + +func FromEntries[K comparable, V any](fa []T.Tuple2[K, V]) map[K]V { + return G.FromEntries[map[K]V](fa) +} + +func UpsertAt[K comparable, V any](k K, v V) func(map[K]V) map[K]V { + return G.UpsertAt[map[K]V](k, v) +} + +func DeleteAt[K comparable, V any](k K) func(map[K]V) map[K]V { + return G.DeleteAt[map[K]V](k) +} + +// Singleton creates a new map with a single entry +func Singleton[K comparable, V any](k K, v V) map[K]V { + return G.Singleton[map[K]V](k, v) +} + +// FilterMapWithIndex creates a new map with only the elements for which the transformation function creates a Some +func FilterMapWithIndex[K comparable, V1, V2 any](f func(K, V1) O.Option[V2]) func(map[K]V1) map[K]V2 { + return G.FilterMapWithIndex[map[K]V1, map[K]V2](f) +} + +// FilterMap creates a new map with only the elements for which the transformation function creates a Some +func FilterMap[K comparable, V1, V2 any](f func(V1) O.Option[V2]) func(map[K]V1) map[K]V2 { + return G.FilterMap[map[K]V1, map[K]V2](f) +} + +// Filter creates a new map with only the elements that match the predicate +func Filter[K comparable, V any](f func(K) bool) func(map[K]V) map[K]V { + return G.Filter[map[K]V](f) +} + +// FilterWithIndex creates a new map with only the elements that match the predicate +func FilterWithIndex[K comparable, V any](f func(K, V) bool) func(map[K]V) map[K]V { + return G.FilterWithIndex[map[K]V](f) +} + +// IsNil checks if the map is set to nil +func IsNil[K comparable, V any](m map[K]V) bool { + return G.IsNil(m) +} + +// IsNonNil checks if the map is set to nil +func IsNonNil[K comparable, V any](m map[K]V) bool { + return G.IsNonNil(m) +} + +// ConstNil return a nil map +func ConstNil[K comparable, V any]() map[K]V { + return (map[K]V)(nil) +} + +func MonadChainWithIndex[V1 any, K comparable, V2 any](m Mo.Monoid[map[K]V2], r map[K]V1, f func(K, V1) map[K]V2) map[K]V2 { + return G.MonadChainWithIndex(m, r, f) +} + +func MonadChain[V1 any, K comparable, V2 any](m Mo.Monoid[map[K]V2], r map[K]V1, f func(V1) map[K]V2) map[K]V2 { + return G.MonadChain(m, r, f) +} + +func ChainWithIndex[V1 any, K comparable, V2 any](m Mo.Monoid[map[K]V2]) func(func(K, V1) map[K]V2) func(map[K]V1) map[K]V2 { + return G.ChainWithIndex[map[K]V1](m) +} + +func Chain[V1 any, K comparable, V2 any](m Mo.Monoid[map[K]V2]) func(func(V1) map[K]V2) func(map[K]V1) map[K]V2 { + return G.Chain[map[K]V1](m) +} + +// Flatten converts a nested map into a regular map +func Flatten[K comparable, V any](m Mo.Monoid[map[K]V]) func(map[K]map[K]V) map[K]V { + return G.Flatten[map[K]map[K]V](m) +} + +// FilterChainWithIndex creates a new map with only the elements for which the transformation function creates a Some +func FilterChainWithIndex[V1 any, K comparable, V2 any](m Mo.Monoid[map[K]V2]) func(func(K, V1) O.Option[map[K]V2]) func(map[K]V1) map[K]V2 { + return G.FilterChainWithIndex[map[K]V1](m) +} + +// FilterChain creates a new map with only the elements for which the transformation function creates a Some +func FilterChain[V1 any, K comparable, V2 any](m Mo.Monoid[map[K]V2]) func(func(V1) O.Option[map[K]V2]) func(map[K]V1) map[K]V2 { + return G.FilterChain[map[K]V1](m) +} + +// FoldMap maps and folds a record. Map the record passing each value to the iterating function. Then fold the results using the provided Monoid. +func FoldMap[K comparable, A, B any](m Mo.Monoid[B]) func(func(A) B) func(map[K]A) B { + return G.FoldMap[map[K]A](m) +} + +// FoldMapWithIndex maps and folds a record. Map the record passing each value to the iterating function. Then fold the results using the provided Monoid. +func FoldMapWithIndex[K comparable, A, B any](m Mo.Monoid[B]) func(func(K, A) B) func(map[K]A) B { + return G.FoldMapWithIndex[map[K]A](m) +} + +// Fold folds the record using the provided Monoid. +func Fold[K comparable, A any](m Mo.Monoid[A]) func(map[K]A) A { + return G.Fold[map[K]A](m) +} + +// ReduceOrdWithIndex reduces a map into a single value via a reducer function making sure that the keys are passed to the reducer in the specified order +func ReduceOrdWithIndex[V, R any, K comparable](o ord.Ord[K]) func(func(K, R, V) R, R) func(map[K]V) R { + return G.ReduceOrdWithIndex[map[K]V, K, V, R](o) +} + +// ReduceOrd reduces a map into a single value via a reducer function making sure that the keys are passed to the reducer in the specified order +func ReduceOrd[V, R any, K comparable](o ord.Ord[K]) func(func(R, V) R, R) func(map[K]V) R { + return G.ReduceOrd[map[K]V, K, V, R](o) +} + +// FoldMap maps and folds a record. Map the record passing each value to the iterating function. Then fold the results using the provided Monoid and the items in the provided order +func FoldMapOrd[A, B any, K comparable](o ord.Ord[K]) func(m Mo.Monoid[B]) func(func(A) B) func(map[K]A) B { + return G.FoldMapOrd[map[K]A, K, A, B](o) +} + +// Fold folds the record using the provided Monoid with the items passed in the given order +func FoldOrd[A any, K comparable](o ord.Ord[K]) func(m Mo.Monoid[A]) func(map[K]A) A { + return G.FoldOrd[map[K]A, K, A](o) +} + +// FoldMapWithIndex maps and folds a record. Map the record passing each value to the iterating function. Then fold the results using the provided Monoid and the items in the provided order +func FoldMapOrdWithIndex[K comparable, A, B any](o ord.Ord[K]) func(m Mo.Monoid[B]) func(func(K, A) B) func(map[K]A) B { + return G.FoldMapOrdWithIndex[map[K]A, K, A, B](o) +} + +// KeysOrd returns the keys in the map in their given order +func KeysOrd[V any, K comparable](o ord.Ord[K]) func(r map[K]V) []K { + return G.KeysOrd[map[K]V, []K, K, V](o) +} + +// ValuesOrd returns the values in the map ordered by their keys in the given order +func ValuesOrd[V any, K comparable](o ord.Ord[K]) func(r map[K]V) []V { + return G.ValuesOrd[map[K]V, []V, K, V](o) +} + +func MonadFlap[B any, K comparable, A any](fab map[K]func(A) B, a A) map[K]B { + return G.MonadFlap[map[K]func(A) B, map[K]B](fab, a) +} + +func Flap[B any, K comparable, A any](a A) func(map[K]func(A) B) map[K]B { + return G.Flap[map[K]func(A) B, map[K]B](a) +} + +// Copy creates a shallow copy of the map +func Copy[K comparable, V any](m map[K]V) map[K]V { + return G.Copy[map[K]V](m) +} + +// Clone creates a deep copy of the map using the provided endomorphism to clone the values +func Clone[K comparable, V any](f EM.Endomorphism[V]) EM.Endomorphism[map[K]V] { + return G.Clone[map[K]V](f) +} + +// FromFoldableMap converts from a reducer to a map +// Duplicate keys are resolved by the provided [Mg.Magma] +func FromFoldableMap[ + FOLDABLE ~func(func(map[K]V, A) map[K]V, map[K]V) func(HKTA) map[K]V, // the reduce function + A any, + HKTA any, + K comparable, + V any](m Mg.Magma[V], red FOLDABLE) func(f func(A) T.Tuple2[K, V]) func(fa HKTA) map[K]V { + return G.FromFoldableMap[func(A) T.Tuple2[K, V]](m, red) +} + +// FromArrayMap converts from an array to a map +// Duplicate keys are resolved by the provided [Mg.Magma] +func FromArrayMap[ + A any, + K comparable, + V any](m Mg.Magma[V]) func(f func(A) T.Tuple2[K, V]) func(fa []A) map[K]V { + return G.FromArrayMap[func(A) T.Tuple2[K, V], []A, map[K]V](m) +} + +// FromFoldable converts from a reducer to a map +// Duplicate keys are resolved by the provided [Mg.Magma] +func FromFoldable[ + HKTA any, + FOLDABLE ~func(func(map[K]V, T.Tuple2[K, V]) map[K]V, map[K]V) func(HKTA) map[K]V, // the reduce function + K comparable, + V any](m Mg.Magma[V], red FOLDABLE) func(fa HKTA) map[K]V { + return G.FromFoldable[HKTA, FOLDABLE](m, red) +} + +// FromArray converts from an array to a map +// Duplicate keys are resolved by the provided [Mg.Magma] +func FromArray[ + K comparable, + V any](m Mg.Magma[V]) func(fa []T.Tuple2[K, V]) map[K]V { + return G.FromArray[[]T.Tuple2[K, V], map[K]V](m) +} + +func MonadAp[A any, K comparable, B any](m Mo.Monoid[map[K]B], fab map[K]func(A) B, fa map[K]A) map[K]B { + return G.MonadAp[map[K]B, map[K]func(A) B, map[K]A](m, fab, fa) +} + +func Ap[A any, K comparable, B any](m Mo.Monoid[map[K]B]) func(fa map[K]A) func(map[K]func(A) B) map[K]B { + return G.Ap[map[K]B, map[K]func(A) B, map[K]A](m) +} diff --git a/v2/record/record_test.go b/v2/record/record_test.go new file mode 100644 index 0000000..ae285c2 --- /dev/null +++ b/v2/record/record_test.go @@ -0,0 +1,200 @@ +// All rights reserved. +// Copyright (c) 2023 - 2025 IBM Corp. +// +// 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. + +package record + +import ( + "fmt" + "sort" + "strings" + "testing" + + A "github.com/IBM/fp-go/v2/array" + "github.com/IBM/fp-go/v2/internal/utils" + Mg "github.com/IBM/fp-go/v2/magma" + O "github.com/IBM/fp-go/v2/option" + S "github.com/IBM/fp-go/v2/string" + T "github.com/IBM/fp-go/v2/tuple" + "github.com/stretchr/testify/assert" +) + +func TestKeys(t *testing.T) { + data := map[string]string{ + "a": "A", + "b": "B", + "c": "C", + } + keys := Keys(data) + sort.Strings(keys) + + assert.Equal(t, []string{"a", "b", "c"}, keys) +} + +func TestValues(t *testing.T) { + data := map[string]string{ + "a": "A", + "b": "B", + "c": "C", + } + keys := Values(data) + sort.Strings(keys) + + assert.Equal(t, []string{"A", "B", "C"}, keys) +} + +func TestMap(t *testing.T) { + data := map[string]string{ + "a": "a", + "b": "b", + "c": "c", + } + expected := map[string]string{ + "a": "A", + "b": "B", + "c": "C", + } + assert.Equal(t, expected, Map[string](utils.Upper)(data)) +} + +func TestLookup(t *testing.T) { + data := map[string]string{ + "a": "a", + "b": "b", + "c": "c", + } + assert.Equal(t, O.Some("a"), Lookup[string]("a")(data)) + assert.Equal(t, O.None[string](), Lookup[string]("a1")(data)) +} + +func TestFilterChain(t *testing.T) { + src := map[string]int{ + "a": 1, + "b": 2, + "c": 3, + } + + f := func(k string, value int) O.Option[map[string]string] { + if value%2 != 0 { + return O.Of(map[string]string{ + k: fmt.Sprintf("%s%d", k, value), + }) + } + return O.None[map[string]string]() + } + + // monoid + monoid := MergeMonoid[string, string]() + + res := FilterChainWithIndex[int](monoid)(f)(src) + + assert.Equal(t, map[string]string{ + "a": "a1", + "c": "c3", + }, res) +} + +func ExampleFoldMap() { + src := map[string]string{ + "a": "a", + "b": "b", + "c": "c", + } + + fold := FoldMapOrd[string, string](S.Ord)(S.Monoid)(strings.ToUpper) + + fmt.Println(fold(src)) + + // Output: ABC + +} + +func ExampleValuesOrd() { + src := map[string]string{ + "c": "a", + "b": "b", + "a": "c", + } + + getValues := ValuesOrd[string](S.Ord) + + fmt.Println(getValues(src)) + + // Output: [c b a] + +} + +func TestCopyVsClone(t *testing.T) { + slc := []string{"b", "c"} + src := map[string][]string{ + "a": slc, + } + // make a shallow copy + cpy := Copy(src) + // make a deep copy + cln := Clone[string](A.Copy[string])(src) + + assert.Equal(t, cpy, cln) + // make a modification to the original slice + slc[0] = "d" + assert.NotEqual(t, cpy, cln) + assert.Equal(t, src, cpy) +} + +func TestFromArrayMap(t *testing.T) { + src1 := A.From("a", "b", "c", "a") + frm := FromArrayMap[string, string](Mg.Second[string]()) + + f := frm(T.Replicate2[string]) + + res1 := f(src1) + + assert.Equal(t, map[string]string{ + "a": "a", + "b": "b", + "c": "c", + }, res1) + + src2 := A.From("A", "B", "C", "A") + + res2 := f(src2) + + assert.Equal(t, map[string]string{ + "A": "A", + "B": "B", + "C": "C", + }, res2) +} + +func TestEmpty(t *testing.T) { + nonEmpty := map[string]string{ + "a": "A", + "b": "B", + } + empty := Empty[string, string]() + + assert.True(t, IsEmpty(empty)) + assert.False(t, IsEmpty(nonEmpty)) + assert.False(t, IsNonEmpty(empty)) + assert.True(t, IsNonEmpty(nonEmpty)) +} + +func TestHas(t *testing.T) { + nonEmpty := map[string]string{ + "a": "A", + "b": "B", + } + assert.True(t, Has("a", nonEmpty)) + assert.False(t, Has("c", nonEmpty)) +} diff --git a/v2/record/semigroup.go b/v2/record/semigroup.go new file mode 100644 index 0000000..b308d14 --- /dev/null +++ b/v2/record/semigroup.go @@ -0,0 +1,33 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package record + +import ( + G "github.com/IBM/fp-go/v2/record/generic" + S "github.com/IBM/fp-go/v2/semigroup" +) + +func UnionSemigroup[K comparable, V any](s S.Semigroup[V]) S.Semigroup[map[K]V] { + return G.UnionSemigroup[map[K]V](s) +} + +func UnionLastSemigroup[K comparable, V any]() S.Semigroup[map[K]V] { + return G.UnionLastSemigroup[map[K]V]() +} + +func UnionFirstSemigroup[K comparable, V any]() S.Semigroup[map[K]V] { + return G.UnionFirstSemigroup[map[K]V]() +} diff --git a/v2/record/traverse.go b/v2/record/traverse.go new file mode 100644 index 0000000..1f3c682 --- /dev/null +++ b/v2/record/traverse.go @@ -0,0 +1,53 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package record + +import ( + G "github.com/IBM/fp-go/v2/internal/record" +) + +func TraverseWithIndex[K comparable, A, B, HKTB, HKTAB, HKTRB any]( + fof func(map[K]B) HKTRB, + fmap func(func(map[K]B) func(B) map[K]B) func(HKTRB) HKTAB, + fap func(HKTB) func(HKTAB) HKTRB, + + f func(K, A) HKTB) func(map[K]A) HKTRB { + return G.TraverseWithIndex[map[K]A](fof, fmap, fap, f) +} + +// HKTA = HKT +// HKTB = HKT +// HKTAB = HKT +// HKTRB = HKT +func Traverse[K comparable, A, B, HKTB, HKTAB, HKTRB any]( + fof func(map[K]B) HKTRB, + fmap func(func(map[K]B) func(B) map[K]B) func(HKTRB) HKTAB, + fap func(HKTB) func(HKTAB) HKTRB, + f func(A) HKTB) func(map[K]A) HKTRB { + return G.Traverse[map[K]A](fof, fmap, fap, f) +} + +// HKTA = HKT[A] +// HKTAA = HKT[func(A)map[K]A] +// HKTRA = HKT[map[K]A] +func Sequence[K comparable, A, HKTA, HKTAA, HKTRA any]( + fof func(map[K]A) HKTRA, + fmap func(func(map[K]A) func(A) map[K]A) func(HKTRA) HKTAA, + fap func(HKTA) func(HKTAA) HKTRA, + ma map[K]HKTA) HKTRA { + return G.Sequence(fof, fmap, fap, ma) + +} diff --git a/v2/record/traverse_test.go b/v2/record/traverse_test.go new file mode 100644 index 0000000..df8ebc6 --- /dev/null +++ b/v2/record/traverse_test.go @@ -0,0 +1,90 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package record + +import ( + "testing" + + F "github.com/IBM/fp-go/v2/function" + O "github.com/IBM/fp-go/v2/option" + "github.com/stretchr/testify/assert" +) + +type MapType = map[string]int +type MapTypeString = map[string]string +type MapTypeO = map[string]O.Option[int] + +func TestSimpleTraversalWithIndex(t *testing.T) { + + f := func(k string, n int) O.Option[int] { + if k != "a" { + return O.Some(n) + } + return O.None[int]() + } + + tWithIndex := TraverseWithIndex( + O.Of[MapType], + O.Map[MapType, func(int) MapType], + O.Ap[MapType, int], + f) + + assert.Equal(t, O.None[MapType](), F.Pipe1(MapType{"a": 1, "b": 2}, tWithIndex)) + assert.Equal(t, O.Some(MapType{"b": 2}), F.Pipe1(MapType{"b": 2}, tWithIndex)) +} + +func TestSimpleTraversalNoIndex(t *testing.T) { + + f := func(k string) O.Option[string] { + if k != "1" { + return O.Some(k) + } + return O.None[string]() + } + + tWithoutIndex := Traverse( + O.Of[MapTypeString], + O.Map[MapTypeString, func(string) MapTypeString], + O.Ap[MapTypeString, string], + f) + + assert.Equal(t, O.None[MapTypeString](), F.Pipe1(MapTypeString{"a": "1", "b": "2"}, tWithoutIndex)) + assert.Equal(t, O.Some(MapTypeString{"b": "2"}), F.Pipe1(MapTypeString{"b": "2"}, tWithoutIndex)) +} + +func TestSequence(t *testing.T) { + // source map + simpleMapO := MapTypeO{"a": O.Of(1), "b": O.Of(2)} + // convert to an option of record + + s := Traverse( + O.Of[MapType], + O.Map[MapType, func(int) MapType], + O.Ap[MapType, int], + F.Identity[O.Option[int]], + ) + + assert.Equal(t, O.Of(MapType{"a": 1, "b": 2}), F.Pipe1(simpleMapO, s)) + + s1 := Sequence( + O.Of[MapType], + O.Map[MapType, func(int) MapType], + O.Ap[MapType, int], + simpleMapO, + ) + + assert.Equal(t, O.Of(MapType{"a": 1, "b": 2}), s1) +} diff --git a/v2/reflect/generic/reflect.go b/v2/reflect/generic/reflect.go new file mode 100644 index 0000000..1d80132 --- /dev/null +++ b/v2/reflect/generic/reflect.go @@ -0,0 +1,31 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + R "reflect" +) + +func Map[GA ~[]A, A any](f func(R.Value) A) func(R.Value) GA { + return func(val R.Value) GA { + l := val.Len() + res := make(GA, l) + for i := l - 1; i >= 0; i-- { + res[i] = f(val.Index(i)) + } + return res + } +} diff --git a/v2/reflect/reflect.go b/v2/reflect/reflect.go new file mode 100644 index 0000000..487a801 --- /dev/null +++ b/v2/reflect/reflect.go @@ -0,0 +1,42 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package option + +import ( + R "reflect" + + F "github.com/IBM/fp-go/v2/function" + G "github.com/IBM/fp-go/v2/reflect/generic" +) + +func ReduceWithIndex[A any](f func(int, A, R.Value) A, initial A) func(R.Value) A { + return func(val R.Value) A { + count := val.Len() + current := initial + for i := 0; i < count; i++ { + current = f(i, current, val.Index(i)) + } + return current + } +} + +func Reduce[A any](f func(A, R.Value) A, initial A) func(R.Value) A { + return ReduceWithIndex(F.Ignore1of3[int](f), initial) +} + +func Map[A any](f func(R.Value) A) func(R.Value) []A { + return G.Map[[]A](f) +} diff --git a/v2/resources/images/logo.png b/v2/resources/images/logo.png new file mode 100644 index 0000000..f0242f5 Binary files /dev/null and b/v2/resources/images/logo.png differ diff --git a/v2/retry/generic/retry.go b/v2/retry/generic/retry.go new file mode 100644 index 0000000..236bbbd --- /dev/null +++ b/v2/retry/generic/retry.go @@ -0,0 +1,102 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +import ( + "time" + + F "github.com/IBM/fp-go/v2/function" + O "github.com/IBM/fp-go/v2/option" + R "github.com/IBM/fp-go/v2/retry" +) + +// Apply policy and delay by its amount if it results in a R. +// Returns updated status. +// HKTSTATUS = HKT +func applyAndDelay[HKTSTATUS any]( + monadOf func(R.RetryStatus) HKTSTATUS, + monadDelay func(time.Duration) func(HKTSTATUS) HKTSTATUS, +) func(policy R.RetryPolicy, status R.RetryStatus) HKTSTATUS { + return func(policy R.RetryPolicy, status R.RetryStatus) HKTSTATUS { + newStatus := R.ApplyPolicy(policy, status) + return F.Pipe1( + newStatus.PreviousDelay, + O.Fold( + F.Nullary2(F.Constant(newStatus), monadOf), + func(delay time.Duration) HKTSTATUS { + return monadDelay(delay)(monadOf(newStatus)) + }, + ), + ) + } +} + +// Retry combinator for actions that don't raise exceptions, but +// signal in their type the outcome has failed. Examples are the +// `Option`, `Either` and `EitherT` monads. +// +// policy - refers to the retry policy +// action - converts a status into an operation to be executed +// check - checks if the result of the action needs to be retried +func Retrying[HKTA, HKTSTATUS, A any]( + monadChain func(func(A) HKTA) func(HKTA) HKTA, + monadChainStatus func(func(R.RetryStatus) HKTA) func(HKTSTATUS) HKTA, + monadOf func(A) HKTA, + monadOfStatus func(R.RetryStatus) HKTSTATUS, + monadDelay func(time.Duration) func(HKTSTATUS) HKTSTATUS, + + policy R.RetryPolicy, + action func(R.RetryStatus) HKTA, + check func(A) bool, +) HKTA { + // delay callback + applyDelay := applyAndDelay(monadOfStatus, monadDelay) + + // function to check if we need to retry or not + checkForRetry := O.FromPredicate(check) + + var f func(status R.RetryStatus) HKTA + + // need some lazy init because we reference it in the chain + f = func(status R.RetryStatus) HKTA { + return F.Pipe2( + status, + action, + monadChain(func(a A) HKTA { + return F.Pipe3( + a, + checkForRetry, + O.Map(func(a A) HKTA { + return F.Pipe1( + applyDelay(policy, status), + monadChainStatus(func(status R.RetryStatus) HKTA { + return F.Pipe1( + status.PreviousDelay, + O.Fold(F.Constant(monadOf(a)), func(_ time.Duration) HKTA { + return f(status) + }), + ) + }), + ) + }), + O.GetOrElse(F.Constant(monadOf(a))), + ) + }), + ) + } + // seed + return f(R.DefaultRetryStatus) +} diff --git a/v2/retry/retry.go b/v2/retry/retry.go new file mode 100644 index 0000000..dea6dde --- /dev/null +++ b/v2/retry/retry.go @@ -0,0 +1,121 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package retry + +import ( + "math" + "time" + + F "github.com/IBM/fp-go/v2/function" + M "github.com/IBM/fp-go/v2/monoid" + O "github.com/IBM/fp-go/v2/option" + "github.com/IBM/fp-go/v2/ord" +) + +type RetryStatus struct { + // Iteration number, where `0` is the first try + IterNumber uint + // Delay incurred so far from retries + CumulativeDelay time.Duration + // Latest attempt's delay. Will always be `none` on first run. + PreviousDelay Option[time.Duration] +} + +// RetryPolicy is a function that takes an `RetryStatus` and +// possibly returns a delay in milliseconds. Iteration numbers start +// at zero and increase by one on each retry. A //None// return value from +// the function implies we have reached the retry limit. +type RetryPolicy = func(RetryStatus) Option[time.Duration] + +const emptyDuration = time.Duration(0) + +var ordDuration = ord.FromStrictCompare[time.Duration]() + +// Monoid 'RetryPolicy' is a 'Monoid'. You can collapse multiple strategies into one using 'concat'. +// The semantics of this combination are as follows: +// +// 1. If either policy returns 'None', the combined policy returns +// 'None'. This can be used to inhibit after a number of retries, +// for example. +// +// 2. If both policies return a delay, the larger delay will be used. +// This is quite natural when combining multiple policies to achieve a +// certain effect. +var Monoid = M.FunctionMonoid[RetryStatus](O.ApplicativeMonoid(M.MakeMonoid( + ord.MaxSemigroup(ordDuration).Concat, emptyDuration))) + +// LimitRetries retries immediately, but only up to `i` times. +func LimitRetries(i uint) RetryPolicy { + pred := func(value uint) bool { + return value < i + } + empty := F.Constant1[uint](emptyDuration) + return func(status RetryStatus) Option[time.Duration] { + return F.Pipe2( + status.IterNumber, + O.FromPredicate(pred), + O.Map(empty), + ) + } +} + +// ConstantDelay delays with unlimited retries +func ConstantDelay(delay time.Duration) RetryPolicy { + return F.Constant1[RetryStatus](O.Of(delay)) +} + +// CapDelay sets a time-upperbound for any delays that may be directed by the +// given policy. This function does not terminate the retrying. The policy +// capDelay(maxDelay, exponentialBackoff(n))` will never stop retrying. It +// will reach a state where it retries forever with a delay of `maxDelay` +// between each one. To get termination you need to use one of the +// 'limitRetries' function variants. +func CapDelay(maxDelay time.Duration, policy RetryPolicy) RetryPolicy { + return F.Flow2( + policy, + O.Map(F.Bind1st(ord.Min(ordDuration), maxDelay)), + ) +} + +// ExponentialBackoff grows delay exponentially each iteration. +// Each delay will increase by a factor of two. +func ExponentialBackoff(delay time.Duration) RetryPolicy { + return func(status RetryStatus) Option[time.Duration] { + return O.Some(delay * time.Duration(math.Pow(2, float64(status.IterNumber)))) + } +} + +// DefaultRetryStatus is the default retry status. Exported mostly to allow user code +// to test their handlers and retry policies. +var DefaultRetryStatus = RetryStatus{ + IterNumber: 0, + CumulativeDelay: 0, + PreviousDelay: O.None[time.Duration](), +} + +var getOrElseDelay = O.GetOrElse(F.Constant(emptyDuration)) + +/** + * Apply policy on status to see what the decision would be. + */ +func ApplyPolicy(policy RetryPolicy, status RetryStatus) RetryStatus { + previousDelay := policy(status) + return RetryStatus{ + IterNumber: status.IterNumber + 1, + CumulativeDelay: status.CumulativeDelay + getOrElseDelay(previousDelay), + PreviousDelay: previousDelay, + } +} diff --git a/v2/retry/types.go b/v2/retry/types.go new file mode 100644 index 0000000..8f755a0 --- /dev/null +++ b/v2/retry/types.go @@ -0,0 +1,22 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package retry + +import "github.com/IBM/fp-go/v2/option" + +type ( + Option[A any] = option.Option[A] +) diff --git a/v2/samples/README.md b/v2/samples/README.md new file mode 100644 index 0000000..fdff004 --- /dev/null +++ b/v2/samples/README.md @@ -0,0 +1,14 @@ +# Samples + +This folder is meant to contain examples that illustrate how to use the library. I recommend the following reading to get an idea of the underlying concepts. These articles talk about [fp-ts](https://github.com/gcanti/fp-ts) but the concepts are very similar, only syntax differs. + +# Video Introduction + +[![introduction to fp-go](presentation/cover.jpg)](https://www.youtube.com/watch?v=Jif3jL6DRdw "introduction to fp-go") + +### References + +- [Ryan's Blog](https://rlee.dev/practical-guide-to-fp-ts-part-1) - practical introduction into FP concepts +- [Investigate Functional Programming Concepts in Go](https://betterprogramming.pub/investigate-functional-programming-concepts-in-go-1dada09bc913) - discussion around FP concepts in golang +- [Investigating the I/O Monad in Go](https://medium.com/better-programming/investigating-the-i-o-monad-in-go-3c0fabbb4b3d) - a closer look at I/O monads in golang +- \ No newline at end of file diff --git a/v2/samples/doc_test.go b/v2/samples/doc_test.go new file mode 100644 index 0000000..dffb948 --- /dev/null +++ b/v2/samples/doc_test.go @@ -0,0 +1,17 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +// Package sample contains examples that illustrate how to use fp-go +package samples diff --git a/v2/samples/getting-started-with-fp-ts/reader/example1/reader_test.go b/v2/samples/getting-started-with-fp-ts/reader/example1/reader_test.go new file mode 100644 index 0000000..5a3a6b5 --- /dev/null +++ b/v2/samples/getting-started-with-fp-ts/reader/example1/reader_test.go @@ -0,0 +1,75 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +// Package example1 implements the first example in [https://dev.to/gcanti/getting-started-with-fp-ts-reader-1ie5] +package example1 + +import ( + "fmt" + + F "github.com/IBM/fp-go/v2/function" + N "github.com/IBM/fp-go/v2/number" + I "github.com/IBM/fp-go/v2/number/integer" + "github.com/IBM/fp-go/v2/ord" + R "github.com/IBM/fp-go/v2/reader" + S "github.com/IBM/fp-go/v2/string" +) + +type ( + I18n struct { + True string + False string + } + + Dependencies struct { + I18n I18n + } +) + +var ( + // g: func(int) R.Reader[*Dependencies, string], note how the implementation does not depend on the dependencies + g = F.Flow2( + ord.Gt(I.Ord)(2), + f, + ) + + // h: func(string) R.Reader[*Dependencies, string], note how the implementation does not depend on the dependencies + h = F.Flow3( + S.Size, + N.Add(1), + g, + ) +) + +func f(b bool) R.Reader[*Dependencies, string] { + return func(deps *Dependencies) string { + if b { + return deps.I18n.True + } + return deps.I18n.False + } +} + +func ExampleReader() { + + deps := Dependencies{I18n: I18n{True: "vero", False: "falso"}} + + fmt.Println(h("foo")(&deps)) + fmt.Println(h("a")(&deps)) + + // Output: + // vero + // falso +} diff --git a/v2/samples/getting-started-with-fp-ts/reader/example2/reader_test.go b/v2/samples/getting-started-with-fp-ts/reader/example2/reader_test.go new file mode 100644 index 0000000..1c6ba8c --- /dev/null +++ b/v2/samples/getting-started-with-fp-ts/reader/example2/reader_test.go @@ -0,0 +1,97 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +// Package example2 implements the second example in [https://dev.to/gcanti/getting-started-with-fp-ts-reader-1ie5] +package example2 + +import ( + "fmt" + + F "github.com/IBM/fp-go/v2/function" + ID "github.com/IBM/fp-go/v2/identity" + N "github.com/IBM/fp-go/v2/number" + I "github.com/IBM/fp-go/v2/number/integer" + "github.com/IBM/fp-go/v2/ord" + R "github.com/IBM/fp-go/v2/reader" + S "github.com/IBM/fp-go/v2/string" +) + +type ( + I18n struct { + True string + False string + } + + Dependencies struct { + I18n I18n + LowerBound int + } +) + +var ( + // h: func(string) R.Reader[*Dependencies, string], note how the implementation does not depend on the dependencies + h = F.Flow3( + S.Size, + N.Add(1), + g, + ) +) + +func getLowerBound(deps *Dependencies) int { + return deps.LowerBound +} + +func g(n int) R.Reader[*Dependencies, string] { + return F.Pipe1( + R.Ask[*Dependencies](), + R.Chain(F.Flow4( + getLowerBound, + ord.Gt(I.Ord), + ID.Flap[bool](n), + f, + )), + ) +} + +func f(b bool) R.Reader[*Dependencies, string] { + return func(deps *Dependencies) string { + if b { + return deps.I18n.True + } + return deps.I18n.False + } +} + +func ExampleReader() { + + deps1 := Dependencies{I18n: I18n{True: "vero", False: "falso"}, LowerBound: 2} + + hFoo := h("foo") + hA := h("a") + + fmt.Println(hFoo(&deps1)) + fmt.Println(hA(&deps1)) + + deps2 := Dependencies{I18n: I18n{True: "vero", False: "falso"}, LowerBound: 4} + + fmt.Println(hFoo(&deps2)) + fmt.Println(hA(&deps2)) + + // Output: + // vero + // falso + // falso + // falso +} diff --git a/v2/samples/http/http_test.go b/v2/samples/http/http_test.go new file mode 100644 index 0000000..453bc29 --- /dev/null +++ b/v2/samples/http/http_test.go @@ -0,0 +1,113 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package http + +import ( + "context" + "fmt" + "testing" + + HTTP "net/http" + + A "github.com/IBM/fp-go/v2/array" + R "github.com/IBM/fp-go/v2/context/readerioeither" + H "github.com/IBM/fp-go/v2/context/readerioeither/http" + E "github.com/IBM/fp-go/v2/either" + F "github.com/IBM/fp-go/v2/function" + IO "github.com/IBM/fp-go/v2/io" + T "github.com/IBM/fp-go/v2/tuple" + "github.com/stretchr/testify/assert" +) + +type PostItem struct { + UserID uint `json:"userId"` + ID uint `json:"id"` + Title string `json:"title"` + Body string `json:"body"` +} + +type CatFact struct { + Fact string `json:"fact"` +} + +func idxToURL(idx int) string { + return fmt.Sprintf("https://jsonplaceholder.typicode.com/posts/%d", idx+1) +} + +// TestMultipleHttpRequests shows how to execute multiple HTTP requests in parallel assuming +// that the response structure of all requests is identical, which is why we can use [R.TraverseArray] +func TestMultipleHttpRequests(t *testing.T) { + // prepare the http client + client := H.MakeClient(HTTP.DefaultClient) + // readSinglePost sends a GET request and parses the response as [PostItem] + readSinglePost := H.ReadJSON[PostItem](client) + + // total number of http requests + count := 10 + + data := F.Pipe3( + A.MakeBy(count, idxToURL), + R.TraverseArray(F.Flow3( + H.MakeGetRequest, + readSinglePost, + R.ChainFirstIOK(IO.Logf[PostItem]("Log Single: %v")), + )), + R.ChainFirstIOK(IO.Logf[[]PostItem]("Log Result: %v")), + R.Map(A.Size[PostItem]), + ) + + result := data(context.Background()) + + assert.Equal(t, E.Of[error](count), result()) +} + +func heterogeneousHTTPRequests() ReaderIOEither[T.Tuple2[PostItem, CatFact]] { + // prepare the http client + client := H.MakeClient(HTTP.DefaultClient) + // readSinglePost sends a GET request and parses the response as [PostItem] + readSinglePost := H.ReadJSON[PostItem](client) + // readSingleCatFact sends a GET request and parses the response as [CatFact] + readSingleCatFact := H.ReadJSON[CatFact](client) + + return F.Pipe3( + T.MakeTuple2("https://jsonplaceholder.typicode.com/posts/1", "https://catfact.ninja/fact"), + T.Map2(H.MakeGetRequest, H.MakeGetRequest), + R.TraverseTuple2( + readSinglePost, + readSingleCatFact, + ), + R.ChainFirstIOK(IO.Logf[T.Tuple2[PostItem, CatFact]]("Log Result: %v")), + ) + +} + +// TestHeterogeneousHttpRequests shows how to execute multiple HTTP requests in parallel when +// the response structure of these requests is different. We use [R.TraverseTuple2] to account for the different types +func TestHeterogeneousHttpRequests(t *testing.T) { + data := heterogeneousHTTPRequests() + + result := data(context.Background()) + + fmt.Println(result()) +} + +// BenchmarkHeterogeneousHttpRequests shows how to execute multiple HTTP requests in parallel when +// the response structure of these requests is different. We use [R.TraverseTuple2] to account for the different types +func BenchmarkHeterogeneousHttpRequests(b *testing.B) { + for n := 0; n < b.N; n++ { + heterogeneousHTTPRequests()(context.Background())() + } +} diff --git a/v2/samples/http/types.go b/v2/samples/http/types.go new file mode 100644 index 0000000..6418963 --- /dev/null +++ b/v2/samples/http/types.go @@ -0,0 +1,24 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package http + +import ( + "github.com/IBM/fp-go/v2/context/readerioeither" +) + +type ( + ReaderIOEither[A any] = readerioeither.ReaderIOEither[A] +) diff --git a/v2/samples/match/examples_match_test.go b/v2/samples/match/examples_match_test.go new file mode 100644 index 0000000..6858247 --- /dev/null +++ b/v2/samples/match/examples_match_test.go @@ -0,0 +1,65 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package match + +import ( + "fmt" + + E "github.com/IBM/fp-go/v2/either" + "github.com/IBM/fp-go/v2/errors" + F "github.com/IBM/fp-go/v2/function" + O "github.com/IBM/fp-go/v2/option" + S "github.com/IBM/fp-go/v2/string" +) + +type Thing struct { + Name string +} + +func (t Thing) GetName() string { + return t.Name +} + +var ( + // func(Thing) Either[error, string] + getName = F.Flow2( + Thing.GetName, + E.FromPredicate(S.IsNonEmpty, errors.OnSome[string]("value [%s] is empty")), + ) + + // func(option.Option[Thing]) Either[error, string] + GetName = F.Flow2( + E.FromOption[Thing](errors.OnNone("value is none")), + E.Chain(getName), + ) +) + +func ExampleEither_match() { + + oThing := O.Of(Thing{"Carsten"}) + + res := F.Pipe2( + oThing, + GetName, + E.Fold(S.Format[error]("failed with error %v"), S.Format[string]("get value %s")), + ) + + fmt.Println(res) + + // Output: + // get value Carsten + +} diff --git a/v2/samples/mostly-adequate/README.md b/v2/samples/mostly-adequate/README.md new file mode 100644 index 0000000..caf7b84 --- /dev/null +++ b/v2/samples/mostly-adequate/README.md @@ -0,0 +1,6 @@ +# Mostly Adequate: fp-go Companion Guide + +This resource is meant to serve as a go "companion" resource to Professor [Frisby's Mostly Adequate Guide](https://github.com/MostlyAdequate/mostly-adequate-guide). + +It is a port of the [mostly-adequate-fp-ts](https://github.com/ChuckJonas/mostly-adequate-fp-ts/) book. + diff --git a/v2/samples/mostly-adequate/chapter01_test.go b/v2/samples/mostly-adequate/chapter01_test.go new file mode 100644 index 0000000..ae1da6a --- /dev/null +++ b/v2/samples/mostly-adequate/chapter01_test.go @@ -0,0 +1,47 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package mostlyadequate + +import "fmt" + +type Flock struct { + Seagulls int +} + +func MakeFlock(n int) Flock { + return Flock{Seagulls: n} +} + +func (f *Flock) Conjoin(other *Flock) *Flock { + f.Seagulls += other.Seagulls + return f +} + +func (f *Flock) Breed(other *Flock) *Flock { + f.Seagulls = f.Seagulls * other.Seagulls + return f +} + +func Example_flock() { + + flockA := MakeFlock(4) + flockB := MakeFlock(2) + flockC := MakeFlock(0) + + fmt.Println(flockA.Conjoin(&flockC).Breed(&flockB).Conjoin(flockA.Breed(&flockB)).Seagulls) + + // Output: 32 +} diff --git a/v2/samples/mostly-adequate/chapter02_firstclassfunctions_test.go b/v2/samples/mostly-adequate/chapter02_firstclassfunctions_test.go new file mode 100644 index 0000000..7d04ef2 --- /dev/null +++ b/v2/samples/mostly-adequate/chapter02_firstclassfunctions_test.go @@ -0,0 +1,38 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package mostlyadequate + +import "fmt" + +func Hi(name string) string { + return fmt.Sprintf("Hi %s", name) +} + +func Greeting(name string) string { + return Hi(name) +} + +func Example_greeting() { + // functions are first class objects + greet := Hi + + fmt.Println(Greeting("times")) + fmt.Println(greet("times")) + + // Output: + // Hi times + // Hi times +} diff --git a/v2/samples/mostly-adequate/chapter04_currying_test.go b/v2/samples/mostly-adequate/chapter04_currying_test.go new file mode 100644 index 0000000..57d5557 --- /dev/null +++ b/v2/samples/mostly-adequate/chapter04_currying_test.go @@ -0,0 +1,83 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package mostlyadequate + +import ( + "fmt" + "math" + "regexp" + "strings" + + A "github.com/IBM/fp-go/v2/array" + F "github.com/IBM/fp-go/v2/function" + N "github.com/IBM/fp-go/v2/number" + I "github.com/IBM/fp-go/v2/number/integer" + S "github.com/IBM/fp-go/v2/string" +) + +var ( + Match = F.Curry2((*regexp.Regexp).FindStringSubmatch) + Matches = F.Curry2((*regexp.Regexp).MatchString) + Split = F.Curry2(F.Bind3of3((*regexp.Regexp).Split)(-1)) + + Add = N.Add[int] + ToString = I.ToString + ToLower = strings.ToLower + ToUpper = strings.ToUpper + Concat = F.Curry2(S.Monoid.Concat) +) + +// Replace cannot be generated via [F.Curry3] because the order of parameters does not match our desired curried order +func Replace(search *regexp.Regexp) func(replace string) func(s string) string { + return func(replace string) func(s string) string { + return func(s string) string { + return search.ReplaceAllString(s, replace) + } + } +} + +func Example_solution04A() { + // words :: String -> [String] + words := Split(regexp.MustCompile(` `)) + + fmt.Println(words("Jingle bells Batman smells")) + + // Output: + // [Jingle bells Batman smells] +} + +func Example_solution04B() { + // filterQs :: [String] -> [String] + filterQs := A.Filter(Matches(regexp.MustCompile(`q`))) + + fmt.Println(filterQs(A.From("quick", "camels", "quarry", "over", "quails"))) + + // Output: + // [quick quarry quails] +} + +func Example_solution04C() { + + keepHighest := N.Max[int] + + // max :: [Number] -> Number + max := A.Reduce(keepHighest, math.MinInt) + + fmt.Println(max(A.From(323, 523, 554, 123, 5234))) + + // Output: + // 5234 +} diff --git a/v2/samples/mostly-adequate/chapter05_composing_test.go b/v2/samples/mostly-adequate/chapter05_composing_test.go new file mode 100644 index 0000000..61837d2 --- /dev/null +++ b/v2/samples/mostly-adequate/chapter05_composing_test.go @@ -0,0 +1,110 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package mostlyadequate + +import ( + "fmt" + "regexp" + + A "github.com/IBM/fp-go/v2/array" + F "github.com/IBM/fp-go/v2/function" + I "github.com/IBM/fp-go/v2/number/integer" + O "github.com/IBM/fp-go/v2/option" + "github.com/IBM/fp-go/v2/ord" + S "github.com/IBM/fp-go/v2/string" +) + +var ( + Exclaim = S.Format[string]("%s!") + Shout = F.Flow2(ToUpper, Exclaim) + Dasherize = F.Flow4( + Replace(regexp.MustCompile(`\s{2,}`))(" "), + Split(regexp.MustCompile(` `)), + A.Map(ToLower), + A.Intercalate(S.Monoid)("-"), + ) +) + +func Example_shout() { + fmt.Println(Shout("send in the clowns")) + + // Output: SEND IN THE CLOWNS! +} + +func Example_dasherize() { + fmt.Println(Dasherize("The world is a vampire")) + + // Output: the-world-is-a-vampire +} + +func Example_pipe() { + output := F.Pipe2( + "send in the clowns", + ToUpper, + Exclaim, + ) + + fmt.Println(output) + + // Output: SEND IN THE CLOWNS! +} + +func Example_solution05A() { + IsLastInStock := F.Flow2( + A.Last[Car], + O.Map(Car.getInStock), + ) + + fmt.Println(IsLastInStock(Cars[0:3])) + fmt.Println(IsLastInStock(Cars[3:])) + + // Output: + // Some[bool](true) + // Some[bool](false) +} + +func Example_solution05B() { + // averageDollarValue :: [Car] -> Int + averageDollarValue := F.Flow2( + A.Map(Car.getDollarValue), + average, + ) + + fmt.Println(averageDollarValue(Cars)) + + // Output: + // 790700 +} + +func Example_solution05C() { + // order by horsepower + ordByHorsepower := ord.Contramap(Car.getHorsepower)(I.Ord) + + // fastestCar :: [Car] -> Option[String] + fastestCar := F.Flow3( + A.Sort(ordByHorsepower), + A.Last[Car], + O.Map(F.Flow2( + Car.getName, + S.Format[string]("%s is the fastest"), + )), + ) + + fmt.Println(fastestCar(Cars)) + + // Output: + // Some[string](Aston Martin One-77 is the fastest) +} diff --git a/v2/samples/mostly-adequate/chapter06_exampleapplication_test.go b/v2/samples/mostly-adequate/chapter06_exampleapplication_test.go new file mode 100644 index 0000000..713c1f9 --- /dev/null +++ b/v2/samples/mostly-adequate/chapter06_exampleapplication_test.go @@ -0,0 +1,115 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package mostlyadequate + +import ( + "context" + "fmt" + "net/http" + "regexp" + + A "github.com/IBM/fp-go/v2/array" + E "github.com/IBM/fp-go/v2/either" + F "github.com/IBM/fp-go/v2/function" + J "github.com/IBM/fp-go/v2/json" + S "github.com/IBM/fp-go/v2/string" + + R "github.com/IBM/fp-go/v2/context/readerioeither" + H "github.com/IBM/fp-go/v2/context/readerioeither/http" +) + +type ( + FlickrMedia struct { + Link string `json:"m"` + } + + FlickrItem struct { + Media FlickrMedia `json:"media"` + } + + FlickrFeed struct { + Items []FlickrItem `json:"items"` + } +) + +func (f FlickrMedia) getLink() string { + return f.Link +} + +func (f FlickrItem) getMedia() FlickrMedia { + return f.Media +} + +func (f FlickrFeed) getItems() []FlickrItem { + return f.Items +} + +func Example_application() { + // pure + host := "api.flickr.com" + path := "/services/feeds/photos_public.gne" + query := S.Format[string]("?tags=%s&format=json&jsoncallback=?") + url := F.Flow2( + query, + S.Format[string](fmt.Sprintf("https://%s%s%%s", host, path)), + ) + // flick returns jsonP, we extract the JSON body, this is handled by jquery in the original code + sanitizeJSONP := Replace(regexp.MustCompile(`(?s)^\s*\((.*)\)\s*$`))("$1") + // parse jsonP + parseJSONP := F.Flow3( + sanitizeJSONP, + S.ToBytes, + J.Unmarshal[FlickrFeed], + ) + // markup + img := S.Format[string]("") + // lenses + mediaURL := F.Flow2( + FlickrItem.getMedia, + FlickrMedia.getLink, + ) + mediaURLs := F.Flow2( + FlickrFeed.getItems, + A.Map(mediaURL), + ) + images := F.Flow2( + mediaURLs, + A.Map(img), + ) + + client := H.MakeClient(http.DefaultClient) + + // func(string) R.ReaderIOEither[[]string] + app := F.Flow5( + url, + H.MakeGetRequest, + H.ReadText(client), + R.ChainEitherK(parseJSONP), + R.Map(images), + ) + + // R.ReaderIOEither[[]string] + // this is the managed effect that can be called to download and render the images + catImageEffect := app("cats") + + // impure, actually executes the effect + catImages := catImageEffect(context.TODO())() + fmt.Println(E.IsRight(catImages)) + + // Output: + // true + +} diff --git a/v2/samples/mostly-adequate/chapter08_tupperware_test.go b/v2/samples/mostly-adequate/chapter08_tupperware_test.go new file mode 100644 index 0000000..0ef9c31 --- /dev/null +++ b/v2/samples/mostly-adequate/chapter08_tupperware_test.go @@ -0,0 +1,276 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package mostlyadequate + +import ( + "fmt" + "time" + + A "github.com/IBM/fp-go/v2/array" + E "github.com/IBM/fp-go/v2/either" + "github.com/IBM/fp-go/v2/errors" + F "github.com/IBM/fp-go/v2/function" + I "github.com/IBM/fp-go/v2/identity" + IOE "github.com/IBM/fp-go/v2/ioeither" + N "github.com/IBM/fp-go/v2/number" + O "github.com/IBM/fp-go/v2/option" + "github.com/IBM/fp-go/v2/ord" + S "github.com/IBM/fp-go/v2/string" +) + +type Account struct { + Balance float32 +} + +func MakeAccount(b float32) Account { + return Account{Balance: b} +} + +func getBalance(a Account) float32 { + return a.Balance +} + +type ( + Chapter08User struct { + Id int + Name string + Active bool + Saved bool + } +) + +var ( + albert08 = Chapter08User{ + Id: 1, + Active: true, + Name: "Albert", + } + + gary08 = Chapter08User{ + Id: 2, + Active: false, + Name: "Gary", + } + + theresa08 = Chapter08User{ + Id: 3, + Active: true, + Name: "Theresa", + } + + yi08 = Chapter08User{Id: 4, Name: "Yi", Active: true} +) + +func (u Chapter08User) getName() string { + return u.Name +} + +func (u Chapter08User) isActive() bool { + return u.Active +} + +var ( + ordFloat32 = ord.FromStrictCompare[float32]() + UpdateLedger = F.Identity[Account] + RemainingBalance = F.Flow2( + getBalance, + S.Format[float32]("Your balance is $%0.2f"), + ) + FinishTransaction = F.Flow2( + UpdateLedger, + RemainingBalance, + ) + getTwenty = F.Flow2( + Withdraw(20), + O.Fold(F.Constant("You're broke!"), FinishTransaction), + ) + + // showWelcome :: User -> String + showWelcome = F.Flow2( + Chapter08User.getName, + S.Format[string]("Welcome %s"), + ) + + // checkActive :: User -> Either error User + checkActive = E.FromPredicate(Chapter08User.isActive, F.Constant1[Chapter08User](fmt.Errorf("your account is not active"))) + + // validateUser :: (User -> Either String ()) -> User -> Either String User + validateUser = F.Curry2(func(validate func(Chapter08User) Either[any], user Chapter08User) Either[Chapter08User] { + return F.Pipe2( + user, + validate, + E.MapTo[error, any](user), + ) + }) + + // save :: User -> IOEither error User + save = func(user Chapter08User) IOEither[Chapter08User] { + return IOE.FromIO[error](func() Chapter08User { + var u = user + u.Saved = true + return u + }) + } +) + +func Withdraw(amount float32) func(account Account) Option[Account] { + + return F.Flow3( + getBalance, + O.FromPredicate(ord.Geq(ordFloat32)(amount)), + O.Map(F.Flow2( + N.Add(-amount), + MakeAccount, + ))) +} + +type User struct { + BirthDate string +} + +func getBirthDate(u User) string { + return u.BirthDate +} + +func MakeUser(d string) User { + return User{BirthDate: d} +} + +var parseDate = F.Bind1of2(E.Eitherize2(time.Parse))(time.DateOnly) + +func GetAge(now time.Time) func(User) Either[float64] { + return F.Flow3( + getBirthDate, + parseDate, + E.Map[error](F.Flow3( + now.Sub, + time.Duration.Hours, + N.Mul(1/24.0), + )), + ) +} + +func Example_widthdraw() { + fmt.Println(getTwenty(MakeAccount(200))) + fmt.Println(getTwenty(MakeAccount(10))) + + // Output: + // Your balance is $180.00 + // You're broke! +} + +func Example_getAge() { + now, err := time.Parse(time.DateOnly, "2023-09-01") + if err != nil { + panic(err) + } + + fmt.Println(GetAge(now)(MakeUser("2005-12-12"))) + fmt.Println(GetAge(now)(MakeUser("July 4, 2001"))) + + fortune := F.Flow3( + N.Add(365.0), + S.Format[float64]("%0.0f"), + Concat("If you survive, you will be "), + ) + + zoltar := F.Flow3( + GetAge(now), + E.Map[error](fortune), + E.GetOrElse(errors.ToString), + ) + + fmt.Println(zoltar(MakeUser("2005-12-12"))) + + // Output: + // Right[float64](6472) + // Left[*time.ParseError](parsing time "July 4, 2001" as "2006-01-02": cannot parse "July 4, 2001" as "2006") + // If you survive, you will be 6837 +} + +func Example_solution08A() { + incrF := I.Map(N.Add(1)) + + fmt.Println(incrF(I.Of(2))) + + // Output: 3 +} + +func Example_solution08B() { + // initial :: User -> Option rune + initial := F.Flow3( + Chapter08User.getName, + S.ToRunes, + A.Head[rune], + ) + + fmt.Println(initial(albert08)) + + // Output: + // Some[int32](65) +} + +func Example_solution08C() { + + // eitherWelcome :: User -> Either String String + eitherWelcome := F.Flow2( + checkActive, + E.Map[error](showWelcome), + ) + + fmt.Println(eitherWelcome(gary08)) + fmt.Println(eitherWelcome(theresa08)) + + // Output: + // Left[*errors.errorString](your account is not active) + // Right[string](Welcome Theresa) +} + +func Example_solution08D() { + + // // validateName :: User -> Either String () + validateName := F.Flow3( + Chapter08User.getName, + E.FromPredicate(F.Flow2( + S.Size, + ord.Gt(ord.FromStrictCompare[int]())(3), + ), errors.OnSome[string]("Your name %s is larger than 3 characters")), + E.Map[error](F.ToAny[string]), + ) + + saveAndWelcome := F.Flow2( + save, + IOE.Map[error](showWelcome), + ) + + register := F.Flow3( + validateUser(validateName), + IOE.FromEither[error, Chapter08User], + IOE.Chain(saveAndWelcome), + ) + + fmt.Println(validateName(gary08)) + fmt.Println(validateName(yi08)) + + fmt.Println(register(albert08)()) + fmt.Println(register(yi08)()) + + // Output: + // Right[string](Gary) + // Left[*errors.errorString](Your name Yi is larger than 3 characters) + // Right[string](Welcome Albert) + // Left[*errors.errorString](Your name Yi is larger than 3 characters) +} diff --git a/v2/samples/mostly-adequate/chapter09_monadiconions_test.go b/v2/samples/mostly-adequate/chapter09_monadiconions_test.go new file mode 100644 index 0000000..ab78afa --- /dev/null +++ b/v2/samples/mostly-adequate/chapter09_monadiconions_test.go @@ -0,0 +1,186 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package mostlyadequate + +import ( + "fmt" + "path" + "regexp" + + A "github.com/IBM/fp-go/v2/array" + E "github.com/IBM/fp-go/v2/either" + "github.com/IBM/fp-go/v2/errors" + F "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/io" + IOE "github.com/IBM/fp-go/v2/ioeither" + O "github.com/IBM/fp-go/v2/option" + S "github.com/IBM/fp-go/v2/string" +) + +type ( + Street struct { + Name string + Number int + } + + Address struct { + Street Street + Postcode string + } + + AddressBook struct { + Addresses []Address + } + + Chapter09User struct { + Id int + Name string + Address Address + } +) + +var ( + albert09 = Chapter09User{ + Id: 1, + Name: "Albert", + Address: Address{ + Street: Street{ + Number: 22, + Name: "Walnut St", + }, + }, + } + + gary09 = Chapter09User{ + Id: 2, + Name: "Gary", + Address: Address{ + Street: Street{ + Number: 14, + }, + }, + } + + theresa09 = Chapter09User{ + Id: 3, + Name: "Theresa", + } +) + +func (ab AddressBook) getAddresses() []Address { + return ab.Addresses +} + +func (s Address) getStreet() Street { + return s.Street +} + +func (s Street) getName() string { + return s.Name +} + +func (u Chapter09User) getAddress() Address { + return u.Address +} + +var ( + FirstAddressStreet = F.Flow3( + AddressBook.getAddresses, + A.Head[Address], + O.Map(Address.getStreet), + ) + + // getFile :: IO String + getFile = io.Of("/home/mostly-adequate/ch09.md") + + // pureLog :: String -> IO () + pureLog = io.Logf[string]("%s") + + // addToMailingList :: Email -> IOEither([Email]) + addToMailingList = F.Flow2( + A.Of[string], + IOE.Of[error, []string], + ) + + // validateEmail :: Email -> Either error Email + validateEmail = E.FromPredicate(Matches(regexp.MustCompile(`\S+@\S+\.\S+`)), errors.OnSome[string]("email %s is invalid")) + + // emailBlast :: [Email] -> IO () + emailBlast = F.Flow2( + A.Intercalate(S.Monoid)(","), + IOE.Of[error, string], + ) +) + +func Example_street() { + s := FirstAddressStreet(AddressBook{ + Addresses: A.From(Address{Street: Street{Name: "Mulburry", Number: 8402}, Postcode: "WC2N"}), + }) + fmt.Println(s) + + // Output: + // Some[mostlyadequate.Street]({Mulburry 8402}) +} + +func Example_solution09A() { + // // getStreetName :: User -> Maybe String + getStreetName := F.Flow4( + Chapter09User.getAddress, + Address.getStreet, + Street.getName, + O.FromPredicate(S.IsNonEmpty), + ) + + fmt.Println(getStreetName(albert09)) + fmt.Println(getStreetName(gary09)) + fmt.Println(getStreetName(theresa09)) + + // Output: + // Some[string](Walnut St) + // None[string] + // None[string] + +} + +func Example_solution09B() { + logFilename := F.Flow2( + io.Map(path.Base), + io.ChainFirst(pureLog), + ) + + fmt.Println(logFilename(getFile)()) + + // Output: + // ch09.md +} + +func Example_solution09C() { + + // // joinMailingList :: Email -> Either String (IO ()) + joinMailingList := F.Flow4( + validateEmail, + IOE.FromEither[error, string], + IOE.Chain(addToMailingList), + IOE.Chain(emailBlast), + ) + + fmt.Println(joinMailingList("sleepy@grandpa.net")()) + fmt.Println(joinMailingList("notanemail")()) + + // Output: + // Right[string](sleepy@grandpa.net) + // Left[*errors.errorString](email notanemail is invalid) +} diff --git a/v2/samples/mostly-adequate/chapter10_applicativefunctor_test.go b/v2/samples/mostly-adequate/chapter10_applicativefunctor_test.go new file mode 100644 index 0000000..80aceab --- /dev/null +++ b/v2/samples/mostly-adequate/chapter10_applicativefunctor_test.go @@ -0,0 +1,183 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package mostlyadequate + +import ( + "context" + "fmt" + "net/http" + + R "github.com/IBM/fp-go/v2/context/readerioeither" + H "github.com/IBM/fp-go/v2/context/readerioeither/http" + F "github.com/IBM/fp-go/v2/function" + IOO "github.com/IBM/fp-go/v2/iooption" + N "github.com/IBM/fp-go/v2/number" + O "github.com/IBM/fp-go/v2/option" + M "github.com/IBM/fp-go/v2/record" + T "github.com/IBM/fp-go/v2/tuple" +) + +type ( + PostItem struct { + UserID uint `json:"userId"` + Id uint `json:"id"` + Title string `json:"title"` + Body string `json:"body"` + } + + Player struct { + Id int + Name string + } + + LocalStorage = map[string]Player +) + +var ( + playerAlbert = Player{ + Id: 1, + Name: "Albert", + } + playerTheresa = Player{ + Id: 2, + Name: "Theresa", + } + localStorage = LocalStorage{ + "player1": playerAlbert, + "player2": playerTheresa, + } + + // getFromCache :: String -> IO User + getFromCache = func(name string) IOOption[Player] { + return func() Option[Player] { + return M.MonadLookup(localStorage, name) + } + } + + // game :: User -> User -> String + game = F.Curry2(func(a, b Player) string { + return fmt.Sprintf("%s vs %s", a.Name, b.Name) + }) +) + +func (player Player) getName() string { + return player.Name +} + +func (player Player) getID() int { + return player.Id +} + +func (item PostItem) getTitle() string { + return item.Title +} + +func idxToURL(idx int) string { + return fmt.Sprintf("https://jsonplaceholder.typicode.com/posts/%d", idx+1) +} + +func renderString(destinations string) func(string) string { + return func(events string) string { + return fmt.Sprintf("
Destinations: [%s], Events: [%s]
", destinations, events) + } +} + +func Example_renderPage() { + // prepare the http client + client := H.MakeClient(http.DefaultClient) + + // get returns the title of the nth item from the REST service + get := F.Flow4( + idxToURL, + H.MakeGetRequest, + H.ReadJSON[PostItem](client), + R.Map(PostItem.getTitle), + ) + + res := F.Pipe2( + R.Of(renderString), // start with a function with 2 unresolved arguments + R.Ap[func(string) string](get(1)), // resolve the first argument + R.Ap[string](get(2)), // in parallel resolve the second argument + ) + + // finally invoke in context and start + fmt.Println(res(context.TODO())()) + + // Output: + // Right[string](
Destinations: [qui est esse], Events: [ea molestias quasi exercitationem repellat qui ipsa sit aut]
) + +} + +func Example_solution10A() { + safeAdd := F.Curry2(func(a, b Option[int]) Option[int] { + return F.Pipe3( + N.Add[int], + O.Of[func(int) func(int) int], + O.Ap[func(int) int](a), + O.Ap[int](b), + ) + }) + + fmt.Println(safeAdd(O.Of(2))(O.Of(3))) + fmt.Println(safeAdd(O.None[int]())(O.Of(3))) + fmt.Println(safeAdd(O.Of(2))(O.None[int]())) + + // Output: + // Some[int](5) + // None[int] + // None[int] +} + +func Example_solution10B() { + + safeAdd := F.Curry2(T.Untupled2(F.Flow2( + O.SequenceTuple2[int, int], + O.Map(T.Tupled2(N.MonoidSum[int]().Concat)), + ))) + + fmt.Println(safeAdd(O.Of(2))(O.Of(3))) + fmt.Println(safeAdd(O.None[int]())(O.Of(3))) + fmt.Println(safeAdd(O.Of(2))(O.None[int]())) + + // Output: + // Some[int](5) + // None[int] + // None[int] +} + +func Example_solution10C() { + // startGame :: IO String + startGame := F.Pipe2( + IOO.Of(game), + IOO.Ap[func(Player) string](getFromCache("player1")), + IOO.Ap[string](getFromCache("player2")), + ) + + startGameTupled := F.Pipe2( + T.MakeTuple2("player1", "player2"), + IOO.TraverseTuple2(getFromCache, getFromCache), + IOO.Map(T.Tupled2(func(a, b Player) string { + return fmt.Sprintf("%s vs %s", a.Name, b.Name) + })), + ) + + fmt.Println(startGame()) + fmt.Println(startGameTupled()) + + // Output: + // Some[string](Albert vs Theresa) + // Some[string](Albert vs Theresa) +} diff --git a/v2/samples/mostly-adequate/chapter11_transformagain_test.go b/v2/samples/mostly-adequate/chapter11_transformagain_test.go new file mode 100644 index 0000000..22d5fbb --- /dev/null +++ b/v2/samples/mostly-adequate/chapter11_transformagain_test.go @@ -0,0 +1,89 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package mostlyadequate + +import ( + "fmt" + "regexp" + + A "github.com/IBM/fp-go/v2/array" + E "github.com/IBM/fp-go/v2/either" + F "github.com/IBM/fp-go/v2/function" + IOE "github.com/IBM/fp-go/v2/ioeither" + S "github.com/IBM/fp-go/v2/string" +) + +func findUserByID(id int) IOEither[Chapter08User] { + switch id { + case 1: + return IOE.Of[error](albert08) + case 2: + return IOE.Of[error](gary08) + case 3: + return IOE.Of[error](theresa08) + default: + return IOE.Left[Chapter08User](fmt.Errorf("user %d not found", id)) + } +} + +func Example_solution11A() { + // eitherToMaybe :: Either b a -> Maybe a + eitherToMaybe := E.ToOption[error, string] + + fmt.Println(eitherToMaybe(E.Of[error]("one eyed willy"))) + fmt.Println(eitherToMaybe(E.Left[string](fmt.Errorf("some error")))) + + // Output: + // Some[string](one eyed willy) + // None[string] +} + +func Example_solution11B() { + findByNameID := F.Flow2( + findUserByID, + IOE.Map[error](Chapter08User.getName), + ) + + fmt.Println(findByNameID(1)()) + fmt.Println(findByNameID(2)()) + fmt.Println(findByNameID(3)()) + fmt.Println(findByNameID(4)()) + + // Output: + // Right[string](Albert) + // Right[string](Gary) + // Right[string](Theresa) + // Left[*errors.errorString](user 4 not found) +} + +func Example_solution11C() { + // strToList :: String -> [Char + strToList := Split(regexp.MustCompile(``)) + + // listToStr :: [Char] -> String + listToStr := A.Intercalate(S.Monoid)("") + + sortLetters := F.Flow3( + strToList, + A.Sort(S.Ord), + listToStr, + ) + + fmt.Println(sortLetters("sortme")) + + // Output: + // emorst +} diff --git a/v2/samples/mostly-adequate/chapter12_traversing_test.go b/v2/samples/mostly-adequate/chapter12_traversing_test.go new file mode 100644 index 0000000..2c57dee --- /dev/null +++ b/v2/samples/mostly-adequate/chapter12_traversing_test.go @@ -0,0 +1,98 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package mostlyadequate + +import ( + "fmt" + + A "github.com/IBM/fp-go/v2/array" + E "github.com/IBM/fp-go/v2/either" + "github.com/IBM/fp-go/v2/errors" + F "github.com/IBM/fp-go/v2/function" + IOE "github.com/IBM/fp-go/v2/ioeither" + O "github.com/IBM/fp-go/v2/option" + P "github.com/IBM/fp-go/v2/predicate" + S "github.com/IBM/fp-go/v2/string" +) + +var ( + // httpGet :: Route -> Task Error JSON + httpGet = F.Flow2( + S.Format[string]("json for %s"), + IOE.Of[error, string], + ) + + // routes :: Map Route Route + routes = map[string]string{ + "/": "/", + "/about": "/about", + } + + // validate :: Player -> Either error Player + validatePlayer = E.FromPredicate(P.ContraMap(Player.getName)(S.IsNonEmpty), F.Flow2(Player.getID, errors.OnSome[int]("player %d must have a name"))) + + // readfile :: String -> String -> Task Error String + readfile = F.Curry2(func(encoding, file string) IOEither[string] { + return IOE.Of[error](fmt.Sprintf("content of %s (%s)", file, encoding)) + }) + + // readdir :: String -> Task Error [String] + readdir = IOE.Of[error](A.From("file1", "file2", "file3")) +) + +func Example_solution12A() { + // getJsons :: Map Route Route -> Task Error (Map Route JSON) + getJsons := IOE.TraverseRecord[string](httpGet) + + fmt.Println(getJsons(routes)()) + + // Output: + // Right[map[string]string](map[/:json for / /about:json for /about]) +} + +func Example_solution12B() { + // startGame :: [Player] -> [Either Error String] + startGame := F.Flow2( + E.TraverseArray(validatePlayer), + E.MapTo[error, []Player]("Game started"), + ) + + fmt.Println(startGame(A.From(playerAlbert, playerTheresa))) + fmt.Println(startGame(A.From(playerAlbert, Player{Id: 4}))) + + // Output: + // Right[string](Game started) + // Left[*errors.errorString](player 4 must have a name) +} + +func Example_solution12C() { + traverseO := O.Traverse[string]( + IOE.Of[error, Option[string]], + IOE.Map[error, string, Option[string]], + ) + + // readFirst :: String -> Task Error (Maybe String) + readFirst := F.Pipe2( + readdir, + IOE.Map[error](A.Head[string]), + IOE.Chain(traverseO(readfile("utf-8"))), + ) + + fmt.Println(readFirst()) + + // Output: + // Right[option.Option[string]](Some[string](content of file1 (utf-8))) +} diff --git a/v2/samples/mostly-adequate/doc_test.go b/v2/samples/mostly-adequate/doc_test.go new file mode 100644 index 0000000..963a2eb --- /dev/null +++ b/v2/samples/mostly-adequate/doc_test.go @@ -0,0 +1,19 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +// Package mostlyadequate is meant to serve as a go "companion" resource to Professor [Frisby's Mostly Adequate Guide]. +// +// [Frisby's Mostly Adequate Guide]: https://github.com/MostlyAdequate/mostly-adequate-guide +package mostlyadequate diff --git a/v2/samples/mostly-adequate/support_test.go b/v2/samples/mostly-adequate/support_test.go new file mode 100644 index 0000000..45e4bd4 --- /dev/null +++ b/v2/samples/mostly-adequate/support_test.go @@ -0,0 +1,89 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package mostlyadequate + +import ( + A "github.com/IBM/fp-go/v2/array" + F "github.com/IBM/fp-go/v2/function" + N "github.com/IBM/fp-go/v2/number" +) + +type ( + Car struct { + Name string + Horsepower int + DollarValue float32 + InStock bool + } +) + +func (car Car) getInStock() bool { + return car.InStock +} + +func (car Car) getDollarValue() float32 { + return car.DollarValue +} + +func (car Car) getHorsepower() int { + return car.Horsepower +} + +func (car Car) getName() string { + return car.Name +} + +func average(val []float32) float32 { + return F.Pipe2( + val, + A.Fold(N.MonoidSum[float32]()), + N.Div(float32(len(val))), + ) +} + +var ( + Cars = A.From(Car{ + Name: "Ferrari FF", + Horsepower: 660, + DollarValue: 700000, + InStock: true, + }, Car{ + Name: "Spyker C12 Zagato", + Horsepower: 650, + DollarValue: 648000, + InStock: false, + }, Car{ + Name: "Jaguar XKR-S", + Horsepower: 550, + DollarValue: 132000, + InStock: true, + }, Car{ + Name: "Audi R8", + Horsepower: 525, + DollarValue: 114200, + InStock: false, + }, Car{ + Name: "Aston Martin One-77", + Horsepower: 750, + DollarValue: 1850000, + InStock: true, + }, Car{ + Name: "Pagani Huayra", + Horsepower: 700, + DollarValue: 1300000, + InStock: false, + }) +) diff --git a/v2/samples/mostly-adequate/types.go b/v2/samples/mostly-adequate/types.go new file mode 100644 index 0000000..dba8589 --- /dev/null +++ b/v2/samples/mostly-adequate/types.go @@ -0,0 +1,30 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package mostlyadequate + +import ( + "github.com/IBM/fp-go/v2/either" + "github.com/IBM/fp-go/v2/ioeither" + "github.com/IBM/fp-go/v2/iooption" + "github.com/IBM/fp-go/v2/option" +) + +type ( + Either[A any] = either.Either[error, A] + IOEither[A any] = ioeither.IOEither[error, A] + IOOption[A any] = iooption.IOOption[A] + Option[A any] = option.Option[A] +) diff --git a/v2/samples/presentation/.gitignore b/v2/samples/presentation/.gitignore new file mode 100644 index 0000000..10f7209 --- /dev/null +++ b/v2/samples/presentation/.gitignore @@ -0,0 +1 @@ +~$* \ No newline at end of file diff --git a/v2/samples/presentation/benchmarks/http_test.go b/v2/samples/presentation/benchmarks/http_test.go new file mode 100644 index 0000000..fd08d3a --- /dev/null +++ b/v2/samples/presentation/benchmarks/http_test.go @@ -0,0 +1,123 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package benchmarks + +import ( + "context" + "encoding/json" + "io" + "testing" + + HTTP "net/http" + + A "github.com/IBM/fp-go/v2/array" + R "github.com/IBM/fp-go/v2/context/readerioeither" + H "github.com/IBM/fp-go/v2/context/readerioeither/http" + F "github.com/IBM/fp-go/v2/function" + T "github.com/IBM/fp-go/v2/tuple" +) + +type PostItem struct { + UserID uint `json:"userId"` + Id uint `json:"id"` + Title string `json:"title"` + Body string `json:"body"` +} + +type CatFact struct { + Fact string `json:"fact"` +} + +func heterogeneousHTTPRequests(count int) R.ReaderIOEither[[]T.Tuple2[PostItem, CatFact]] { + // prepare the http client + client := H.MakeClient(HTTP.DefaultClient) + // readSinglePost sends a GET request and parses the response as [PostItem] + readSinglePost := H.ReadJSON[PostItem](client) + // readSingleCatFact sends a GET request and parses the response as [CatFact] + readSingleCatFact := H.ReadJSON[CatFact](client) + + single := F.Pipe2( + T.MakeTuple2("https://jsonplaceholder.typicode.com/posts/1", "https://catfact.ninja/fact"), + T.Map2(H.MakeGetRequest, H.MakeGetRequest), + R.TraverseTuple2( + readSinglePost, + readSingleCatFact, + ), + ) + + return F.Pipe1( + A.Replicate(count, single), + R.SequenceArray[T.Tuple2[PostItem, CatFact]], + ) +} + +func heterogeneousHTTPRequestsIdiomatic(count int) ([]T.Tuple2[PostItem, CatFact], error) { + // prepare the http client + var result []T.Tuple2[PostItem, CatFact] + + for i := 0; i < count; i++ { + resp, err := HTTP.Get("https://jsonplaceholder.typicode.com/posts/1") + if err != nil { + return nil, err + } + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + var item PostItem + err = json.Unmarshal(body, &item) + if err != nil { + return nil, err + } + resp, err = HTTP.Get("https://catfact.ninja/fact") + if err != nil { + return nil, err + } + body, err = io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + var fact CatFact + err = json.Unmarshal(body, &item) + if err != nil { + return nil, err + } + result = append(result, T.MakeTuple2(item, fact)) + } + return result, nil +} + +// BenchmarkHeterogeneousHttpRequests shows how to execute multiple HTTP requests in parallel when +// the response structure of these requests is different. We use [R.TraverseTuple2] to account for the different types +func BenchmarkHeterogeneousHttpRequests(b *testing.B) { + + count := 100 + var benchResults any + + b.Run("functional", func(b *testing.B) { + for n := 0; n < b.N; n++ { + benchResults = heterogeneousHTTPRequests(count)(context.Background())() + } + }) + + b.Run("idiomatic", func(b *testing.B) { + for n := 0; n < b.N; n++ { + benchResults, _ = heterogeneousHTTPRequestsIdiomatic(count) + } + }) + + globalResult = benchResults +} diff --git a/v2/samples/presentation/benchmarks/map_test.go b/v2/samples/presentation/benchmarks/map_test.go new file mode 100644 index 0000000..0234ab9 --- /dev/null +++ b/v2/samples/presentation/benchmarks/map_test.go @@ -0,0 +1,177 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package benchmarks + +import ( + "math/big" + "strings" + "testing" + + A "github.com/IBM/fp-go/v2/array" + F "github.com/IBM/fp-go/v2/function" + N "github.com/IBM/fp-go/v2/number" + O "github.com/IBM/fp-go/v2/option" +) + +var ( + createStringSet = createRandom(createRandomString(256))(256) + createIntDataSet = createRandom(randInt(10000))(256) + + globalResult any +) + +func BenchmarkMap(b *testing.B) { + + data := createStringSet() + + var benchResult []string + + b.Run("functional", func(b *testing.B) { + for n := 0; n < b.N; n++ { + benchResult = F.Pipe1( + data, + A.Map(strings.ToUpper), + ) + } + }) + + b.Run("idiomatic", func(b *testing.B) { + for n := 0; n < b.N; n++ { + var result = make([]string, 0, len(data)) + for _, value := range data { + result = append(result, strings.ToUpper(value)) + } + benchResult = result + } + }) + + globalResult = benchResult +} + +func isEven(data int) bool { + return data%2 == 0 +} + +func isPrime(data int) bool { + return big.NewInt(int64(data)).ProbablyPrime(0) +} + +func BenchmarkMapThenFilter(b *testing.B) { + + data := createIntDataSet() + var benchResult []int + + b.Run("functional isPrime", func(b *testing.B) { + for n := 0; n < b.N; n++ { + benchResult = F.Pipe2( + data, + A.Filter(isPrime), + A.Map(N.Div[int](2)), + ) + } + }) + + b.Run("idiomatic isPrime", func(b *testing.B) { + for n := 0; n < b.N; n++ { + var result []int + for _, value := range data { + if isPrime(value) { + result = append(result, value/2) + } + } + benchResult = result + } + }) + b.Run("functional isEven", func(b *testing.B) { + for n := 0; n < b.N; n++ { + benchResult = F.Pipe2( + data, + A.Filter(isEven), + A.Map(N.Div[int](2)), + ) + } + }) + + b.Run("idiomatic isEven", func(b *testing.B) { + for n := 0; n < b.N; n++ { + var result []int + for _, value := range data { + if isEven(value) { + result = append(result, value/2) + } + } + benchResult = result + } + }) + + globalResult = benchResult +} + +func BenchmarkFilterMap(b *testing.B) { + + data := createIntDataSet() + var benchResult []int + + b.Run("functional isPrime", func(b *testing.B) { + for n := 0; n < b.N; n++ { + benchResult = F.Pipe1( + data, + A.FilterMap(F.Flow2( + O.FromPredicate(isPrime), + O.Map(N.Div[int](2)), + )), + ) + } + }) + + b.Run("idiomatic isPrime", func(b *testing.B) { + for n := 0; n < b.N; n++ { + var result []int + for _, value := range data { + if isPrime(value) { + result = append(result, value/2) + } + } + benchResult = result + } + }) + + b.Run("functional isEven", func(b *testing.B) { + for n := 0; n < b.N; n++ { + benchResult = F.Pipe1( + data, + A.FilterMap(F.Flow2( + O.FromPredicate(isEven), + O.Map(N.Div[int](2)), + )), + ) + } + }) + + b.Run("idiomatic isEven", func(b *testing.B) { + for n := 0; n < b.N; n++ { + var result []int + for _, value := range data { + if isEven(value) { + result = append(result, value/2) + } + } + benchResult = result + } + }) + + globalResult = benchResult +} diff --git a/v2/samples/presentation/benchmarks/utils_test.go b/v2/samples/presentation/benchmarks/utils_test.go new file mode 100644 index 0000000..45595eb --- /dev/null +++ b/v2/samples/presentation/benchmarks/utils_test.go @@ -0,0 +1,59 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package benchmarks + +import ( + "math/rand" + "time" + + A "github.com/IBM/fp-go/v2/array" + B "github.com/IBM/fp-go/v2/bytes" + F "github.com/IBM/fp-go/v2/function" + IO "github.com/IBM/fp-go/v2/io" +) + +const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + +var ( + seededRand = rand.New(rand.NewSource(time.Now().UnixNano())) + randChar = F.Pipe2( + len(charset), + randInt, + IO.Map(charAt), + ) + createRandomString = F.Flow3( + F.Bind2of2(A.Replicate[IO.IO[byte]])(randChar), + IO.SequenceArray[byte], + IO.Map(B.ToString), + ) +) + +func createRandom[T any](single IO.IO[T]) func(size int) IO.IO[[]T] { + return F.Flow2( + F.Bind2of2(A.Replicate[IO.IO[T]])(single), + IO.SequenceArray[T], + ) +} + +func charAt(idx int) byte { + return charset[idx] +} + +func randInt(count int) IO.IO[int] { + return func() int { + return seededRand.Intn(count) + } +} diff --git a/v2/samples/presentation/cover.jpg b/v2/samples/presentation/cover.jpg new file mode 100644 index 0000000..0831740 Binary files /dev/null and b/v2/samples/presentation/cover.jpg differ diff --git a/v2/samples/presentation/examples/data/file1.txt b/v2/samples/presentation/examples/data/file1.txt new file mode 100644 index 0000000..9fb0773 --- /dev/null +++ b/v2/samples/presentation/examples/data/file1.txt @@ -0,0 +1 @@ +Some data \ No newline at end of file diff --git a/v2/samples/presentation/examples/data/file2.json b/v2/samples/presentation/examples/data/file2.json new file mode 100644 index 0000000..d37b1b0 --- /dev/null +++ b/v2/samples/presentation/examples/data/file2.json @@ -0,0 +1,3 @@ +{ + "a": 10 +} \ No newline at end of file diff --git a/v2/samples/presentation/examples/example_composition_test.go b/v2/samples/presentation/examples/example_composition_test.go new file mode 100644 index 0000000..918d053 --- /dev/null +++ b/v2/samples/presentation/examples/example_composition_test.go @@ -0,0 +1,70 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package examples + +import ( + "fmt" + + A "github.com/IBM/fp-go/v2/array" + F "github.com/IBM/fp-go/v2/function" +) + +func Example_composition_pipe() { + + filter := func(i int) bool { + return i%2 == 0 + } + + double := func(i int) int { + return i * 2 + } + + input := []int{1, 2, 3, 4} + + res := F.Pipe2( + input, + A.Filter(filter), + A.Map(double), + ) + + fmt.Println(res) + + // Output: + // [4 8] +} + +func Example_composition_flow() { + + filter := func(i int) bool { + return i%2 == 0 + } + + double := func(i int) int { + return i * 2 + } + + input := []int{1, 2, 3, 4} + + filterAndDouble := F.Flow2( + A.Filter(filter), + A.Map(double), + ) // func([]int) []int + + fmt.Println(filterAndDouble(input)) + + // Output: + // [4 8] +} diff --git a/v2/samples/presentation/examples/example_either_test.go b/v2/samples/presentation/examples/example_either_test.go new file mode 100644 index 0000000..f559ac2 --- /dev/null +++ b/v2/samples/presentation/examples/example_either_test.go @@ -0,0 +1,103 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package examples + +import ( + "fmt" + "strconv" + + E "github.com/IBM/fp-go/v2/either" + F "github.com/IBM/fp-go/v2/function" + S "github.com/IBM/fp-go/v2/string" +) + +func validatePort(port int) (int, error) { + if port > 0 { + return port, nil + } + return 0, fmt.Errorf("Value %d is not a valid port number", port) +} + +func Example_either_monad() { + + // func(string) E.Either[error, int] + atoi := E.Eitherize1(strconv.Atoi) + // func(int) E.Either[error, int] + valPort := E.Eitherize1(validatePort) + + // func(string) E.Either[error, string] + makeUrl := F.Flow3( + atoi, + E.Chain(valPort), + E.Map[error](S.Format[int]("http://localhost:%d")), + ) + + fmt.Println(makeUrl("8080")) + + // Output: + // Right[string](http://localhost:8080) +} + +func Example_either_idiomatic() { + + makeUrl := func(port string) (string, error) { + parsed, err := strconv.Atoi(port) + if err != nil { + return "", err + } + valid, err := validatePort(parsed) + if err != nil { + return "", err + } + return fmt.Sprintf("http://localhost:%d", valid), nil + } + + url, err := makeUrl("8080") + if err != nil { + panic(err) + } + fmt.Println(url) + + // Output: + // http://localhost:8080 +} + +func Example_either_worlds() { + + // func(string) E.Either[error, int] + atoi := E.Eitherize1(strconv.Atoi) + // func(int) E.Either[error, int] + valPort := E.Eitherize1(validatePort) + + // func(string) E.Either[error, string] + makeUrl := F.Flow3( + atoi, + E.Chain(valPort), + E.Map[error](S.Format[int]("http://localhost:%d")), + ) + + // func(string) (string, error) + makeUrlGo := E.Uneitherize1(makeUrl) + + url, err := makeUrlGo("8080") + if err != nil { + panic(err) + } + fmt.Println(url) + + // Output: + // http://localhost:8080 +} diff --git a/v2/samples/presentation/examples/example_generics_test.go b/v2/samples/presentation/examples/example_generics_test.go new file mode 100644 index 0000000..2ff023d --- /dev/null +++ b/v2/samples/presentation/examples/example_generics_test.go @@ -0,0 +1,46 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package examples + +import ( + "fmt" + + N "github.com/IBM/fp-go/v2/number" +) + +// addInts adds two integers +func addInts(left, right int) int { + return left + right +} + +// addNumbers adds two numbers +func addNumbers[T N.Number](left, right T) T { + return left + right +} + +func Example_generics() { + // invoke the non generic version + fmt.Println(addInts(1, 2)) + + // invoke the generic version + fmt.Println(addNumbers(1, 2)) + fmt.Println(addNumbers(1.0, 2.0)) + + // Output: + // 3 + // 3 + // 3 +} diff --git a/v2/samples/presentation/examples/example_hof_test.go b/v2/samples/presentation/examples/example_hof_test.go new file mode 100644 index 0000000..bb46a87 --- /dev/null +++ b/v2/samples/presentation/examples/example_hof_test.go @@ -0,0 +1,34 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package examples + +import "fmt" + +func captureValue[T any](captured T) func() string { + return func() string { + return fmt.Sprintf("Value: %v", captured) + } +} + +func Example_closure() { + + hof := captureValue("Carsten") // func() string + + fmt.Println(hof()) + + // Output: + // Value: Carsten +} diff --git a/v2/samples/presentation/examples/example_immutability_test.go b/v2/samples/presentation/examples/example_immutability_test.go new file mode 100644 index 0000000..136fc55 --- /dev/null +++ b/v2/samples/presentation/examples/example_immutability_test.go @@ -0,0 +1,156 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package examples + +import ( + "fmt" + "strings" + + F "github.com/IBM/fp-go/v2/function" + N "github.com/IBM/fp-go/v2/number" + L "github.com/IBM/fp-go/v2/optics/lens" +) + +type Person struct { + name string + age int +} + +func (p Person) GetName() string { + return p.name +} + +func (p Person) GetAge() int { + return p.age +} + +func (p Person) SetName(name string) Person { + p.name = name + return p +} + +func (p Person) SetAge(age int) Person { + p.age = age + return p +} + +type Address struct { + city string +} + +func (a Address) GetCity() string { + return a.city +} + +func (a Address) SetCity(city string) Address { + a.city = city + return a +} + +type Client struct { + person Person + address Address +} + +func (c Client) GetPerson() Person { + return c.person +} + +func (c Client) SetPerson(person Person) Client { + c.person = person + return c +} + +func (c Client) GetAddress() Address { + return c.address +} + +func (c Client) SetAddress(address Address) Client { + c.address = address + return c +} + +func MakePerson(name string, age int) Person { + return Person{name, age} +} + +func MakeClient(city string, name string, age int) Client { + return Client{person: Person{name, age}, address: Address{city}} +} + +func Example_immutability_struct() { + p1 := MakePerson("Carsten", 53) + + // func(int) func(Person) Person + setAge := F.Curry2(F.Swap(Person.SetAge)) + + p2 := F.Pipe1( + p1, + setAge(54), + ) + + fmt.Println(p1) + fmt.Println(p2) + + // Output: + // {Carsten 53} + // {Carsten 54} +} + +func Example_immutability_optics() { + + // Lens[Person, int] + ageLens := L.MakeLens(Person.GetAge, Person.SetAge) + // func(Person) Person + incAge := L.Modify[Person](N.Inc[int])(ageLens) + + p1 := MakePerson("Carsten", 53) + p2 := incAge(p1) + + fmt.Println(p1) + fmt.Println(p2) + + // Output: + // {Carsten 53} + // {Carsten 54} +} + +func Example_immutability_lenses() { + + // Lens[Person, string] + nameLens := L.MakeLens(Person.GetName, Person.SetName) + // Lens[Client, Person] + personLens := L.MakeLens(Client.GetPerson, Client.SetPerson) + + // Lens[Client, string] + clientNameLens := F.Pipe1( + personLens, + L.Compose[Client](nameLens), + ) + // func(Client) Client + upperName := L.Modify[Client](strings.ToUpper)(clientNameLens) + + c1 := MakeClient("Böblingen", "Carsten", 53) + + c2 := upperName(c1) + + fmt.Println(c1) + fmt.Println(c2) + + // Output: + // {{Carsten 53} {Böblingen}} + // {{CARSTEN 53} {Böblingen}} +} diff --git a/v2/samples/presentation/examples/example_map_test.go b/v2/samples/presentation/examples/example_map_test.go new file mode 100644 index 0000000..4552056 --- /dev/null +++ b/v2/samples/presentation/examples/example_map_test.go @@ -0,0 +1,64 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package examples + +import ( + "fmt" + + A "github.com/IBM/fp-go/v2/array" + N "github.com/IBM/fp-go/v2/number" +) + +func Example_map() { + + f := func(i int) int { + return i * 2 + } + + input := []int{1, 2, 3, 4} + + // idiomatic go + res1 := make([]int, 0, len(input)) + for _, i := range input { + res1 = append(res1, f(i)) + } + fmt.Println(res1) + + // map + res2 := A.Map(f)(input) + fmt.Println(res2) + + // Output: + // [2 4 6 8] + // [2 4 6 8] +} + +func Example_reduce() { + + input := []int{1, 2, 3, 4} + + // reduce + red := A.Reduce(N.MonoidSum[int]().Concat, 0)(input) + fmt.Println(red) + + // fold + fld := A.Fold(N.MonoidSum[int]())(input) + fmt.Println(fld) + + // Output: + // 10 + // 10 +} diff --git a/v2/samples/presentation/examples/example_sideeffect_test.go b/v2/samples/presentation/examples/example_sideeffect_test.go new file mode 100644 index 0000000..9ff9315 --- /dev/null +++ b/v2/samples/presentation/examples/example_sideeffect_test.go @@ -0,0 +1,105 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package examples + +import ( + "encoding/json" + "fmt" + "os" + + B "github.com/IBM/fp-go/v2/bytes" + F "github.com/IBM/fp-go/v2/function" + IOE "github.com/IBM/fp-go/v2/ioeither" + "github.com/IBM/fp-go/v2/ioeither/file" + J "github.com/IBM/fp-go/v2/json" + T "github.com/IBM/fp-go/v2/tuple" +) + +type Sample struct { + Value int `json:"a"` +} + +func (s Sample) getValue() int { + return s.Value +} + +func Example_io_flow() { + + // IOE.IOEither[error, string] + text := F.Pipe2( + "data/file1.txt", + file.ReadFile, + IOE.Map[error](B.ToString), + ) + + // IOE.IOEither[error, int] + value := F.Pipe3( + "data/file2.json", + file.ReadFile, + IOE.ChainEitherK(J.Unmarshal[Sample]), + IOE.Map[error](Sample.getValue), + ) + + // IOE.IOEither[error, string] + result := F.Pipe1( + IOE.SequenceT2(text, value), + IOE.Map[error](func(res T.Tuple2[string, int]) string { + return fmt.Sprintf("Text: %s, Number: %d", res.F1, res.F2) + }), + ) + + fmt.Println(result()) + + // Output: + // Right[string](Text: Some data, Number: 10) + +} + +func io_flow_idiomatic() error { + + // []byte + file1AsBytes, err := os.ReadFile("data/file1.txt") + if err != nil { + return err + } + // string + text := string(file1AsBytes) + + // []byte + file2AsBytes, err := os.ReadFile("data/file2.json") + if err != nil { + return err + } + var value Sample + if err := json.Unmarshal(file2AsBytes, &value); err != nil { + return err + } + // string + result := fmt.Sprintf("Text: %s, Number: %d", text, value.Value) + + fmt.Println(result) + + return nil +} + +func Example_io_flow_idiomatic() { + if err := io_flow_idiomatic(); err != nil { + panic(err) + } + + // Output: + // Text: Some data, Number: 10 +} diff --git a/v2/samples/presentation/examples/examples_monad_test.go b/v2/samples/presentation/examples/examples_monad_test.go new file mode 100644 index 0000000..4e4be79 --- /dev/null +++ b/v2/samples/presentation/examples/examples_monad_test.go @@ -0,0 +1,50 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package examples + +type HKT[T any] struct { +} + +// Pointed +func Of[A any](A) HKT[A] { return HKT[A]{} } + +// Functor +func Map[A, B any](func(A) B) func(HKT[A]) HKT[B] { return func(HKT[A]) HKT[B] { return HKT[B]{} } } +func MapTo[A, B any](A) func(HKT[A]) HKT[B] { return func(HKT[A]) HKT[B] { return HKT[B]{} } } + +// Chain +func Chain[A, B any](func(A) HKT[B]) func(HKT[A]) HKT[B] { + return func(HKT[A]) HKT[B] { return HKT[B]{} } +} +func ChainTo[A, B any](HKT[B]) func(HKT[A]) HKT[B] { + return func(HKT[A]) HKT[B] { return HKT[B]{} } +} +func ChainFirst[A, B any](func(A) HKT[B]) func(HKT[A]) HKT[A] { + return func(HKT[A]) HKT[A] { return HKT[A]{} } +} + +// Apply +func Ap[A, B any](HKT[A]) func(HKT[func(A) B]) HKT[B] { + return func(HKT[func(A) B]) HKT[B] { return HKT[B]{} } +} + +// Derived +func Flatten[A, B any](HKT[HKT[A]]) HKT[A] { + return HKT[A]{} +} +func Reduce[A, B any](func(B, A) B, B) func(HKT[A]) HKT[B] { + return func(HKT[A]) HKT[B] { return HKT[B]{} } +} diff --git a/v2/samples/presentation/introduction.pptx b/v2/samples/presentation/introduction.pptx new file mode 100644 index 0000000..6c1042d Binary files /dev/null and b/v2/samples/presentation/introduction.pptx differ diff --git a/v2/samples/readerioeither/example1/reader_test.go b/v2/samples/readerioeither/example1/reader_test.go new file mode 100644 index 0000000..b3b9d60 --- /dev/null +++ b/v2/samples/readerioeither/example1/reader_test.go @@ -0,0 +1,89 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package example1 + +import ( + "os" + + B "github.com/IBM/fp-go/v2/bytes" + E "github.com/IBM/fp-go/v2/either" + F "github.com/IBM/fp-go/v2/function" + I "github.com/IBM/fp-go/v2/identity" + IOE "github.com/IBM/fp-go/v2/ioeither" + J "github.com/IBM/fp-go/v2/json" + RIOE "github.com/IBM/fp-go/v2/readerioeither" +) + +type ( + WriterType = func([]byte) IOE.IOEither[error, []byte] + + Dependencies struct { + Writer WriterType + } +) + +func getWriter(deps *Dependencies) WriterType { + return deps.Writer +} + +// SerializeToWriter marshals the input to JSON and persists the result via the [Writer] passed in via the [*Dependencies] +func SerializeToWriter[A any](data A) RIOE.ReaderIOEither[*Dependencies, error, []byte] { + return F.Pipe1( + RIOE.Ask[*Dependencies, error](), + RIOE.ChainIOEitherK[*Dependencies](F.Flow2( + getWriter, + F.Pipe2( + data, + J.Marshal[A], + E.Fold(F.Flow2( + IOE.Left[[]byte, error], + F.Constant1[WriterType, IOE.IOEither[error, []byte]], + ), I.Ap[IOE.IOEither[error, []byte], []byte]), + ), + )), + ) +} + +func ExampleReaderIOEither() { + + // writeToStdOut implements a writer to stdout + writeToStdOut := func(data []byte) IOE.IOEither[error, []byte] { + return IOE.TryCatchError(func() ([]byte, error) { + _, err := os.Stdout.Write(data) + return data, err + }) + } + + deps := Dependencies{ + Writer: writeToStdOut, + } + + data := map[string]string{ + "a": "b", + "c": "d", + } + + // writeData will persist to a configurable target + writeData := F.Pipe1( + SerializeToWriter(data), + RIOE.Map[*Dependencies, error](B.ToString), + ) + + _ = writeData(&deps)() + + // Output: + // {"a":"b","c":"d"} +} diff --git a/v2/samples/readfile/data/file.json b/v2/samples/readfile/data/file.json new file mode 100644 index 0000000..6f6b333 --- /dev/null +++ b/v2/samples/readfile/data/file.json @@ -0,0 +1,3 @@ +{ + "data": "Carsten" +} \ No newline at end of file diff --git a/v2/samples/readfile/data/file1.json b/v2/samples/readfile/data/file1.json new file mode 100644 index 0000000..5d25584 --- /dev/null +++ b/v2/samples/readfile/data/file1.json @@ -0,0 +1,3 @@ +{ + "data": "file1" +} \ No newline at end of file diff --git a/v2/samples/readfile/data/file2.json b/v2/samples/readfile/data/file2.json new file mode 100644 index 0000000..03f5d1a --- /dev/null +++ b/v2/samples/readfile/data/file2.json @@ -0,0 +1,3 @@ +{ + "data": "file2" +} \ No newline at end of file diff --git a/v2/samples/readfile/data/file3.json b/v2/samples/readfile/data/file3.json new file mode 100644 index 0000000..27ed7e8 --- /dev/null +++ b/v2/samples/readfile/data/file3.json @@ -0,0 +1,3 @@ +{ + "data": "file3" +} \ No newline at end of file diff --git a/v2/samples/readfile/readfile_test.go b/v2/samples/readfile/readfile_test.go new file mode 100644 index 0000000..1a62a17 --- /dev/null +++ b/v2/samples/readfile/readfile_test.go @@ -0,0 +1,73 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package readfile + +import ( + "context" + "fmt" + "testing" + + A "github.com/IBM/fp-go/v2/array" + R "github.com/IBM/fp-go/v2/context/readerioeither" + "github.com/IBM/fp-go/v2/context/readerioeither/file" + E "github.com/IBM/fp-go/v2/either" + F "github.com/IBM/fp-go/v2/function" + IO "github.com/IBM/fp-go/v2/io" + J "github.com/IBM/fp-go/v2/json" + "github.com/stretchr/testify/assert" +) + +type RecordType struct { + Data string `json:"data"` +} + +// TestReadSingleFile reads the content of a file from disk and parses it into +// a struct +func TestReadSingleFile(t *testing.T) { + + data := F.Pipe2( + file.ReadFile("./data/file.json"), + R.ChainEitherK(J.Unmarshal[RecordType]), + R.ChainFirstIOK(IO.Logf[RecordType]("Log: %v")), + ) + + result := data(context.Background()) + + assert.Equal(t, E.Of[error](RecordType{"Carsten"}), result()) +} + +func idxToFilename(idx int) string { + return fmt.Sprintf("./data/file%d.json", idx+1) +} + +// TestReadMultipleFiles reads the content of a multiple from disk and parses them into +// structs +func TestReadMultipleFiles(t *testing.T) { + + data := F.Pipe2( + A.MakeBy(3, idxToFilename), + R.TraverseArray(F.Flow3( + file.ReadFile, + R.ChainEitherK(J.Unmarshal[RecordType]), + R.ChainFirstIOK(IO.Logf[RecordType]("Log Single: %v")), + )), + R.ChainFirstIOK(IO.Logf[[]RecordType]("Log Result: %v")), + ) + + result := data(context.Background()) + + assert.Equal(t, E.Of[error](A.From(RecordType{"file1"}, RecordType{"file2"}, RecordType{"file3"})), result()) +} diff --git a/v2/samples/tuples/option_test.go b/v2/samples/tuples/option_test.go new file mode 100644 index 0000000..39bde40 --- /dev/null +++ b/v2/samples/tuples/option_test.go @@ -0,0 +1,59 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package tuples + +import ( + "fmt" + "os" + "path/filepath" + "testing" + + F "github.com/IBM/fp-go/v2/function" + IOE "github.com/IBM/fp-go/v2/ioeither" + IOEF "github.com/IBM/fp-go/v2/ioeither/file" + IOEG "github.com/IBM/fp-go/v2/ioeither/generic" + IOO "github.com/IBM/fp-go/v2/iooption" +) + +func TestIOEitherToOption1(t *testing.T) { + tmpDir := t.TempDir() + content := []byte("abc") + + resIOO := F.Pipe2( + content, + IOEF.WriteFile(filepath.Join(tmpDir, "test.txt"), os.ModePerm), + IOEG.Fold[IOE.IOEither[error, []byte]]( + IOO.Of[error], + F.Ignore1of1[[]byte](IOO.None[error]), + ), + ) + + fmt.Println(resIOO()) +} + +func TestIOEitherToOption2(t *testing.T) { + tmpDir := t.TempDir() + content := []byte("abc") + + resIOO := F.Pipe3( + content, + IOEF.WriteFile(filepath.Join(tmpDir, "test.txt"), os.ModePerm), + IOE.Swap[error, []byte], + IOE.ToIOOption[[]byte, error], + ) + + fmt.Println(resIOO()) +} diff --git a/v2/samples/tuples/samples/data.txt b/v2/samples/tuples/samples/data.txt new file mode 100644 index 0000000..f2ba8f8 --- /dev/null +++ b/v2/samples/tuples/samples/data.txt @@ -0,0 +1 @@ +abc \ No newline at end of file diff --git a/v2/samples/tuples/tuple_test.go b/v2/samples/tuples/tuple_test.go new file mode 100644 index 0000000..162da27 --- /dev/null +++ b/v2/samples/tuples/tuple_test.go @@ -0,0 +1,128 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package tuples + +import ( + "bytes" + "io" + "strings" + "testing" + + E "github.com/IBM/fp-go/v2/either" + F "github.com/IBM/fp-go/v2/function" + IOE "github.com/IBM/fp-go/v2/ioeither" + IOEF "github.com/IBM/fp-go/v2/ioeither/file" + T "github.com/IBM/fp-go/v2/tuple" + "github.com/stretchr/testify/assert" +) + +func sampleConvertDocx(r io.Reader) (string, map[string]string, error) { + content, err := io.ReadAll(r) + return string(content), map[string]string{}, err +} + +func TestSampleConvertDocx1(t *testing.T) { + // this conversion approach has the disadvantage that it exhausts the reader + // so we cannot invoke the resulting IOEither multiple times + convertDocx := func(r io.Reader) IOE.IOEither[error, T.Tuple2[string, map[string]string]] { + return IOE.TryCatchError(func() (T.Tuple2[string, map[string]string], error) { + text, meta, err := sampleConvertDocx(r) + return T.MakeTuple2(text, meta), err + }) + } + + rdr := strings.NewReader("abc") + resIOE := convertDocx(rdr) + + resE := resIOE() + + assert.True(t, E.IsRight(resE)) +} + +func TestSampleConvertDocx2(t *testing.T) { + // this approach assumes that `sampleConvertDocx` does not have any side effects + // other than reading from a `Reader`. As a consequence it can be a pure function itself. + // The disadvantage is that its input has to exist in memory which is probably not a good + // idea for large inputs + convertDocx := func(data []byte) Either[T.Tuple2[string, map[string]string]] { + text, meta, err := sampleConvertDocx(bytes.NewReader(data)) + return E.TryCatchError(T.MakeTuple2(text, meta), err) + } + + resE := convertDocx([]byte("abc")) + + assert.True(t, E.IsRight(resE)) +} + +// onClose closes a closeable resource +func onClose[R io.Closer](r R) IOE.IOEither[error, R] { + return IOE.TryCatchError(func() (R, error) { + return r, r.Close() + }) +} + +// convertDocx3 takes an `acquire` function that creates an instance or a [ReaderCloser] whenever the resulting [IOEither] is invoked. Since +// we return a [Closer] the instance will be closed after use, automatically. This design makes sure that the resulting [IOEither] can be invoked +// as many times as necessary +func convertDocx3[R io.ReadCloser](acquire IOE.IOEither[error, R]) IOE.IOEither[error, T.Tuple2[string, map[string]string]] { + return IOE.WithResource[T.Tuple2[string, map[string]string]]( + acquire, + onClose[R])( + func(r R) IOE.IOEither[error, T.Tuple2[string, map[string]string]] { + return IOE.TryCatchError(func() (T.Tuple2[string, map[string]string], error) { + text, meta, err := sampleConvertDocx(r) + return T.MakeTuple2(text, meta), err + }) + }, + ) +} + +// convertDocx4 takes an `acquire` function that creates an instance or a [Reader] whenever the resulting [IOEither] is invoked. +// This design makes sure that the resulting [IOEither] can be invoked +// as many times as necessary +func convertDocx4[R io.Reader](acquire IOE.IOEither[error, R]) IOE.IOEither[error, T.Tuple2[string, map[string]string]] { + return F.Pipe1( + acquire, + IOE.Chain(func(r R) IOE.IOEither[error, T.Tuple2[string, map[string]string]] { + return IOE.TryCatchError(func() (T.Tuple2[string, map[string]string], error) { + text, meta, err := sampleConvertDocx(r) + return T.MakeTuple2(text, meta), err + }) + }), + ) +} + +func TestSampleConvertDocx3(t *testing.T) { + // IOEither that creates the reader + acquire := IOEF.Open("./samples/data.txt") + + resIOE := convertDocx3(acquire) + resE := resIOE() + + assert.True(t, E.IsRight(resE)) +} + +func TestSampleConvertDocx4(t *testing.T) { + // IOEither that creates the reader + acquire := IOE.FromIO[error](func() *strings.Reader { + return strings.NewReader("abc") + }) + + resIOE := convertDocx4(acquire) + resE := resIOE() + + assert.True(t, E.IsRight(resE)) +} diff --git a/v2/samples/tuples/types.go b/v2/samples/tuples/types.go new file mode 100644 index 0000000..bf5938f --- /dev/null +++ b/v2/samples/tuples/types.go @@ -0,0 +1,22 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package tuples + +import "github.com/IBM/fp-go/v2/either" + +type ( + Either[A any] = either.Either[error, A] +) diff --git a/v2/semigroup/alt.go b/v2/semigroup/alt.go new file mode 100644 index 0000000..8d8a4ed --- /dev/null +++ b/v2/semigroup/alt.go @@ -0,0 +1,28 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package semigroup + +func AltSemigroup[HKTA any, LAZYHKTA ~func() HKTA]( + falt func(HKTA, LAZYHKTA) HKTA, + +) Semigroup[HKTA] { + + return MakeSemigroup( + func(first, second HKTA) HKTA { + return falt(first, func() HKTA { return second }) + }, + ) +} diff --git a/v2/semigroup/apply.go b/v2/semigroup/apply.go new file mode 100644 index 0000000..6571a01 --- /dev/null +++ b/v2/semigroup/apply.go @@ -0,0 +1,38 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package semigroup + +import ( + F "github.com/IBM/fp-go/v2/function" +) + +/* +* +HKTA = HKT
+HKTFA = HKT +*/ +func ApplySemigroup[A, HKTA, HKTFA any]( + fmap func(HKTA, func(A) func(A) A) HKTFA, + fap func(HKTFA, HKTA) HKTA, + + s Semigroup[A], +) Semigroup[HKTA] { + + cb := F.Curry2(s.Concat) + return MakeSemigroup(func(first HKTA, second HKTA) HKTA { + return fap(fmap(first, cb), second) + }) +} diff --git a/v2/semigroup/array.go b/v2/semigroup/array.go new file mode 100644 index 0000000..7f818f0 --- /dev/null +++ b/v2/semigroup/array.go @@ -0,0 +1,36 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package semigroup + +import ( + M "github.com/IBM/fp-go/v2/magma" +) + +func GenericMonadConcatAll[GA ~[]A, A any](s Semigroup[A]) func(GA, A) A { + return M.GenericMonadConcatAll[GA](M.MakeMagma(s.Concat)) +} + +func GenericConcatAll[GA ~[]A, A any](s Semigroup[A]) func(A) func(GA) A { + return M.GenericConcatAll[GA](M.MakeMagma(s.Concat)) +} + +func MonadConcatAll[A any](s Semigroup[A]) func([]A, A) A { + return GenericMonadConcatAll[[]A](s) +} + +func ConcatAll[A any](s Semigroup[A]) func(A) func([]A) A { + return GenericConcatAll[[]A](s) +} diff --git a/v2/semigroup/doc.go b/v2/semigroup/doc.go new file mode 100644 index 0000000..569ac05 --- /dev/null +++ b/v2/semigroup/doc.go @@ -0,0 +1,303 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +/* +Package semigroup provides implementations of the Semigroup algebraic structure. + +# Semigroup + +A Semigroup is an algebraic structure consisting of a set together with an associative +binary operation. It extends the Magma structure by adding the associativity law. + +Mathematical Definition: + +A semigroup is a pair (S, •) where: + - S is a set + - • is a binary operation: S × S → S + - The operation must be associative: (a • b) • c = a • (b • c) + +The key difference from Magma is the associativity requirement, which allows operations +to be chained without worrying about parentheses. + +# Basic Usage + +Creating and using a semigroup: + + import ( + "fmt" + SG "github.com/IBM/fp-go/v2/semigroup" + ) + + // Create a semigroup for string concatenation + stringConcat := SG.MakeSemigroup(func(a, b string) string { + return a + b + }) + + result := stringConcat.Concat("Hello, ", "World!") + fmt.Println(result) // Output: Hello, World! + + // Associativity holds + s1 := stringConcat.Concat(stringConcat.Concat("a", "b"), "c") + s2 := stringConcat.Concat("a", stringConcat.Concat("b", "c")) + fmt.Println(s1 == s2) // Output: true + +# Built-in Semigroups + +The package provides several pre-defined semigroups: + +First - Always returns the first argument: + + first := SG.First[int]() + result := first.Concat(1, 2) // Returns: 1 + +Last - Always returns the last argument: + + last := SG.Last[int]() + result := last.Concat(1, 2) // Returns: 2 + +# Semigroup Transformations + +Reverse - Swaps the order of arguments: + + import N "github.com/IBM/fp-go/v2/number" + + sub := SG.MakeSemigroup(func(a, b int) int { return a - b }) + reversed := SG.Reverse(sub) + + result1 := sub.Concat(10, 3) // 10 - 3 = 7 + result2 := reversed.Concat(10, 3) // 3 - 10 = -7 + +FunctionSemigroup - Lifts a semigroup to work with functions: + + import N "github.com/IBM/fp-go/v2/number" + + // Semigroup for integers + intSum := N.SemigroupSum[int]() + + // Lift to functions that return integers + funcSG := SG.FunctionSemigroup[string](intSum) + + f := func(s string) int { return len(s) } + g := func(s string) int { return len(s) * 2 } + + // Combine functions + combined := funcSG.Concat(f, g) + result := combined("hello") // len("hello") + len("hello")*2 = 5 + 10 = 15 + +# Array Operations + +ConcatAll - Concatenates all elements in an array with a starting value: + + import N "github.com/IBM/fp-go/v2/number" + + sum := N.SemigroupSum[int]() + concatAll := SG.ConcatAll(sum) + + result := concatAll(10)([]int{1, 2, 3, 4}) // 10 + 1 + 2 + 3 + 4 = 20 + +MonadConcatAll - Concatenates all elements with a starting value (uncurried): + + import N "github.com/IBM/fp-go/v2/number" + + sum := N.SemigroupSum[int]() + result := SG.MonadConcatAll(sum)([]int{1, 2, 3, 4}, 10) // 20 + +GenericConcatAll - Generic version for custom slice types: + + type MyInts []int + + sum := N.SemigroupSum[int]() + concatAll := SG.GenericConcatAll[MyInts](sum) + + result := concatAll(0)(MyInts{1, 2, 3}) // 6 + +# Higher-Kinded Type Semigroups + +ApplySemigroup - Creates a semigroup for applicative functors: + + // For a type HKT with map and ap operations + applySG := SG.ApplySemigroup( + fmap, // func(HKT, func(A) func(A) A) HKT + fap, // func(HKT, HKT) HKT + baseSemigroup, + ) + +AltSemigroup - Creates a semigroup for alternative functors: + + // For a type HKT with an alt operation + altSG := SG.AltSemigroup( + falt, // func(HKT, func() HKT) HKT + ) + +# Practical Examples + +Example 1: Merging Configurations + + type Config struct { + Timeout int + Retries int + } + + configSG := SG.MakeSemigroup(func(a, b Config) Config { + return Config{ + Timeout: max(a.Timeout, b.Timeout), + Retries: a.Retries + b.Retries, + } + }) + + default := Config{Timeout: 30, Retries: 3} + user := Config{Timeout: 60, Retries: 5} + override := Config{Timeout: 45, Retries: 2} + + // Merge configurations (associative) + final := configSG.Concat(configSG.Concat(default, user), override) + // Result: Config{Timeout: 60, Retries: 10} + +Example 2: Combining Validators + + type Validator func(string) []string // Returns list of errors + + validatorSG := SG.MakeSemigroup(func(v1, v2 Validator) Validator { + return func(s string) []string { + errors1 := v1(s) + errors2 := v2(s) + return append(errors1, errors2...) + } + }) + + notEmpty := func(s string) []string { + if s == "" { + return []string{"must not be empty"} + } + return nil + } + + minLength := func(s string) []string { + if len(s) < 3 { + return []string{"must be at least 3 characters"} + } + return nil + } + + // Combine validators + combined := validatorSG.Concat(notEmpty, minLength) + errors := combined("ab") // ["must be at least 3 characters"] + +Example 3: Aggregating Statistics + + type Stats struct { + Count int + Sum float64 + Min float64 + Max float64 + } + + statsSG := SG.MakeSemigroup(func(a, b Stats) Stats { + return Stats{ + Count: a.Count + b.Count, + Sum: a.Sum + b.Sum, + Min: min(a.Min, b.Min), + Max: max(a.Max, b.Max), + } + }) + + s1 := Stats{Count: 3, Sum: 15.0, Min: 2.0, Max: 8.0} + s2 := Stats{Count: 2, Sum: 12.0, Min: 5.0, Max: 7.0} + s3 := Stats{Count: 4, Sum: 20.0, Min: 1.0, Max: 9.0} + + // Aggregate statistics (order doesn't matter due to associativity) + total := statsSG.Concat(statsSG.Concat(s1, s2), s3) + // Result: Stats{Count: 9, Sum: 47.0, Min: 1.0, Max: 9.0} + +Example 4: Building Query Strings + + querySG := SG.MakeSemigroup(func(a, b string) string { + if a == "" { + return b + } + if b == "" { + return a + } + return a + "&" + b + }) + + base := "api/users" + filter := "status=active" + sort := "sort=name" + page := "page=1" + + // Build query string + query := querySG.Concat(querySG.Concat(base+"?"+filter, sort), page) + // Result: "api/users?status=active&sort=name&page=1" + +# Relationship to Other Structures + +Semigroup extends Magma by adding the associativity law: + - Magma: Has a binary operation + - Semigroup: Has an associative binary operation + - Monoid: Semigroup with an identity element + +Converting between structures: + + // Semigroup to Magma + magma := SG.ToMagma(semigroup) + + // Semigroup to Monoid (requires identity element) + // See the monoid package + +# Laws + +A valid Semigroup must satisfy the associativity law: + + // Associativity: (a • b) • c = a • (b • c) + s.Concat(s.Concat(a, b), c) == s.Concat(a, s.Concat(b, c)) + +This law ensures that the order of evaluation doesn't matter, allowing for +parallel computation and optimization. + +# Function Reference + +Core Functions: + - MakeSemigroup[A](func(A, A) A) Semigroup[A] - Creates a semigroup from a binary operation + - Reverse[A](Semigroup[A]) Semigroup[A] - Returns the dual semigroup with swapped arguments + - ToMagma[A](Semigroup[A]) Magma[A] - Converts a semigroup to a magma + +Built-in Semigroups: + - First[A]() Semigroup[A] - Always returns the first argument + - Last[A]() Semigroup[A] - Always returns the last argument + +Higher-Order Functions: + - FunctionSemigroup[A, B](Semigroup[B]) Semigroup[func(A) B] - Lifts a semigroup to functions + +Array Operations: + - ConcatAll[A](Semigroup[A]) func(A) func([]A) A - Concatenates array elements with initial value + - MonadConcatAll[A](Semigroup[A]) func([]A, A) A - Uncurried version of ConcatAll + - GenericConcatAll[GA ~[]A, A](Semigroup[A]) func(A) func(GA) A - Generic version for custom slices + - GenericMonadConcatAll[GA ~[]A, A](Semigroup[A]) func(GA, A) A - Generic uncurried version + +Higher-Kinded Type Operations: + - ApplySemigroup[A, HKTA, HKTFA](fmap, fap, Semigroup[A]) Semigroup[HKTA] - Semigroup for applicatives + - AltSemigroup[HKTA, LAZYHKTA](falt) Semigroup[HKTA] - Semigroup for alternatives + +# Related Packages + + - github.com/IBM/fp-go/v2/magma - Base algebraic structure without associativity + - github.com/IBM/fp-go/v2/monoid - Semigroup with identity element + - github.com/IBM/fp-go/v2/number - Numeric semigroups (sum, product, min, max) + - github.com/IBM/fp-go/v2/string - String semigroups + - github.com/IBM/fp-go/v2/array - Array operations using semigroups + - github.com/IBM/fp-go/v2/function - Function composition utilities +*/ +package semigroup diff --git a/v2/semigroup/ord/semigroup.go b/v2/semigroup/ord/semigroup.go new file mode 100644 index 0000000..5402e9d --- /dev/null +++ b/v2/semigroup/ord/semigroup.go @@ -0,0 +1,31 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package ord + +import ( + "github.com/IBM/fp-go/v2/ord" + S "github.com/IBM/fp-go/v2/semigroup" +) + +// Max gets a semigroup where `concat` will return the maximum, based on the provided order. +func Max[A any](o ord.Ord[A]) S.Semigroup[A] { + return S.MakeSemigroup(ord.Max(o)) +} + +// Min gets a semigroup where `concat` will return the minimum, based on the provided order. +func Min[A any](o ord.Ord[A]) S.Semigroup[A] { + return S.MakeSemigroup(ord.Min(o)) +} diff --git a/v2/semigroup/semigroup.go b/v2/semigroup/semigroup.go new file mode 100644 index 0000000..882f1ae --- /dev/null +++ b/v2/semigroup/semigroup.go @@ -0,0 +1,66 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package semigroup + +import ( + F "github.com/IBM/fp-go/v2/function" + M "github.com/IBM/fp-go/v2/magma" +) + +type Semigroup[A any] interface { + M.Magma[A] +} + +type semigroup[A any] struct { + c func(A, A) A +} + +func (self semigroup[A]) Concat(x A, y A) A { + return self.c(x, y) +} + +func MakeSemigroup[A any](c func(A, A) A) Semigroup[A] { + return semigroup[A]{c: c} +} + +// Reverse returns The dual of a `Semigroup`, obtained by swapping the arguments of `concat`. +func Reverse[A any](m Semigroup[A]) Semigroup[A] { + return MakeSemigroup(M.Reverse[A](m).Concat) +} + +// FunctionSemigroup forms a semigroup as long as you can provide a semigroup for the codomain. +func FunctionSemigroup[A, B any](s Semigroup[B]) Semigroup[func(A) B] { + return MakeSemigroup(func(f func(A) B, g func(A) B) func(A) B { + return func(a A) B { + return s.Concat(f(a), g(a)) + } + }) +} + +// First always returns the first argument. +func First[A any]() Semigroup[A] { + return MakeSemigroup(F.First[A, A]) +} + +// Last always returns the last argument. +func Last[A any]() Semigroup[A] { + return MakeSemigroup(F.Second[A, A]) +} + +// ToMagma converts a semigroup to a magma +func ToMagma[A any](s Semigroup[A]) M.Magma[A] { + return s +} diff --git a/v2/semigroup/semigroup_test.go b/v2/semigroup/semigroup_test.go new file mode 100644 index 0000000..9f5b481 --- /dev/null +++ b/v2/semigroup/semigroup_test.go @@ -0,0 +1,446 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package semigroup + +import ( + "testing" + + M "github.com/IBM/fp-go/v2/magma" + "github.com/stretchr/testify/assert" +) + +// Test basic First semigroup +func TestFirst(t *testing.T) { + first := First[int]() + assert.Equal(t, 1, first.Concat(1, 2)) + assert.Equal(t, 10, first.Concat(10, 20)) + assert.Equal(t, "a", First[string]().Concat("a", "b")) +} + +// Test basic Last semigroup +func TestLast(t *testing.T) { + last := Last[int]() + assert.Equal(t, 2, last.Concat(1, 2)) + assert.Equal(t, 20, last.Concat(10, 20)) + assert.Equal(t, "b", Last[string]().Concat("a", "b")) +} + +// Test MakeSemigroup +func TestMakeSemigroup(t *testing.T) { + // Integer addition semigroup + add := MakeSemigroup(func(a, b int) int { return a + b }) + assert.Equal(t, 5, add.Concat(2, 3)) + assert.Equal(t, 10, add.Concat(4, 6)) + + // String concatenation semigroup + concat := MakeSemigroup(func(a, b string) string { return a + b }) + assert.Equal(t, "hello", concat.Concat("hel", "lo")) + assert.Equal(t, "foobar", concat.Concat("foo", "bar")) + + // Max semigroup + max := MakeSemigroup(func(a, b int) int { + if a > b { + return a + } + return b + }) + assert.Equal(t, 10, max.Concat(5, 10)) + assert.Equal(t, 20, max.Concat(20, 15)) +} + +// Test Reverse semigroup +func TestReverse(t *testing.T) { + // Subtraction is not commutative, so reverse changes the result + sub := MakeSemigroup(func(a, b int) int { return a - b }) + reversed := Reverse(sub) + + assert.Equal(t, 7, sub.Concat(10, 3)) // 10 - 3 = 7 + assert.Equal(t, -7, reversed.Concat(10, 3)) // 3 - 10 = -7 + + // String concatenation + concat := MakeSemigroup(func(a, b string) string { return a + b }) + reversedConcat := Reverse(concat) + + assert.Equal(t, "ab", concat.Concat("a", "b")) + assert.Equal(t, "ba", reversedConcat.Concat("a", "b")) +} + +// Test FunctionSemigroup +func TestFunctionSemigroup(t *testing.T) { + // Base semigroup for integers (addition) + add := MakeSemigroup(func(a, b int) int { return a + b }) + + // Lift to functions + funcSG := FunctionSemigroup[string](add) + + // Create two functions + f := func(s string) int { return len(s) } + g := func(s string) int { return len(s) * 2 } + + // Combine functions + combined := funcSG.Concat(f, g) + + // Test with different strings + assert.Equal(t, 15, combined("hello")) // 5 + 10 = 15 + assert.Equal(t, 9, combined("abc")) // 3 + 6 = 9 + assert.Equal(t, 0, combined("")) // 0 + 0 = 0 +} + +// Test FunctionSemigroup with different types +func TestFunctionSemigroupMultipleTypes(t *testing.T) { + // String concatenation semigroup + concat := MakeSemigroup(func(a, b string) string { return a + b }) + + // Lift to functions from int to string + funcSG := FunctionSemigroup[int](concat) + + f := func(n int) string { return "a" } + g := func(n int) string { return "b" } + + combined := funcSG.Concat(f, g) + assert.Equal(t, "ab", combined(42)) +} + +// Test ToMagma conversion +func TestToMagma(t *testing.T) { + sg := MakeSemigroup(func(a, b int) int { return a + b }) + magma := ToMagma(sg) + + // Should work as a magma + assert.Equal(t, 5, magma.Concat(2, 3)) + assert.Equal(t, 10, magma.Concat(4, 6)) + + // Verify it's a Magma interface + var _ M.Magma[int] = magma +} + +// Test ConcatAll +func TestConcatAll(t *testing.T) { + add := MakeSemigroup(func(a, b int) int { return a + b }) + concatAll := ConcatAll(add) + + // Test with various arrays + assert.Equal(t, 10, concatAll(0)([]int{1, 2, 3, 4})) + assert.Equal(t, 20, concatAll(10)([]int{1, 2, 3, 4})) + assert.Equal(t, 5, concatAll(5)([]int{})) + assert.Equal(t, 15, concatAll(0)([]int{15})) + + // Test with string concatenation + concat := MakeSemigroup(func(a, b string) string { return a + b }) + concatAllStr := ConcatAll(concat) + + assert.Equal(t, "hello", concatAllStr("")([]string{"h", "e", "l", "l", "o"})) + assert.Equal(t, "prefix_abc", concatAllStr("prefix_")([]string{"a", "b", "c"})) +} + +// Test MonadConcatAll +func TestMonadConcatAll(t *testing.T) { + add := MakeSemigroup(func(a, b int) int { return a + b }) + monadConcatAll := MonadConcatAll(add) + + // Test with various arrays + assert.Equal(t, 10, monadConcatAll([]int{1, 2, 3, 4}, 0)) + assert.Equal(t, 20, monadConcatAll([]int{1, 2, 3, 4}, 10)) + assert.Equal(t, 5, monadConcatAll([]int{}, 5)) + assert.Equal(t, 15, monadConcatAll([]int{15}, 0)) + + // Test with multiplication + mul := MakeSemigroup(func(a, b int) int { return a * b }) + monadConcatAllMul := MonadConcatAll(mul) + + assert.Equal(t, 24, monadConcatAllMul([]int{2, 3, 4}, 1)) + assert.Equal(t, 120, monadConcatAllMul([]int{2, 3, 4, 5}, 1)) +} + +// Test GenericConcatAll with custom slice type +func TestGenericConcatAll(t *testing.T) { + type MyInts []int + + add := MakeSemigroup(func(a, b int) int { return a + b }) + concatAll := GenericConcatAll[MyInts](add) + + assert.Equal(t, 6, concatAll(0)(MyInts{1, 2, 3})) + assert.Equal(t, 16, concatAll(10)(MyInts{1, 2, 3})) + assert.Equal(t, 5, concatAll(5)(MyInts{})) +} + +// Test GenericMonadConcatAll with custom slice type +func TestGenericMonadConcatAll(t *testing.T) { + type MyInts []int + + add := MakeSemigroup(func(a, b int) int { return a + b }) + monadConcatAll := GenericMonadConcatAll[MyInts](add) + + assert.Equal(t, 6, monadConcatAll(MyInts{1, 2, 3}, 0)) + assert.Equal(t, 16, monadConcatAll(MyInts{1, 2, 3}, 10)) + assert.Equal(t, 5, monadConcatAll(MyInts{}, 5)) +} + +// Test ApplySemigroup +func TestApplySemigroup(t *testing.T) { + // Base semigroup for integers + add := MakeSemigroup(func(a, b int) int { return a + b }) + + // Simple HKT simulation using slices + type HKT []int + + fmap := func(hkt HKT, f func(int) func(int) int) []func(int) int { + result := make([]func(int) int, len(hkt)) + for i, v := range hkt { + result[i] = f(v) + } + return result + } + + fap := func(fs []func(int) int, hkt HKT) HKT { + result := make(HKT, 0) + for _, f := range fs { + for _, v := range hkt { + result = append(result, f(v)) + } + } + return result + } + + applySG := ApplySemigroup[int, HKT, []func(int) int](fmap, fap, add) + + hkt1 := HKT{1, 2} + hkt2 := HKT{3, 4} + + result := applySG.Concat(hkt1, hkt2) + // Should apply the semigroup operation to all combinations + assert.NotEmpty(t, result) +} + +// Test AltSemigroup +func TestAltSemigroup(t *testing.T) { + // Simple HKT simulation using Option-like type + type Option[A any] struct { + value A + hasValue bool + } + + falt := func(first Option[int], second func() Option[int]) Option[int] { + if first.hasValue { + return first + } + return second() + } + + altSG := AltSemigroup[Option[int], func() Option[int]](falt) + + some := Option[int]{value: 42, hasValue: true} + none := Option[int]{hasValue: false} + other := Option[int]{value: 100, hasValue: true} + + // First has value, should return first + result1 := altSG.Concat(some, none) + assert.True(t, result1.hasValue) + assert.Equal(t, 42, result1.value) + + // First is none, should return second + result2 := altSG.Concat(none, other) + assert.True(t, result2.hasValue) + assert.Equal(t, 100, result2.value) + + // Both have values, should return first + result3 := altSG.Concat(some, other) + assert.True(t, result3.hasValue) + assert.Equal(t, 42, result3.value) +} + +// Test associativity law for various semigroups +func TestAssociativityLaw(t *testing.T) { + testCases := []struct { + name string + sg Semigroup[int] + a, b, c int + }{ + {"Addition", MakeSemigroup(func(a, b int) int { return a + b }), 1, 2, 3}, + {"Multiplication", MakeSemigroup(func(a, b int) int { return a * b }), 2, 3, 4}, + {"Max", MakeSemigroup(func(a, b int) int { + if a > b { + return a + } + return b + }), 5, 10, 3}, + {"Min", MakeSemigroup(func(a, b int) int { + if a < b { + return a + } + return b + }), 5, 10, 3}, + {"First", First[int](), 1, 2, 3}, + {"Last", Last[int](), 1, 2, 3}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // (a • b) • c + left := tc.sg.Concat(tc.sg.Concat(tc.a, tc.b), tc.c) + // a • (b • c) + right := tc.sg.Concat(tc.a, tc.sg.Concat(tc.b, tc.c)) + + assert.Equal(t, left, right, "Associativity law failed for %s", tc.name) + }) + } +} + +// Test associativity law for string semigroups +func TestAssociativityLawString(t *testing.T) { + concat := MakeSemigroup(func(a, b string) string { return a + b }) + + a, b, c := "hello", " ", "world" + + left := concat.Concat(concat.Concat(a, b), c) + right := concat.Concat(a, concat.Concat(b, c)) + + assert.Equal(t, left, right) + assert.Equal(t, "hello world", left) +} + +// Test complex types +func TestComplexTypes(t *testing.T) { + type Config struct { + Timeout int + Retries int + } + + configSG := MakeSemigroup(func(a, b Config) Config { + maxTimeout := a.Timeout + if b.Timeout > maxTimeout { + maxTimeout = b.Timeout + } + return Config{ + Timeout: maxTimeout, + Retries: a.Retries + b.Retries, + } + }) + + c1 := Config{Timeout: 30, Retries: 3} + c2 := Config{Timeout: 60, Retries: 5} + c3 := Config{Timeout: 45, Retries: 2} + + result := configSG.Concat(configSG.Concat(c1, c2), c3) + assert.Equal(t, 60, result.Timeout) + assert.Equal(t, 10, result.Retries) + + // Test associativity + left := configSG.Concat(configSG.Concat(c1, c2), c3) + right := configSG.Concat(c1, configSG.Concat(c2, c3)) + assert.Equal(t, left, right) +} + +// Test with slices +func TestSliceSemigroup(t *testing.T) { + sliceConcat := MakeSemigroup(func(a, b []int) []int { + result := make([]int, len(a)+len(b)) + copy(result, a) + copy(result[len(a):], b) + return result + }) + + s1 := []int{1, 2, 3} + s2 := []int{4, 5} + s3 := []int{6} + + result := sliceConcat.Concat(sliceConcat.Concat(s1, s2), s3) + assert.Equal(t, []int{1, 2, 3, 4, 5, 6}, result) + + // Test associativity + left := sliceConcat.Concat(sliceConcat.Concat(s1, s2), s3) + right := sliceConcat.Concat(s1, sliceConcat.Concat(s2, s3)) + assert.Equal(t, left, right) +} + +// Test with maps +func TestMapSemigroup(t *testing.T) { + mapMerge := MakeSemigroup(func(a, b map[string]int) map[string]int { + result := make(map[string]int) + for k, v := range a { + result[k] = v + } + for k, v := range b { + result[k] = v // Later values override + } + return result + }) + + m1 := map[string]int{"a": 1, "b": 2} + m2 := map[string]int{"b": 3, "c": 4} + m3 := map[string]int{"c": 5, "d": 6} + + result := mapMerge.Concat(mapMerge.Concat(m1, m2), m3) + assert.Equal(t, 1, result["a"]) + assert.Equal(t, 3, result["b"]) + assert.Equal(t, 5, result["c"]) + assert.Equal(t, 6, result["d"]) +} + +// Benchmark tests +func BenchmarkFirst(b *testing.B) { + first := First[int]() + for i := 0; i < b.N; i++ { + first.Concat(1, 2) + } +} + +func BenchmarkLast(b *testing.B) { + last := Last[int]() + for i := 0; i < b.N; i++ { + last.Concat(1, 2) + } +} + +func BenchmarkMakeSemigroupAdd(b *testing.B) { + add := MakeSemigroup(func(a, b int) int { return a + b }) + for i := 0; i < b.N; i++ { + add.Concat(1, 2) + } +} + +func BenchmarkReverse(b *testing.B) { + sub := MakeSemigroup(func(a, b int) int { return a - b }) + reversed := Reverse(sub) + for i := 0; i < b.N; i++ { + reversed.Concat(10, 3) + } +} + +func BenchmarkConcatAll(b *testing.B) { + add := MakeSemigroup(func(a, b int) int { return a + b }) + concatAll := ConcatAll(add) + arr := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} + + b.ResetTimer() + for i := 0; i < b.N; i++ { + concatAll(0)(arr) + } +} + +func BenchmarkFunctionSemigroup(b *testing.B) { + add := MakeSemigroup(func(a, b int) int { return a + b }) + funcSG := FunctionSemigroup[string](add) + + f := func(s string) int { return len(s) } + g := func(s string) int { return len(s) * 2 } + combined := funcSG.Concat(f, g) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + combined("hello") + } +} diff --git a/v2/state/eq.go b/v2/state/eq.go new file mode 100644 index 0000000..a5b5ba2 --- /dev/null +++ b/v2/state/eq.go @@ -0,0 +1,36 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package state + +import ( + "github.com/IBM/fp-go/v2/eq" + "github.com/IBM/fp-go/v2/pair" +) + +// Constructs an equal predicate for a [State] +func Eq[S, A any](w eq.Eq[S], a eq.Eq[A]) func(S) eq.Eq[State[S, A]] { + eqp := pair.Eq(w, a) + return func(s S) eq.Eq[State[S, A]] { + return eq.FromEquals(func(l, r State[S, A]) bool { + return eqp.Equals(l(s), r(s)) + }) + } +} + +// FromStrictEquals constructs an [eq.Eq] from the canonical comparison function +func FromStrictEquals[S, A comparable]() func(S) eq.Eq[State[S, A]] { + return Eq(eq.FromStrictEquals[S](), eq.FromStrictEquals[A]()) +} diff --git a/v2/state/monad.go b/v2/state/monad.go new file mode 100644 index 0000000..851bc96 --- /dev/null +++ b/v2/state/monad.go @@ -0,0 +1,87 @@ +// Copyright (c) 2024 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package state + +import ( + "github.com/IBM/fp-go/v2/internal/applicative" + "github.com/IBM/fp-go/v2/internal/functor" + "github.com/IBM/fp-go/v2/internal/monad" + "github.com/IBM/fp-go/v2/internal/pointed" +) + +type statePointed[S, A any] struct{} + +type stateFunctor[S, A, B any] struct{} + +type stateApplicative[S, A, B any] struct{} + +type stateMonad[S, A, B any] struct{} + +func (o *statePointed[S, A]) Of(a A) State[S, A] { + return Of[S](a) +} + +func (o *stateApplicative[S, A, B]) Of(a A) State[S, A] { + return Of[S](a) +} + +func (o *stateMonad[S, A, B]) Of(a A) State[S, A] { + return Of[S](a) +} + +func (o *stateFunctor[S, A, B]) Map(f func(A) B) func(State[S, A]) State[S, B] { + return Map[S](f) +} + +func (o *stateApplicative[S, A, B]) Map(f func(A) B) func(State[S, A]) State[S, B] { + return Map[S](f) +} + +func (o *stateMonad[S, A, B]) Map(f func(A) B) func(State[S, A]) State[S, B] { + return Map[S](f) +} + +func (o *stateMonad[S, A, B]) Chain(f func(A) State[S, B]) func(State[S, A]) State[S, B] { + return Chain(f) +} + +func (o *stateApplicative[S, A, B]) Ap(fa State[S, A]) func(State[S, func(A) B]) State[S, B] { + return Ap[B](fa) +} + +func (o *stateMonad[S, A, B]) Ap(fa State[S, A]) func(State[S, func(A) B]) State[S, B] { + return Ap[B](fa) +} + +// Pointed implements the pointed operations for [State] +func Pointed[S, A any]() pointed.Pointed[A, State[S, A]] { + return &statePointed[S, A]{} +} + +// Functor implements the functor operations for [State] +func Functor[S, A, B any]() functor.Functor[A, B, State[S, A], State[S, B]] { + return &stateFunctor[S, A, B]{} +} + +// Applicative implements the applicative operations for [State] +func Applicative[S, A, B any]() applicative.Applicative[A, B, State[S, A], State[S, B], State[S, func(A) B]] { + return &stateApplicative[S, A, B]{} +} + +// Monad implements the monadic operations for [State] +func Monad[S, A, B any]() monad.Monad[A, B, State[S, A], State[S, B], State[S, func(A) B]] { + return &stateMonad[S, A, B]{} +} diff --git a/v2/state/state.go b/v2/state/state.go new file mode 100644 index 0000000..4edd377 --- /dev/null +++ b/v2/state/state.go @@ -0,0 +1,133 @@ +// Copyright (c) 2024 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package state + +import ( + "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/internal/chain" + "github.com/IBM/fp-go/v2/internal/functor" + "github.com/IBM/fp-go/v2/pair" +) + +var ( + undefined any = struct{}{} +) + +func Get[S any]() State[S, S] { + return pair.Of[S] +} + +func Gets[FCT ~func(S) A, A, S any](f FCT) State[S, A] { + return func(s S) Pair[S, A] { + return pair.MakePair(s, f(s)) + } +} + +func Put[S any]() State[S, any] { + return function.Bind2nd(pair.MakePair[S, any], undefined) +} + +func Modify[FCT ~func(S) S, S any](f FCT) State[S, any] { + return function.Flow2( + f, + function.Bind2nd(pair.MakePair[S, any], undefined), + ) +} + +func Of[S, A any](a A) State[S, A] { + return function.Bind2nd(pair.MakePair[S, A], a) +} + +func MonadMap[S any, FCT ~func(A) B, A, B any](fa State[S, A], f FCT) State[S, B] { + return func(s S) Pair[S, B] { + p2 := fa(s) + return pair.MakePair(pair.Head(p2), f(pair.Tail(p2))) + } +} + +func Map[S any, FCT ~func(A) B, A, B any](f FCT) Operator[S, A, B] { + return function.Bind2nd(MonadMap[S, FCT, A, B], f) +} + +func MonadChain[S any, FCT ~func(A) State[S, B], A, B any](fa State[S, A], f FCT) State[S, B] { + return func(s S) Pair[S, B] { + a := fa(s) + return f(pair.Tail(a))(pair.Head(a)) + } +} + +func Chain[S any, FCT ~func(A) State[S, B], A, B any](f FCT) Operator[S, A, B] { + return function.Bind2nd(MonadChain[S, FCT, A, B], f) +} + +func MonadAp[B, S, A any](fab State[S, func(A) B], fa State[S, A]) State[S, B] { + return func(s S) Pair[S, B] { + f := fab(s) + a := fa(pair.Head(f)) + + return pair.MakePair(pair.Head(a), pair.Tail(f)(pair.Tail(a))) + } +} + +func Ap[B, S, A any](ga State[S, A]) Operator[S, func(A) B, B] { + return function.Bind2nd(MonadAp[B, S, A], ga) +} + +func MonadChainFirst[S any, FCT ~func(A) State[S, B], A, B any](ma State[S, A], f FCT) State[S, A] { + return chain.MonadChainFirst( + MonadChain[S, func(A) State[S, A], A, A], + MonadMap[S, func(B) A], + ma, + f, + ) +} + +func ChainFirst[S any, FCT ~func(A) State[S, B], A, B any](f FCT) Operator[S, A, A] { + return chain.ChainFirst( + Chain[S, func(A) State[S, A], A, A], + Map[S, func(B) A], + f, + ) +} + +func Flatten[S, A any](mma State[S, State[S, A]]) State[S, A] { + return MonadChain(mma, function.Identity[State[S, A]]) +} + +func Execute[A, S any](s S) func(State[S, A]) S { + return func(fa State[S, A]) S { + return pair.Head(fa(s)) + } +} + +func Evaluate[A, S any](s S) func(State[S, A]) A { + return func(fa State[S, A]) A { + return pair.Tail(fa(s)) + } +} + +func MonadFlap[FAB ~func(A) B, S, A, B any](fab State[S, FAB], a A) State[S, B] { + return functor.MonadFlap( + MonadMap[S, func(FAB) B], + fab, + a) +} + +func Flap[S, A, B any](a A) Operator[S, func(A) B, B] { + return functor.Flap( + Map[S, func(func(A) B) B], + a) +} diff --git a/v2/state/testing/laws.go b/v2/state/testing/laws.go new file mode 100644 index 0000000..6b08347 --- /dev/null +++ b/v2/state/testing/laws.go @@ -0,0 +1,78 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package testing + +import ( + "testing" + + EQ "github.com/IBM/fp-go/v2/eq" + L "github.com/IBM/fp-go/v2/internal/monad/testing" + ST "github.com/IBM/fp-go/v2/state" +) + +// AssertLaws asserts the apply monad laws for the `Either` monad +func AssertLaws[S, A, B, C any](t *testing.T, + eqw EQ.Eq[S], + eqa EQ.Eq[A], + eqb EQ.Eq[B], + eqc EQ.Eq[C], + + ab func(A) B, + bc func(B) C, + + s S, +) func(a A) bool { + + fofc := ST.Pointed[S, C]() + fofaa := ST.Pointed[S, func(A) A]() + fofbc := ST.Pointed[S, func(B) C]() + fofabb := ST.Pointed[S, func(func(A) B) B]() + + fmap := ST.Functor[S, func(B) C, func(func(A) B) func(A) C]() + + fapabb := ST.Applicative[S, func(A) B, B]() + fapabac := ST.Applicative[S, func(A) B, func(A) C]() + + maa := ST.Monad[S, A, A]() + mab := ST.Monad[S, A, B]() + mac := ST.Monad[S, A, C]() + mbc := ST.Monad[S, B, C]() + + return L.MonadAssertLaws(t, + ST.Eq(eqw, eqa)(s), + ST.Eq(eqw, eqb)(s), + ST.Eq(eqw, eqc)(s), + + fofc, + fofaa, + fofbc, + fofabb, + + fmap, + + fapabb, + fapabac, + + maa, + mab, + mac, + mbc, + + ab, + bc, + ) + +} diff --git a/v2/state/testing/laws_test.go b/v2/state/testing/laws_test.go new file mode 100644 index 0000000..32dbd51 --- /dev/null +++ b/v2/state/testing/laws_test.go @@ -0,0 +1,49 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package testing + +import ( + "fmt" + "testing" + + A "github.com/IBM/fp-go/v2/array" + EQ "github.com/IBM/fp-go/v2/eq" + "github.com/stretchr/testify/assert" +) + +func TestMonadLaws(t *testing.T) { + // some comparison + eqs := A.Eq[string](EQ.FromStrictEquals[string]()) + eqa := EQ.FromStrictEquals[bool]() + eqb := EQ.FromStrictEquals[int]() + eqc := EQ.FromStrictEquals[string]() + + ab := func(a bool) int { + if a { + return 1 + } + return 0 + } + + bc := func(b int) string { + return fmt.Sprintf("value %d", b) + } + + laws := AssertLaws(t, eqs, eqa, eqb, eqc, ab, bc, A.Empty[string]()) + + assert.True(t, laws(true)) + assert.True(t, laws(false)) +} diff --git a/v2/state/types.go b/v2/state/types.go new file mode 100644 index 0000000..588cf0b --- /dev/null +++ b/v2/state/types.go @@ -0,0 +1,32 @@ +// Copyright (c) 2024 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package state + +import ( + "github.com/IBM/fp-go/v2/pair" + "github.com/IBM/fp-go/v2/reader" +) + +type ( + // some type aliases + Reader[R, A any] = reader.Reader[R, A] + Pair[L, R any] = pair.Pair[L, R] + + // State represents an operation on top of a current [State] that produces a value and a new [State] + State[S, A any] = Reader[S, pair.Pair[S, A]] + + Operator[S, A, B any] = Reader[State[S, A], State[S, B]] +) diff --git a/v2/statereaderioeither/eq.go b/v2/statereaderioeither/eq.go new file mode 100644 index 0000000..1aebeb6 --- /dev/null +++ b/v2/statereaderioeither/eq.go @@ -0,0 +1,41 @@ +// Copyright (c) 2024 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package statereaderioeither + +import ( + "github.com/IBM/fp-go/v2/eq" + "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/readerioeither" +) + +// Eq implements the equals predicate for values contained in the [StateReaderIOEither] monad +func Eq[ + S, R, E, A any](eqr eq.Eq[ReaderIOEither[R, E, Pair[S, A]]]) func(S) eq.Eq[StateReaderIOEither[S, R, E, A]] { + return func(s S) eq.Eq[StateReaderIOEither[S, R, E, A]] { + return eq.FromEquals(func(l, r StateReaderIOEither[S, R, E, A]) bool { + return eqr.Equals(l(s), r(s)) + }) + } +} + +// FromStrictEquals constructs an [eq.Eq] from the canonical comparison function +func FromStrictEquals[ + S, R any, E, A comparable]() func(R) func(S) eq.Eq[StateReaderIOEither[S, R, E, A]] { + return function.Flow2( + readerioeither.FromStrictEquals[R, E, Pair[S, A]](), + Eq[S, R, E, A], + ) +} diff --git a/v2/statereaderioeither/monad.go b/v2/statereaderioeither/monad.go new file mode 100644 index 0000000..1149aae --- /dev/null +++ b/v2/statereaderioeither/monad.go @@ -0,0 +1,103 @@ +// Copyright (c) 2024 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package statereaderioeither + +import ( + "github.com/IBM/fp-go/v2/internal/applicative" + "github.com/IBM/fp-go/v2/internal/functor" + "github.com/IBM/fp-go/v2/internal/monad" + "github.com/IBM/fp-go/v2/internal/pointed" +) + +type stateReaderIOEitherPointed[ + S, R, E, A any, +] struct{} + +type stateReaderIOEitherFunctor[ + S, R, E, A, B any, +] struct{} + +type stateReaderIOEitherApplicative[ + S, R, E, A, B any, +] struct{} + +type stateReaderIOEitherMonad[ + S, R, E, A, B any, +] struct{} + +func (o *stateReaderIOEitherPointed[S, R, E, A]) Of(a A) StateReaderIOEither[S, R, E, A] { + return Of[S, R, E](a) +} + +func (o *stateReaderIOEitherMonad[S, R, E, A, B]) Of(a A) StateReaderIOEither[S, R, E, A] { + return Of[S, R, E](a) +} + +func (o *stateReaderIOEitherApplicative[S, R, E, A, B]) Of(a A) StateReaderIOEither[S, R, E, A] { + return Of[S, R, E](a) +} + +func (o *stateReaderIOEitherMonad[S, R, E, A, B]) Map(f func(A) B) Operator[S, R, E, A, B] { + return Map[S, R, E](f) +} + +func (o *stateReaderIOEitherApplicative[S, R, E, A, B]) Map(f func(A) B) Operator[S, R, E, A, B] { + return Map[S, R, E](f) +} + +func (o *stateReaderIOEitherFunctor[S, R, E, A, B]) Map(f func(A) B) Operator[S, R, E, A, B] { + return Map[S, R, E](f) +} + +func (o *stateReaderIOEitherMonad[S, R, E, A, B]) Chain(f func(A) StateReaderIOEither[S, R, E, B]) Operator[S, R, E, A, B] { + return Chain(f) +} + +func (o *stateReaderIOEitherMonad[S, R, E, A, B]) Ap(fa StateReaderIOEither[S, R, E, A]) Operator[S, R, E, func(A) B, B] { + return Ap[B](fa) +} + +func (o *stateReaderIOEitherApplicative[S, R, E, A, B]) Ap(fa StateReaderIOEither[S, R, E, A]) Operator[S, R, E, func(A) B, B] { + return Ap[B](fa) +} + +// Pointed implements the [pointed.Pointed] operations for [StateReaderIOEither] +func Pointed[ + S, R, E, A any, +]() pointed.Pointed[A, StateReaderIOEither[S, R, E, A]] { + return &stateReaderIOEitherPointed[S, R, E, A]{} +} + +// Functor implements the [functor.Functor] operations for [StateReaderIOEither] +func Functor[ + S, R, E, A, B any, +]() functor.Functor[A, B, StateReaderIOEither[S, R, E, A], StateReaderIOEither[S, R, E, B]] { + return &stateReaderIOEitherFunctor[S, R, E, A, B]{} +} + +// Applicative implements the [applicative.Applicative] operations for [StateReaderIOEither] +func Applicative[ + S, R, E, A, B any, +]() applicative.Applicative[A, B, StateReaderIOEither[S, R, E, A], StateReaderIOEither[S, R, E, B], StateReaderIOEither[S, R, E, func(A) B]] { + return &stateReaderIOEitherApplicative[S, R, E, A, B]{} +} + +// Monad implements the [monad.Monad] operations for [StateReaderIOEither] +func Monad[ + S, R, E, A, B any, +]() monad.Monad[A, B, StateReaderIOEither[S, R, E, A], StateReaderIOEither[S, R, E, B], StateReaderIOEither[S, R, E, func(A) B]] { + return &stateReaderIOEitherMonad[S, R, E, A, B]{} +} diff --git a/v2/statereaderioeither/state.go b/v2/statereaderioeither/state.go new file mode 100644 index 0000000..640f747 --- /dev/null +++ b/v2/statereaderioeither/state.go @@ -0,0 +1,185 @@ +// Copyright (c) 2024 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package statereaderioeither + +import ( + "github.com/IBM/fp-go/v2/either" + "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/internal/statet" + "github.com/IBM/fp-go/v2/readerioeither" +) + +func Left[S, R, A, E any](e E) StateReaderIOEither[S, R, E, A] { + return function.Constant1[S](readerioeither.Left[R, Pair[S, A]](e)) +} + +func Right[S, R, E, A any](a A) StateReaderIOEither[S, R, E, A] { + return statet.Of[StateReaderIOEither[S, R, E, A]](readerioeither.Of[R, E, Pair[S, A]], a) +} + +func Of[S, R, E, A any](a A) StateReaderIOEither[S, R, E, A] { + return Right[S, R, E](a) +} + +func MonadMap[S, R, E, A, B any](fa StateReaderIOEither[S, R, E, A], f func(A) B) StateReaderIOEither[S, R, E, B] { + return statet.MonadMap[StateReaderIOEither[S, R, E, A], StateReaderIOEither[S, R, E, B]]( + readerioeither.MonadMap[R, E, Pair[S, A], Pair[S, B]], + fa, + f, + ) +} + +func Map[S, R, E, A, B any](f func(A) B) Operator[S, R, E, A, B] { + return statet.Map[StateReaderIOEither[S, R, E, A], StateReaderIOEither[S, R, E, B]]( + readerioeither.Map[R, E, Pair[S, A], Pair[S, B]], + f, + ) +} + +func MonadChain[S, R, E, A, B any](fa StateReaderIOEither[S, R, E, A], f func(A) StateReaderIOEither[S, R, E, B]) StateReaderIOEither[S, R, E, B] { + return statet.MonadChain( + readerioeither.MonadChain[R, E, Pair[S, A], Pair[S, B]], + fa, + f, + ) +} + +func Chain[S, R, E, A, B any](f func(A) StateReaderIOEither[S, R, E, B]) Operator[S, R, E, A, B] { + return statet.Chain[StateReaderIOEither[S, R, E, A]]( + readerioeither.Chain[R, E, Pair[S, A], Pair[S, B]], + f, + ) +} + +func MonadAp[B, S, R, E, A any](fab StateReaderIOEither[S, R, E, func(A) B], fa StateReaderIOEither[S, R, E, A]) StateReaderIOEither[S, R, E, B] { + return statet.MonadAp[StateReaderIOEither[S, R, E, A], StateReaderIOEither[S, R, E, B]]( + readerioeither.MonadMap[R, E, Pair[S, A], Pair[S, B]], + readerioeither.MonadChain[R, E, Pair[S, func(A) B], Pair[S, B]], + fab, + fa, + ) +} + +func Ap[B, S, R, E, A any](fa StateReaderIOEither[S, R, E, A]) Operator[S, R, E, func(A) B, B] { + return statet.Ap[StateReaderIOEither[S, R, E, A], StateReaderIOEither[S, R, E, B], StateReaderIOEither[S, R, E, func(A) B]]( + readerioeither.Map[R, E, Pair[S, A], Pair[S, B]], + readerioeither.Chain[R, E, Pair[S, func(A) B], Pair[S, B]], + fa, + ) +} + +func FromReaderIOEither[S, R, E, A any](fa readerioeither.ReaderIOEither[R, E, A]) StateReaderIOEither[S, R, E, A] { + return statet.FromF[StateReaderIOEither[S, R, E, A]]( + readerioeither.MonadMap[R, E, A], + fa, + ) +} + +func FromReaderEither[S, R, E, A any](fa ReaderEither[R, E, A]) StateReaderIOEither[S, R, E, A] { + return FromReaderIOEither[S](readerioeither.FromReaderEither(fa)) +} + +func FromIOEither[S, R, E, A any](fa IOEither[E, A]) StateReaderIOEither[S, R, E, A] { + return FromReaderIOEither[S](readerioeither.FromIOEither[R](fa)) +} + +func FromState[R, E, S, A any](sa State[S, A]) StateReaderIOEither[S, R, E, A] { + return statet.FromState[StateReaderIOEither[S, R, E, A]](readerioeither.Of[R, E, Pair[S, A]], sa) +} + +func FromIO[S, R, E, A any](fa IO[A]) StateReaderIOEither[S, R, E, A] { + return FromReaderIOEither[S](readerioeither.FromIO[R, E](fa)) +} + +func FromReader[S, E, R, A any](fa Reader[R, A]) StateReaderIOEither[S, R, E, A] { + return FromReaderIOEither[S](readerioeither.FromReader[E](fa)) +} + +func FromEither[S, R, E, A any](ma Either[E, A]) StateReaderIOEither[S, R, E, A] { + return either.MonadFold(ma, Left[S, R, A, E], Right[S, R, E, A]) +} + +// Combinators + +func Local[S, E, A, B, R1, R2 any](f func(R2) R1) func(StateReaderIOEither[S, R1, E, A]) StateReaderIOEither[S, R2, E, A] { + return func(ma StateReaderIOEither[S, R1, E, A]) StateReaderIOEither[S, R2, E, A] { + return function.Flow2(ma, readerioeither.Local[R1, R2, E, Pair[S, A]](f)) + } +} + +func Asks[ + S, R, E, A any, +](f func(R) StateReaderIOEither[S, R, E, A]) StateReaderIOEither[S, R, E, A] { + return func(s S) ReaderIOEither[R, E, Pair[S, A]] { + return func(r R) IOEither[E, Pair[S, A]] { + return f(r)(s)(r) + } + } +} + +func FromEitherK[S, R, E, A, B any](f func(A) Either[E, B]) func(A) StateReaderIOEither[S, R, E, B] { + return function.Flow2( + f, + FromEither[S, R, E, B], + ) +} + +func FromIOK[S, R, E, A, B any](f func(A) IO[B]) func(A) StateReaderIOEither[S, R, E, B] { + return function.Flow2( + f, + FromIO[S, R, E, B], + ) +} + +func FromIOEitherK[ + S, R, E, A, B any, +](f func(A) IOEither[E, B]) func(A) StateReaderIOEither[S, R, E, B] { + return function.Flow2( + f, + FromIOEither[S, R, E, B], + ) +} + +func FromReaderIOEitherK[S, R, E, A, B any](f func(A) readerioeither.ReaderIOEither[R, E, B]) func(A) StateReaderIOEither[S, R, E, B] { + return function.Flow2( + f, + FromReaderIOEither[S, R, E, B], + ) +} + +func MonadChainReaderIOEitherK[S, R, E, A, B any](ma StateReaderIOEither[S, R, E, A], f func(A) readerioeither.ReaderIOEither[R, E, B]) StateReaderIOEither[S, R, E, B] { + return MonadChain(ma, FromReaderIOEitherK[S](f)) +} + +func ChainReaderIOEitherK[S, R, E, A, B any](f func(A) readerioeither.ReaderIOEither[R, E, B]) Operator[S, R, E, A, B] { + return Chain(FromReaderIOEitherK[S](f)) +} + +func MonadChainIOEitherK[S, R, E, A, B any](ma StateReaderIOEither[S, R, E, A], f func(A) IOEither[E, B]) StateReaderIOEither[S, R, E, B] { + return MonadChain(ma, FromIOEitherK[S, R](f)) +} + +func ChainIOEitherK[S, R, E, A, B any](f func(A) IOEither[E, B]) Operator[S, R, E, A, B] { + return Chain(FromIOEitherK[S, R](f)) +} + +func MonadChainEitherK[S, R, E, A, B any](ma StateReaderIOEither[S, R, E, A], f func(A) Either[E, B]) StateReaderIOEither[S, R, E, B] { + return MonadChain(ma, FromEitherK[S, R](f)) +} + +func ChainEitherK[S, R, E, A, B any](f func(A) Either[E, B]) Operator[S, R, E, A, B] { + return Chain(FromEitherK[S, R](f)) +} diff --git a/v2/statereaderioeither/testing/laws.go b/v2/statereaderioeither/testing/laws.go new file mode 100644 index 0000000..afdb9cd --- /dev/null +++ b/v2/statereaderioeither/testing/laws.go @@ -0,0 +1,87 @@ +// Copyright (c) 2024 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package testing + +import ( + "testing" + + ET "github.com/IBM/fp-go/v2/either" + EQ "github.com/IBM/fp-go/v2/eq" + L "github.com/IBM/fp-go/v2/internal/monad/testing" + P "github.com/IBM/fp-go/v2/pair" + RIOE "github.com/IBM/fp-go/v2/readerioeither" + ST "github.com/IBM/fp-go/v2/statereaderioeither" +) + +// AssertLaws asserts the apply monad laws for the `Either` monad +func AssertLaws[S, E, R, A, B, C any](t *testing.T, + eqs EQ.Eq[S], + eqe EQ.Eq[E], + eqa EQ.Eq[A], + eqb EQ.Eq[B], + eqc EQ.Eq[C], + + ab func(A) B, + bc func(B) C, + + s S, + r R, +) func(a A) bool { + + eqra := RIOE.Eq[R](ET.Eq(eqe, P.Eq(eqs, eqa)))(r) + eqrb := RIOE.Eq[R](ET.Eq(eqe, P.Eq(eqs, eqb)))(r) + eqrc := RIOE.Eq[R](ET.Eq(eqe, P.Eq(eqs, eqc)))(r) + + fofc := ST.Pointed[S, R, E, C]() + fofaa := ST.Pointed[S, R, E, func(A) A]() + fofbc := ST.Pointed[S, R, E, func(B) C]() + fofabb := ST.Pointed[S, R, E, func(func(A) B) B]() + + fmap := ST.Functor[S, R, E, func(B) C, func(func(A) B) func(A) C]() + + fapabb := ST.Applicative[S, R, E, func(A) B, B]() + fapabac := ST.Applicative[S, R, E, func(A) B, func(A) C]() + + maa := ST.Monad[S, R, E, A, A]() + mab := ST.Monad[S, R, E, A, B]() + mac := ST.Monad[S, R, E, A, C]() + mbc := ST.Monad[S, R, E, B, C]() + + return L.MonadAssertLaws(t, + ST.Eq(eqra)(s), + ST.Eq(eqrb)(s), + ST.Eq(eqrc)(s), + + fofc, + fofaa, + fofbc, + fofabb, + + fmap, + + fapabb, + fapabac, + + maa, + mab, + mac, + mbc, + + ab, + bc, + ) + +} diff --git a/v2/statereaderioeither/testing/laws_test.go b/v2/statereaderioeither/testing/laws_test.go new file mode 100644 index 0000000..c05ffaa --- /dev/null +++ b/v2/statereaderioeither/testing/laws_test.go @@ -0,0 +1,51 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package testing + +import ( + "context" + "fmt" + "testing" + + A "github.com/IBM/fp-go/v2/array" + EQ "github.com/IBM/fp-go/v2/eq" + "github.com/stretchr/testify/assert" +) + +func TestMonadLaws(t *testing.T) { + // some comparison + eqs := A.Eq(EQ.FromStrictEquals[string]()) + eqe := EQ.FromStrictEquals[error]() + eqa := EQ.FromStrictEquals[bool]() + eqb := EQ.FromStrictEquals[int]() + eqc := EQ.FromStrictEquals[string]() + + ab := func(a bool) int { + if a { + return 1 + } + return 0 + } + + bc := func(b int) string { + return fmt.Sprintf("value %d", b) + } + + laws := AssertLaws(t, eqs, eqe, eqa, eqb, eqc, ab, bc, A.Empty[string](), context.Background()) + + assert.True(t, laws(true)) + assert.True(t, laws(false)) +} diff --git a/v2/statereaderioeither/type.go b/v2/statereaderioeither/type.go new file mode 100644 index 0000000..df28f61 --- /dev/null +++ b/v2/statereaderioeither/type.go @@ -0,0 +1,40 @@ +// Copyright (c) 2024 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package statereaderioeither + +import ( + "github.com/IBM/fp-go/v2/either" + "github.com/IBM/fp-go/v2/io" + "github.com/IBM/fp-go/v2/ioeither" + "github.com/IBM/fp-go/v2/pair" + "github.com/IBM/fp-go/v2/reader" + "github.com/IBM/fp-go/v2/readereither" + "github.com/IBM/fp-go/v2/readerioeither" + "github.com/IBM/fp-go/v2/state" +) + +type ( + State[S, A any] = state.State[S, A] + Pair[L, R any] = pair.Pair[L, R] + Reader[R, A any] = reader.Reader[R, A] + Either[E, A any] = either.Either[E, A] + IO[A any] = io.IO[A] + IOEither[E, A any] = ioeither.IOEither[E, A] + ReaderIOEither[R, E, A any] = readerioeither.ReaderIOEither[R, E, A] + ReaderEither[R, E, A any] = readereither.ReaderEither[R, E, A] + StateReaderIOEither[S, R, E, A any] = Reader[S, ReaderIOEither[R, E, Pair[S, A]]] + Operator[S, R, E, A, B any] = Reader[StateReaderIOEither[S, R, E, A], StateReaderIOEither[S, R, E, B]] +) diff --git a/v2/string/generic/string.go b/v2/string/generic/string.go new file mode 100644 index 0000000..584cdb9 --- /dev/null +++ b/v2/string/generic/string.go @@ -0,0 +1,41 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package generic + +// ToBytes converts the string to bytes +func ToBytes[T ~string](s T) []byte { + return []byte(s) +} + +// ToRunes converts the string to runes +func ToRunes[T ~string](s T) []rune { + return []rune(s) +} + +// IsEmpty tests if the string is empty +func IsEmpty[T ~string](s T) bool { + return len(s) == 0 +} + +// IsNonEmpty tests if the string is not empty +func IsNonEmpty[T ~string](s T) bool { + return len(s) > 0 +} + +// Size returns the size of the string +func Size[T ~string](s T) int { + return len(s) +} diff --git a/v2/string/monoid.go b/v2/string/monoid.go new file mode 100644 index 0000000..04e8a3f --- /dev/null +++ b/v2/string/monoid.go @@ -0,0 +1,23 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package string + +import ( + M "github.com/IBM/fp-go/v2/monoid" +) + +// Monoid is the monoid implementing string concatenation +var Monoid = M.MakeMonoid(concat, "") diff --git a/v2/string/monoid_test.go b/v2/string/monoid_test.go new file mode 100644 index 0000000..ef77f42 --- /dev/null +++ b/v2/string/monoid_test.go @@ -0,0 +1,26 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package string + +import ( + "testing" + + M "github.com/IBM/fp-go/v2/monoid/testing" +) + +func TestMonoid(t *testing.T) { + M.AssertLaws(t, Monoid)([]string{"", "a", "some value"}) +} diff --git a/v2/string/semigroup.go b/v2/string/semigroup.go new file mode 100644 index 0000000..e1c000a --- /dev/null +++ b/v2/string/semigroup.go @@ -0,0 +1,30 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package string + +import ( + "fmt" + + S "github.com/IBM/fp-go/v2/semigroup" +) + +func concat(left string, right string) string { + return fmt.Sprintf("%s%s", left, right) +} + +func Semigroup() S.Semigroup[string] { + return S.MakeSemigroup(concat) +} diff --git a/v2/string/string.go b/v2/string/string.go new file mode 100644 index 0000000..398ddbf --- /dev/null +++ b/v2/string/string.go @@ -0,0 +1,75 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package string + +import ( + "fmt" + "strings" + + F "github.com/IBM/fp-go/v2/function" + "github.com/IBM/fp-go/v2/ord" +) + +var ( + // ToUpperCase converts the string to uppercase + ToUpperCase = strings.ToUpper + + // ToLowerCase converts the string to lowercase + ToLowerCase = strings.ToLower + + // Ord implements the default ordering for strings + Ord = ord.FromStrictCompare[string]() + + // Join joins strings + Join = F.Curry2(F.Bind2nd[[]string, string, string])(strings.Join) + + // Equals returns a predicate that tests if a string is equal + Equals = F.Curry2(Eq) + + // Includes returns a predicate that tests for the existence of the search string + Includes = F.Curry2(F.Swap(strings.Contains)) +) + +func Eq(left string, right string) bool { + return left == right +} + +func ToBytes(s string) []byte { + return []byte(s) +} + +func ToRunes(s string) []rune { + return []rune(s) +} + +func IsEmpty(s string) bool { + return len(s) == 0 +} + +func IsNonEmpty(s string) bool { + return len(s) > 0 +} + +func Size(s string) int { + return len(s) +} + +// Format applies a format string to an arbitrary value +func Format[T any](format string) func(t T) string { + return func(t T) string { + return fmt.Sprintf(format, t) + } +} diff --git a/v2/string/string_test.go b/v2/string/string_test.go new file mode 100644 index 0000000..a966208 --- /dev/null +++ b/v2/string/string_test.go @@ -0,0 +1,50 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package string + +import ( + "testing" + + A "github.com/IBM/fp-go/v2/array" + "github.com/stretchr/testify/assert" +) + +func TestEmpty(t *testing.T) { + assert.True(t, IsEmpty("")) + assert.False(t, IsEmpty("Carsten")) +} + +func TestJoin(t *testing.T) { + + x := Join(",")(A.From("a", "b", "c")) + assert.Equal(t, x, x) + + assert.Equal(t, "a,b,c", Join(",")(A.From("a", "b", "c"))) + assert.Equal(t, "a", Join(",")(A.From("a"))) + assert.Equal(t, "", Join(",")(A.Empty[string]())) +} + +func TestEquals(t *testing.T) { + assert.True(t, Equals("a")("a")) + assert.False(t, Equals("a")("b")) + assert.False(t, Equals("b")("a")) +} + +func TestIncludes(t *testing.T) { + assert.True(t, Includes("a")("bab")) + assert.False(t, Includes("bab")("a")) + assert.False(t, Includes("b")("a")) +} diff --git a/v2/tuple/doc.go b/v2/tuple/doc.go new file mode 100644 index 0000000..52b04ef --- /dev/null +++ b/v2/tuple/doc.go @@ -0,0 +1,169 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +// Package tuple provides type-safe heterogeneous tuple data structures and operations. +// +// Tuples are immutable data structures that can hold a fixed number of values of different types. +// Unlike arrays or slices which hold homogeneous data, tuples can contain values of different types +// at compile-time known positions. +// +// # Tuple Types +// +// The package provides tuple types from Tuple1 to Tuple15, where the number indicates how many +// elements the tuple contains. Each tuple type is generic over its element types: +// +// Tuple1[T1] // Single element +// Tuple2[T1, T2] // Pair +// Tuple3[T1, T2, T3] // Triple +// // ... up to Tuple15 +// +// # Creating Tuples +// +// Tuples can be created using the MakeTupleN functions: +// +// t2 := tuple.MakeTuple2("hello", 42) // Tuple2[string, int] +// t3 := tuple.MakeTuple3(1.5, true, "world") // Tuple3[float64, bool, string] +// +// For single-element tuples, you can also use the Of function: +// +// t1 := tuple.Of(42) // Equivalent to MakeTuple1(42) +// +// # Accessing Elements +// +// Tuple elements are accessed via their F1, F2, F3, ... fields: +// +// t := tuple.MakeTuple2("hello", 42) +// s := t.F1 // "hello" +// n := t.F2 // 42 +// +// For Tuple2, convenience accessors are provided: +// +// s := tuple.First(t) // Same as t.F1 +// n := tuple.Second(t) // Same as t.F2 +// +// # Transforming Tuples +// +// The package provides several transformation functions: +// +// Map functions transform each element independently: +// +// t := tuple.MakeTuple2(5, "hello") +// mapper := tuple.Map2( +// func(n int) string { return fmt.Sprintf("%d", n) }, +// func(s string) int { return len(s) }, +// ) +// result := mapper(t) // Tuple2[string, int]{"5", 5} +// +// BiMap transforms both elements of a Tuple2: +// +// mapper := tuple.BiMap( +// func(s string) int { return len(s) }, +// func(n int) string { return fmt.Sprintf("%d", n*2) }, +// ) +// +// Swap exchanges the elements of a Tuple2: +// +// t := tuple.MakeTuple2("hello", 42) +// swapped := tuple.Swap(t) // Tuple2[int, string]{42, "hello"} +// +// # Function Conversion +// +// Tupled and Untupled functions convert between regular multi-parameter functions +// and functions that take/return tuples: +// +// // Regular function +// add := func(a, b int) int { return a + b } +// +// // Convert to tuple-taking function +// tupledAdd := tuple.Tupled2(add) +// result := tupledAdd(tuple.MakeTuple2(3, 4)) // 7 +// +// // Convert back +// untupledAdd := tuple.Untupled2(tupledAdd) +// result = untupledAdd(3, 4) // 7 +// +// # Array Conversion +// +// Tuples can be converted to and from arrays using transformation functions: +// +// t := tuple.MakeTuple3(1, 2, 3) +// toArray := tuple.ToArray3( +// func(n int) int { return n }, +// func(n int) int { return n }, +// func(n int) int { return n }, +// ) +// arr := toArray(t) // []int{1, 2, 3} +// +// # Algebraic Operations +// +// The package supports algebraic structures: +// +// Monoid operations for combining tuples: +// +// import "github.com/IBM/fp-go/v2/monoid" +// import "github.com/IBM/fp-go/v2/string" +// +// m := tuple.Monoid2(string.Monoid, monoid.MonoidSum[int]()) +// t1 := tuple.MakeTuple2("hello", 5) +// t2 := tuple.MakeTuple2(" world", 3) +// result := m.Concat(t1, t2) // Tuple2[string, int]{"hello world", 8} +// +// Ord operations for comparing tuples: +// +// import "github.com/IBM/fp-go/v2/ord" +// +// o := tuple.Ord2(ord.FromStrictCompare[string](), ord.FromStrictCompare[int]()) +// t1 := tuple.MakeTuple2("a", 1) +// t2 := tuple.MakeTuple2("b", 2) +// cmp := o.Compare(t1, t2) // -1 (t1 < t2) +// +// # JSON Serialization +// +// Tuples support JSON marshaling and unmarshaling as arrays: +// +// t := tuple.MakeTuple2("hello", 42) +// data, _ := json.Marshal(t) // ["hello", 42] +// +// var t2 tuple.Tuple2[string, int] +// json.Unmarshal(data, &t2) // Reconstructs the tuple +// +// # Building Tuples Incrementally +// +// The Push functions allow building larger tuples from smaller ones: +// +// t1 := tuple.MakeTuple1(42) +// push := tuple.Push1[int, string]("hello") +// t2 := push(t1) // Tuple2[int, string]{42, "hello"} +// +// # Replication +// +// Create tuples with all elements set to the same value: +// +// t := tuple.Replicate3(42) // Tuple3[int, int, int]{42, 42, 42} +// +// # Use Cases +// +// Tuples are useful when you need to: +// - Return multiple values from a function in a type-safe way +// - Group related but differently-typed values together +// - Work with fixed-size heterogeneous collections +// - Implement functional programming patterns like bifunctors +// - Avoid defining custom struct types for simple data groupings +// +// For homogeneous collections of unknown or variable size, consider using +// arrays or slices instead. +package tuple + +//go:generate go run .. tuple --count 15 --filename gen.go diff --git a/v2/tuple/gen.go b/v2/tuple/gen.go new file mode 100644 index 0000000..4b7b494 --- /dev/null +++ b/v2/tuple/gen.go @@ -0,0 +1,2014 @@ +// Code generated by go generate; DO NOT EDIT. +// This file was generated by robots at +// 2025-03-09 23:53:14.1811403 +0100 CET m=+0.002096201 + +package tuple + + +import ( + M "github.com/IBM/fp-go/v2/monoid" + O "github.com/IBM/fp-go/v2/ord" +) + +// Tuple1 is a struct that carries 1 independently typed values +type Tuple1[T1 any] struct { + F1 T1 +} + +// Tuple2 is a struct that carries 2 independently typed values +type Tuple2[T1, T2 any] struct { + F1 T1 + F2 T2 +} + +// Tuple3 is a struct that carries 3 independently typed values +type Tuple3[T1, T2, T3 any] struct { + F1 T1 + F2 T2 + F3 T3 +} + +// Tuple4 is a struct that carries 4 independently typed values +type Tuple4[T1, T2, T3, T4 any] struct { + F1 T1 + F2 T2 + F3 T3 + F4 T4 +} + +// Tuple5 is a struct that carries 5 independently typed values +type Tuple5[T1, T2, T3, T4, T5 any] struct { + F1 T1 + F2 T2 + F3 T3 + F4 T4 + F5 T5 +} + +// Tuple6 is a struct that carries 6 independently typed values +type Tuple6[T1, T2, T3, T4, T5, T6 any] struct { + F1 T1 + F2 T2 + F3 T3 + F4 T4 + F5 T5 + F6 T6 +} + +// Tuple7 is a struct that carries 7 independently typed values +type Tuple7[T1, T2, T3, T4, T5, T6, T7 any] struct { + F1 T1 + F2 T2 + F3 T3 + F4 T4 + F5 T5 + F6 T6 + F7 T7 +} + +// Tuple8 is a struct that carries 8 independently typed values +type Tuple8[T1, T2, T3, T4, T5, T6, T7, T8 any] struct { + F1 T1 + F2 T2 + F3 T3 + F4 T4 + F5 T5 + F6 T6 + F7 T7 + F8 T8 +} + +// Tuple9 is a struct that carries 9 independently typed values +type Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9 any] struct { + F1 T1 + F2 T2 + F3 T3 + F4 T4 + F5 T5 + F6 T6 + F7 T7 + F8 T8 + F9 T9 +} + +// Tuple10 is a struct that carries 10 independently typed values +type Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any] struct { + F1 T1 + F2 T2 + F3 T3 + F4 T4 + F5 T5 + F6 T6 + F7 T7 + F8 T8 + F9 T9 + F10 T10 +} + +// Tuple11 is a struct that carries 11 independently typed values +type Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11 any] struct { + F1 T1 + F2 T2 + F3 T3 + F4 T4 + F5 T5 + F6 T6 + F7 T7 + F8 T8 + F9 T9 + F10 T10 + F11 T11 +} + +// Tuple12 is a struct that carries 12 independently typed values +type Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12 any] struct { + F1 T1 + F2 T2 + F3 T3 + F4 T4 + F5 T5 + F6 T6 + F7 T7 + F8 T8 + F9 T9 + F10 T10 + F11 T11 + F12 T12 +} + +// Tuple13 is a struct that carries 13 independently typed values +type Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13 any] struct { + F1 T1 + F2 T2 + F3 T3 + F4 T4 + F5 T5 + F6 T6 + F7 T7 + F8 T8 + F9 T9 + F10 T10 + F11 T11 + F12 T12 + F13 T13 +} + +// Tuple14 is a struct that carries 14 independently typed values +type Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14 any] struct { + F1 T1 + F2 T2 + F3 T3 + F4 T4 + F5 T5 + F6 T6 + F7 T7 + F8 T8 + F9 T9 + F10 T10 + F11 T11 + F12 T12 + F13 T13 + F14 T14 +} + +// Tuple15 is a struct that carries 15 independently typed values +type Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15 any] struct { + F1 T1 + F2 T2 + F3 T3 + F4 T4 + F5 T5 + F6 T6 + F7 T7 + F8 T8 + F9 T9 + F10 T10 + F11 T11 + F12 T12 + F13 T13 + F14 T14 + F15 T15 +} + +// MakeTuple1 is a function that converts its 1 parameters into a [Tuple1] +func MakeTuple1[T1 any](t1 T1) Tuple1[T1] { + return Tuple1[T1]{t1} +} + +// Tupled1 converts a function with 1 parameters into a function taking a Tuple1 +// The inverse function is [Untupled1] +func Tupled1[F ~func(T1) R, T1, R any](f F) func(Tuple1[T1]) R { + return func(t Tuple1[T1]) R { + return f(t.F1) + } +} + +// Untupled1 converts a function with a [Tuple1] parameter into a function with 1 parameters +// The inverse function is [Tupled1] +func Untupled1[F ~func(Tuple1[T1]) R, T1, R any](f F) func(T1) R { + return func(t1 T1) R { + return f(MakeTuple1(t1)) + } +} + +// Monoid1 creates a [Monoid] for a [Tuple1] based on 1 monoids for the contained types +func Monoid1[T1 any](m1 M.Monoid[T1]) M.Monoid[Tuple1[T1]] { + return M.MakeMonoid(func(l, r Tuple1[T1]) Tuple1[T1]{ + return MakeTuple1(m1.Concat(l.F1, r.F1)) + }, MakeTuple1(m1.Empty())) +} + +// Ord1 creates n [Ord] for a [Tuple1] based on 1 [Ord]s for the contained types +func Ord1[T1 any](o1 O.Ord[T1]) O.Ord[Tuple1[T1]] { + return O.MakeOrd(func(l, r Tuple1[T1]) int { + if c:= o1.Compare(l.F1, r.F1); c != 0 {return c} + return 0 + }, func(l, r Tuple1[T1]) bool { + return o1.Equals(l.F1, r.F1) + }) +} + +// Map1 maps each value of a [Tuple1] via a mapping function +func Map1[F1 ~func(T1) R1, T1, R1 any](f1 F1) func(Tuple1[T1]) Tuple1[R1] { + return func(t Tuple1[T1]) Tuple1[R1] { + return MakeTuple1( + f1(t.F1), + ) + } +} + +// Replicate1 creates a [Tuple1] with all fields set to the input value `t` +func Replicate1[T any](t T) Tuple1[T] { + return MakeTuple1(t) +} + +// String prints some debug info for the [Tuple1] +func (t Tuple1[T1]) String() string { + return tupleString(t.F1) +} + +// MarshalJSON marshals the [Tuple1] into a JSON array +func (t Tuple1[T1]) MarshalJSON() ([]byte, error) { + return tupleMarshalJSON(t.F1) +} + +// UnmarshalJSON unmarshals a JSON array into a [Tuple1] +func (t *Tuple1[T1]) UnmarshalJSON(data []byte) error { + return tupleUnmarshalJSON(data, &t.F1) +} + +// ToArray converts the [Tuple1] into an array of type [R] using 1 transformation functions from [T] to [R] +// The inverse function is [FromArray1] +func ToArray1[F1 ~func(T1) R, T1, R any](f1 F1) func(t Tuple1[T1]) []R { + return func(t Tuple1[T1]) []R { + return []R{ + f1(t.F1), + } + } +} + +// FromArray converts an array of [R] into a [Tuple1] using 1 functions from [R] to [T] +// The inverse function is [ToArray1] +func FromArray1[F1 ~func(R) T1, T1, R any](f1 F1) func(r []R) Tuple1[T1] { + return func(r []R) Tuple1[T1] { + return MakeTuple1( + f1(r[0]), + ) + } +} + +// Push1 creates a [Tuple2] from a [Tuple1] by appending a constant value +func Push1[T1, T2 any](value T2) func(Tuple1[T1]) Tuple2[T1, T2] { + return func(t Tuple1[T1]) Tuple2[T1, T2] { + return MakeTuple2(t.F1, value) + } +} + +// MakeTuple2 is a function that converts its 2 parameters into a [Tuple2] +func MakeTuple2[T1, T2 any](t1 T1, t2 T2) Tuple2[T1, T2] { + return Tuple2[T1, T2]{t1, t2} +} + +// Tupled2 converts a function with 2 parameters into a function taking a Tuple2 +// The inverse function is [Untupled2] +func Tupled2[F ~func(T1, T2) R, T1, T2, R any](f F) func(Tuple2[T1, T2]) R { + return func(t Tuple2[T1, T2]) R { + return f(t.F1, t.F2) + } +} + +// Untupled2 converts a function with a [Tuple2] parameter into a function with 2 parameters +// The inverse function is [Tupled2] +func Untupled2[F ~func(Tuple2[T1, T2]) R, T1, T2, R any](f F) func(T1, T2) R { + return func(t1 T1, t2 T2) R { + return f(MakeTuple2(t1, t2)) + } +} + +// Monoid2 creates a [Monoid] for a [Tuple2] based on 2 monoids for the contained types +func Monoid2[T1, T2 any](m1 M.Monoid[T1], m2 M.Monoid[T2]) M.Monoid[Tuple2[T1, T2]] { + return M.MakeMonoid(func(l, r Tuple2[T1, T2]) Tuple2[T1, T2]{ + return MakeTuple2(m1.Concat(l.F1, r.F1), m2.Concat(l.F2, r.F2)) + }, MakeTuple2(m1.Empty(), m2.Empty())) +} + +// Ord2 creates n [Ord] for a [Tuple2] based on 2 [Ord]s for the contained types +func Ord2[T1, T2 any](o1 O.Ord[T1], o2 O.Ord[T2]) O.Ord[Tuple2[T1, T2]] { + return O.MakeOrd(func(l, r Tuple2[T1, T2]) int { + if c:= o1.Compare(l.F1, r.F1); c != 0 {return c} + if c:= o2.Compare(l.F2, r.F2); c != 0 {return c} + return 0 + }, func(l, r Tuple2[T1, T2]) bool { + return o1.Equals(l.F1, r.F1) && o2.Equals(l.F2, r.F2) + }) +} + +// Map2 maps each value of a [Tuple2] via a mapping function +func Map2[F1 ~func(T1) R1, F2 ~func(T2) R2, T1, R1, T2, R2 any](f1 F1, f2 F2) func(Tuple2[T1, T2]) Tuple2[R1, R2] { + return func(t Tuple2[T1, T2]) Tuple2[R1, R2] { + return MakeTuple2( + f1(t.F1), + f2(t.F2), + ) + } +} + +// Replicate2 creates a [Tuple2] with all fields set to the input value `t` +func Replicate2[T any](t T) Tuple2[T, T] { + return MakeTuple2(t, t) +} + +// String prints some debug info for the [Tuple2] +func (t Tuple2[T1, T2]) String() string { + return tupleString(t.F1, t.F2) +} + +// MarshalJSON marshals the [Tuple2] into a JSON array +func (t Tuple2[T1, T2]) MarshalJSON() ([]byte, error) { + return tupleMarshalJSON(t.F1, t.F2) +} + +// UnmarshalJSON unmarshals a JSON array into a [Tuple2] +func (t *Tuple2[T1, T2]) UnmarshalJSON(data []byte) error { + return tupleUnmarshalJSON(data, &t.F1, &t.F2) +} + +// ToArray converts the [Tuple2] into an array of type [R] using 2 transformation functions from [T] to [R] +// The inverse function is [FromArray2] +func ToArray2[F1 ~func(T1) R, F2 ~func(T2) R, T1, T2, R any](f1 F1, f2 F2) func(t Tuple2[T1, T2]) []R { + return func(t Tuple2[T1, T2]) []R { + return []R{ + f1(t.F1), + f2(t.F2), + } + } +} + +// FromArray converts an array of [R] into a [Tuple2] using 2 functions from [R] to [T] +// The inverse function is [ToArray2] +func FromArray2[F1 ~func(R) T1, F2 ~func(R) T2, T1, T2, R any](f1 F1, f2 F2) func(r []R) Tuple2[T1, T2] { + return func(r []R) Tuple2[T1, T2] { + return MakeTuple2( + f1(r[0]), + f2(r[1]), + ) + } +} + +// Push2 creates a [Tuple3] from a [Tuple2] by appending a constant value +func Push2[T1, T2, T3 any](value T3) func(Tuple2[T1, T2]) Tuple3[T1, T2, T3] { + return func(t Tuple2[T1, T2]) Tuple3[T1, T2, T3] { + return MakeTuple3(t.F1, t.F2, value) + } +} + +// MakeTuple3 is a function that converts its 3 parameters into a [Tuple3] +func MakeTuple3[T1, T2, T3 any](t1 T1, t2 T2, t3 T3) Tuple3[T1, T2, T3] { + return Tuple3[T1, T2, T3]{t1, t2, t3} +} + +// Tupled3 converts a function with 3 parameters into a function taking a Tuple3 +// The inverse function is [Untupled3] +func Tupled3[F ~func(T1, T2, T3) R, T1, T2, T3, R any](f F) func(Tuple3[T1, T2, T3]) R { + return func(t Tuple3[T1, T2, T3]) R { + return f(t.F1, t.F2, t.F3) + } +} + +// Untupled3 converts a function with a [Tuple3] parameter into a function with 3 parameters +// The inverse function is [Tupled3] +func Untupled3[F ~func(Tuple3[T1, T2, T3]) R, T1, T2, T3, R any](f F) func(T1, T2, T3) R { + return func(t1 T1, t2 T2, t3 T3) R { + return f(MakeTuple3(t1, t2, t3)) + } +} + +// Monoid3 creates a [Monoid] for a [Tuple3] based on 3 monoids for the contained types +func Monoid3[T1, T2, T3 any](m1 M.Monoid[T1], m2 M.Monoid[T2], m3 M.Monoid[T3]) M.Monoid[Tuple3[T1, T2, T3]] { + return M.MakeMonoid(func(l, r Tuple3[T1, T2, T3]) Tuple3[T1, T2, T3]{ + return MakeTuple3(m1.Concat(l.F1, r.F1), m2.Concat(l.F2, r.F2), m3.Concat(l.F3, r.F3)) + }, MakeTuple3(m1.Empty(), m2.Empty(), m3.Empty())) +} + +// Ord3 creates n [Ord] for a [Tuple3] based on 3 [Ord]s for the contained types +func Ord3[T1, T2, T3 any](o1 O.Ord[T1], o2 O.Ord[T2], o3 O.Ord[T3]) O.Ord[Tuple3[T1, T2, T3]] { + return O.MakeOrd(func(l, r Tuple3[T1, T2, T3]) int { + if c:= o1.Compare(l.F1, r.F1); c != 0 {return c} + if c:= o2.Compare(l.F2, r.F2); c != 0 {return c} + if c:= o3.Compare(l.F3, r.F3); c != 0 {return c} + return 0 + }, func(l, r Tuple3[T1, T2, T3]) bool { + return o1.Equals(l.F1, r.F1) && o2.Equals(l.F2, r.F2) && o3.Equals(l.F3, r.F3) + }) +} + +// Map3 maps each value of a [Tuple3] via a mapping function +func Map3[F1 ~func(T1) R1, F2 ~func(T2) R2, F3 ~func(T3) R3, T1, R1, T2, R2, T3, R3 any](f1 F1, f2 F2, f3 F3) func(Tuple3[T1, T2, T3]) Tuple3[R1, R2, R3] { + return func(t Tuple3[T1, T2, T3]) Tuple3[R1, R2, R3] { + return MakeTuple3( + f1(t.F1), + f2(t.F2), + f3(t.F3), + ) + } +} + +// Replicate3 creates a [Tuple3] with all fields set to the input value `t` +func Replicate3[T any](t T) Tuple3[T, T, T] { + return MakeTuple3(t, t, t) +} + +// String prints some debug info for the [Tuple3] +func (t Tuple3[T1, T2, T3]) String() string { + return tupleString(t.F1, t.F2, t.F3) +} + +// MarshalJSON marshals the [Tuple3] into a JSON array +func (t Tuple3[T1, T2, T3]) MarshalJSON() ([]byte, error) { + return tupleMarshalJSON(t.F1, t.F2, t.F3) +} + +// UnmarshalJSON unmarshals a JSON array into a [Tuple3] +func (t *Tuple3[T1, T2, T3]) UnmarshalJSON(data []byte) error { + return tupleUnmarshalJSON(data, &t.F1, &t.F2, &t.F3) +} + +// ToArray converts the [Tuple3] into an array of type [R] using 3 transformation functions from [T] to [R] +// The inverse function is [FromArray3] +func ToArray3[F1 ~func(T1) R, F2 ~func(T2) R, F3 ~func(T3) R, T1, T2, T3, R any](f1 F1, f2 F2, f3 F3) func(t Tuple3[T1, T2, T3]) []R { + return func(t Tuple3[T1, T2, T3]) []R { + return []R{ + f1(t.F1), + f2(t.F2), + f3(t.F3), + } + } +} + +// FromArray converts an array of [R] into a [Tuple3] using 3 functions from [R] to [T] +// The inverse function is [ToArray3] +func FromArray3[F1 ~func(R) T1, F2 ~func(R) T2, F3 ~func(R) T3, T1, T2, T3, R any](f1 F1, f2 F2, f3 F3) func(r []R) Tuple3[T1, T2, T3] { + return func(r []R) Tuple3[T1, T2, T3] { + return MakeTuple3( + f1(r[0]), + f2(r[1]), + f3(r[2]), + ) + } +} + +// Push3 creates a [Tuple4] from a [Tuple3] by appending a constant value +func Push3[T1, T2, T3, T4 any](value T4) func(Tuple3[T1, T2, T3]) Tuple4[T1, T2, T3, T4] { + return func(t Tuple3[T1, T2, T3]) Tuple4[T1, T2, T3, T4] { + return MakeTuple4(t.F1, t.F2, t.F3, value) + } +} + +// MakeTuple4 is a function that converts its 4 parameters into a [Tuple4] +func MakeTuple4[T1, T2, T3, T4 any](t1 T1, t2 T2, t3 T3, t4 T4) Tuple4[T1, T2, T3, T4] { + return Tuple4[T1, T2, T3, T4]{t1, t2, t3, t4} +} + +// Tupled4 converts a function with 4 parameters into a function taking a Tuple4 +// The inverse function is [Untupled4] +func Tupled4[F ~func(T1, T2, T3, T4) R, T1, T2, T3, T4, R any](f F) func(Tuple4[T1, T2, T3, T4]) R { + return func(t Tuple4[T1, T2, T3, T4]) R { + return f(t.F1, t.F2, t.F3, t.F4) + } +} + +// Untupled4 converts a function with a [Tuple4] parameter into a function with 4 parameters +// The inverse function is [Tupled4] +func Untupled4[F ~func(Tuple4[T1, T2, T3, T4]) R, T1, T2, T3, T4, R any](f F) func(T1, T2, T3, T4) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4) R { + return f(MakeTuple4(t1, t2, t3, t4)) + } +} + +// Monoid4 creates a [Monoid] for a [Tuple4] based on 4 monoids for the contained types +func Monoid4[T1, T2, T3, T4 any](m1 M.Monoid[T1], m2 M.Monoid[T2], m3 M.Monoid[T3], m4 M.Monoid[T4]) M.Monoid[Tuple4[T1, T2, T3, T4]] { + return M.MakeMonoid(func(l, r Tuple4[T1, T2, T3, T4]) Tuple4[T1, T2, T3, T4]{ + return MakeTuple4(m1.Concat(l.F1, r.F1), m2.Concat(l.F2, r.F2), m3.Concat(l.F3, r.F3), m4.Concat(l.F4, r.F4)) + }, MakeTuple4(m1.Empty(), m2.Empty(), m3.Empty(), m4.Empty())) +} + +// Ord4 creates n [Ord] for a [Tuple4] based on 4 [Ord]s for the contained types +func Ord4[T1, T2, T3, T4 any](o1 O.Ord[T1], o2 O.Ord[T2], o3 O.Ord[T3], o4 O.Ord[T4]) O.Ord[Tuple4[T1, T2, T3, T4]] { + return O.MakeOrd(func(l, r Tuple4[T1, T2, T3, T4]) int { + if c:= o1.Compare(l.F1, r.F1); c != 0 {return c} + if c:= o2.Compare(l.F2, r.F2); c != 0 {return c} + if c:= o3.Compare(l.F3, r.F3); c != 0 {return c} + if c:= o4.Compare(l.F4, r.F4); c != 0 {return c} + return 0 + }, func(l, r Tuple4[T1, T2, T3, T4]) bool { + return o1.Equals(l.F1, r.F1) && o2.Equals(l.F2, r.F2) && o3.Equals(l.F3, r.F3) && o4.Equals(l.F4, r.F4) + }) +} + +// Map4 maps each value of a [Tuple4] via a mapping function +func Map4[F1 ~func(T1) R1, F2 ~func(T2) R2, F3 ~func(T3) R3, F4 ~func(T4) R4, T1, R1, T2, R2, T3, R3, T4, R4 any](f1 F1, f2 F2, f3 F3, f4 F4) func(Tuple4[T1, T2, T3, T4]) Tuple4[R1, R2, R3, R4] { + return func(t Tuple4[T1, T2, T3, T4]) Tuple4[R1, R2, R3, R4] { + return MakeTuple4( + f1(t.F1), + f2(t.F2), + f3(t.F3), + f4(t.F4), + ) + } +} + +// Replicate4 creates a [Tuple4] with all fields set to the input value `t` +func Replicate4[T any](t T) Tuple4[T, T, T, T] { + return MakeTuple4(t, t, t, t) +} + +// String prints some debug info for the [Tuple4] +func (t Tuple4[T1, T2, T3, T4]) String() string { + return tupleString(t.F1, t.F2, t.F3, t.F4) +} + +// MarshalJSON marshals the [Tuple4] into a JSON array +func (t Tuple4[T1, T2, T3, T4]) MarshalJSON() ([]byte, error) { + return tupleMarshalJSON(t.F1, t.F2, t.F3, t.F4) +} + +// UnmarshalJSON unmarshals a JSON array into a [Tuple4] +func (t *Tuple4[T1, T2, T3, T4]) UnmarshalJSON(data []byte) error { + return tupleUnmarshalJSON(data, &t.F1, &t.F2, &t.F3, &t.F4) +} + +// ToArray converts the [Tuple4] into an array of type [R] using 4 transformation functions from [T] to [R] +// The inverse function is [FromArray4] +func ToArray4[F1 ~func(T1) R, F2 ~func(T2) R, F3 ~func(T3) R, F4 ~func(T4) R, T1, T2, T3, T4, R any](f1 F1, f2 F2, f3 F3, f4 F4) func(t Tuple4[T1, T2, T3, T4]) []R { + return func(t Tuple4[T1, T2, T3, T4]) []R { + return []R{ + f1(t.F1), + f2(t.F2), + f3(t.F3), + f4(t.F4), + } + } +} + +// FromArray converts an array of [R] into a [Tuple4] using 4 functions from [R] to [T] +// The inverse function is [ToArray4] +func FromArray4[F1 ~func(R) T1, F2 ~func(R) T2, F3 ~func(R) T3, F4 ~func(R) T4, T1, T2, T3, T4, R any](f1 F1, f2 F2, f3 F3, f4 F4) func(r []R) Tuple4[T1, T2, T3, T4] { + return func(r []R) Tuple4[T1, T2, T3, T4] { + return MakeTuple4( + f1(r[0]), + f2(r[1]), + f3(r[2]), + f4(r[3]), + ) + } +} + +// Push4 creates a [Tuple5] from a [Tuple4] by appending a constant value +func Push4[T1, T2, T3, T4, T5 any](value T5) func(Tuple4[T1, T2, T3, T4]) Tuple5[T1, T2, T3, T4, T5] { + return func(t Tuple4[T1, T2, T3, T4]) Tuple5[T1, T2, T3, T4, T5] { + return MakeTuple5(t.F1, t.F2, t.F3, t.F4, value) + } +} + +// MakeTuple5 is a function that converts its 5 parameters into a [Tuple5] +func MakeTuple5[T1, T2, T3, T4, T5 any](t1 T1, t2 T2, t3 T3, t4 T4, t5 T5) Tuple5[T1, T2, T3, T4, T5] { + return Tuple5[T1, T2, T3, T4, T5]{t1, t2, t3, t4, t5} +} + +// Tupled5 converts a function with 5 parameters into a function taking a Tuple5 +// The inverse function is [Untupled5] +func Tupled5[F ~func(T1, T2, T3, T4, T5) R, T1, T2, T3, T4, T5, R any](f F) func(Tuple5[T1, T2, T3, T4, T5]) R { + return func(t Tuple5[T1, T2, T3, T4, T5]) R { + return f(t.F1, t.F2, t.F3, t.F4, t.F5) + } +} + +// Untupled5 converts a function with a [Tuple5] parameter into a function with 5 parameters +// The inverse function is [Tupled5] +func Untupled5[F ~func(Tuple5[T1, T2, T3, T4, T5]) R, T1, T2, T3, T4, T5, R any](f F) func(T1, T2, T3, T4, T5) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5) R { + return f(MakeTuple5(t1, t2, t3, t4, t5)) + } +} + +// Monoid5 creates a [Monoid] for a [Tuple5] based on 5 monoids for the contained types +func Monoid5[T1, T2, T3, T4, T5 any](m1 M.Monoid[T1], m2 M.Monoid[T2], m3 M.Monoid[T3], m4 M.Monoid[T4], m5 M.Monoid[T5]) M.Monoid[Tuple5[T1, T2, T3, T4, T5]] { + return M.MakeMonoid(func(l, r Tuple5[T1, T2, T3, T4, T5]) Tuple5[T1, T2, T3, T4, T5]{ + return MakeTuple5(m1.Concat(l.F1, r.F1), m2.Concat(l.F2, r.F2), m3.Concat(l.F3, r.F3), m4.Concat(l.F4, r.F4), m5.Concat(l.F5, r.F5)) + }, MakeTuple5(m1.Empty(), m2.Empty(), m3.Empty(), m4.Empty(), m5.Empty())) +} + +// Ord5 creates n [Ord] for a [Tuple5] based on 5 [Ord]s for the contained types +func Ord5[T1, T2, T3, T4, T5 any](o1 O.Ord[T1], o2 O.Ord[T2], o3 O.Ord[T3], o4 O.Ord[T4], o5 O.Ord[T5]) O.Ord[Tuple5[T1, T2, T3, T4, T5]] { + return O.MakeOrd(func(l, r Tuple5[T1, T2, T3, T4, T5]) int { + if c:= o1.Compare(l.F1, r.F1); c != 0 {return c} + if c:= o2.Compare(l.F2, r.F2); c != 0 {return c} + if c:= o3.Compare(l.F3, r.F3); c != 0 {return c} + if c:= o4.Compare(l.F4, r.F4); c != 0 {return c} + if c:= o5.Compare(l.F5, r.F5); c != 0 {return c} + return 0 + }, func(l, r Tuple5[T1, T2, T3, T4, T5]) bool { + return o1.Equals(l.F1, r.F1) && o2.Equals(l.F2, r.F2) && o3.Equals(l.F3, r.F3) && o4.Equals(l.F4, r.F4) && o5.Equals(l.F5, r.F5) + }) +} + +// Map5 maps each value of a [Tuple5] via a mapping function +func Map5[F1 ~func(T1) R1, F2 ~func(T2) R2, F3 ~func(T3) R3, F4 ~func(T4) R4, F5 ~func(T5) R5, T1, R1, T2, R2, T3, R3, T4, R4, T5, R5 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5) func(Tuple5[T1, T2, T3, T4, T5]) Tuple5[R1, R2, R3, R4, R5] { + return func(t Tuple5[T1, T2, T3, T4, T5]) Tuple5[R1, R2, R3, R4, R5] { + return MakeTuple5( + f1(t.F1), + f2(t.F2), + f3(t.F3), + f4(t.F4), + f5(t.F5), + ) + } +} + +// Replicate5 creates a [Tuple5] with all fields set to the input value `t` +func Replicate5[T any](t T) Tuple5[T, T, T, T, T] { + return MakeTuple5(t, t, t, t, t) +} + +// String prints some debug info for the [Tuple5] +func (t Tuple5[T1, T2, T3, T4, T5]) String() string { + return tupleString(t.F1, t.F2, t.F3, t.F4, t.F5) +} + +// MarshalJSON marshals the [Tuple5] into a JSON array +func (t Tuple5[T1, T2, T3, T4, T5]) MarshalJSON() ([]byte, error) { + return tupleMarshalJSON(t.F1, t.F2, t.F3, t.F4, t.F5) +} + +// UnmarshalJSON unmarshals a JSON array into a [Tuple5] +func (t *Tuple5[T1, T2, T3, T4, T5]) UnmarshalJSON(data []byte) error { + return tupleUnmarshalJSON(data, &t.F1, &t.F2, &t.F3, &t.F4, &t.F5) +} + +// ToArray converts the [Tuple5] into an array of type [R] using 5 transformation functions from [T] to [R] +// The inverse function is [FromArray5] +func ToArray5[F1 ~func(T1) R, F2 ~func(T2) R, F3 ~func(T3) R, F4 ~func(T4) R, F5 ~func(T5) R, T1, T2, T3, T4, T5, R any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5) func(t Tuple5[T1, T2, T3, T4, T5]) []R { + return func(t Tuple5[T1, T2, T3, T4, T5]) []R { + return []R{ + f1(t.F1), + f2(t.F2), + f3(t.F3), + f4(t.F4), + f5(t.F5), + } + } +} + +// FromArray converts an array of [R] into a [Tuple5] using 5 functions from [R] to [T] +// The inverse function is [ToArray5] +func FromArray5[F1 ~func(R) T1, F2 ~func(R) T2, F3 ~func(R) T3, F4 ~func(R) T4, F5 ~func(R) T5, T1, T2, T3, T4, T5, R any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5) func(r []R) Tuple5[T1, T2, T3, T4, T5] { + return func(r []R) Tuple5[T1, T2, T3, T4, T5] { + return MakeTuple5( + f1(r[0]), + f2(r[1]), + f3(r[2]), + f4(r[3]), + f5(r[4]), + ) + } +} + +// Push5 creates a [Tuple6] from a [Tuple5] by appending a constant value +func Push5[T1, T2, T3, T4, T5, T6 any](value T6) func(Tuple5[T1, T2, T3, T4, T5]) Tuple6[T1, T2, T3, T4, T5, T6] { + return func(t Tuple5[T1, T2, T3, T4, T5]) Tuple6[T1, T2, T3, T4, T5, T6] { + return MakeTuple6(t.F1, t.F2, t.F3, t.F4, t.F5, value) + } +} + +// MakeTuple6 is a function that converts its 6 parameters into a [Tuple6] +func MakeTuple6[T1, T2, T3, T4, T5, T6 any](t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6) Tuple6[T1, T2, T3, T4, T5, T6] { + return Tuple6[T1, T2, T3, T4, T5, T6]{t1, t2, t3, t4, t5, t6} +} + +// Tupled6 converts a function with 6 parameters into a function taking a Tuple6 +// The inverse function is [Untupled6] +func Tupled6[F ~func(T1, T2, T3, T4, T5, T6) R, T1, T2, T3, T4, T5, T6, R any](f F) func(Tuple6[T1, T2, T3, T4, T5, T6]) R { + return func(t Tuple6[T1, T2, T3, T4, T5, T6]) R { + return f(t.F1, t.F2, t.F3, t.F4, t.F5, t.F6) + } +} + +// Untupled6 converts a function with a [Tuple6] parameter into a function with 6 parameters +// The inverse function is [Tupled6] +func Untupled6[F ~func(Tuple6[T1, T2, T3, T4, T5, T6]) R, T1, T2, T3, T4, T5, T6, R any](f F) func(T1, T2, T3, T4, T5, T6) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6) R { + return f(MakeTuple6(t1, t2, t3, t4, t5, t6)) + } +} + +// Monoid6 creates a [Monoid] for a [Tuple6] based on 6 monoids for the contained types +func Monoid6[T1, T2, T3, T4, T5, T6 any](m1 M.Monoid[T1], m2 M.Monoid[T2], m3 M.Monoid[T3], m4 M.Monoid[T4], m5 M.Monoid[T5], m6 M.Monoid[T6]) M.Monoid[Tuple6[T1, T2, T3, T4, T5, T6]] { + return M.MakeMonoid(func(l, r Tuple6[T1, T2, T3, T4, T5, T6]) Tuple6[T1, T2, T3, T4, T5, T6]{ + return MakeTuple6(m1.Concat(l.F1, r.F1), m2.Concat(l.F2, r.F2), m3.Concat(l.F3, r.F3), m4.Concat(l.F4, r.F4), m5.Concat(l.F5, r.F5), m6.Concat(l.F6, r.F6)) + }, MakeTuple6(m1.Empty(), m2.Empty(), m3.Empty(), m4.Empty(), m5.Empty(), m6.Empty())) +} + +// Ord6 creates n [Ord] for a [Tuple6] based on 6 [Ord]s for the contained types +func Ord6[T1, T2, T3, T4, T5, T6 any](o1 O.Ord[T1], o2 O.Ord[T2], o3 O.Ord[T3], o4 O.Ord[T4], o5 O.Ord[T5], o6 O.Ord[T6]) O.Ord[Tuple6[T1, T2, T3, T4, T5, T6]] { + return O.MakeOrd(func(l, r Tuple6[T1, T2, T3, T4, T5, T6]) int { + if c:= o1.Compare(l.F1, r.F1); c != 0 {return c} + if c:= o2.Compare(l.F2, r.F2); c != 0 {return c} + if c:= o3.Compare(l.F3, r.F3); c != 0 {return c} + if c:= o4.Compare(l.F4, r.F4); c != 0 {return c} + if c:= o5.Compare(l.F5, r.F5); c != 0 {return c} + if c:= o6.Compare(l.F6, r.F6); c != 0 {return c} + return 0 + }, func(l, r Tuple6[T1, T2, T3, T4, T5, T6]) bool { + return o1.Equals(l.F1, r.F1) && o2.Equals(l.F2, r.F2) && o3.Equals(l.F3, r.F3) && o4.Equals(l.F4, r.F4) && o5.Equals(l.F5, r.F5) && o6.Equals(l.F6, r.F6) + }) +} + +// Map6 maps each value of a [Tuple6] via a mapping function +func Map6[F1 ~func(T1) R1, F2 ~func(T2) R2, F3 ~func(T3) R3, F4 ~func(T4) R4, F5 ~func(T5) R5, F6 ~func(T6) R6, T1, R1, T2, R2, T3, R3, T4, R4, T5, R5, T6, R6 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6) func(Tuple6[T1, T2, T3, T4, T5, T6]) Tuple6[R1, R2, R3, R4, R5, R6] { + return func(t Tuple6[T1, T2, T3, T4, T5, T6]) Tuple6[R1, R2, R3, R4, R5, R6] { + return MakeTuple6( + f1(t.F1), + f2(t.F2), + f3(t.F3), + f4(t.F4), + f5(t.F5), + f6(t.F6), + ) + } +} + +// Replicate6 creates a [Tuple6] with all fields set to the input value `t` +func Replicate6[T any](t T) Tuple6[T, T, T, T, T, T] { + return MakeTuple6(t, t, t, t, t, t) +} + +// String prints some debug info for the [Tuple6] +func (t Tuple6[T1, T2, T3, T4, T5, T6]) String() string { + return tupleString(t.F1, t.F2, t.F3, t.F4, t.F5, t.F6) +} + +// MarshalJSON marshals the [Tuple6] into a JSON array +func (t Tuple6[T1, T2, T3, T4, T5, T6]) MarshalJSON() ([]byte, error) { + return tupleMarshalJSON(t.F1, t.F2, t.F3, t.F4, t.F5, t.F6) +} + +// UnmarshalJSON unmarshals a JSON array into a [Tuple6] +func (t *Tuple6[T1, T2, T3, T4, T5, T6]) UnmarshalJSON(data []byte) error { + return tupleUnmarshalJSON(data, &t.F1, &t.F2, &t.F3, &t.F4, &t.F5, &t.F6) +} + +// ToArray converts the [Tuple6] into an array of type [R] using 6 transformation functions from [T] to [R] +// The inverse function is [FromArray6] +func ToArray6[F1 ~func(T1) R, F2 ~func(T2) R, F3 ~func(T3) R, F4 ~func(T4) R, F5 ~func(T5) R, F6 ~func(T6) R, T1, T2, T3, T4, T5, T6, R any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6) func(t Tuple6[T1, T2, T3, T4, T5, T6]) []R { + return func(t Tuple6[T1, T2, T3, T4, T5, T6]) []R { + return []R{ + f1(t.F1), + f2(t.F2), + f3(t.F3), + f4(t.F4), + f5(t.F5), + f6(t.F6), + } + } +} + +// FromArray converts an array of [R] into a [Tuple6] using 6 functions from [R] to [T] +// The inverse function is [ToArray6] +func FromArray6[F1 ~func(R) T1, F2 ~func(R) T2, F3 ~func(R) T3, F4 ~func(R) T4, F5 ~func(R) T5, F6 ~func(R) T6, T1, T2, T3, T4, T5, T6, R any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6) func(r []R) Tuple6[T1, T2, T3, T4, T5, T6] { + return func(r []R) Tuple6[T1, T2, T3, T4, T5, T6] { + return MakeTuple6( + f1(r[0]), + f2(r[1]), + f3(r[2]), + f4(r[3]), + f5(r[4]), + f6(r[5]), + ) + } +} + +// Push6 creates a [Tuple7] from a [Tuple6] by appending a constant value +func Push6[T1, T2, T3, T4, T5, T6, T7 any](value T7) func(Tuple6[T1, T2, T3, T4, T5, T6]) Tuple7[T1, T2, T3, T4, T5, T6, T7] { + return func(t Tuple6[T1, T2, T3, T4, T5, T6]) Tuple7[T1, T2, T3, T4, T5, T6, T7] { + return MakeTuple7(t.F1, t.F2, t.F3, t.F4, t.F5, t.F6, value) + } +} + +// MakeTuple7 is a function that converts its 7 parameters into a [Tuple7] +func MakeTuple7[T1, T2, T3, T4, T5, T6, T7 any](t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7) Tuple7[T1, T2, T3, T4, T5, T6, T7] { + return Tuple7[T1, T2, T3, T4, T5, T6, T7]{t1, t2, t3, t4, t5, t6, t7} +} + +// Tupled7 converts a function with 7 parameters into a function taking a Tuple7 +// The inverse function is [Untupled7] +func Tupled7[F ~func(T1, T2, T3, T4, T5, T6, T7) R, T1, T2, T3, T4, T5, T6, T7, R any](f F) func(Tuple7[T1, T2, T3, T4, T5, T6, T7]) R { + return func(t Tuple7[T1, T2, T3, T4, T5, T6, T7]) R { + return f(t.F1, t.F2, t.F3, t.F4, t.F5, t.F6, t.F7) + } +} + +// Untupled7 converts a function with a [Tuple7] parameter into a function with 7 parameters +// The inverse function is [Tupled7] +func Untupled7[F ~func(Tuple7[T1, T2, T3, T4, T5, T6, T7]) R, T1, T2, T3, T4, T5, T6, T7, R any](f F) func(T1, T2, T3, T4, T5, T6, T7) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7) R { + return f(MakeTuple7(t1, t2, t3, t4, t5, t6, t7)) + } +} + +// Monoid7 creates a [Monoid] for a [Tuple7] based on 7 monoids for the contained types +func Monoid7[T1, T2, T3, T4, T5, T6, T7 any](m1 M.Monoid[T1], m2 M.Monoid[T2], m3 M.Monoid[T3], m4 M.Monoid[T4], m5 M.Monoid[T5], m6 M.Monoid[T6], m7 M.Monoid[T7]) M.Monoid[Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return M.MakeMonoid(func(l, r Tuple7[T1, T2, T3, T4, T5, T6, T7]) Tuple7[T1, T2, T3, T4, T5, T6, T7]{ + return MakeTuple7(m1.Concat(l.F1, r.F1), m2.Concat(l.F2, r.F2), m3.Concat(l.F3, r.F3), m4.Concat(l.F4, r.F4), m5.Concat(l.F5, r.F5), m6.Concat(l.F6, r.F6), m7.Concat(l.F7, r.F7)) + }, MakeTuple7(m1.Empty(), m2.Empty(), m3.Empty(), m4.Empty(), m5.Empty(), m6.Empty(), m7.Empty())) +} + +// Ord7 creates n [Ord] for a [Tuple7] based on 7 [Ord]s for the contained types +func Ord7[T1, T2, T3, T4, T5, T6, T7 any](o1 O.Ord[T1], o2 O.Ord[T2], o3 O.Ord[T3], o4 O.Ord[T4], o5 O.Ord[T5], o6 O.Ord[T6], o7 O.Ord[T7]) O.Ord[Tuple7[T1, T2, T3, T4, T5, T6, T7]] { + return O.MakeOrd(func(l, r Tuple7[T1, T2, T3, T4, T5, T6, T7]) int { + if c:= o1.Compare(l.F1, r.F1); c != 0 {return c} + if c:= o2.Compare(l.F2, r.F2); c != 0 {return c} + if c:= o3.Compare(l.F3, r.F3); c != 0 {return c} + if c:= o4.Compare(l.F4, r.F4); c != 0 {return c} + if c:= o5.Compare(l.F5, r.F5); c != 0 {return c} + if c:= o6.Compare(l.F6, r.F6); c != 0 {return c} + if c:= o7.Compare(l.F7, r.F7); c != 0 {return c} + return 0 + }, func(l, r Tuple7[T1, T2, T3, T4, T5, T6, T7]) bool { + return o1.Equals(l.F1, r.F1) && o2.Equals(l.F2, r.F2) && o3.Equals(l.F3, r.F3) && o4.Equals(l.F4, r.F4) && o5.Equals(l.F5, r.F5) && o6.Equals(l.F6, r.F6) && o7.Equals(l.F7, r.F7) + }) +} + +// Map7 maps each value of a [Tuple7] via a mapping function +func Map7[F1 ~func(T1) R1, F2 ~func(T2) R2, F3 ~func(T3) R3, F4 ~func(T4) R4, F5 ~func(T5) R5, F6 ~func(T6) R6, F7 ~func(T7) R7, T1, R1, T2, R2, T3, R3, T4, R4, T5, R5, T6, R6, T7, R7 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7) func(Tuple7[T1, T2, T3, T4, T5, T6, T7]) Tuple7[R1, R2, R3, R4, R5, R6, R7] { + return func(t Tuple7[T1, T2, T3, T4, T5, T6, T7]) Tuple7[R1, R2, R3, R4, R5, R6, R7] { + return MakeTuple7( + f1(t.F1), + f2(t.F2), + f3(t.F3), + f4(t.F4), + f5(t.F5), + f6(t.F6), + f7(t.F7), + ) + } +} + +// Replicate7 creates a [Tuple7] with all fields set to the input value `t` +func Replicate7[T any](t T) Tuple7[T, T, T, T, T, T, T] { + return MakeTuple7(t, t, t, t, t, t, t) +} + +// String prints some debug info for the [Tuple7] +func (t Tuple7[T1, T2, T3, T4, T5, T6, T7]) String() string { + return tupleString(t.F1, t.F2, t.F3, t.F4, t.F5, t.F6, t.F7) +} + +// MarshalJSON marshals the [Tuple7] into a JSON array +func (t Tuple7[T1, T2, T3, T4, T5, T6, T7]) MarshalJSON() ([]byte, error) { + return tupleMarshalJSON(t.F1, t.F2, t.F3, t.F4, t.F5, t.F6, t.F7) +} + +// UnmarshalJSON unmarshals a JSON array into a [Tuple7] +func (t *Tuple7[T1, T2, T3, T4, T5, T6, T7]) UnmarshalJSON(data []byte) error { + return tupleUnmarshalJSON(data, &t.F1, &t.F2, &t.F3, &t.F4, &t.F5, &t.F6, &t.F7) +} + +// ToArray converts the [Tuple7] into an array of type [R] using 7 transformation functions from [T] to [R] +// The inverse function is [FromArray7] +func ToArray7[F1 ~func(T1) R, F2 ~func(T2) R, F3 ~func(T3) R, F4 ~func(T4) R, F5 ~func(T5) R, F6 ~func(T6) R, F7 ~func(T7) R, T1, T2, T3, T4, T5, T6, T7, R any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7) func(t Tuple7[T1, T2, T3, T4, T5, T6, T7]) []R { + return func(t Tuple7[T1, T2, T3, T4, T5, T6, T7]) []R { + return []R{ + f1(t.F1), + f2(t.F2), + f3(t.F3), + f4(t.F4), + f5(t.F5), + f6(t.F6), + f7(t.F7), + } + } +} + +// FromArray converts an array of [R] into a [Tuple7] using 7 functions from [R] to [T] +// The inverse function is [ToArray7] +func FromArray7[F1 ~func(R) T1, F2 ~func(R) T2, F3 ~func(R) T3, F4 ~func(R) T4, F5 ~func(R) T5, F6 ~func(R) T6, F7 ~func(R) T7, T1, T2, T3, T4, T5, T6, T7, R any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7) func(r []R) Tuple7[T1, T2, T3, T4, T5, T6, T7] { + return func(r []R) Tuple7[T1, T2, T3, T4, T5, T6, T7] { + return MakeTuple7( + f1(r[0]), + f2(r[1]), + f3(r[2]), + f4(r[3]), + f5(r[4]), + f6(r[5]), + f7(r[6]), + ) + } +} + +// Push7 creates a [Tuple8] from a [Tuple7] by appending a constant value +func Push7[T1, T2, T3, T4, T5, T6, T7, T8 any](value T8) func(Tuple7[T1, T2, T3, T4, T5, T6, T7]) Tuple8[T1, T2, T3, T4, T5, T6, T7, T8] { + return func(t Tuple7[T1, T2, T3, T4, T5, T6, T7]) Tuple8[T1, T2, T3, T4, T5, T6, T7, T8] { + return MakeTuple8(t.F1, t.F2, t.F3, t.F4, t.F5, t.F6, t.F7, value) + } +} + +// MakeTuple8 is a function that converts its 8 parameters into a [Tuple8] +func MakeTuple8[T1, T2, T3, T4, T5, T6, T7, T8 any](t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8) Tuple8[T1, T2, T3, T4, T5, T6, T7, T8] { + return Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]{t1, t2, t3, t4, t5, t6, t7, t8} +} + +// Tupled8 converts a function with 8 parameters into a function taking a Tuple8 +// The inverse function is [Untupled8] +func Tupled8[F ~func(T1, T2, T3, T4, T5, T6, T7, T8) R, T1, T2, T3, T4, T5, T6, T7, T8, R any](f F) func(Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]) R { + return func(t Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]) R { + return f(t.F1, t.F2, t.F3, t.F4, t.F5, t.F6, t.F7, t.F8) + } +} + +// Untupled8 converts a function with a [Tuple8] parameter into a function with 8 parameters +// The inverse function is [Tupled8] +func Untupled8[F ~func(Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]) R, T1, T2, T3, T4, T5, T6, T7, T8, R any](f F) func(T1, T2, T3, T4, T5, T6, T7, T8) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8) R { + return f(MakeTuple8(t1, t2, t3, t4, t5, t6, t7, t8)) + } +} + +// Monoid8 creates a [Monoid] for a [Tuple8] based on 8 monoids for the contained types +func Monoid8[T1, T2, T3, T4, T5, T6, T7, T8 any](m1 M.Monoid[T1], m2 M.Monoid[T2], m3 M.Monoid[T3], m4 M.Monoid[T4], m5 M.Monoid[T5], m6 M.Monoid[T6], m7 M.Monoid[T7], m8 M.Monoid[T8]) M.Monoid[Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return M.MakeMonoid(func(l, r Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]) Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]{ + return MakeTuple8(m1.Concat(l.F1, r.F1), m2.Concat(l.F2, r.F2), m3.Concat(l.F3, r.F3), m4.Concat(l.F4, r.F4), m5.Concat(l.F5, r.F5), m6.Concat(l.F6, r.F6), m7.Concat(l.F7, r.F7), m8.Concat(l.F8, r.F8)) + }, MakeTuple8(m1.Empty(), m2.Empty(), m3.Empty(), m4.Empty(), m5.Empty(), m6.Empty(), m7.Empty(), m8.Empty())) +} + +// Ord8 creates n [Ord] for a [Tuple8] based on 8 [Ord]s for the contained types +func Ord8[T1, T2, T3, T4, T5, T6, T7, T8 any](o1 O.Ord[T1], o2 O.Ord[T2], o3 O.Ord[T3], o4 O.Ord[T4], o5 O.Ord[T5], o6 O.Ord[T6], o7 O.Ord[T7], o8 O.Ord[T8]) O.Ord[Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]] { + return O.MakeOrd(func(l, r Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]) int { + if c:= o1.Compare(l.F1, r.F1); c != 0 {return c} + if c:= o2.Compare(l.F2, r.F2); c != 0 {return c} + if c:= o3.Compare(l.F3, r.F3); c != 0 {return c} + if c:= o4.Compare(l.F4, r.F4); c != 0 {return c} + if c:= o5.Compare(l.F5, r.F5); c != 0 {return c} + if c:= o6.Compare(l.F6, r.F6); c != 0 {return c} + if c:= o7.Compare(l.F7, r.F7); c != 0 {return c} + if c:= o8.Compare(l.F8, r.F8); c != 0 {return c} + return 0 + }, func(l, r Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]) bool { + return o1.Equals(l.F1, r.F1) && o2.Equals(l.F2, r.F2) && o3.Equals(l.F3, r.F3) && o4.Equals(l.F4, r.F4) && o5.Equals(l.F5, r.F5) && o6.Equals(l.F6, r.F6) && o7.Equals(l.F7, r.F7) && o8.Equals(l.F8, r.F8) + }) +} + +// Map8 maps each value of a [Tuple8] via a mapping function +func Map8[F1 ~func(T1) R1, F2 ~func(T2) R2, F3 ~func(T3) R3, F4 ~func(T4) R4, F5 ~func(T5) R5, F6 ~func(T6) R6, F7 ~func(T7) R7, F8 ~func(T8) R8, T1, R1, T2, R2, T3, R3, T4, R4, T5, R5, T6, R6, T7, R7, T8, R8 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8) func(Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]) Tuple8[R1, R2, R3, R4, R5, R6, R7, R8] { + return func(t Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]) Tuple8[R1, R2, R3, R4, R5, R6, R7, R8] { + return MakeTuple8( + f1(t.F1), + f2(t.F2), + f3(t.F3), + f4(t.F4), + f5(t.F5), + f6(t.F6), + f7(t.F7), + f8(t.F8), + ) + } +} + +// Replicate8 creates a [Tuple8] with all fields set to the input value `t` +func Replicate8[T any](t T) Tuple8[T, T, T, T, T, T, T, T] { + return MakeTuple8(t, t, t, t, t, t, t, t) +} + +// String prints some debug info for the [Tuple8] +func (t Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]) String() string { + return tupleString(t.F1, t.F2, t.F3, t.F4, t.F5, t.F6, t.F7, t.F8) +} + +// MarshalJSON marshals the [Tuple8] into a JSON array +func (t Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]) MarshalJSON() ([]byte, error) { + return tupleMarshalJSON(t.F1, t.F2, t.F3, t.F4, t.F5, t.F6, t.F7, t.F8) +} + +// UnmarshalJSON unmarshals a JSON array into a [Tuple8] +func (t *Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]) UnmarshalJSON(data []byte) error { + return tupleUnmarshalJSON(data, &t.F1, &t.F2, &t.F3, &t.F4, &t.F5, &t.F6, &t.F7, &t.F8) +} + +// ToArray converts the [Tuple8] into an array of type [R] using 8 transformation functions from [T] to [R] +// The inverse function is [FromArray8] +func ToArray8[F1 ~func(T1) R, F2 ~func(T2) R, F3 ~func(T3) R, F4 ~func(T4) R, F5 ~func(T5) R, F6 ~func(T6) R, F7 ~func(T7) R, F8 ~func(T8) R, T1, T2, T3, T4, T5, T6, T7, T8, R any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8) func(t Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]) []R { + return func(t Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]) []R { + return []R{ + f1(t.F1), + f2(t.F2), + f3(t.F3), + f4(t.F4), + f5(t.F5), + f6(t.F6), + f7(t.F7), + f8(t.F8), + } + } +} + +// FromArray converts an array of [R] into a [Tuple8] using 8 functions from [R] to [T] +// The inverse function is [ToArray8] +func FromArray8[F1 ~func(R) T1, F2 ~func(R) T2, F3 ~func(R) T3, F4 ~func(R) T4, F5 ~func(R) T5, F6 ~func(R) T6, F7 ~func(R) T7, F8 ~func(R) T8, T1, T2, T3, T4, T5, T6, T7, T8, R any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8) func(r []R) Tuple8[T1, T2, T3, T4, T5, T6, T7, T8] { + return func(r []R) Tuple8[T1, T2, T3, T4, T5, T6, T7, T8] { + return MakeTuple8( + f1(r[0]), + f2(r[1]), + f3(r[2]), + f4(r[3]), + f5(r[4]), + f6(r[5]), + f7(r[6]), + f8(r[7]), + ) + } +} + +// Push8 creates a [Tuple9] from a [Tuple8] by appending a constant value +func Push8[T1, T2, T3, T4, T5, T6, T7, T8, T9 any](value T9) func(Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]) Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9] { + return func(t Tuple8[T1, T2, T3, T4, T5, T6, T7, T8]) Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9] { + return MakeTuple9(t.F1, t.F2, t.F3, t.F4, t.F5, t.F6, t.F7, t.F8, value) + } +} + +// MakeTuple9 is a function that converts its 9 parameters into a [Tuple9] +func MakeTuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9 any](t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9) Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9] { + return Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]{t1, t2, t3, t4, t5, t6, t7, t8, t9} +} + +// Tupled9 converts a function with 9 parameters into a function taking a Tuple9 +// The inverse function is [Untupled9] +func Tupled9[F ~func(T1, T2, T3, T4, T5, T6, T7, T8, T9) R, T1, T2, T3, T4, T5, T6, T7, T8, T9, R any](f F) func(Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]) R { + return func(t Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]) R { + return f(t.F1, t.F2, t.F3, t.F4, t.F5, t.F6, t.F7, t.F8, t.F9) + } +} + +// Untupled9 converts a function with a [Tuple9] parameter into a function with 9 parameters +// The inverse function is [Tupled9] +func Untupled9[F ~func(Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]) R, T1, T2, T3, T4, T5, T6, T7, T8, T9, R any](f F) func(T1, T2, T3, T4, T5, T6, T7, T8, T9) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9) R { + return f(MakeTuple9(t1, t2, t3, t4, t5, t6, t7, t8, t9)) + } +} + +// Monoid9 creates a [Monoid] for a [Tuple9] based on 9 monoids for the contained types +func Monoid9[T1, T2, T3, T4, T5, T6, T7, T8, T9 any](m1 M.Monoid[T1], m2 M.Monoid[T2], m3 M.Monoid[T3], m4 M.Monoid[T4], m5 M.Monoid[T5], m6 M.Monoid[T6], m7 M.Monoid[T7], m8 M.Monoid[T8], m9 M.Monoid[T9]) M.Monoid[Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return M.MakeMonoid(func(l, r Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]) Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]{ + return MakeTuple9(m1.Concat(l.F1, r.F1), m2.Concat(l.F2, r.F2), m3.Concat(l.F3, r.F3), m4.Concat(l.F4, r.F4), m5.Concat(l.F5, r.F5), m6.Concat(l.F6, r.F6), m7.Concat(l.F7, r.F7), m8.Concat(l.F8, r.F8), m9.Concat(l.F9, r.F9)) + }, MakeTuple9(m1.Empty(), m2.Empty(), m3.Empty(), m4.Empty(), m5.Empty(), m6.Empty(), m7.Empty(), m8.Empty(), m9.Empty())) +} + +// Ord9 creates n [Ord] for a [Tuple9] based on 9 [Ord]s for the contained types +func Ord9[T1, T2, T3, T4, T5, T6, T7, T8, T9 any](o1 O.Ord[T1], o2 O.Ord[T2], o3 O.Ord[T3], o4 O.Ord[T4], o5 O.Ord[T5], o6 O.Ord[T6], o7 O.Ord[T7], o8 O.Ord[T8], o9 O.Ord[T9]) O.Ord[Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]] { + return O.MakeOrd(func(l, r Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]) int { + if c:= o1.Compare(l.F1, r.F1); c != 0 {return c} + if c:= o2.Compare(l.F2, r.F2); c != 0 {return c} + if c:= o3.Compare(l.F3, r.F3); c != 0 {return c} + if c:= o4.Compare(l.F4, r.F4); c != 0 {return c} + if c:= o5.Compare(l.F5, r.F5); c != 0 {return c} + if c:= o6.Compare(l.F6, r.F6); c != 0 {return c} + if c:= o7.Compare(l.F7, r.F7); c != 0 {return c} + if c:= o8.Compare(l.F8, r.F8); c != 0 {return c} + if c:= o9.Compare(l.F9, r.F9); c != 0 {return c} + return 0 + }, func(l, r Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]) bool { + return o1.Equals(l.F1, r.F1) && o2.Equals(l.F2, r.F2) && o3.Equals(l.F3, r.F3) && o4.Equals(l.F4, r.F4) && o5.Equals(l.F5, r.F5) && o6.Equals(l.F6, r.F6) && o7.Equals(l.F7, r.F7) && o8.Equals(l.F8, r.F8) && o9.Equals(l.F9, r.F9) + }) +} + +// Map9 maps each value of a [Tuple9] via a mapping function +func Map9[F1 ~func(T1) R1, F2 ~func(T2) R2, F3 ~func(T3) R3, F4 ~func(T4) R4, F5 ~func(T5) R5, F6 ~func(T6) R6, F7 ~func(T7) R7, F8 ~func(T8) R8, F9 ~func(T9) R9, T1, R1, T2, R2, T3, R3, T4, R4, T5, R5, T6, R6, T7, R7, T8, R8, T9, R9 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9) func(Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]) Tuple9[R1, R2, R3, R4, R5, R6, R7, R8, R9] { + return func(t Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]) Tuple9[R1, R2, R3, R4, R5, R6, R7, R8, R9] { + return MakeTuple9( + f1(t.F1), + f2(t.F2), + f3(t.F3), + f4(t.F4), + f5(t.F5), + f6(t.F6), + f7(t.F7), + f8(t.F8), + f9(t.F9), + ) + } +} + +// Replicate9 creates a [Tuple9] with all fields set to the input value `t` +func Replicate9[T any](t T) Tuple9[T, T, T, T, T, T, T, T, T] { + return MakeTuple9(t, t, t, t, t, t, t, t, t) +} + +// String prints some debug info for the [Tuple9] +func (t Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]) String() string { + return tupleString(t.F1, t.F2, t.F3, t.F4, t.F5, t.F6, t.F7, t.F8, t.F9) +} + +// MarshalJSON marshals the [Tuple9] into a JSON array +func (t Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]) MarshalJSON() ([]byte, error) { + return tupleMarshalJSON(t.F1, t.F2, t.F3, t.F4, t.F5, t.F6, t.F7, t.F8, t.F9) +} + +// UnmarshalJSON unmarshals a JSON array into a [Tuple9] +func (t *Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]) UnmarshalJSON(data []byte) error { + return tupleUnmarshalJSON(data, &t.F1, &t.F2, &t.F3, &t.F4, &t.F5, &t.F6, &t.F7, &t.F8, &t.F9) +} + +// ToArray converts the [Tuple9] into an array of type [R] using 9 transformation functions from [T] to [R] +// The inverse function is [FromArray9] +func ToArray9[F1 ~func(T1) R, F2 ~func(T2) R, F3 ~func(T3) R, F4 ~func(T4) R, F5 ~func(T5) R, F6 ~func(T6) R, F7 ~func(T7) R, F8 ~func(T8) R, F9 ~func(T9) R, T1, T2, T3, T4, T5, T6, T7, T8, T9, R any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9) func(t Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]) []R { + return func(t Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]) []R { + return []R{ + f1(t.F1), + f2(t.F2), + f3(t.F3), + f4(t.F4), + f5(t.F5), + f6(t.F6), + f7(t.F7), + f8(t.F8), + f9(t.F9), + } + } +} + +// FromArray converts an array of [R] into a [Tuple9] using 9 functions from [R] to [T] +// The inverse function is [ToArray9] +func FromArray9[F1 ~func(R) T1, F2 ~func(R) T2, F3 ~func(R) T3, F4 ~func(R) T4, F5 ~func(R) T5, F6 ~func(R) T6, F7 ~func(R) T7, F8 ~func(R) T8, F9 ~func(R) T9, T1, T2, T3, T4, T5, T6, T7, T8, T9, R any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9) func(r []R) Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9] { + return func(r []R) Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9] { + return MakeTuple9( + f1(r[0]), + f2(r[1]), + f3(r[2]), + f4(r[3]), + f5(r[4]), + f6(r[5]), + f7(r[6]), + f8(r[7]), + f9(r[8]), + ) + } +} + +// Push9 creates a [Tuple10] from a [Tuple9] by appending a constant value +func Push9[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any](value T10) func(Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]) Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10] { + return func(t Tuple9[T1, T2, T3, T4, T5, T6, T7, T8, T9]) Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10] { + return MakeTuple10(t.F1, t.F2, t.F3, t.F4, t.F5, t.F6, t.F7, t.F8, t.F9, value) + } +} + +// MakeTuple10 is a function that converts its 10 parameters into a [Tuple10] +func MakeTuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any](t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10) Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10] { + return Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]{t1, t2, t3, t4, t5, t6, t7, t8, t9, t10} +} + +// Tupled10 converts a function with 10 parameters into a function taking a Tuple10 +// The inverse function is [Untupled10] +func Tupled10[F ~func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) R, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, R any](f F) func(Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]) R { + return func(t Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]) R { + return f(t.F1, t.F2, t.F3, t.F4, t.F5, t.F6, t.F7, t.F8, t.F9, t.F10) + } +} + +// Untupled10 converts a function with a [Tuple10] parameter into a function with 10 parameters +// The inverse function is [Tupled10] +func Untupled10[F ~func(Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]) R, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, R any](f F) func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10) R { + return f(MakeTuple10(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10)) + } +} + +// Monoid10 creates a [Monoid] for a [Tuple10] based on 10 monoids for the contained types +func Monoid10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any](m1 M.Monoid[T1], m2 M.Monoid[T2], m3 M.Monoid[T3], m4 M.Monoid[T4], m5 M.Monoid[T5], m6 M.Monoid[T6], m7 M.Monoid[T7], m8 M.Monoid[T8], m9 M.Monoid[T9], m10 M.Monoid[T10]) M.Monoid[Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return M.MakeMonoid(func(l, r Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]) Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]{ + return MakeTuple10(m1.Concat(l.F1, r.F1), m2.Concat(l.F2, r.F2), m3.Concat(l.F3, r.F3), m4.Concat(l.F4, r.F4), m5.Concat(l.F5, r.F5), m6.Concat(l.F6, r.F6), m7.Concat(l.F7, r.F7), m8.Concat(l.F8, r.F8), m9.Concat(l.F9, r.F9), m10.Concat(l.F10, r.F10)) + }, MakeTuple10(m1.Empty(), m2.Empty(), m3.Empty(), m4.Empty(), m5.Empty(), m6.Empty(), m7.Empty(), m8.Empty(), m9.Empty(), m10.Empty())) +} + +// Ord10 creates n [Ord] for a [Tuple10] based on 10 [Ord]s for the contained types +func Ord10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10 any](o1 O.Ord[T1], o2 O.Ord[T2], o3 O.Ord[T3], o4 O.Ord[T4], o5 O.Ord[T5], o6 O.Ord[T6], o7 O.Ord[T7], o8 O.Ord[T8], o9 O.Ord[T9], o10 O.Ord[T10]) O.Ord[Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]] { + return O.MakeOrd(func(l, r Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]) int { + if c:= o1.Compare(l.F1, r.F1); c != 0 {return c} + if c:= o2.Compare(l.F2, r.F2); c != 0 {return c} + if c:= o3.Compare(l.F3, r.F3); c != 0 {return c} + if c:= o4.Compare(l.F4, r.F4); c != 0 {return c} + if c:= o5.Compare(l.F5, r.F5); c != 0 {return c} + if c:= o6.Compare(l.F6, r.F6); c != 0 {return c} + if c:= o7.Compare(l.F7, r.F7); c != 0 {return c} + if c:= o8.Compare(l.F8, r.F8); c != 0 {return c} + if c:= o9.Compare(l.F9, r.F9); c != 0 {return c} + if c:= o10.Compare(l.F10, r.F10); c != 0 {return c} + return 0 + }, func(l, r Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]) bool { + return o1.Equals(l.F1, r.F1) && o2.Equals(l.F2, r.F2) && o3.Equals(l.F3, r.F3) && o4.Equals(l.F4, r.F4) && o5.Equals(l.F5, r.F5) && o6.Equals(l.F6, r.F6) && o7.Equals(l.F7, r.F7) && o8.Equals(l.F8, r.F8) && o9.Equals(l.F9, r.F9) && o10.Equals(l.F10, r.F10) + }) +} + +// Map10 maps each value of a [Tuple10] via a mapping function +func Map10[F1 ~func(T1) R1, F2 ~func(T2) R2, F3 ~func(T3) R3, F4 ~func(T4) R4, F5 ~func(T5) R5, F6 ~func(T6) R6, F7 ~func(T7) R7, F8 ~func(T8) R8, F9 ~func(T9) R9, F10 ~func(T10) R10, T1, R1, T2, R2, T3, R3, T4, R4, T5, R5, T6, R6, T7, R7, T8, R8, T9, R9, T10, R10 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10) func(Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]) Tuple10[R1, R2, R3, R4, R5, R6, R7, R8, R9, R10] { + return func(t Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]) Tuple10[R1, R2, R3, R4, R5, R6, R7, R8, R9, R10] { + return MakeTuple10( + f1(t.F1), + f2(t.F2), + f3(t.F3), + f4(t.F4), + f5(t.F5), + f6(t.F6), + f7(t.F7), + f8(t.F8), + f9(t.F9), + f10(t.F10), + ) + } +} + +// Replicate10 creates a [Tuple10] with all fields set to the input value `t` +func Replicate10[T any](t T) Tuple10[T, T, T, T, T, T, T, T, T, T] { + return MakeTuple10(t, t, t, t, t, t, t, t, t, t) +} + +// String prints some debug info for the [Tuple10] +func (t Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]) String() string { + return tupleString(t.F1, t.F2, t.F3, t.F4, t.F5, t.F6, t.F7, t.F8, t.F9, t.F10) +} + +// MarshalJSON marshals the [Tuple10] into a JSON array +func (t Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]) MarshalJSON() ([]byte, error) { + return tupleMarshalJSON(t.F1, t.F2, t.F3, t.F4, t.F5, t.F6, t.F7, t.F8, t.F9, t.F10) +} + +// UnmarshalJSON unmarshals a JSON array into a [Tuple10] +func (t *Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]) UnmarshalJSON(data []byte) error { + return tupleUnmarshalJSON(data, &t.F1, &t.F2, &t.F3, &t.F4, &t.F5, &t.F6, &t.F7, &t.F8, &t.F9, &t.F10) +} + +// ToArray converts the [Tuple10] into an array of type [R] using 10 transformation functions from [T] to [R] +// The inverse function is [FromArray10] +func ToArray10[F1 ~func(T1) R, F2 ~func(T2) R, F3 ~func(T3) R, F4 ~func(T4) R, F5 ~func(T5) R, F6 ~func(T6) R, F7 ~func(T7) R, F8 ~func(T8) R, F9 ~func(T9) R, F10 ~func(T10) R, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, R any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10) func(t Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]) []R { + return func(t Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]) []R { + return []R{ + f1(t.F1), + f2(t.F2), + f3(t.F3), + f4(t.F4), + f5(t.F5), + f6(t.F6), + f7(t.F7), + f8(t.F8), + f9(t.F9), + f10(t.F10), + } + } +} + +// FromArray converts an array of [R] into a [Tuple10] using 10 functions from [R] to [T] +// The inverse function is [ToArray10] +func FromArray10[F1 ~func(R) T1, F2 ~func(R) T2, F3 ~func(R) T3, F4 ~func(R) T4, F5 ~func(R) T5, F6 ~func(R) T6, F7 ~func(R) T7, F8 ~func(R) T8, F9 ~func(R) T9, F10 ~func(R) T10, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, R any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10) func(r []R) Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10] { + return func(r []R) Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10] { + return MakeTuple10( + f1(r[0]), + f2(r[1]), + f3(r[2]), + f4(r[3]), + f5(r[4]), + f6(r[5]), + f7(r[6]), + f8(r[7]), + f9(r[8]), + f10(r[9]), + ) + } +} + +// Push10 creates a [Tuple11] from a [Tuple10] by appending a constant value +func Push10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11 any](value T11) func(Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]) Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11] { + return func(t Tuple10[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]) Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11] { + return MakeTuple11(t.F1, t.F2, t.F3, t.F4, t.F5, t.F6, t.F7, t.F8, t.F9, t.F10, value) + } +} + +// MakeTuple11 is a function that converts its 11 parameters into a [Tuple11] +func MakeTuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11 any](t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11) Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11] { + return Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]{t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11} +} + +// Tupled11 converts a function with 11 parameters into a function taking a Tuple11 +// The inverse function is [Untupled11] +func Tupled11[F ~func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11) R, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, R any](f F) func(Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]) R { + return func(t Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]) R { + return f(t.F1, t.F2, t.F3, t.F4, t.F5, t.F6, t.F7, t.F8, t.F9, t.F10, t.F11) + } +} + +// Untupled11 converts a function with a [Tuple11] parameter into a function with 11 parameters +// The inverse function is [Tupled11] +func Untupled11[F ~func(Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]) R, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, R any](f F) func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11) R { + return f(MakeTuple11(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11)) + } +} + +// Monoid11 creates a [Monoid] for a [Tuple11] based on 11 monoids for the contained types +func Monoid11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11 any](m1 M.Monoid[T1], m2 M.Monoid[T2], m3 M.Monoid[T3], m4 M.Monoid[T4], m5 M.Monoid[T5], m6 M.Monoid[T6], m7 M.Monoid[T7], m8 M.Monoid[T8], m9 M.Monoid[T9], m10 M.Monoid[T10], m11 M.Monoid[T11]) M.Monoid[Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]] { + return M.MakeMonoid(func(l, r Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]) Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]{ + return MakeTuple11(m1.Concat(l.F1, r.F1), m2.Concat(l.F2, r.F2), m3.Concat(l.F3, r.F3), m4.Concat(l.F4, r.F4), m5.Concat(l.F5, r.F5), m6.Concat(l.F6, r.F6), m7.Concat(l.F7, r.F7), m8.Concat(l.F8, r.F8), m9.Concat(l.F9, r.F9), m10.Concat(l.F10, r.F10), m11.Concat(l.F11, r.F11)) + }, MakeTuple11(m1.Empty(), m2.Empty(), m3.Empty(), m4.Empty(), m5.Empty(), m6.Empty(), m7.Empty(), m8.Empty(), m9.Empty(), m10.Empty(), m11.Empty())) +} + +// Ord11 creates n [Ord] for a [Tuple11] based on 11 [Ord]s for the contained types +func Ord11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11 any](o1 O.Ord[T1], o2 O.Ord[T2], o3 O.Ord[T3], o4 O.Ord[T4], o5 O.Ord[T5], o6 O.Ord[T6], o7 O.Ord[T7], o8 O.Ord[T8], o9 O.Ord[T9], o10 O.Ord[T10], o11 O.Ord[T11]) O.Ord[Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]] { + return O.MakeOrd(func(l, r Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]) int { + if c:= o1.Compare(l.F1, r.F1); c != 0 {return c} + if c:= o2.Compare(l.F2, r.F2); c != 0 {return c} + if c:= o3.Compare(l.F3, r.F3); c != 0 {return c} + if c:= o4.Compare(l.F4, r.F4); c != 0 {return c} + if c:= o5.Compare(l.F5, r.F5); c != 0 {return c} + if c:= o6.Compare(l.F6, r.F6); c != 0 {return c} + if c:= o7.Compare(l.F7, r.F7); c != 0 {return c} + if c:= o8.Compare(l.F8, r.F8); c != 0 {return c} + if c:= o9.Compare(l.F9, r.F9); c != 0 {return c} + if c:= o10.Compare(l.F10, r.F10); c != 0 {return c} + if c:= o11.Compare(l.F11, r.F11); c != 0 {return c} + return 0 + }, func(l, r Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]) bool { + return o1.Equals(l.F1, r.F1) && o2.Equals(l.F2, r.F2) && o3.Equals(l.F3, r.F3) && o4.Equals(l.F4, r.F4) && o5.Equals(l.F5, r.F5) && o6.Equals(l.F6, r.F6) && o7.Equals(l.F7, r.F7) && o8.Equals(l.F8, r.F8) && o9.Equals(l.F9, r.F9) && o10.Equals(l.F10, r.F10) && o11.Equals(l.F11, r.F11) + }) +} + +// Map11 maps each value of a [Tuple11] via a mapping function +func Map11[F1 ~func(T1) R1, F2 ~func(T2) R2, F3 ~func(T3) R3, F4 ~func(T4) R4, F5 ~func(T5) R5, F6 ~func(T6) R6, F7 ~func(T7) R7, F8 ~func(T8) R8, F9 ~func(T9) R9, F10 ~func(T10) R10, F11 ~func(T11) R11, T1, R1, T2, R2, T3, R3, T4, R4, T5, R5, T6, R6, T7, R7, T8, R8, T9, R9, T10, R10, T11, R11 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10, f11 F11) func(Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]) Tuple11[R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11] { + return func(t Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]) Tuple11[R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11] { + return MakeTuple11( + f1(t.F1), + f2(t.F2), + f3(t.F3), + f4(t.F4), + f5(t.F5), + f6(t.F6), + f7(t.F7), + f8(t.F8), + f9(t.F9), + f10(t.F10), + f11(t.F11), + ) + } +} + +// Replicate11 creates a [Tuple11] with all fields set to the input value `t` +func Replicate11[T any](t T) Tuple11[T, T, T, T, T, T, T, T, T, T, T] { + return MakeTuple11(t, t, t, t, t, t, t, t, t, t, t) +} + +// String prints some debug info for the [Tuple11] +func (t Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]) String() string { + return tupleString(t.F1, t.F2, t.F3, t.F4, t.F5, t.F6, t.F7, t.F8, t.F9, t.F10, t.F11) +} + +// MarshalJSON marshals the [Tuple11] into a JSON array +func (t Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]) MarshalJSON() ([]byte, error) { + return tupleMarshalJSON(t.F1, t.F2, t.F3, t.F4, t.F5, t.F6, t.F7, t.F8, t.F9, t.F10, t.F11) +} + +// UnmarshalJSON unmarshals a JSON array into a [Tuple11] +func (t *Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]) UnmarshalJSON(data []byte) error { + return tupleUnmarshalJSON(data, &t.F1, &t.F2, &t.F3, &t.F4, &t.F5, &t.F6, &t.F7, &t.F8, &t.F9, &t.F10, &t.F11) +} + +// ToArray converts the [Tuple11] into an array of type [R] using 11 transformation functions from [T] to [R] +// The inverse function is [FromArray11] +func ToArray11[F1 ~func(T1) R, F2 ~func(T2) R, F3 ~func(T3) R, F4 ~func(T4) R, F5 ~func(T5) R, F6 ~func(T6) R, F7 ~func(T7) R, F8 ~func(T8) R, F9 ~func(T9) R, F10 ~func(T10) R, F11 ~func(T11) R, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, R any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10, f11 F11) func(t Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]) []R { + return func(t Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]) []R { + return []R{ + f1(t.F1), + f2(t.F2), + f3(t.F3), + f4(t.F4), + f5(t.F5), + f6(t.F6), + f7(t.F7), + f8(t.F8), + f9(t.F9), + f10(t.F10), + f11(t.F11), + } + } +} + +// FromArray converts an array of [R] into a [Tuple11] using 11 functions from [R] to [T] +// The inverse function is [ToArray11] +func FromArray11[F1 ~func(R) T1, F2 ~func(R) T2, F3 ~func(R) T3, F4 ~func(R) T4, F5 ~func(R) T5, F6 ~func(R) T6, F7 ~func(R) T7, F8 ~func(R) T8, F9 ~func(R) T9, F10 ~func(R) T10, F11 ~func(R) T11, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, R any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10, f11 F11) func(r []R) Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11] { + return func(r []R) Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11] { + return MakeTuple11( + f1(r[0]), + f2(r[1]), + f3(r[2]), + f4(r[3]), + f5(r[4]), + f6(r[5]), + f7(r[6]), + f8(r[7]), + f9(r[8]), + f10(r[9]), + f11(r[10]), + ) + } +} + +// Push11 creates a [Tuple12] from a [Tuple11] by appending a constant value +func Push11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12 any](value T12) func(Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]) Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12] { + return func(t Tuple11[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11]) Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12] { + return MakeTuple12(t.F1, t.F2, t.F3, t.F4, t.F5, t.F6, t.F7, t.F8, t.F9, t.F10, t.F11, value) + } +} + +// MakeTuple12 is a function that converts its 12 parameters into a [Tuple12] +func MakeTuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12 any](t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11, t12 T12) Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12] { + return Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]{t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12} +} + +// Tupled12 converts a function with 12 parameters into a function taking a Tuple12 +// The inverse function is [Untupled12] +func Tupled12[F ~func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12) R, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, R any](f F) func(Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]) R { + return func(t Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]) R { + return f(t.F1, t.F2, t.F3, t.F4, t.F5, t.F6, t.F7, t.F8, t.F9, t.F10, t.F11, t.F12) + } +} + +// Untupled12 converts a function with a [Tuple12] parameter into a function with 12 parameters +// The inverse function is [Tupled12] +func Untupled12[F ~func(Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]) R, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, R any](f F) func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11, t12 T12) R { + return f(MakeTuple12(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12)) + } +} + +// Monoid12 creates a [Monoid] for a [Tuple12] based on 12 monoids for the contained types +func Monoid12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12 any](m1 M.Monoid[T1], m2 M.Monoid[T2], m3 M.Monoid[T3], m4 M.Monoid[T4], m5 M.Monoid[T5], m6 M.Monoid[T6], m7 M.Monoid[T7], m8 M.Monoid[T8], m9 M.Monoid[T9], m10 M.Monoid[T10], m11 M.Monoid[T11], m12 M.Monoid[T12]) M.Monoid[Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]] { + return M.MakeMonoid(func(l, r Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]) Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]{ + return MakeTuple12(m1.Concat(l.F1, r.F1), m2.Concat(l.F2, r.F2), m3.Concat(l.F3, r.F3), m4.Concat(l.F4, r.F4), m5.Concat(l.F5, r.F5), m6.Concat(l.F6, r.F6), m7.Concat(l.F7, r.F7), m8.Concat(l.F8, r.F8), m9.Concat(l.F9, r.F9), m10.Concat(l.F10, r.F10), m11.Concat(l.F11, r.F11), m12.Concat(l.F12, r.F12)) + }, MakeTuple12(m1.Empty(), m2.Empty(), m3.Empty(), m4.Empty(), m5.Empty(), m6.Empty(), m7.Empty(), m8.Empty(), m9.Empty(), m10.Empty(), m11.Empty(), m12.Empty())) +} + +// Ord12 creates n [Ord] for a [Tuple12] based on 12 [Ord]s for the contained types +func Ord12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12 any](o1 O.Ord[T1], o2 O.Ord[T2], o3 O.Ord[T3], o4 O.Ord[T4], o5 O.Ord[T5], o6 O.Ord[T6], o7 O.Ord[T7], o8 O.Ord[T8], o9 O.Ord[T9], o10 O.Ord[T10], o11 O.Ord[T11], o12 O.Ord[T12]) O.Ord[Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]] { + return O.MakeOrd(func(l, r Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]) int { + if c:= o1.Compare(l.F1, r.F1); c != 0 {return c} + if c:= o2.Compare(l.F2, r.F2); c != 0 {return c} + if c:= o3.Compare(l.F3, r.F3); c != 0 {return c} + if c:= o4.Compare(l.F4, r.F4); c != 0 {return c} + if c:= o5.Compare(l.F5, r.F5); c != 0 {return c} + if c:= o6.Compare(l.F6, r.F6); c != 0 {return c} + if c:= o7.Compare(l.F7, r.F7); c != 0 {return c} + if c:= o8.Compare(l.F8, r.F8); c != 0 {return c} + if c:= o9.Compare(l.F9, r.F9); c != 0 {return c} + if c:= o10.Compare(l.F10, r.F10); c != 0 {return c} + if c:= o11.Compare(l.F11, r.F11); c != 0 {return c} + if c:= o12.Compare(l.F12, r.F12); c != 0 {return c} + return 0 + }, func(l, r Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]) bool { + return o1.Equals(l.F1, r.F1) && o2.Equals(l.F2, r.F2) && o3.Equals(l.F3, r.F3) && o4.Equals(l.F4, r.F4) && o5.Equals(l.F5, r.F5) && o6.Equals(l.F6, r.F6) && o7.Equals(l.F7, r.F7) && o8.Equals(l.F8, r.F8) && o9.Equals(l.F9, r.F9) && o10.Equals(l.F10, r.F10) && o11.Equals(l.F11, r.F11) && o12.Equals(l.F12, r.F12) + }) +} + +// Map12 maps each value of a [Tuple12] via a mapping function +func Map12[F1 ~func(T1) R1, F2 ~func(T2) R2, F3 ~func(T3) R3, F4 ~func(T4) R4, F5 ~func(T5) R5, F6 ~func(T6) R6, F7 ~func(T7) R7, F8 ~func(T8) R8, F9 ~func(T9) R9, F10 ~func(T10) R10, F11 ~func(T11) R11, F12 ~func(T12) R12, T1, R1, T2, R2, T3, R3, T4, R4, T5, R5, T6, R6, T7, R7, T8, R8, T9, R9, T10, R10, T11, R11, T12, R12 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10, f11 F11, f12 F12) func(Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]) Tuple12[R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12] { + return func(t Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]) Tuple12[R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12] { + return MakeTuple12( + f1(t.F1), + f2(t.F2), + f3(t.F3), + f4(t.F4), + f5(t.F5), + f6(t.F6), + f7(t.F7), + f8(t.F8), + f9(t.F9), + f10(t.F10), + f11(t.F11), + f12(t.F12), + ) + } +} + +// Replicate12 creates a [Tuple12] with all fields set to the input value `t` +func Replicate12[T any](t T) Tuple12[T, T, T, T, T, T, T, T, T, T, T, T] { + return MakeTuple12(t, t, t, t, t, t, t, t, t, t, t, t) +} + +// String prints some debug info for the [Tuple12] +func (t Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]) String() string { + return tupleString(t.F1, t.F2, t.F3, t.F4, t.F5, t.F6, t.F7, t.F8, t.F9, t.F10, t.F11, t.F12) +} + +// MarshalJSON marshals the [Tuple12] into a JSON array +func (t Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]) MarshalJSON() ([]byte, error) { + return tupleMarshalJSON(t.F1, t.F2, t.F3, t.F4, t.F5, t.F6, t.F7, t.F8, t.F9, t.F10, t.F11, t.F12) +} + +// UnmarshalJSON unmarshals a JSON array into a [Tuple12] +func (t *Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]) UnmarshalJSON(data []byte) error { + return tupleUnmarshalJSON(data, &t.F1, &t.F2, &t.F3, &t.F4, &t.F5, &t.F6, &t.F7, &t.F8, &t.F9, &t.F10, &t.F11, &t.F12) +} + +// ToArray converts the [Tuple12] into an array of type [R] using 12 transformation functions from [T] to [R] +// The inverse function is [FromArray12] +func ToArray12[F1 ~func(T1) R, F2 ~func(T2) R, F3 ~func(T3) R, F4 ~func(T4) R, F5 ~func(T5) R, F6 ~func(T6) R, F7 ~func(T7) R, F8 ~func(T8) R, F9 ~func(T9) R, F10 ~func(T10) R, F11 ~func(T11) R, F12 ~func(T12) R, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, R any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10, f11 F11, f12 F12) func(t Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]) []R { + return func(t Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]) []R { + return []R{ + f1(t.F1), + f2(t.F2), + f3(t.F3), + f4(t.F4), + f5(t.F5), + f6(t.F6), + f7(t.F7), + f8(t.F8), + f9(t.F9), + f10(t.F10), + f11(t.F11), + f12(t.F12), + } + } +} + +// FromArray converts an array of [R] into a [Tuple12] using 12 functions from [R] to [T] +// The inverse function is [ToArray12] +func FromArray12[F1 ~func(R) T1, F2 ~func(R) T2, F3 ~func(R) T3, F4 ~func(R) T4, F5 ~func(R) T5, F6 ~func(R) T6, F7 ~func(R) T7, F8 ~func(R) T8, F9 ~func(R) T9, F10 ~func(R) T10, F11 ~func(R) T11, F12 ~func(R) T12, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, R any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10, f11 F11, f12 F12) func(r []R) Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12] { + return func(r []R) Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12] { + return MakeTuple12( + f1(r[0]), + f2(r[1]), + f3(r[2]), + f4(r[3]), + f5(r[4]), + f6(r[5]), + f7(r[6]), + f8(r[7]), + f9(r[8]), + f10(r[9]), + f11(r[10]), + f12(r[11]), + ) + } +} + +// Push12 creates a [Tuple13] from a [Tuple12] by appending a constant value +func Push12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13 any](value T13) func(Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]) Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13] { + return func(t Tuple12[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12]) Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13] { + return MakeTuple13(t.F1, t.F2, t.F3, t.F4, t.F5, t.F6, t.F7, t.F8, t.F9, t.F10, t.F11, t.F12, value) + } +} + +// MakeTuple13 is a function that converts its 13 parameters into a [Tuple13] +func MakeTuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13 any](t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11, t12 T12, t13 T13) Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13] { + return Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]{t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13} +} + +// Tupled13 converts a function with 13 parameters into a function taking a Tuple13 +// The inverse function is [Untupled13] +func Tupled13[F ~func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13) R, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, R any](f F) func(Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]) R { + return func(t Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]) R { + return f(t.F1, t.F2, t.F3, t.F4, t.F5, t.F6, t.F7, t.F8, t.F9, t.F10, t.F11, t.F12, t.F13) + } +} + +// Untupled13 converts a function with a [Tuple13] parameter into a function with 13 parameters +// The inverse function is [Tupled13] +func Untupled13[F ~func(Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]) R, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, R any](f F) func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11, t12 T12, t13 T13) R { + return f(MakeTuple13(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13)) + } +} + +// Monoid13 creates a [Monoid] for a [Tuple13] based on 13 monoids for the contained types +func Monoid13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13 any](m1 M.Monoid[T1], m2 M.Monoid[T2], m3 M.Monoid[T3], m4 M.Monoid[T4], m5 M.Monoid[T5], m6 M.Monoid[T6], m7 M.Monoid[T7], m8 M.Monoid[T8], m9 M.Monoid[T9], m10 M.Monoid[T10], m11 M.Monoid[T11], m12 M.Monoid[T12], m13 M.Monoid[T13]) M.Monoid[Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]] { + return M.MakeMonoid(func(l, r Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]) Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]{ + return MakeTuple13(m1.Concat(l.F1, r.F1), m2.Concat(l.F2, r.F2), m3.Concat(l.F3, r.F3), m4.Concat(l.F4, r.F4), m5.Concat(l.F5, r.F5), m6.Concat(l.F6, r.F6), m7.Concat(l.F7, r.F7), m8.Concat(l.F8, r.F8), m9.Concat(l.F9, r.F9), m10.Concat(l.F10, r.F10), m11.Concat(l.F11, r.F11), m12.Concat(l.F12, r.F12), m13.Concat(l.F13, r.F13)) + }, MakeTuple13(m1.Empty(), m2.Empty(), m3.Empty(), m4.Empty(), m5.Empty(), m6.Empty(), m7.Empty(), m8.Empty(), m9.Empty(), m10.Empty(), m11.Empty(), m12.Empty(), m13.Empty())) +} + +// Ord13 creates n [Ord] for a [Tuple13] based on 13 [Ord]s for the contained types +func Ord13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13 any](o1 O.Ord[T1], o2 O.Ord[T2], o3 O.Ord[T3], o4 O.Ord[T4], o5 O.Ord[T5], o6 O.Ord[T6], o7 O.Ord[T7], o8 O.Ord[T8], o9 O.Ord[T9], o10 O.Ord[T10], o11 O.Ord[T11], o12 O.Ord[T12], o13 O.Ord[T13]) O.Ord[Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]] { + return O.MakeOrd(func(l, r Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]) int { + if c:= o1.Compare(l.F1, r.F1); c != 0 {return c} + if c:= o2.Compare(l.F2, r.F2); c != 0 {return c} + if c:= o3.Compare(l.F3, r.F3); c != 0 {return c} + if c:= o4.Compare(l.F4, r.F4); c != 0 {return c} + if c:= o5.Compare(l.F5, r.F5); c != 0 {return c} + if c:= o6.Compare(l.F6, r.F6); c != 0 {return c} + if c:= o7.Compare(l.F7, r.F7); c != 0 {return c} + if c:= o8.Compare(l.F8, r.F8); c != 0 {return c} + if c:= o9.Compare(l.F9, r.F9); c != 0 {return c} + if c:= o10.Compare(l.F10, r.F10); c != 0 {return c} + if c:= o11.Compare(l.F11, r.F11); c != 0 {return c} + if c:= o12.Compare(l.F12, r.F12); c != 0 {return c} + if c:= o13.Compare(l.F13, r.F13); c != 0 {return c} + return 0 + }, func(l, r Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]) bool { + return o1.Equals(l.F1, r.F1) && o2.Equals(l.F2, r.F2) && o3.Equals(l.F3, r.F3) && o4.Equals(l.F4, r.F4) && o5.Equals(l.F5, r.F5) && o6.Equals(l.F6, r.F6) && o7.Equals(l.F7, r.F7) && o8.Equals(l.F8, r.F8) && o9.Equals(l.F9, r.F9) && o10.Equals(l.F10, r.F10) && o11.Equals(l.F11, r.F11) && o12.Equals(l.F12, r.F12) && o13.Equals(l.F13, r.F13) + }) +} + +// Map13 maps each value of a [Tuple13] via a mapping function +func Map13[F1 ~func(T1) R1, F2 ~func(T2) R2, F3 ~func(T3) R3, F4 ~func(T4) R4, F5 ~func(T5) R5, F6 ~func(T6) R6, F7 ~func(T7) R7, F8 ~func(T8) R8, F9 ~func(T9) R9, F10 ~func(T10) R10, F11 ~func(T11) R11, F12 ~func(T12) R12, F13 ~func(T13) R13, T1, R1, T2, R2, T3, R3, T4, R4, T5, R5, T6, R6, T7, R7, T8, R8, T9, R9, T10, R10, T11, R11, T12, R12, T13, R13 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10, f11 F11, f12 F12, f13 F13) func(Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]) Tuple13[R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13] { + return func(t Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]) Tuple13[R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13] { + return MakeTuple13( + f1(t.F1), + f2(t.F2), + f3(t.F3), + f4(t.F4), + f5(t.F5), + f6(t.F6), + f7(t.F7), + f8(t.F8), + f9(t.F9), + f10(t.F10), + f11(t.F11), + f12(t.F12), + f13(t.F13), + ) + } +} + +// Replicate13 creates a [Tuple13] with all fields set to the input value `t` +func Replicate13[T any](t T) Tuple13[T, T, T, T, T, T, T, T, T, T, T, T, T] { + return MakeTuple13(t, t, t, t, t, t, t, t, t, t, t, t, t) +} + +// String prints some debug info for the [Tuple13] +func (t Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]) String() string { + return tupleString(t.F1, t.F2, t.F3, t.F4, t.F5, t.F6, t.F7, t.F8, t.F9, t.F10, t.F11, t.F12, t.F13) +} + +// MarshalJSON marshals the [Tuple13] into a JSON array +func (t Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]) MarshalJSON() ([]byte, error) { + return tupleMarshalJSON(t.F1, t.F2, t.F3, t.F4, t.F5, t.F6, t.F7, t.F8, t.F9, t.F10, t.F11, t.F12, t.F13) +} + +// UnmarshalJSON unmarshals a JSON array into a [Tuple13] +func (t *Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]) UnmarshalJSON(data []byte) error { + return tupleUnmarshalJSON(data, &t.F1, &t.F2, &t.F3, &t.F4, &t.F5, &t.F6, &t.F7, &t.F8, &t.F9, &t.F10, &t.F11, &t.F12, &t.F13) +} + +// ToArray converts the [Tuple13] into an array of type [R] using 13 transformation functions from [T] to [R] +// The inverse function is [FromArray13] +func ToArray13[F1 ~func(T1) R, F2 ~func(T2) R, F3 ~func(T3) R, F4 ~func(T4) R, F5 ~func(T5) R, F6 ~func(T6) R, F7 ~func(T7) R, F8 ~func(T8) R, F9 ~func(T9) R, F10 ~func(T10) R, F11 ~func(T11) R, F12 ~func(T12) R, F13 ~func(T13) R, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, R any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10, f11 F11, f12 F12, f13 F13) func(t Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]) []R { + return func(t Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]) []R { + return []R{ + f1(t.F1), + f2(t.F2), + f3(t.F3), + f4(t.F4), + f5(t.F5), + f6(t.F6), + f7(t.F7), + f8(t.F8), + f9(t.F9), + f10(t.F10), + f11(t.F11), + f12(t.F12), + f13(t.F13), + } + } +} + +// FromArray converts an array of [R] into a [Tuple13] using 13 functions from [R] to [T] +// The inverse function is [ToArray13] +func FromArray13[F1 ~func(R) T1, F2 ~func(R) T2, F3 ~func(R) T3, F4 ~func(R) T4, F5 ~func(R) T5, F6 ~func(R) T6, F7 ~func(R) T7, F8 ~func(R) T8, F9 ~func(R) T9, F10 ~func(R) T10, F11 ~func(R) T11, F12 ~func(R) T12, F13 ~func(R) T13, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, R any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10, f11 F11, f12 F12, f13 F13) func(r []R) Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13] { + return func(r []R) Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13] { + return MakeTuple13( + f1(r[0]), + f2(r[1]), + f3(r[2]), + f4(r[3]), + f5(r[4]), + f6(r[5]), + f7(r[6]), + f8(r[7]), + f9(r[8]), + f10(r[9]), + f11(r[10]), + f12(r[11]), + f13(r[12]), + ) + } +} + +// Push13 creates a [Tuple14] from a [Tuple13] by appending a constant value +func Push13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14 any](value T14) func(Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]) Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14] { + return func(t Tuple13[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13]) Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14] { + return MakeTuple14(t.F1, t.F2, t.F3, t.F4, t.F5, t.F6, t.F7, t.F8, t.F9, t.F10, t.F11, t.F12, t.F13, value) + } +} + +// MakeTuple14 is a function that converts its 14 parameters into a [Tuple14] +func MakeTuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14 any](t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11, t12 T12, t13 T13, t14 T14) Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14] { + return Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]{t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14} +} + +// Tupled14 converts a function with 14 parameters into a function taking a Tuple14 +// The inverse function is [Untupled14] +func Tupled14[F ~func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14) R, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, R any](f F) func(Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]) R { + return func(t Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]) R { + return f(t.F1, t.F2, t.F3, t.F4, t.F5, t.F6, t.F7, t.F8, t.F9, t.F10, t.F11, t.F12, t.F13, t.F14) + } +} + +// Untupled14 converts a function with a [Tuple14] parameter into a function with 14 parameters +// The inverse function is [Tupled14] +func Untupled14[F ~func(Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]) R, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, R any](f F) func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11, t12 T12, t13 T13, t14 T14) R { + return f(MakeTuple14(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14)) + } +} + +// Monoid14 creates a [Monoid] for a [Tuple14] based on 14 monoids for the contained types +func Monoid14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14 any](m1 M.Monoid[T1], m2 M.Monoid[T2], m3 M.Monoid[T3], m4 M.Monoid[T4], m5 M.Monoid[T5], m6 M.Monoid[T6], m7 M.Monoid[T7], m8 M.Monoid[T8], m9 M.Monoid[T9], m10 M.Monoid[T10], m11 M.Monoid[T11], m12 M.Monoid[T12], m13 M.Monoid[T13], m14 M.Monoid[T14]) M.Monoid[Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]] { + return M.MakeMonoid(func(l, r Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]) Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]{ + return MakeTuple14(m1.Concat(l.F1, r.F1), m2.Concat(l.F2, r.F2), m3.Concat(l.F3, r.F3), m4.Concat(l.F4, r.F4), m5.Concat(l.F5, r.F5), m6.Concat(l.F6, r.F6), m7.Concat(l.F7, r.F7), m8.Concat(l.F8, r.F8), m9.Concat(l.F9, r.F9), m10.Concat(l.F10, r.F10), m11.Concat(l.F11, r.F11), m12.Concat(l.F12, r.F12), m13.Concat(l.F13, r.F13), m14.Concat(l.F14, r.F14)) + }, MakeTuple14(m1.Empty(), m2.Empty(), m3.Empty(), m4.Empty(), m5.Empty(), m6.Empty(), m7.Empty(), m8.Empty(), m9.Empty(), m10.Empty(), m11.Empty(), m12.Empty(), m13.Empty(), m14.Empty())) +} + +// Ord14 creates n [Ord] for a [Tuple14] based on 14 [Ord]s for the contained types +func Ord14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14 any](o1 O.Ord[T1], o2 O.Ord[T2], o3 O.Ord[T3], o4 O.Ord[T4], o5 O.Ord[T5], o6 O.Ord[T6], o7 O.Ord[T7], o8 O.Ord[T8], o9 O.Ord[T9], o10 O.Ord[T10], o11 O.Ord[T11], o12 O.Ord[T12], o13 O.Ord[T13], o14 O.Ord[T14]) O.Ord[Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]] { + return O.MakeOrd(func(l, r Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]) int { + if c:= o1.Compare(l.F1, r.F1); c != 0 {return c} + if c:= o2.Compare(l.F2, r.F2); c != 0 {return c} + if c:= o3.Compare(l.F3, r.F3); c != 0 {return c} + if c:= o4.Compare(l.F4, r.F4); c != 0 {return c} + if c:= o5.Compare(l.F5, r.F5); c != 0 {return c} + if c:= o6.Compare(l.F6, r.F6); c != 0 {return c} + if c:= o7.Compare(l.F7, r.F7); c != 0 {return c} + if c:= o8.Compare(l.F8, r.F8); c != 0 {return c} + if c:= o9.Compare(l.F9, r.F9); c != 0 {return c} + if c:= o10.Compare(l.F10, r.F10); c != 0 {return c} + if c:= o11.Compare(l.F11, r.F11); c != 0 {return c} + if c:= o12.Compare(l.F12, r.F12); c != 0 {return c} + if c:= o13.Compare(l.F13, r.F13); c != 0 {return c} + if c:= o14.Compare(l.F14, r.F14); c != 0 {return c} + return 0 + }, func(l, r Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]) bool { + return o1.Equals(l.F1, r.F1) && o2.Equals(l.F2, r.F2) && o3.Equals(l.F3, r.F3) && o4.Equals(l.F4, r.F4) && o5.Equals(l.F5, r.F5) && o6.Equals(l.F6, r.F6) && o7.Equals(l.F7, r.F7) && o8.Equals(l.F8, r.F8) && o9.Equals(l.F9, r.F9) && o10.Equals(l.F10, r.F10) && o11.Equals(l.F11, r.F11) && o12.Equals(l.F12, r.F12) && o13.Equals(l.F13, r.F13) && o14.Equals(l.F14, r.F14) + }) +} + +// Map14 maps each value of a [Tuple14] via a mapping function +func Map14[F1 ~func(T1) R1, F2 ~func(T2) R2, F3 ~func(T3) R3, F4 ~func(T4) R4, F5 ~func(T5) R5, F6 ~func(T6) R6, F7 ~func(T7) R7, F8 ~func(T8) R8, F9 ~func(T9) R9, F10 ~func(T10) R10, F11 ~func(T11) R11, F12 ~func(T12) R12, F13 ~func(T13) R13, F14 ~func(T14) R14, T1, R1, T2, R2, T3, R3, T4, R4, T5, R5, T6, R6, T7, R7, T8, R8, T9, R9, T10, R10, T11, R11, T12, R12, T13, R13, T14, R14 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10, f11 F11, f12 F12, f13 F13, f14 F14) func(Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]) Tuple14[R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14] { + return func(t Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]) Tuple14[R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14] { + return MakeTuple14( + f1(t.F1), + f2(t.F2), + f3(t.F3), + f4(t.F4), + f5(t.F5), + f6(t.F6), + f7(t.F7), + f8(t.F8), + f9(t.F9), + f10(t.F10), + f11(t.F11), + f12(t.F12), + f13(t.F13), + f14(t.F14), + ) + } +} + +// Replicate14 creates a [Tuple14] with all fields set to the input value `t` +func Replicate14[T any](t T) Tuple14[T, T, T, T, T, T, T, T, T, T, T, T, T, T] { + return MakeTuple14(t, t, t, t, t, t, t, t, t, t, t, t, t, t) +} + +// String prints some debug info for the [Tuple14] +func (t Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]) String() string { + return tupleString(t.F1, t.F2, t.F3, t.F4, t.F5, t.F6, t.F7, t.F8, t.F9, t.F10, t.F11, t.F12, t.F13, t.F14) +} + +// MarshalJSON marshals the [Tuple14] into a JSON array +func (t Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]) MarshalJSON() ([]byte, error) { + return tupleMarshalJSON(t.F1, t.F2, t.F3, t.F4, t.F5, t.F6, t.F7, t.F8, t.F9, t.F10, t.F11, t.F12, t.F13, t.F14) +} + +// UnmarshalJSON unmarshals a JSON array into a [Tuple14] +func (t *Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]) UnmarshalJSON(data []byte) error { + return tupleUnmarshalJSON(data, &t.F1, &t.F2, &t.F3, &t.F4, &t.F5, &t.F6, &t.F7, &t.F8, &t.F9, &t.F10, &t.F11, &t.F12, &t.F13, &t.F14) +} + +// ToArray converts the [Tuple14] into an array of type [R] using 14 transformation functions from [T] to [R] +// The inverse function is [FromArray14] +func ToArray14[F1 ~func(T1) R, F2 ~func(T2) R, F3 ~func(T3) R, F4 ~func(T4) R, F5 ~func(T5) R, F6 ~func(T6) R, F7 ~func(T7) R, F8 ~func(T8) R, F9 ~func(T9) R, F10 ~func(T10) R, F11 ~func(T11) R, F12 ~func(T12) R, F13 ~func(T13) R, F14 ~func(T14) R, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, R any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10, f11 F11, f12 F12, f13 F13, f14 F14) func(t Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]) []R { + return func(t Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]) []R { + return []R{ + f1(t.F1), + f2(t.F2), + f3(t.F3), + f4(t.F4), + f5(t.F5), + f6(t.F6), + f7(t.F7), + f8(t.F8), + f9(t.F9), + f10(t.F10), + f11(t.F11), + f12(t.F12), + f13(t.F13), + f14(t.F14), + } + } +} + +// FromArray converts an array of [R] into a [Tuple14] using 14 functions from [R] to [T] +// The inverse function is [ToArray14] +func FromArray14[F1 ~func(R) T1, F2 ~func(R) T2, F3 ~func(R) T3, F4 ~func(R) T4, F5 ~func(R) T5, F6 ~func(R) T6, F7 ~func(R) T7, F8 ~func(R) T8, F9 ~func(R) T9, F10 ~func(R) T10, F11 ~func(R) T11, F12 ~func(R) T12, F13 ~func(R) T13, F14 ~func(R) T14, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, R any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10, f11 F11, f12 F12, f13 F13, f14 F14) func(r []R) Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14] { + return func(r []R) Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14] { + return MakeTuple14( + f1(r[0]), + f2(r[1]), + f3(r[2]), + f4(r[3]), + f5(r[4]), + f6(r[5]), + f7(r[6]), + f8(r[7]), + f9(r[8]), + f10(r[9]), + f11(r[10]), + f12(r[11]), + f13(r[12]), + f14(r[13]), + ) + } +} + +// Push14 creates a [Tuple15] from a [Tuple14] by appending a constant value +func Push14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15 any](value T15) func(Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]) Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15] { + return func(t Tuple14[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14]) Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15] { + return MakeTuple15(t.F1, t.F2, t.F3, t.F4, t.F5, t.F6, t.F7, t.F8, t.F9, t.F10, t.F11, t.F12, t.F13, t.F14, value) + } +} + +// MakeTuple15 is a function that converts its 15 parameters into a [Tuple15] +func MakeTuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15 any](t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11, t12 T12, t13 T13, t14 T14, t15 T15) Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15] { + return Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]{t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15} +} + +// Tupled15 converts a function with 15 parameters into a function taking a Tuple15 +// The inverse function is [Untupled15] +func Tupled15[F ~func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15) R, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, R any](f F) func(Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]) R { + return func(t Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]) R { + return f(t.F1, t.F2, t.F3, t.F4, t.F5, t.F6, t.F7, t.F8, t.F9, t.F10, t.F11, t.F12, t.F13, t.F14, t.F15) + } +} + +// Untupled15 converts a function with a [Tuple15] parameter into a function with 15 parameters +// The inverse function is [Tupled15] +func Untupled15[F ~func(Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]) R, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, R any](f F) func(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15) R { + return func(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11, t12 T12, t13 T13, t14 T14, t15 T15) R { + return f(MakeTuple15(t1, t2, t3, t4, t5, t6, t7, t8, t9, t10, t11, t12, t13, t14, t15)) + } +} + +// Monoid15 creates a [Monoid] for a [Tuple15] based on 15 monoids for the contained types +func Monoid15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15 any](m1 M.Monoid[T1], m2 M.Monoid[T2], m3 M.Monoid[T3], m4 M.Monoid[T4], m5 M.Monoid[T5], m6 M.Monoid[T6], m7 M.Monoid[T7], m8 M.Monoid[T8], m9 M.Monoid[T9], m10 M.Monoid[T10], m11 M.Monoid[T11], m12 M.Monoid[T12], m13 M.Monoid[T13], m14 M.Monoid[T14], m15 M.Monoid[T15]) M.Monoid[Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] { + return M.MakeMonoid(func(l, r Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]) Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]{ + return MakeTuple15(m1.Concat(l.F1, r.F1), m2.Concat(l.F2, r.F2), m3.Concat(l.F3, r.F3), m4.Concat(l.F4, r.F4), m5.Concat(l.F5, r.F5), m6.Concat(l.F6, r.F6), m7.Concat(l.F7, r.F7), m8.Concat(l.F8, r.F8), m9.Concat(l.F9, r.F9), m10.Concat(l.F10, r.F10), m11.Concat(l.F11, r.F11), m12.Concat(l.F12, r.F12), m13.Concat(l.F13, r.F13), m14.Concat(l.F14, r.F14), m15.Concat(l.F15, r.F15)) + }, MakeTuple15(m1.Empty(), m2.Empty(), m3.Empty(), m4.Empty(), m5.Empty(), m6.Empty(), m7.Empty(), m8.Empty(), m9.Empty(), m10.Empty(), m11.Empty(), m12.Empty(), m13.Empty(), m14.Empty(), m15.Empty())) +} + +// Ord15 creates n [Ord] for a [Tuple15] based on 15 [Ord]s for the contained types +func Ord15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15 any](o1 O.Ord[T1], o2 O.Ord[T2], o3 O.Ord[T3], o4 O.Ord[T4], o5 O.Ord[T5], o6 O.Ord[T6], o7 O.Ord[T7], o8 O.Ord[T8], o9 O.Ord[T9], o10 O.Ord[T10], o11 O.Ord[T11], o12 O.Ord[T12], o13 O.Ord[T13], o14 O.Ord[T14], o15 O.Ord[T15]) O.Ord[Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]] { + return O.MakeOrd(func(l, r Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]) int { + if c:= o1.Compare(l.F1, r.F1); c != 0 {return c} + if c:= o2.Compare(l.F2, r.F2); c != 0 {return c} + if c:= o3.Compare(l.F3, r.F3); c != 0 {return c} + if c:= o4.Compare(l.F4, r.F4); c != 0 {return c} + if c:= o5.Compare(l.F5, r.F5); c != 0 {return c} + if c:= o6.Compare(l.F6, r.F6); c != 0 {return c} + if c:= o7.Compare(l.F7, r.F7); c != 0 {return c} + if c:= o8.Compare(l.F8, r.F8); c != 0 {return c} + if c:= o9.Compare(l.F9, r.F9); c != 0 {return c} + if c:= o10.Compare(l.F10, r.F10); c != 0 {return c} + if c:= o11.Compare(l.F11, r.F11); c != 0 {return c} + if c:= o12.Compare(l.F12, r.F12); c != 0 {return c} + if c:= o13.Compare(l.F13, r.F13); c != 0 {return c} + if c:= o14.Compare(l.F14, r.F14); c != 0 {return c} + if c:= o15.Compare(l.F15, r.F15); c != 0 {return c} + return 0 + }, func(l, r Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]) bool { + return o1.Equals(l.F1, r.F1) && o2.Equals(l.F2, r.F2) && o3.Equals(l.F3, r.F3) && o4.Equals(l.F4, r.F4) && o5.Equals(l.F5, r.F5) && o6.Equals(l.F6, r.F6) && o7.Equals(l.F7, r.F7) && o8.Equals(l.F8, r.F8) && o9.Equals(l.F9, r.F9) && o10.Equals(l.F10, r.F10) && o11.Equals(l.F11, r.F11) && o12.Equals(l.F12, r.F12) && o13.Equals(l.F13, r.F13) && o14.Equals(l.F14, r.F14) && o15.Equals(l.F15, r.F15) + }) +} + +// Map15 maps each value of a [Tuple15] via a mapping function +func Map15[F1 ~func(T1) R1, F2 ~func(T2) R2, F3 ~func(T3) R3, F4 ~func(T4) R4, F5 ~func(T5) R5, F6 ~func(T6) R6, F7 ~func(T7) R7, F8 ~func(T8) R8, F9 ~func(T9) R9, F10 ~func(T10) R10, F11 ~func(T11) R11, F12 ~func(T12) R12, F13 ~func(T13) R13, F14 ~func(T14) R14, F15 ~func(T15) R15, T1, R1, T2, R2, T3, R3, T4, R4, T5, R5, T6, R6, T7, R7, T8, R8, T9, R9, T10, R10, T11, R11, T12, R12, T13, R13, T14, R14, T15, R15 any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10, f11 F11, f12 F12, f13 F13, f14 F14, f15 F15) func(Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]) Tuple15[R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14, R15] { + return func(t Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]) Tuple15[R1, R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13, R14, R15] { + return MakeTuple15( + f1(t.F1), + f2(t.F2), + f3(t.F3), + f4(t.F4), + f5(t.F5), + f6(t.F6), + f7(t.F7), + f8(t.F8), + f9(t.F9), + f10(t.F10), + f11(t.F11), + f12(t.F12), + f13(t.F13), + f14(t.F14), + f15(t.F15), + ) + } +} + +// Replicate15 creates a [Tuple15] with all fields set to the input value `t` +func Replicate15[T any](t T) Tuple15[T, T, T, T, T, T, T, T, T, T, T, T, T, T, T] { + return MakeTuple15(t, t, t, t, t, t, t, t, t, t, t, t, t, t, t) +} + +// String prints some debug info for the [Tuple15] +func (t Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]) String() string { + return tupleString(t.F1, t.F2, t.F3, t.F4, t.F5, t.F6, t.F7, t.F8, t.F9, t.F10, t.F11, t.F12, t.F13, t.F14, t.F15) +} + +// MarshalJSON marshals the [Tuple15] into a JSON array +func (t Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]) MarshalJSON() ([]byte, error) { + return tupleMarshalJSON(t.F1, t.F2, t.F3, t.F4, t.F5, t.F6, t.F7, t.F8, t.F9, t.F10, t.F11, t.F12, t.F13, t.F14, t.F15) +} + +// UnmarshalJSON unmarshals a JSON array into a [Tuple15] +func (t *Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]) UnmarshalJSON(data []byte) error { + return tupleUnmarshalJSON(data, &t.F1, &t.F2, &t.F3, &t.F4, &t.F5, &t.F6, &t.F7, &t.F8, &t.F9, &t.F10, &t.F11, &t.F12, &t.F13, &t.F14, &t.F15) +} + +// ToArray converts the [Tuple15] into an array of type [R] using 15 transformation functions from [T] to [R] +// The inverse function is [FromArray15] +func ToArray15[F1 ~func(T1) R, F2 ~func(T2) R, F3 ~func(T3) R, F4 ~func(T4) R, F5 ~func(T5) R, F6 ~func(T6) R, F7 ~func(T7) R, F8 ~func(T8) R, F9 ~func(T9) R, F10 ~func(T10) R, F11 ~func(T11) R, F12 ~func(T12) R, F13 ~func(T13) R, F14 ~func(T14) R, F15 ~func(T15) R, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, R any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10, f11 F11, f12 F12, f13 F13, f14 F14, f15 F15) func(t Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]) []R { + return func(t Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15]) []R { + return []R{ + f1(t.F1), + f2(t.F2), + f3(t.F3), + f4(t.F4), + f5(t.F5), + f6(t.F6), + f7(t.F7), + f8(t.F8), + f9(t.F9), + f10(t.F10), + f11(t.F11), + f12(t.F12), + f13(t.F13), + f14(t.F14), + f15(t.F15), + } + } +} + +// FromArray converts an array of [R] into a [Tuple15] using 15 functions from [R] to [T] +// The inverse function is [ToArray15] +func FromArray15[F1 ~func(R) T1, F2 ~func(R) T2, F3 ~func(R) T3, F4 ~func(R) T4, F5 ~func(R) T5, F6 ~func(R) T6, F7 ~func(R) T7, F8 ~func(R) T8, F9 ~func(R) T9, F10 ~func(R) T10, F11 ~func(R) T11, F12 ~func(R) T12, F13 ~func(R) T13, F14 ~func(R) T14, F15 ~func(R) T15, T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, R any](f1 F1, f2 F2, f3 F3, f4 F4, f5 F5, f6 F6, f7 F7, f8 F8, f9 F9, f10 F10, f11 F11, f12 F12, f13 F13, f14 F14, f15 F15) func(r []R) Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15] { + return func(r []R) Tuple15[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15] { + return MakeTuple15( + f1(r[0]), + f2(r[1]), + f3(r[2]), + f4(r[3]), + f5(r[4]), + f6(r[5]), + f7(r[6]), + f8(r[7]), + f9(r[8]), + f10(r[9]), + f11(r[10]), + f12(r[11]), + f13(r[12]), + f14(r[13]), + f15(r[14]), + ) + } +} diff --git a/v2/tuple/tuple.go b/v2/tuple/tuple.go new file mode 100644 index 0000000..1d8f879 --- /dev/null +++ b/v2/tuple/tuple.go @@ -0,0 +1,134 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +// Package tuple contains type definitions and functions for data structures for tuples of heterogenous types. For homogeneous types +// consider to use arrays for simplicity +package tuple + +import ( + "encoding/json" + "fmt" + "strings" + + N "github.com/IBM/fp-go/v2/number" +) + +// Of creates a [Tuple1] from a single value. +// This is a convenience function equivalent to [MakeTuple1]. +// +// Example: +// +// t := tuple.Of(42) // Creates Tuple1[int]{F1: 42} +func Of[T1 any](t T1) Tuple1[T1] { + return MakeTuple1(t) +} + +// First returns the first element of a [Tuple2]. +// This is a convenience accessor for the F1 field. +// +// Example: +// +// t := tuple.MakeTuple2("hello", 42) +// s := tuple.First(t) // Returns "hello" +func First[T1, T2 any](t Tuple2[T1, T2]) T1 { + return t.F1 +} + +// Second returns the second element of a [Tuple2]. +// This is a convenience accessor for the F2 field. +// +// Example: +// +// t := tuple.MakeTuple2("hello", 42) +// n := tuple.Second(t) // Returns 42 +func Second[T1, T2 any](t Tuple2[T1, T2]) T2 { + return t.F2 +} + +// Swap exchanges the positions of the two elements in a [Tuple2]. +// The first element becomes the second, and the second becomes the first. +// +// Example: +// +// t := tuple.MakeTuple2("hello", 42) +// swapped := tuple.Swap(t) // Returns Tuple2[int, string]{F1: 42, F2: "hello"} +func Swap[T1, T2 any](t Tuple2[T1, T2]) Tuple2[T2, T1] { + return MakeTuple2(t.F2, t.F1) +} + +// Of2 creates a curried function that pairs a value with a constant second element. +// It returns a function that takes a value of type T1 and creates a [Tuple2] with +// the provided constant value e as the second element. +// +// This is useful for partial application and functional composition. +// +// Example: +// +// pairWith42 := tuple.Of2[string](42) +// t := pairWith42("hello") // Returns Tuple2[string, int]{F1: "hello", F2: 42} +func Of2[T1, T2 any](e T2) func(T1) Tuple2[T1, T2] { + return func(t T1) Tuple2[T1, T2] { + return MakeTuple2(t, e) + } +} + +// BiMap applies two mapping functions to both elements of a [Tuple2]. +// The first function (mapSnd) is applied to the second element, +// and the second function (mapFst) is applied to the first element. +// +// This is a bifunctor map operation that allows independent transformation +// of both tuple elements. +// +// Example: +// +// t := tuple.MakeTuple2(5, "hello") +// mapper := tuple.BiMap( +// func(s string) int { return len(s) }, +// func(n int) string { return fmt.Sprintf("%d", n*2) }, +// ) +// result := mapper(t) // Returns Tuple2[string, int]{F1: "10", F2: 5} +func BiMap[E, G, A, B any](mapSnd func(E) G, mapFst func(A) B) func(Tuple2[A, E]) Tuple2[B, G] { + return func(t Tuple2[A, E]) Tuple2[B, G] { + return MakeTuple2(mapFst(First(t)), mapSnd(Second(t))) + } +} + +// marshalJSON marshals the tuple into a JSON array +func tupleMarshalJSON(src ...any) ([]byte, error) { + return json.Marshal(src) +} + +// tupleUnmarshalJSON unmarshals a JSON array into a tuple +func tupleUnmarshalJSON(data []byte, dst ...any) error { + var src []json.RawMessage + if err := json.Unmarshal(data, &src); err != nil { + return err + } + l := N.Min(len(src), len(dst)) + // unmarshal + for i := 0; i < l; i++ { + if err := json.Unmarshal(src[i], dst[i]); err != nil { + return err + } + } + // successfully decoded the tuple + return nil +} + +// tupleString converts a tuple to a string +func tupleString(src ...any) string { + l := len(src) + return fmt.Sprintf("Tuple%d[%s](%s)", l, fmt.Sprintf(strings.Repeat(", %T", l)[2:], src...), fmt.Sprintf(strings.Repeat(", %v", l)[2:], src...)) +} diff --git a/v2/tuple/tuple.test.exe b/v2/tuple/tuple.test.exe new file mode 100644 index 0000000..8522482 Binary files /dev/null and b/v2/tuple/tuple.test.exe differ diff --git a/v2/tuple/tuple_test.go b/v2/tuple/tuple_test.go new file mode 100644 index 0000000..3e10f01 --- /dev/null +++ b/v2/tuple/tuple_test.go @@ -0,0 +1,2013 @@ +// Copyright (c) 2023 - 2025 IBM Corp. +// All rights reserved. +// +// 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. + +package tuple + +import ( + "encoding/json" + "fmt" + "testing" + + "github.com/IBM/fp-go/v2/number" + O "github.com/IBM/fp-go/v2/ord" + S "github.com/IBM/fp-go/v2/string" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestString(t *testing.T) { + value := MakeTuple2("Carsten", 1) + assert.Equal(t, "Tuple2[string, int](Carsten, 1)", value.String()) +} + +func TestMarshal(t *testing.T) { + value := MakeTuple3("Carsten", 1, true) + + data, err := json.Marshal(value) + require.NoError(t, err) + + var unmarshaled Tuple3[string, int, bool] + err = json.Unmarshal(data, &unmarshaled) + require.NoError(t, err) + + assert.Equal(t, value, unmarshaled) +} + +func TestMarshalSmallArray(t *testing.T) { + value := `["Carsten"]` + + var unmarshaled Tuple3[string, int, bool] + err := json.Unmarshal([]byte(value), &unmarshaled) + require.NoError(t, err) + + assert.Equal(t, MakeTuple3("Carsten", 0, false), unmarshaled) +} + +// Test Of function +func TestOf(t *testing.T) { + t1 := Of(42) + assert.Equal(t, Tuple1[int]{F1: 42}, t1) + assert.Equal(t, 42, t1.F1) +} + +// Test First and Second functions +func TestFirstSecond(t *testing.T) { + t2 := MakeTuple2("hello", 42) + assert.Equal(t, "hello", First(t2)) + assert.Equal(t, 42, Second(t2)) +} + +// Test Swap function +func TestSwap(t *testing.T) { + t2 := MakeTuple2("hello", 42) + swapped := Swap(t2) + assert.Equal(t, MakeTuple2(42, "hello"), swapped) + assert.Equal(t, 42, swapped.F1) + assert.Equal(t, "hello", swapped.F2) +} + +// Test Of2 function +func TestOf2(t *testing.T) { + pairWith42 := Of2[string](42) + result := pairWith42("hello") + assert.Equal(t, MakeTuple2("hello", 42), result) +} + +// Test BiMap function +func TestBiMap(t *testing.T) { + t2 := MakeTuple2(5, "hello") + mapper := BiMap( + func(s string) int { return len(s) }, + func(n int) string { return fmt.Sprintf("%d", n*2) }, + ) + result := mapper(t2) + assert.Equal(t, MakeTuple2("10", 5), result) +} + +// Test Tupled and Untupled functions +func TestTupled2Untupled2(t *testing.T) { + add := func(a, b int) int { return a + b } + + // Test Tupled2 + tupledAdd := Tupled2(add) + result := tupledAdd(MakeTuple2(3, 4)) + assert.Equal(t, 7, result) + + // Test Untupled2 + untupledAdd := Untupled2(tupledAdd) + result2 := untupledAdd(5, 6) + assert.Equal(t, 11, result2) +} + +func TestTupled3Untupled3(t *testing.T) { + sum3 := func(a, b, c int) int { return a + b + c } + + tupled := Tupled3(sum3) + result := tupled(MakeTuple3(1, 2, 3)) + assert.Equal(t, 6, result) + + untupled := Untupled3(tupled) + result2 := untupled(4, 5, 6) + assert.Equal(t, 15, result2) +} + +// Test Map functions +func TestMap1(t *testing.T) { + t1 := MakeTuple1(5) + mapper := Map1(func(n int) string { return fmt.Sprintf("%d", n*2) }) + result := mapper(t1) + assert.Equal(t, MakeTuple1("10"), result) +} + +func TestMap2(t *testing.T) { + t2 := MakeTuple2(5, "hello") + mapper := Map2( + func(n int) string { return fmt.Sprintf("%d", n*2) }, + func(s string) int { return len(s) }, + ) + result := mapper(t2) + assert.Equal(t, MakeTuple2("10", 5), result) +} + +func TestMap3(t *testing.T) { + t3 := MakeTuple3(1, 2, 3) + mapper := Map3( + func(n int) int { return n * 2 }, + func(n int) int { return n * 3 }, + func(n int) int { return n * 4 }, + ) + result := mapper(t3) + assert.Equal(t, MakeTuple3(2, 6, 12), result) +} + +// Test Replicate functions +func TestReplicate1(t *testing.T) { + result := Replicate1(42) + assert.Equal(t, MakeTuple1(42), result) +} + +func TestReplicate2(t *testing.T) { + result := Replicate2(42) + assert.Equal(t, MakeTuple2(42, 42), result) +} + +func TestReplicate3(t *testing.T) { + result := Replicate3(42) + assert.Equal(t, MakeTuple3(42, 42, 42), result) +} + +// Test ToArray and FromArray functions +func TestToArray1FromArray1(t *testing.T) { + t1 := MakeTuple1(42) + toArray := ToArray1(func(n int) int { return n }) + arr := toArray(t1) + assert.Equal(t, []int{42}, arr) + + fromArray := FromArray1(func(n int) int { return n }) + result := fromArray(arr) + assert.Equal(t, t1, result) +} + +func TestToArray2FromArray2(t *testing.T) { + t2 := MakeTuple2(1, 2) + toArray := ToArray2( + func(n int) int { return n }, + func(n int) int { return n }, + ) + arr := toArray(t2) + assert.Equal(t, []int{1, 2}, arr) + + fromArray := FromArray2( + func(n int) int { return n }, + func(n int) int { return n }, + ) + result := fromArray(arr) + assert.Equal(t, t2, result) +} + +func TestToArray3FromArray3(t *testing.T) { + t3 := MakeTuple3(1, 2, 3) + toArray := ToArray3( + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + ) + arr := toArray(t3) + assert.Equal(t, []int{1, 2, 3}, arr) + + fromArray := FromArray3( + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + ) + result := fromArray(arr) + assert.Equal(t, t3, result) +} + +// Test Push functions +func TestPush1(t *testing.T) { + t1 := MakeTuple1(42) + push := Push1[int, string]("hello") + result := push(t1) + assert.Equal(t, MakeTuple2(42, "hello"), result) +} + +func TestPush2(t *testing.T) { + t2 := MakeTuple2(1, 2) + push := Push2[int, int, int](3) + result := push(t2) + assert.Equal(t, MakeTuple3(1, 2, 3), result) +} + +func TestPush3(t *testing.T) { + t3 := MakeTuple3(1, 2, 3) + push := Push3[int, int, int, int](4) + result := push(t3) + assert.Equal(t, MakeTuple4(1, 2, 3, 4), result) +} + +// Test Monoid functions +func TestMonoid1(t *testing.T) { + m := Monoid1(number.MonoidSum[int]()) + t1 := MakeTuple1(5) + t2 := MakeTuple1(3) + result := m.Concat(t1, t2) + assert.Equal(t, MakeTuple1(8), result) + assert.Equal(t, MakeTuple1(0), m.Empty()) +} + +func TestMonoid2(t *testing.T) { + m := Monoid2(S.Monoid, number.MonoidSum[int]()) + t1 := MakeTuple2("hello", 5) + t2 := MakeTuple2(" world", 3) + result := m.Concat(t1, t2) + assert.Equal(t, MakeTuple2("hello world", 8), result) + assert.Equal(t, MakeTuple2("", 0), m.Empty()) +} + +func TestMonoid3(t *testing.T) { + m := Monoid3(S.Monoid, number.MonoidSum[int](), number.MonoidProduct[int]()) + t1 := MakeTuple3("a", 2, 3) + t2 := MakeTuple3("b", 4, 5) + result := m.Concat(t1, t2) + assert.Equal(t, MakeTuple3("ab", 6, 15), result) +} + +// Test Ord functions +func TestOrd1(t *testing.T) { + o := Ord1(O.FromStrictCompare[int]()) + t1 := MakeTuple1(5) + t2 := MakeTuple1(3) + t3 := MakeTuple1(5) + + assert.Equal(t, 1, o.Compare(t1, t2)) + assert.Equal(t, -1, o.Compare(t2, t1)) + assert.Equal(t, 0, o.Compare(t1, t3)) + assert.True(t, o.Equals(t1, t3)) + assert.False(t, o.Equals(t1, t2)) +} + +func TestOrd2(t *testing.T) { + o := Ord2(O.FromStrictCompare[string](), O.FromStrictCompare[int]()) + t1 := MakeTuple2("a", 1) + t2 := MakeTuple2("b", 2) + t3 := MakeTuple2("a", 1) + t4 := MakeTuple2("a", 2) + + assert.Equal(t, -1, o.Compare(t1, t2)) + assert.Equal(t, 1, o.Compare(t2, t1)) + assert.Equal(t, 0, o.Compare(t1, t3)) + assert.Equal(t, -1, o.Compare(t1, t4)) + assert.True(t, o.Equals(t1, t3)) + assert.False(t, o.Equals(t1, t2)) +} + +func TestOrd3(t *testing.T) { + o := Ord3(O.FromStrictCompare[int](), O.FromStrictCompare[int](), O.FromStrictCompare[int]()) + t1 := MakeTuple3(1, 2, 3) + t2 := MakeTuple3(1, 2, 4) + t3 := MakeTuple3(1, 2, 3) + + assert.Equal(t, -1, o.Compare(t1, t2)) + assert.Equal(t, 0, o.Compare(t1, t3)) + assert.True(t, o.Equals(t1, t3)) +} + +// Test String methods for different tuple sizes +func TestTuple1String(t *testing.T) { + t1 := MakeTuple1(42) + assert.Equal(t, "Tuple1[int](42)", t1.String()) +} + +func TestTuple3String(t *testing.T) { + t3 := MakeTuple3("test", 42, true) + assert.Equal(t, "Tuple3[string, int, bool](test, 42, true)", t3.String()) +} + +func TestTuple4String(t *testing.T) { + t4 := MakeTuple4(1, 2, 3, 4) + assert.Equal(t, "Tuple4[int, int, int, int](1, 2, 3, 4)", t4.String()) +} + +// Test JSON marshaling for different tuple sizes +func TestTuple1JSON(t *testing.T) { + t1 := MakeTuple1(42) + data, err := json.Marshal(t1) + require.NoError(t, err) + assert.Equal(t, "[42]", string(data)) + + var unmarshaled Tuple1[int] + err = json.Unmarshal(data, &unmarshaled) + require.NoError(t, err) + assert.Equal(t, t1, unmarshaled) +} + +func TestTuple2JSON(t *testing.T) { + t2 := MakeTuple2("hello", 42) + data, err := json.Marshal(t2) + require.NoError(t, err) + + var unmarshaled Tuple2[string, int] + err = json.Unmarshal(data, &unmarshaled) + require.NoError(t, err) + assert.Equal(t, t2, unmarshaled) +} + +func TestTuple4JSON(t *testing.T) { + t4 := MakeTuple4(1, 2, 3, 4) + data, err := json.Marshal(t4) + require.NoError(t, err) + + var unmarshaled Tuple4[int, int, int, int] + err = json.Unmarshal(data, &unmarshaled) + require.NoError(t, err) + assert.Equal(t, t4, unmarshaled) +} + +func TestTuple5JSON(t *testing.T) { + t5 := MakeTuple5(1, 2, 3, 4, 5) + data, err := json.Marshal(t5) + require.NoError(t, err) + + var unmarshaled Tuple5[int, int, int, int, int] + err = json.Unmarshal(data, &unmarshaled) + require.NoError(t, err) + assert.Equal(t, t5, unmarshaled) +} + +// Test JSON unmarshal error cases +func TestUnmarshalInvalidJSON(t *testing.T) { + var t2 Tuple2[string, int] + err := json.Unmarshal([]byte("invalid json"), &t2) + assert.Error(t, err) +} + +func TestUnmarshalInvalidType(t *testing.T) { + var t2 Tuple2[int, int] + err := json.Unmarshal([]byte(`["string", 42]`), &t2) + assert.Error(t, err) +} + +// Test MakeTuple functions for various sizes +func TestMakeTuple4(t *testing.T) { + t4 := MakeTuple4(1, "two", 3.0, true) + assert.Equal(t, 1, t4.F1) + assert.Equal(t, "two", t4.F2) + assert.Equal(t, 3.0, t4.F3) + assert.Equal(t, true, t4.F4) +} + +func TestMakeTuple5(t *testing.T) { + t5 := MakeTuple5(1, 2, 3, 4, 5) + assert.Equal(t, 1, t5.F1) + assert.Equal(t, 5, t5.F5) +} + +func TestMakeTuple6(t *testing.T) { + t6 := MakeTuple6(1, 2, 3, 4, 5, 6) + assert.Equal(t, 1, t6.F1) + assert.Equal(t, 6, t6.F6) +} + +// Test Tupled/Untupled for larger tuples +func TestTupled4Untupled4(t *testing.T) { + sum4 := func(a, b, c, d int) int { return a + b + c + d } + + tupled := Tupled4(sum4) + result := tupled(MakeTuple4(1, 2, 3, 4)) + assert.Equal(t, 10, result) + + untupled := Untupled4(tupled) + result2 := untupled(2, 3, 4, 5) + assert.Equal(t, 14, result2) +} + +func TestTupled5Untupled5(t *testing.T) { + sum5 := func(a, b, c, d, e int) int { return a + b + c + d + e } + + tupled := Tupled5(sum5) + result := tupled(MakeTuple5(1, 2, 3, 4, 5)) + assert.Equal(t, 15, result) + + untupled := Untupled5(tupled) + result2 := untupled(1, 1, 1, 1, 1) + assert.Equal(t, 5, result2) +} + +// Test Map for larger tuples +func TestMap4(t *testing.T) { + t4 := MakeTuple4(1, 2, 3, 4) + mapper := Map4( + func(n int) int { return n * 2 }, + func(n int) int { return n * 3 }, + func(n int) int { return n * 4 }, + func(n int) int { return n * 5 }, + ) + result := mapper(t4) + assert.Equal(t, MakeTuple4(2, 6, 12, 20), result) +} + +func TestMap5(t *testing.T) { + t5 := MakeTuple5(1, 2, 3, 4, 5) + mapper := Map5( + func(n int) int { return n + 1 }, + func(n int) int { return n + 2 }, + func(n int) int { return n + 3 }, + func(n int) int { return n + 4 }, + func(n int) int { return n + 5 }, + ) + result := mapper(t5) + assert.Equal(t, MakeTuple5(2, 4, 6, 8, 10), result) +} + +// Test Replicate for larger tuples +func TestReplicate4(t *testing.T) { + result := Replicate4(7) + assert.Equal(t, MakeTuple4(7, 7, 7, 7), result) +} + +func TestReplicate5(t *testing.T) { + result := Replicate5(9) + assert.Equal(t, MakeTuple5(9, 9, 9, 9, 9), result) +} + +// Test ToArray/FromArray for larger tuples +func TestToArray4FromArray4(t *testing.T) { + t4 := MakeTuple4(1, 2, 3, 4) + toArray := ToArray4( + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + ) + arr := toArray(t4) + assert.Equal(t, []int{1, 2, 3, 4}, arr) + + fromArray := FromArray4( + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + ) + result := fromArray(arr) + assert.Equal(t, t4, result) +} + +func TestToArray5FromArray5(t *testing.T) { + t5 := MakeTuple5(1, 2, 3, 4, 5) + toArray := ToArray5( + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + ) + arr := toArray(t5) + assert.Equal(t, []int{1, 2, 3, 4, 5}, arr) + + fromArray := FromArray5( + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + ) + result := fromArray(arr) + assert.Equal(t, t5, result) +} + +// Test Push for larger tuples +func TestPush4(t *testing.T) { + t4 := MakeTuple4(1, 2, 3, 4) + push := Push4[int, int, int, int, int](5) + result := push(t4) + assert.Equal(t, MakeTuple5(1, 2, 3, 4, 5), result) +} + +func TestPush5(t *testing.T) { + t5 := MakeTuple5(1, 2, 3, 4, 5) + push := Push5[int, int, int, int, int, int](6) + result := push(t5) + assert.Equal(t, MakeTuple6(1, 2, 3, 4, 5, 6), result) +} + +// Test Monoid for larger tuples +func TestMonoid4(t *testing.T) { + m := Monoid4( + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + ) + t1 := MakeTuple4(1, 2, 3, 4) + t2 := MakeTuple4(5, 6, 7, 8) + result := m.Concat(t1, t2) + assert.Equal(t, MakeTuple4(6, 8, 10, 12), result) +} + +func TestMonoid5(t *testing.T) { + m := Monoid5( + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + ) + t1 := MakeTuple5(1, 2, 3, 4, 5) + t2 := MakeTuple5(1, 1, 1, 1, 1) + result := m.Concat(t1, t2) + assert.Equal(t, MakeTuple5(2, 3, 4, 5, 6), result) +} + +// Test Ord for larger tuples +func TestOrd4(t *testing.T) { + o := Ord4( + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + ) + t1 := MakeTuple4(1, 2, 3, 4) + t2 := MakeTuple4(1, 2, 3, 5) + t3 := MakeTuple4(1, 2, 3, 4) + + assert.Equal(t, -1, o.Compare(t1, t2)) + assert.Equal(t, 0, o.Compare(t1, t3)) + assert.True(t, o.Equals(t1, t3)) +} + +func TestOrd5(t *testing.T) { + o := Ord5( + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + ) + t1 := MakeTuple5(1, 2, 3, 4, 5) + t2 := MakeTuple5(1, 2, 3, 4, 6) + t3 := MakeTuple5(1, 2, 3, 4, 5) + + assert.Equal(t, -1, o.Compare(t1, t2)) + assert.Equal(t, 0, o.Compare(t1, t3)) + assert.True(t, o.Equals(t1, t3)) +} + +// Test larger tuple sizes (6-10) +func TestMakeTuple7(t *testing.T) { + t7 := MakeTuple7(1, 2, 3, 4, 5, 6, 7) + assert.Equal(t, 1, t7.F1) + assert.Equal(t, 7, t7.F7) +} + +func TestMakeTuple8(t *testing.T) { + t8 := MakeTuple8(1, 2, 3, 4, 5, 6, 7, 8) + assert.Equal(t, 1, t8.F1) + assert.Equal(t, 8, t8.F8) +} + +func TestMakeTuple9(t *testing.T) { + t9 := MakeTuple9(1, 2, 3, 4, 5, 6, 7, 8, 9) + assert.Equal(t, 1, t9.F1) + assert.Equal(t, 9, t9.F9) +} + +func TestMakeTuple10(t *testing.T) { + t10 := MakeTuple10(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + assert.Equal(t, 1, t10.F1) + assert.Equal(t, 10, t10.F10) +} + +// Test Tupled/Untupled for sizes 6-10 +func TestTupled6Untupled6(t *testing.T) { + sum6 := func(a, b, c, d, e, f int) int { return a + b + c + d + e + f } + + tupled := Tupled6(sum6) + result := tupled(MakeTuple6(1, 2, 3, 4, 5, 6)) + assert.Equal(t, 21, result) + + untupled := Untupled6(tupled) + result2 := untupled(1, 1, 1, 1, 1, 1) + assert.Equal(t, 6, result2) +} + +func TestTupled7Untupled7(t *testing.T) { + sum7 := func(a, b, c, d, e, f, g int) int { return a + b + c + d + e + f + g } + + tupled := Tupled7(sum7) + result := tupled(MakeTuple7(1, 2, 3, 4, 5, 6, 7)) + assert.Equal(t, 28, result) + + untupled := Untupled7(tupled) + result2 := untupled(1, 1, 1, 1, 1, 1, 1) + assert.Equal(t, 7, result2) +} + +func TestTupled8Untupled8(t *testing.T) { + sum8 := func(a, b, c, d, e, f, g, h int) int { return a + b + c + d + e + f + g + h } + + tupled := Tupled8(sum8) + result := tupled(MakeTuple8(1, 2, 3, 4, 5, 6, 7, 8)) + assert.Equal(t, 36, result) + + untupled := Untupled8(tupled) + result2 := untupled(1, 1, 1, 1, 1, 1, 1, 1) + assert.Equal(t, 8, result2) +} + +func TestTupled9Untupled9(t *testing.T) { + sum9 := func(a, b, c, d, e, f, g, h, i int) int { return a + b + c + d + e + f + g + h + i } + + tupled := Tupled9(sum9) + result := tupled(MakeTuple9(1, 2, 3, 4, 5, 6, 7, 8, 9)) + assert.Equal(t, 45, result) + + untupled := Untupled9(tupled) + result2 := untupled(1, 1, 1, 1, 1, 1, 1, 1, 1) + assert.Equal(t, 9, result2) +} + +func TestTupled10Untupled10(t *testing.T) { + sum10 := func(a, b, c, d, e, f, g, h, i, j int) int { return a + b + c + d + e + f + g + h + i + j } + + tupled := Tupled10(sum10) + result := tupled(MakeTuple10(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)) + assert.Equal(t, 55, result) + + untupled := Untupled10(tupled) + result2 := untupled(1, 1, 1, 1, 1, 1, 1, 1, 1, 1) + assert.Equal(t, 10, result2) +} + +// Test Map for sizes 6-10 +func TestMap6(t *testing.T) { + t6 := MakeTuple6(1, 2, 3, 4, 5, 6) + mapper := Map6( + func(n int) int { return n + 1 }, + func(n int) int { return n + 2 }, + func(n int) int { return n + 3 }, + func(n int) int { return n + 4 }, + func(n int) int { return n + 5 }, + func(n int) int { return n + 6 }, + ) + result := mapper(t6) + assert.Equal(t, MakeTuple6(2, 4, 6, 8, 10, 12), result) +} + +func TestMap7(t *testing.T) { + t7 := MakeTuple7(1, 2, 3, 4, 5, 6, 7) + mapper := Map7( + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + ) + result := mapper(t7) + assert.Equal(t, MakeTuple7(2, 4, 6, 8, 10, 12, 14), result) +} + +func TestMap8(t *testing.T) { + t8 := MakeTuple8(1, 2, 3, 4, 5, 6, 7, 8) + mapper := Map8( + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + ) + result := mapper(t8) + assert.Equal(t, MakeTuple8(2, 4, 6, 8, 10, 12, 14, 16), result) +} + +func TestMap9(t *testing.T) { + t9 := MakeTuple9(1, 2, 3, 4, 5, 6, 7, 8, 9) + mapper := Map9( + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + ) + result := mapper(t9) + assert.Equal(t, MakeTuple9(2, 4, 6, 8, 10, 12, 14, 16, 18), result) +} + +func TestMap10(t *testing.T) { + t10 := MakeTuple10(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + mapper := Map10( + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + ) + result := mapper(t10) + assert.Equal(t, MakeTuple10(2, 4, 6, 8, 10, 12, 14, 16, 18, 20), result) +} + +// Test Replicate for sizes 6-10 +func TestReplicate6(t *testing.T) { + result := Replicate6(11) + assert.Equal(t, MakeTuple6(11, 11, 11, 11, 11, 11), result) +} + +func TestReplicate7(t *testing.T) { + result := Replicate7(13) + assert.Equal(t, MakeTuple7(13, 13, 13, 13, 13, 13, 13), result) +} + +func TestReplicate8(t *testing.T) { + result := Replicate8(15) + assert.Equal(t, MakeTuple8(15, 15, 15, 15, 15, 15, 15, 15), result) +} + +func TestReplicate9(t *testing.T) { + result := Replicate9(17) + assert.Equal(t, MakeTuple9(17, 17, 17, 17, 17, 17, 17, 17, 17), result) +} + +func TestReplicate10(t *testing.T) { + result := Replicate10(19) + assert.Equal(t, MakeTuple10(19, 19, 19, 19, 19, 19, 19, 19, 19, 19), result) +} + +// Test ToArray/FromArray for sizes 6-10 +func TestToArray6FromArray6(t *testing.T) { + t6 := MakeTuple6(1, 2, 3, 4, 5, 6) + toArray := ToArray6( + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + ) + arr := toArray(t6) + assert.Equal(t, []int{1, 2, 3, 4, 5, 6}, arr) + + fromArray := FromArray6( + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + ) + result := fromArray(arr) + assert.Equal(t, t6, result) +} + +func TestToArray7FromArray7(t *testing.T) { + t7 := MakeTuple7(1, 2, 3, 4, 5, 6, 7) + toArray := ToArray7( + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + ) + arr := toArray(t7) + assert.Equal(t, []int{1, 2, 3, 4, 5, 6, 7}, arr) + + fromArray := FromArray7( + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + ) + result := fromArray(arr) + assert.Equal(t, t7, result) +} + +func TestToArray8FromArray8(t *testing.T) { + t8 := MakeTuple8(1, 2, 3, 4, 5, 6, 7, 8) + toArray := ToArray8( + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + ) + arr := toArray(t8) + assert.Equal(t, []int{1, 2, 3, 4, 5, 6, 7, 8}, arr) + + fromArray := FromArray8( + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + ) + result := fromArray(arr) + assert.Equal(t, t8, result) +} + +func TestToArray9FromArray9(t *testing.T) { + t9 := MakeTuple9(1, 2, 3, 4, 5, 6, 7, 8, 9) + toArray := ToArray9( + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + ) + arr := toArray(t9) + assert.Equal(t, []int{1, 2, 3, 4, 5, 6, 7, 8, 9}, arr) + + fromArray := FromArray9( + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + ) + result := fromArray(arr) + assert.Equal(t, t9, result) +} + +func TestToArray10FromArray10(t *testing.T) { + t10 := MakeTuple10(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + toArray := ToArray10( + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + ) + arr := toArray(t10) + assert.Equal(t, []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, arr) + + fromArray := FromArray10( + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + ) + result := fromArray(arr) + assert.Equal(t, t10, result) +} + +// Test Push for sizes 6-10 +func TestPush6(t *testing.T) { + t6 := MakeTuple6(1, 2, 3, 4, 5, 6) + push := Push6[int, int, int, int, int, int, int](7) + result := push(t6) + assert.Equal(t, MakeTuple7(1, 2, 3, 4, 5, 6, 7), result) +} + +func TestPush7(t *testing.T) { + t7 := MakeTuple7(1, 2, 3, 4, 5, 6, 7) + push := Push7[int, int, int, int, int, int, int, int](8) + result := push(t7) + assert.Equal(t, MakeTuple8(1, 2, 3, 4, 5, 6, 7, 8), result) +} + +func TestPush8(t *testing.T) { + t8 := MakeTuple8(1, 2, 3, 4, 5, 6, 7, 8) + push := Push8[int, int, int, int, int, int, int, int, int](9) + result := push(t8) + assert.Equal(t, MakeTuple9(1, 2, 3, 4, 5, 6, 7, 8, 9), result) +} + +func TestPush9(t *testing.T) { + t9 := MakeTuple9(1, 2, 3, 4, 5, 6, 7, 8, 9) + push := Push9[int, int, int, int, int, int, int, int, int, int](10) + result := push(t9) + assert.Equal(t, MakeTuple10(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), result) +} + +// Test String methods for sizes 5-10 +func TestTuple5String(t *testing.T) { + t5 := MakeTuple5(1, 2, 3, 4, 5) + assert.Equal(t, "Tuple5[int, int, int, int, int](1, 2, 3, 4, 5)", t5.String()) +} + +func TestTuple6String(t *testing.T) { + t6 := MakeTuple6(1, 2, 3, 4, 5, 6) + assert.Equal(t, "Tuple6[int, int, int, int, int, int](1, 2, 3, 4, 5, 6)", t6.String()) +} + +func TestTuple7String(t *testing.T) { + t7 := MakeTuple7(1, 2, 3, 4, 5, 6, 7) + assert.Equal(t, "Tuple7[int, int, int, int, int, int, int](1, 2, 3, 4, 5, 6, 7)", t7.String()) +} + +func TestTuple8String(t *testing.T) { + t8 := MakeTuple8(1, 2, 3, 4, 5, 6, 7, 8) + assert.Equal(t, "Tuple8[int, int, int, int, int, int, int, int](1, 2, 3, 4, 5, 6, 7, 8)", t8.String()) +} + +func TestTuple9String(t *testing.T) { + t9 := MakeTuple9(1, 2, 3, 4, 5, 6, 7, 8, 9) + assert.Equal(t, "Tuple9[int, int, int, int, int, int, int, int, int](1, 2, 3, 4, 5, 6, 7, 8, 9)", t9.String()) +} + +func TestTuple10String(t *testing.T) { + t10 := MakeTuple10(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + assert.Equal(t, "Tuple10[int, int, int, int, int, int, int, int, int, int](1, 2, 3, 4, 5, 6, 7, 8, 9, 10)", t10.String()) +} + +// Test JSON for sizes 6-10 +func TestTuple6JSON(t *testing.T) { + t6 := MakeTuple6(1, 2, 3, 4, 5, 6) + data, err := json.Marshal(t6) + require.NoError(t, err) + + var unmarshaled Tuple6[int, int, int, int, int, int] + err = json.Unmarshal(data, &unmarshaled) + require.NoError(t, err) + assert.Equal(t, t6, unmarshaled) +} + +func TestTuple7JSON(t *testing.T) { + t7 := MakeTuple7(1, 2, 3, 4, 5, 6, 7) + data, err := json.Marshal(t7) + require.NoError(t, err) + + var unmarshaled Tuple7[int, int, int, int, int, int, int] + err = json.Unmarshal(data, &unmarshaled) + require.NoError(t, err) + assert.Equal(t, t7, unmarshaled) +} + +func TestTuple8JSON(t *testing.T) { + t8 := MakeTuple8(1, 2, 3, 4, 5, 6, 7, 8) + data, err := json.Marshal(t8) + require.NoError(t, err) + + var unmarshaled Tuple8[int, int, int, int, int, int, int, int] + err = json.Unmarshal(data, &unmarshaled) + require.NoError(t, err) + assert.Equal(t, t8, unmarshaled) +} + +func TestTuple9JSON(t *testing.T) { + t9 := MakeTuple9(1, 2, 3, 4, 5, 6, 7, 8, 9) + data, err := json.Marshal(t9) + require.NoError(t, err) + + var unmarshaled Tuple9[int, int, int, int, int, int, int, int, int] + err = json.Unmarshal(data, &unmarshaled) + require.NoError(t, err) + assert.Equal(t, t9, unmarshaled) +} + +func TestTuple10JSON(t *testing.T) { + t10 := MakeTuple10(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + data, err := json.Marshal(t10) + require.NoError(t, err) + + var unmarshaled Tuple10[int, int, int, int, int, int, int, int, int, int] + err = json.Unmarshal(data, &unmarshaled) + require.NoError(t, err) + assert.Equal(t, t10, unmarshaled) +} + +// Test Monoid for sizes 6-10 +func TestMonoid6(t *testing.T) { + m := Monoid6( + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + ) + t1 := MakeTuple6(1, 2, 3, 4, 5, 6) + t2 := MakeTuple6(1, 1, 1, 1, 1, 1) + result := m.Concat(t1, t2) + assert.Equal(t, MakeTuple6(2, 3, 4, 5, 6, 7), result) +} + +func TestMonoid7(t *testing.T) { + m := Monoid7( + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + ) + t1 := MakeTuple7(1, 2, 3, 4, 5, 6, 7) + t2 := MakeTuple7(1, 1, 1, 1, 1, 1, 1) + result := m.Concat(t1, t2) + assert.Equal(t, MakeTuple7(2, 3, 4, 5, 6, 7, 8), result) +} + +func TestMonoid8(t *testing.T) { + m := Monoid8( + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + ) + t1 := MakeTuple8(1, 2, 3, 4, 5, 6, 7, 8) + t2 := MakeTuple8(1, 1, 1, 1, 1, 1, 1, 1) + result := m.Concat(t1, t2) + assert.Equal(t, MakeTuple8(2, 3, 4, 5, 6, 7, 8, 9), result) +} + +func TestMonoid9(t *testing.T) { + m := Monoid9( + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + ) + t1 := MakeTuple9(1, 2, 3, 4, 5, 6, 7, 8, 9) + t2 := MakeTuple9(1, 1, 1, 1, 1, 1, 1, 1, 1) + result := m.Concat(t1, t2) + assert.Equal(t, MakeTuple9(2, 3, 4, 5, 6, 7, 8, 9, 10), result) +} + +func TestMonoid10(t *testing.T) { + m := Monoid10( + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + ) + t1 := MakeTuple10(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + t2 := MakeTuple10(1, 1, 1, 1, 1, 1, 1, 1, 1, 1) + result := m.Concat(t1, t2) + assert.Equal(t, MakeTuple10(2, 3, 4, 5, 6, 7, 8, 9, 10, 11), result) +} + +// Test Ord for sizes 6-10 +func TestOrd6(t *testing.T) { + o := Ord6( + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + ) + t1 := MakeTuple6(1, 2, 3, 4, 5, 6) + t2 := MakeTuple6(1, 2, 3, 4, 5, 7) + t3 := MakeTuple6(1, 2, 3, 4, 5, 6) + + assert.Equal(t, -1, o.Compare(t1, t2)) + assert.Equal(t, 0, o.Compare(t1, t3)) + assert.True(t, o.Equals(t1, t3)) +} + +func TestOrd7(t *testing.T) { + o := Ord7( + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + ) + t1 := MakeTuple7(1, 2, 3, 4, 5, 6, 7) + t2 := MakeTuple7(1, 2, 3, 4, 5, 6, 8) + t3 := MakeTuple7(1, 2, 3, 4, 5, 6, 7) + + assert.Equal(t, -1, o.Compare(t1, t2)) + assert.Equal(t, 0, o.Compare(t1, t3)) + assert.True(t, o.Equals(t1, t3)) +} + +func TestOrd8(t *testing.T) { + o := Ord8( + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + ) + t1 := MakeTuple8(1, 2, 3, 4, 5, 6, 7, 8) + t2 := MakeTuple8(1, 2, 3, 4, 5, 6, 7, 9) + t3 := MakeTuple8(1, 2, 3, 4, 5, 6, 7, 8) + + assert.Equal(t, -1, o.Compare(t1, t2)) + assert.Equal(t, 0, o.Compare(t1, t3)) + assert.True(t, o.Equals(t1, t3)) +} + +func TestOrd9(t *testing.T) { + o := Ord9( + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + ) + t1 := MakeTuple9(1, 2, 3, 4, 5, 6, 7, 8, 9) + t2 := MakeTuple9(1, 2, 3, 4, 5, 6, 7, 8, 10) + t3 := MakeTuple9(1, 2, 3, 4, 5, 6, 7, 8, 9) + + assert.Equal(t, -1, o.Compare(t1, t2)) + assert.Equal(t, 0, o.Compare(t1, t3)) + assert.True(t, o.Equals(t1, t3)) +} + +func TestOrd10(t *testing.T) { + o := Ord10( + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + ) + t1 := MakeTuple10(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + t2 := MakeTuple10(1, 2, 3, 4, 5, 6, 7, 8, 9, 11) + t3 := MakeTuple10(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + + assert.Equal(t, -1, o.Compare(t1, t2)) + assert.Equal(t, 0, o.Compare(t1, t3)) + assert.True(t, o.Equals(t1, t3)) +} + +// Test tuple sizes 11-15 +func TestMakeTuple11(t *testing.T) { + t11 := MakeTuple11(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11) + assert.Equal(t, 1, t11.F1) + assert.Equal(t, 11, t11.F11) +} + +func TestMakeTuple12(t *testing.T) { + t12 := MakeTuple12(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12) + assert.Equal(t, 1, t12.F1) + assert.Equal(t, 12, t12.F12) +} + +func TestMakeTuple13(t *testing.T) { + t13 := MakeTuple13(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13) + assert.Equal(t, 1, t13.F1) + assert.Equal(t, 13, t13.F13) +} + +func TestMakeTuple14(t *testing.T) { + t14 := MakeTuple14(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14) + assert.Equal(t, 1, t14.F1) + assert.Equal(t, 14, t14.F14) +} + +func TestMakeTuple15(t *testing.T) { + t15 := MakeTuple15(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15) + assert.Equal(t, 1, t15.F1) + assert.Equal(t, 15, t15.F15) +} + +// Test Tupled/Untupled for sizes 11-15 +func TestTupled11Untupled11(t *testing.T) { + sum11 := func(a, b, c, d, e, f, g, h, i, j, k int) int { + return a + b + c + d + e + f + g + h + i + j + k + } + + tupled := Tupled11(sum11) + result := tupled(MakeTuple11(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)) + assert.Equal(t, 66, result) + + untupled := Untupled11(tupled) + result2 := untupled(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1) + assert.Equal(t, 11, result2) +} + +func TestTupled12Untupled12(t *testing.T) { + sum12 := func(a, b, c, d, e, f, g, h, i, j, k, l int) int { + return a + b + c + d + e + f + g + h + i + j + k + l + } + + tupled := Tupled12(sum12) + result := tupled(MakeTuple12(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)) + assert.Equal(t, 78, result) + + untupled := Untupled12(tupled) + result2 := untupled(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1) + assert.Equal(t, 12, result2) +} + +func TestTupled13Untupled13(t *testing.T) { + sum13 := func(a, b, c, d, e, f, g, h, i, j, k, l, m int) int { + return a + b + c + d + e + f + g + h + i + j + k + l + m + } + + tupled := Tupled13(sum13) + result := tupled(MakeTuple13(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13)) + assert.Equal(t, 91, result) + + untupled := Untupled13(tupled) + result2 := untupled(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1) + assert.Equal(t, 13, result2) +} + +func TestTupled14Untupled14(t *testing.T) { + sum14 := func(a, b, c, d, e, f, g, h, i, j, k, l, m, n int) int { + return a + b + c + d + e + f + g + h + i + j + k + l + m + n + } + + tupled := Tupled14(sum14) + result := tupled(MakeTuple14(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14)) + assert.Equal(t, 105, result) + + untupled := Untupled14(tupled) + result2 := untupled(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1) + assert.Equal(t, 14, result2) +} + +func TestTupled15Untupled15(t *testing.T) { + sum15 := func(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o int) int { + return a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + } + + tupled := Tupled15(sum15) + result := tupled(MakeTuple15(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)) + assert.Equal(t, 120, result) + + untupled := Untupled15(tupled) + result2 := untupled(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1) + assert.Equal(t, 15, result2) +} + +// Test Map for sizes 11-15 +func TestMap11(t *testing.T) { + t11 := MakeTuple11(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11) + mapper := Map11( + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + ) + result := mapper(t11) + assert.Equal(t, MakeTuple11(2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22), result) +} + +func TestMap12(t *testing.T) { + t12 := MakeTuple12(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12) + mapper := Map12( + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + ) + result := mapper(t12) + assert.Equal(t, MakeTuple12(2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24), result) +} + +func TestMap13(t *testing.T) { + t13 := MakeTuple13(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13) + mapper := Map13( + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + ) + result := mapper(t13) + assert.Equal(t, MakeTuple13(2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26), result) +} + +func TestMap14(t *testing.T) { + t14 := MakeTuple14(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14) + mapper := Map14( + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + ) + result := mapper(t14) + assert.Equal(t, MakeTuple14(2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28), result) +} + +func TestMap15(t *testing.T) { + t15 := MakeTuple15(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15) + mapper := Map15( + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + func(n int) int { return n * 2 }, + ) + result := mapper(t15) + assert.Equal(t, MakeTuple15(2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30), result) +} + +// Test Replicate for sizes 11-15 +func TestReplicate11(t *testing.T) { + result := Replicate11(21) + assert.Equal(t, MakeTuple11(21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21), result) +} + +func TestReplicate12(t *testing.T) { + result := Replicate12(23) + assert.Equal(t, MakeTuple12(23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23), result) +} + +func TestReplicate13(t *testing.T) { + result := Replicate13(25) + assert.Equal(t, MakeTuple13(25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25, 25), result) +} + +func TestReplicate14(t *testing.T) { + result := Replicate14(27) + assert.Equal(t, MakeTuple14(27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27, 27), result) +} + +func TestReplicate15(t *testing.T) { + result := Replicate15(29) + assert.Equal(t, MakeTuple15(29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29, 29), result) +} + +// Test ToArray/FromArray for sizes 11-15 +func TestToArray11FromArray11(t *testing.T) { + t11 := MakeTuple11(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11) + toArray := ToArray11( + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + ) + arr := toArray(t11) + assert.Equal(t, []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}, arr) + + fromArray := FromArray11( + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + ) + result := fromArray(arr) + assert.Equal(t, t11, result) +} + +func TestToArray12FromArray12(t *testing.T) { + t12 := MakeTuple12(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12) + toArray := ToArray12( + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + ) + arr := toArray(t12) + assert.Equal(t, []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}, arr) + + fromArray := FromArray12( + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + ) + result := fromArray(arr) + assert.Equal(t, t12, result) +} + +func TestToArray13FromArray13(t *testing.T) { + t13 := MakeTuple13(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13) + toArray := ToArray13( + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + ) + arr := toArray(t13) + assert.Equal(t, []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13}, arr) + + fromArray := FromArray13( + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + ) + result := fromArray(arr) + assert.Equal(t, t13, result) +} + +func TestToArray14FromArray14(t *testing.T) { + t14 := MakeTuple14(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14) + toArray := ToArray14( + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + ) + arr := toArray(t14) + assert.Equal(t, []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14}, arr) + + fromArray := FromArray14( + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + ) + result := fromArray(arr) + assert.Equal(t, t14, result) +} + +func TestToArray15FromArray15(t *testing.T) { + t15 := MakeTuple15(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15) + toArray := ToArray15( + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + ) + arr := toArray(t15) + assert.Equal(t, []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, arr) + + fromArray := FromArray15( + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + func(n int) int { return n }, + ) + result := fromArray(arr) + assert.Equal(t, t15, result) +} + +// Test Push for sizes 10-14 +func TestPush10(t *testing.T) { + t10 := MakeTuple10(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + push := Push10[int, int, int, int, int, int, int, int, int, int, int](11) + result := push(t10) + assert.Equal(t, MakeTuple11(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11), result) +} + +func TestPush11(t *testing.T) { + t11 := MakeTuple11(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11) + push := Push11[int, int, int, int, int, int, int, int, int, int, int, int](12) + result := push(t11) + assert.Equal(t, MakeTuple12(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12), result) +} + +func TestPush12(t *testing.T) { + t12 := MakeTuple12(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12) + push := Push12[int, int, int, int, int, int, int, int, int, int, int, int, int](13) + result := push(t12) + assert.Equal(t, MakeTuple13(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13), result) +} + +func TestPush13(t *testing.T) { + t13 := MakeTuple13(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13) + push := Push13[int, int, int, int, int, int, int, int, int, int, int, int, int, int](14) + result := push(t13) + assert.Equal(t, MakeTuple14(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14), result) +} + +func TestPush14(t *testing.T) { + t14 := MakeTuple14(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14) + push := Push14[int, int, int, int, int, int, int, int, int, int, int, int, int, int, int](15) + result := push(t14) + assert.Equal(t, MakeTuple15(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15), result) +} + +// Test String methods for sizes 11-15 +func TestTuple11String(t *testing.T) { + t11 := MakeTuple11(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11) + assert.Equal(t, "Tuple11[int, int, int, int, int, int, int, int, int, int, int](1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)", t11.String()) +} + +func TestTuple12String(t *testing.T) { + t12 := MakeTuple12(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12) + assert.Equal(t, "Tuple12[int, int, int, int, int, int, int, int, int, int, int, int](1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)", t12.String()) +} + +func TestTuple13String(t *testing.T) { + t13 := MakeTuple13(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13) + assert.Equal(t, "Tuple13[int, int, int, int, int, int, int, int, int, int, int, int, int](1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13)", t13.String()) +} + +func TestTuple14String(t *testing.T) { + t14 := MakeTuple14(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14) + assert.Equal(t, "Tuple14[int, int, int, int, int, int, int, int, int, int, int, int, int, int](1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14)", t14.String()) +} + +func TestTuple15String(t *testing.T) { + t15 := MakeTuple15(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15) + assert.Equal(t, "Tuple15[int, int, int, int, int, int, int, int, int, int, int, int, int, int, int](1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15)", t15.String()) +} + +// Test JSON for sizes 11-15 +func TestTuple11JSON(t *testing.T) { + t11 := MakeTuple11(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11) + data, err := json.Marshal(t11) + require.NoError(t, err) + + var unmarshaled Tuple11[int, int, int, int, int, int, int, int, int, int, int] + err = json.Unmarshal(data, &unmarshaled) + require.NoError(t, err) + assert.Equal(t, t11, unmarshaled) +} + +func TestTuple12JSON(t *testing.T) { + t12 := MakeTuple12(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12) + data, err := json.Marshal(t12) + require.NoError(t, err) + + var unmarshaled Tuple12[int, int, int, int, int, int, int, int, int, int, int, int] + err = json.Unmarshal(data, &unmarshaled) + require.NoError(t, err) + assert.Equal(t, t12, unmarshaled) +} + +func TestTuple13JSON(t *testing.T) { + t13 := MakeTuple13(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13) + data, err := json.Marshal(t13) + require.NoError(t, err) + + var unmarshaled Tuple13[int, int, int, int, int, int, int, int, int, int, int, int, int] + err = json.Unmarshal(data, &unmarshaled) + require.NoError(t, err) + assert.Equal(t, t13, unmarshaled) +} + +func TestTuple14JSON(t *testing.T) { + t14 := MakeTuple14(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14) + data, err := json.Marshal(t14) + require.NoError(t, err) + + var unmarshaled Tuple14[int, int, int, int, int, int, int, int, int, int, int, int, int, int] + err = json.Unmarshal(data, &unmarshaled) + require.NoError(t, err) + assert.Equal(t, t14, unmarshaled) +} + +func TestTuple15JSON(t *testing.T) { + t15 := MakeTuple15(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15) + data, err := json.Marshal(t15) + require.NoError(t, err) + + var unmarshaled Tuple15[int, int, int, int, int, int, int, int, int, int, int, int, int, int, int] + err = json.Unmarshal(data, &unmarshaled) + require.NoError(t, err) + assert.Equal(t, t15, unmarshaled) +} + +// Test Monoid for sizes 11-15 +func TestMonoid11(t *testing.T) { + m := Monoid11( + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + ) + t1 := MakeTuple11(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11) + t2 := MakeTuple11(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1) + result := m.Concat(t1, t2) + assert.Equal(t, MakeTuple11(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12), result) +} + +func TestMonoid12(t *testing.T) { + m := Monoid12( + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + ) + t1 := MakeTuple12(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12) + t2 := MakeTuple12(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1) + result := m.Concat(t1, t2) + assert.Equal(t, MakeTuple12(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13), result) +} + +func TestMonoid13(t *testing.T) { + m := Monoid13( + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + ) + t1 := MakeTuple13(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13) + t2 := MakeTuple13(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1) + result := m.Concat(t1, t2) + assert.Equal(t, MakeTuple13(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14), result) +} + +func TestMonoid14(t *testing.T) { + m := Monoid14( + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + ) + t1 := MakeTuple14(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14) + t2 := MakeTuple14(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1) + result := m.Concat(t1, t2) + assert.Equal(t, MakeTuple14(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15), result) +} + +func TestMonoid15(t *testing.T) { + m := Monoid15( + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + number.MonoidSum[int](), + ) + t1 := MakeTuple15(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15) + t2 := MakeTuple15(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1) + result := m.Concat(t1, t2) + assert.Equal(t, MakeTuple15(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16), result) +} + +// Test Ord for sizes 11-15 +func TestOrd11(t *testing.T) { + o := Ord11( + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + ) + t1 := MakeTuple11(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11) + t2 := MakeTuple11(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12) + t3 := MakeTuple11(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11) + + assert.Equal(t, -1, o.Compare(t1, t2)) + assert.Equal(t, 0, o.Compare(t1, t3)) + assert.True(t, o.Equals(t1, t3)) +} + +func TestOrd12(t *testing.T) { + o := Ord12( + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + ) + t1 := MakeTuple12(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12) + t2 := MakeTuple12(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13) + t3 := MakeTuple12(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12) + + assert.Equal(t, -1, o.Compare(t1, t2)) + assert.Equal(t, 0, o.Compare(t1, t3)) + assert.True(t, o.Equals(t1, t3)) +} + +func TestOrd13(t *testing.T) { + o := Ord13( + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + ) + t1 := MakeTuple13(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13) + t2 := MakeTuple13(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14) + t3 := MakeTuple13(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13) + + assert.Equal(t, -1, o.Compare(t1, t2)) + assert.Equal(t, 0, o.Compare(t1, t3)) + assert.True(t, o.Equals(t1, t3)) +} + +func TestOrd14(t *testing.T) { + o := Ord14( + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + ) + t1 := MakeTuple14(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14) + t2 := MakeTuple14(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15) + t3 := MakeTuple14(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14) + + assert.Equal(t, -1, o.Compare(t1, t2)) + assert.Equal(t, 0, o.Compare(t1, t3)) + assert.True(t, o.Equals(t1, t3)) +} + +func TestOrd15(t *testing.T) { + o := Ord15( + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + O.FromStrictCompare[int](), + ) + t1 := MakeTuple15(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15) + t2 := MakeTuple15(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16) + t3 := MakeTuple15(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15) + + assert.Equal(t, -1, o.Compare(t1, t2)) + assert.Equal(t, 0, o.Compare(t1, t3)) + assert.True(t, o.Equals(t1, t3)) +}