diff --git a/array/array.go b/array/array.go index 20320a7..6aba87a 100644 --- a/array/array.go +++ b/array/array.go @@ -52,6 +52,10 @@ func MonadMapRef[A, B any](as []A, f func(a *A) B) []B { return bs } +func MapWithIndex[A, B any](f func(int, A) B) func([]A) []B { + return G.MapWithIndex[[]A, []B](f) +} + func Map[A, B any](f func(a A) B) func([]A) []B { return F.Bind2nd(MonadMap[A, B], f) } @@ -300,6 +304,11 @@ 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) diff --git a/array/generic/array.go b/array/generic/array.go index 3ff3bd0..bc4ef14 100644 --- a/array/generic/array.go +++ b/array/generic/array.go @@ -118,6 +118,14 @@ func Map[GA ~[]A, GB ~[]B, A, B any](f func(a A) B) func(GA) GB { return F.Bind2nd(MonadMap[GA, GB, A, B], 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) } @@ -242,6 +250,16 @@ func FoldMap[AS ~[]A, A, B any](m M.Monoid[B]) func(func(A) B) func(AS) B { } } +func FoldMapWithIndex[AS ~[]A, A, B any](m M.Monoid[B]) func(func(int, A) B) func(AS) B { + 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 m.Concat(cur, f(idx, a)) + }, m.Empty()) + } + } +} + func Fold[AS ~[]A, A any](m M.Monoid[A]) func(AS) A { return func(as AS) A { return array.Reduce(as, m.Concat, m.Empty()) diff --git a/array/monoid.go b/array/monoid.go index c312f2b..ef87f5e 100644 --- a/array/monoid.go +++ b/array/monoid.go @@ -18,6 +18,7 @@ package array import ( "github.com/IBM/fp-go/internal/array" M "github.com/IBM/fp-go/monoid" + S "github.com/IBM/fp-go/semigroup" ) func concat[T any](left, right []T) []T { @@ -40,6 +41,10 @@ func Monoid[T any]() M.Monoid[[]T] { return M.MakeMonoid(concat[T], Empty[T]()) } +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) } diff --git a/di/erasure/injector.go b/di/erasure/injector.go new file mode 100644 index 0000000..704b167 --- /dev/null +++ b/di/erasure/injector.go @@ -0,0 +1,89 @@ +// 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 erasure + +import ( + "fmt" + + A "github.com/IBM/fp-go/array" + F "github.com/IBM/fp-go/function" + I "github.com/IBM/fp-go/identity" + IOE "github.com/IBM/fp-go/ioeither" + O "github.com/IBM/fp-go/option" + R "github.com/IBM/fp-go/record" + T "github.com/IBM/fp-go/tuple" +) + +func providerToEntry(p Provider) T.Tuple2[string, ProviderFactory] { + return T.MakeTuple2(p.Provides().Id(), p.Factory()) +} + +func missingProviderError(name string) func() IOE.IOEither[error, any] { + return func() IOE.IOEither[error, any] { + return IOE.Left[any](fmt.Errorf("No provider for dependency [%s]", name)) + } +} + +func MakeInjector(providers []Provider) InjectableFactory { + + type Result = IOE.IOEither[error, any] + + // provide a mapping for all providers + factoryById := F.Pipe2( + providers, + A.Map(providerToEntry), + R.FromEntries[string, ProviderFactory], + ) + // the resolved map + var resolved = R.Empty[string, Result]() + // the callback + var injFct InjectableFactory + + // lazy initialization, so we can cross reference it + injFct = func(token Token) Result { + + hit := F.Pipe3( + token, + Token.Id, + R.Lookup[Result, string], + I.Ap[O.Option[Result]](resolved), + ) + + provFct := F.Pipe2( + token, + T.Replicate2[Token], + T.Map2(F.Flow3( + Token.Id, + R.Lookup[ProviderFactory, string], + I.Ap[O.Option[ProviderFactory]](factoryById), + ), F.Flow2( + Token.String, + missingProviderError, + )), + ) + + x := F.Pipe4( + token, + Token.Id, + R.Lookup[Result, string], + I.Ap[O.Option[Result]](resolved), + O.GetOrElse(F.Flow2()), + ) + } + + return injFct +} diff --git a/di/erasure/provider.go b/di/erasure/provider.go new file mode 100644 index 0000000..50c2ac8 --- /dev/null +++ b/di/erasure/provider.go @@ -0,0 +1,167 @@ +// 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 erasure + +import ( + "fmt" + + A "github.com/IBM/fp-go/array" + E "github.com/IBM/fp-go/either" + F "github.com/IBM/fp-go/function" + IO "github.com/IBM/fp-go/io" + IOG "github.com/IBM/fp-go/io/generic" + IOE "github.com/IBM/fp-go/ioeither" + O "github.com/IBM/fp-go/option" + RIOE "github.com/IBM/fp-go/readerioeither" + R "github.com/IBM/fp-go/record" + T "github.com/IBM/fp-go/tuple" +) + +type InjectableFactory = RIOE.ReaderIOEither[Token, error, any] +type ProviderFactory = RIOE.ReaderIOEither[InjectableFactory, error, any] + +type Provider interface { + fmt.Stringer + Provides() Token + Factory() ProviderFactory +} + +type provider struct { + provides Token + factory ProviderFactory +} + +func (p *provider) Provides() Token { + 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 Token, fct ProviderFactory) Provider { + return &provider{token, fct} +} + +func mapFromToken(idx int, token Token) map[TokenType]map[int]int { + return R.Singleton(token.Type(), R.Singleton(idx, idx)) +} + +var mergeTokenMaps = R.UnionMonoid[TokenType](R.UnionLastSemigroup[int, int]()) +var foldDeps = A.FoldMapWithIndex[Token](mergeTokenMaps)(mapFromToken) + +var lookupMandatory = R.Lookup[map[int]int](Mandatory) +var lookupOption = R.Lookup[map[int]int](Option) + +type Mapping = map[TokenType]map[int]int + +func getAt[T any](ar []T) func(idx int) T { + return func(idx int) T { + return ar[idx] + } +} + +func handleMandatory(mp Mapping) func(res []IOE.IOEither[error, any]) IOE.IOEither[error, map[int]any] { + + onNone := F.Nullary2(R.Empty[int, any], IOE.Of[error, map[int]any]) + + return func(res []IOE.IOEither[error, any]) IOE.IOEither[error, map[int]any] { + return F.Pipe2( + mp, + lookupMandatory, + O.Fold( + onNone, + IOE.TraverseRecord[int](getAt(res)), + ), + ) + } +} + +func handleOption(mp Mapping) func(res []IOE.IOEither[error, any]) IO.IO[map[int]O.Option[any]] { + + onNone := F.Nullary2(R.Empty[int, O.Option[any]], IO.Of[map[int]O.Option[any]]) + + return func(res []IOE.IOEither[error, any]) IO.IO[map[int]O.Option[any]] { + + return F.Pipe2( + mp, + lookupOption, + O.Fold( + onNone, + F.Flow2( + IOG.TraverseRecord[IO.IO[map[int]E.Either[error, any]], map[int]int](getAt(res)), + IO.Map(R.Map[int](E.ToOption[error, any])), + ), + ), + ) + } +} + +func mergeArguments(count int) func( + mandatory IOE.IOEither[error, map[int]any], + optonal IO.IO[map[int]O.Option[any]], +) IOE.IOEither[error, []any] { + + optMapToAny := R.Map[int](F.ToAny[O.Option[any]]) + mergeMaps := R.UnionLastMonoid[int, any]() + + return func( + mandatory IOE.IOEither[error, map[int]any], + optional IO.IO[map[int]O.Option[any]], + ) IOE.IOEither[error, []any] { + + return F.Pipe1( + IOE.SequenceT2(mandatory, IOE.FromIO[error](optional)), + IOE.Map[error](T.Tupled2(func(mnd map[int]any, opt map[int]O.Option[any]) []any { + // merge all parameters + merged := mergeMaps.Concat(mnd, optMapToAny(opt)) + + return R.ReduceWithIndex(func(idx int, res []any, value any) []any { + res[idx] = value + return res + }, make([]any, count))(merged) + })), + ) + } +} + +func MakeProviderFactory( + deps []Token, + fct func(param ...any) IOE.IOEither[error, any]) ProviderFactory { + + mapping := foldDeps(deps) + + mandatory := handleMandatory(mapping) + optional := handleOption(mapping) + + merge := mergeArguments(A.Size(deps)) + + f := F.Unvariadic0(fct) + + return func(inj InjectableFactory) IOE.IOEither[error, any] { + // resolve all dependencies + resolved := A.MonadMap(deps, inj) + // resolve dependencies + return F.Pipe1( + merge(mandatory(resolved), optional(resolved)), + IOE.Chain(f), + ) + } +} diff --git a/di/erasure/token.go b/di/erasure/token.go new file mode 100644 index 0000000..8f0fe9f --- /dev/null +++ b/di/erasure/token.go @@ -0,0 +1,33 @@ +// 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 erasure + +import "fmt" + +type TokenType int + +const ( + Mandatory TokenType = iota + Option + IOEither + IOOption +) + +type Token interface { + fmt.Stringer + Id() string + Type() TokenType +} diff --git a/di/provider.go b/di/provider.go new file mode 100644 index 0000000..d513031 --- /dev/null +++ b/di/provider.go @@ -0,0 +1,85 @@ +// 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 di + +import ( + A "github.com/IBM/fp-go/array" + DIE "github.com/IBM/fp-go/di/erasure" + E "github.com/IBM/fp-go/either" + ER "github.com/IBM/fp-go/erasure" + "github.com/IBM/fp-go/errors" + F "github.com/IBM/fp-go/function" + IOE "github.com/IBM/fp-go/ioeither" + T "github.com/IBM/fp-go/tuple" +) + +func lookupAt[T any](idx int) 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(ER.SafeUnerase[T]), + ) +} + +func eraseProviderFactory0[R any](f func() IOE.IOEither[error, R]) func(params ...any) IOE.IOEither[error, any] { + return func(params ...any) IOE.IOEither[error, any] { + return F.Pipe1( + f(), + IOE.Map[error](ER.Erase[R]), + ) + } +} + +func eraseProviderFactory1[T1 any, R any]( + f func(T1) IOE.IOEither[error, R]) func(params ...any) IOE.IOEither[error, any] { + ft := T.Tupled1(f) + t1 := lookupAt[T1](0) + return func(params ...any) IOE.IOEither[error, any] { + return F.Pipe3( + E.SequenceT1(t1(params)), + IOE.FromEither[error, T.Tuple1[T1]], + IOE.Chain(ft), + IOE.Map[error](ER.Erase[R]), + ) + } +} + +func MakeProvider0[R any]( + token InjectionToken[R], + fct func() IOE.IOEither[error, R], +) DIE.Provider { + return DIE.MakeProvider( + token, + DIE.MakeProviderFactory( + A.Empty[DIE.Token](), + eraseProviderFactory0(fct), + ), + ) +} + +func MakeProvider1[T1, R any]( + token InjectionToken[R], + d1 InjectionToken[T1], + fct func(T1) IOE.IOEither[error, R], +) DIE.Provider { + + return DIE.MakeProvider( + token, + DIE.MakeProviderFactory( + A.From[DIE.Token](d1), + eraseProviderFactory1(fct), + ), + ) +} diff --git a/di/provider_test.go b/di/provider_test.go new file mode 100644 index 0000000..bb9f53c --- /dev/null +++ b/di/provider_test.go @@ -0,0 +1,38 @@ +// 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 di + +import ( + "fmt" + "testing" + + F "github.com/IBM/fp-go/function" + IOE "github.com/IBM/fp-go/ioeither" +) + +func staticValue(value string) func() IOE.IOEither[error, string] { + return F.Constant(IOE.Of[error](value)) +} + +var ( + INJ_KEY1 = MakeToken[string]("INJ_KEY1") + INJ_KEY2 = MakeToken[string]("INJ_KEY2") +) + +func TestSimpleProvider(t *testing.T) { + p1 := MakeProvider0(INJ_KEY1, staticValue("Carsten")) + + fmt.Println(p1) +} diff --git a/di/token.go b/di/token.go new file mode 100644 index 0000000..1bbc346 --- /dev/null +++ b/di/token.go @@ -0,0 +1,105 @@ +// 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 di + +import ( + "fmt" + "strconv" + "sync/atomic" + + DIE "github.com/IBM/fp-go/di/erasure" + IO "github.com/IBM/fp-go/io" + IOE "github.com/IBM/fp-go/ioeither" + IOO "github.com/IBM/fp-go/iooption" + O "github.com/IBM/fp-go/option" +) + +type Token[T any] interface { + DIE.Token + ToType(any) O.Option[T] +} + +type InjectionToken[T any] interface { + Token[T] + Option() Token[O.Option[T]] + IOEither() Token[IOE.IOEither[error, T]] + IOOption() Token[IOO.IOOption[T]] +} + +func makeId() IO.IO[string] { + var count int64 + return func() string { + return strconv.FormatInt(atomic.AddInt64(&count, 1), 16) + } +} + +var genId = makeId() + +type token[T any] struct { + name string + id string + typ DIE.TokenType +} + +func (t *token[T]) Id() string { + return t.id +} + +func (t *token[T]) Type() DIE.TokenType { + return t.typ +} + +func (t *token[T]) ToType(value any) O.Option[T] { + return O.ToType[T](value) +} + +func (t *token[T]) String() string { + return t.name +} + +func makeToken[T any](name string, id string, typ DIE.TokenType) Token[T] { + return &token[T]{name, id, typ} +} + +type injectionToken[T any] struct { + token[T] + option Token[O.Option[T]] + ioeither Token[IOE.IOEither[error, T]] + iooption Token[IOO.IOOption[T]] +} + +func (i *injectionToken[T]) Option() Token[O.Option[T]] { + return i.option +} + +func (i *injectionToken[T]) IOEither() Token[IOE.IOEither[error, T]] { + return i.ioeither +} + +func (i *injectionToken[T]) IOOption() Token[IOO.IOOption[T]] { + return i.iooption +} + +// MakeToken create a unique injection token for a specific type +func MakeToken[T any](name string) InjectionToken[T] { + id := genId() + return &injectionToken[T]{ + token[T]{name, id, DIE.Mandatory}, + makeToken[O.Option[T]](fmt.Sprintf("Option[%s]", name), id, DIE.Option), + makeToken[IOE.IOEither[error, T]](fmt.Sprintf("IOEither[%s]", name), id, DIE.IOEither), + makeToken[IOO.IOOption[T]](fmt.Sprintf("IOOption[%s]", name), id, DIE.IOOption), + } +} diff --git a/erasure/erasure.go b/erasure/erasure.go index c217a81..c939592 100644 --- a/erasure/erasure.go +++ b/erasure/erasure.go @@ -16,6 +16,8 @@ package erasure import ( + E "github.com/IBM/fp-go/either" + "github.com/IBM/fp-go/errors" F "github.com/IBM/fp-go/function" ) @@ -29,6 +31,15 @@ func Unerase[T any](t any) T { return *t.(*T) } +// SafeUnerase converts an erased variable back to its original value +func SafeUnerase[T any](t any) E.Either[error, T] { + return F.Pipe2( + t, + E.ToType[*T](errors.OnSome[any]("Value %T is not unerased")), + E.Map[error](F.Deref[T]), + ) +} + // Erase0 converts a type safe function into an erased function func Erase0[T1 any](f func() T1) func() any { return F.Nullary2(f, Erase[T1]) diff --git a/internal/array/array.go b/internal/array/array.go index 035e618..015001b 100644 --- a/internal/array/array.go +++ b/internal/array/array.go @@ -88,6 +88,15 @@ func MonadMap[GA ~[]A, GB ~[]B, A, B any](as GA, f func(a A) B) GB { return bs } +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/io/generic/traverse.go b/io/generic/traverse.go index 71e256c..613c882 100644 --- a/io/generic/traverse.go +++ b/io/generic/traverse.go @@ -57,7 +57,7 @@ func SequenceArray[GA ~func() A, GAS ~func() AAS, AAS ~[]A, GAAS ~[]GA, A any](t } // MonadTraverseRecord transforms a record using an IO transform an IO of a record -func MonadTraverseRecord[GB ~func() B, GBS ~func() MB, MA ~map[K]A, MB ~map[K]B, K comparable, A, B any](ma MA, f func(A) GB) GBS { +func MonadTraverseRecord[GBS ~func() MB, MA ~map[K]A, GB ~func() B, MB ~map[K]B, K comparable, A, B any](ma MA, f func(A) GB) GBS { return RR.MonadTraverse[MA]( Of[GBS, MB], Map[GBS, func() func(B) MB, MB, func(B) MB], @@ -67,7 +67,7 @@ func MonadTraverseRecord[GB ~func() B, GBS ~func() MB, MA ~map[K]A, MB ~map[K]B, } // TraverseRecord transforms a record using an IO transform an IO of a record -func TraverseRecord[GB ~func() B, GBS ~func() MB, MA ~map[K]A, MB ~map[K]B, K comparable, A, B any](f func(A) GB) func(MA) GBS { +func TraverseRecord[GBS ~func() MB, MA ~map[K]A, GB ~func() B, MB ~map[K]B, K comparable, A, B any](f func(A) GB) func(MA) GBS { return RR.Traverse[MA]( Of[GBS, MB], Map[GBS, func() func(B) MB, MB, func(B) MB], @@ -87,5 +87,5 @@ func TraverseRecordWithIndex[GB ~func() B, GBS ~func() MB, MA ~map[K]A, MB ~map[ } func SequenceRecord[GA ~func() A, GAS ~func() AAS, AAS ~map[K]A, GAAS ~map[K]GA, K comparable, A any](tas GAAS) GAS { - return MonadTraverseRecord[GA, GAS](tas, F.Identity[GA]) + return MonadTraverseRecord[GAS](tas, F.Identity[GA]) } diff --git a/io/traverse.go b/io/traverse.go index 4065afa..6fdf192 100644 --- a/io/traverse.go +++ b/io/traverse.go @@ -41,13 +41,13 @@ func SequenceArray[A any](tas []IO[A]) IO[[]A] { } func MonadTraverseRecord[K comparable, A, B any](tas map[K]A, f func(A) IO[B]) IO[map[K]B] { - return G.MonadTraverseRecord[IO[B], IO[map[K]B]](tas, f) + return G.MonadTraverseRecord[IO[map[K]B]](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) IO[B]) func(map[K]A) IO[map[K]B] { - return G.TraverseRecord[IO[B], IO[map[K]B], map[K]A](f) + return G.TraverseRecord[IO[map[K]B], map[K]A, IO[B]](f) } // TraverseRecordWithIndex applies a function returning an [IO] to all elements in a record and the diff --git a/lazy/traverse.go b/lazy/traverse.go index 3498569..0d75267 100644 --- a/lazy/traverse.go +++ b/lazy/traverse.go @@ -41,13 +41,13 @@ func SequenceArray[A any](tas []Lazy[A]) Lazy[[]A] { } func MonadTraverseRecord[K comparable, A, B any](tas map[K]A, f func(A) Lazy[B]) Lazy[map[K]B] { - return G.MonadTraverseRecord[Lazy[B], Lazy[map[K]B]](tas, f) + return G.MonadTraverseRecord[Lazy[map[K]B]](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 G.TraverseRecord[Lazy[B], Lazy[map[K]B], map[K]A](f) + return G.TraverseRecord[Lazy[map[K]B], map[K]A, Lazy[B]](f) } // TraverseRecord applies a function returning an [IO] to all elements in a record and the