mirror of
https://github.com/IBM/fp-go.git
synced 2025-08-10 22:31:32 +02:00
fix: add initial DI implementation
Signed-off-by: Carsten Leue <carsten.leue@de.ibm.com>
This commit is contained in:
@@ -17,30 +17,48 @@
|
||||
package erasure
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
A "github.com/IBM/fp-go/array"
|
||||
"github.com/IBM/fp-go/errors"
|
||||
F "github.com/IBM/fp-go/function"
|
||||
I "github.com/IBM/fp-go/identity"
|
||||
IG "github.com/IBM/fp-go/identity/generic"
|
||||
IOE "github.com/IBM/fp-go/ioeither"
|
||||
L "github.com/IBM/fp-go/lazy"
|
||||
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"
|
||||
|
||||
"sync"
|
||||
)
|
||||
|
||||
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))
|
||||
var missingProviderError = F.Flow3(
|
||||
errors.OnSome[string]("no provider for dependency [%s]"),
|
||||
RIOE.Left[InjectableFactory, any, error],
|
||||
F.Constant[ProviderFactory],
|
||||
)
|
||||
|
||||
func logEntryExit(name string, token Dependency) func() {
|
||||
log.Printf("Entry: [%s] -> [%s]:[%s]", name, token.Id(), token.String())
|
||||
return func() {
|
||||
log.Printf("Exit: [%s] -> [%s]:[%s]", name, token.Id(), token.String())
|
||||
}
|
||||
}
|
||||
|
||||
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 := F.Pipe2(
|
||||
@@ -48,41 +66,48 @@ func MakeInjector(providers []Provider) InjectableFactory {
|
||||
A.Map(providerToEntry),
|
||||
R.FromEntries[string, ProviderFactory],
|
||||
)
|
||||
// the resolved map
|
||||
var resolved = R.Empty[string, Result]()
|
||||
// the callback
|
||||
|
||||
// the actual factory, we need lazy initialization
|
||||
var injFct InjectableFactory
|
||||
|
||||
// lazy initialization, so we can cross reference it
|
||||
injFct = func(token Token) Result {
|
||||
injFct = func(token Dependency) Result {
|
||||
|
||||
hit := F.Pipe3(
|
||||
token,
|
||||
Token.Id,
|
||||
R.Lookup[Result, string],
|
||||
I.Ap[O.Option[Result]](resolved),
|
||||
)
|
||||
defer logEntryExit("inj", token)()
|
||||
|
||||
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,
|
||||
)),
|
||||
)
|
||||
key := token.Id()
|
||||
|
||||
x := F.Pipe4(
|
||||
token,
|
||||
Token.Id,
|
||||
R.Lookup[Result, string],
|
||||
I.Ap[O.Option[Result]](resolved),
|
||||
O.GetOrElse(F.Flow2()),
|
||||
)
|
||||
// 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 {
|
||||
defer logEntryExit("computeResult", token)()
|
||||
return F.Pipe5(
|
||||
token,
|
||||
T.Replicate2[Dependency],
|
||||
T.Map2(F.Flow3(
|
||||
Dependency.Id,
|
||||
R.Lookup[ProviderFactory, string],
|
||||
I.Ap[O.Option[ProviderFactory]](factoryById),
|
||||
), F.Flow2(
|
||||
Dependency.String,
|
||||
missingProviderError,
|
||||
)),
|
||||
T.Tupled2(O.MonadGetOrElse[ProviderFactory]),
|
||||
IG.Ap[ProviderFactory](injFct),
|
||||
IOE.Memoize[error, any],
|
||||
)
|
||||
}
|
||||
|
||||
actual, _ = resolved.LoadOrStore(key, F.Pipe1(
|
||||
computeResult,
|
||||
L.Memoize[Result],
|
||||
))
|
||||
}
|
||||
|
||||
return actual.(LazyResult)()
|
||||
}
|
||||
|
||||
return injFct
|
||||
|
@@ -24,27 +24,29 @@ import (
|
||||
IO "github.com/IBM/fp-go/io"
|
||||
IOG "github.com/IBM/fp-go/io/generic"
|
||||
IOE "github.com/IBM/fp-go/ioeither"
|
||||
IOO "github.com/IBM/fp-go/iooption"
|
||||
Int "github.com/IBM/fp-go/number/integer"
|
||||
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 InjectableFactory = RIOE.ReaderIOEither[Dependency, error, any]
|
||||
type ProviderFactory = RIOE.ReaderIOEither[InjectableFactory, error, any]
|
||||
type paramIndex = map[int]int
|
||||
|
||||
type Provider interface {
|
||||
fmt.Stringer
|
||||
Provides() Token
|
||||
Provides() Dependency
|
||||
Factory() ProviderFactory
|
||||
}
|
||||
|
||||
type provider struct {
|
||||
provides Token
|
||||
provides Dependency
|
||||
factory ProviderFactory
|
||||
}
|
||||
|
||||
func (p *provider) Provides() Token {
|
||||
func (p *provider) Provides() Dependency {
|
||||
return p.provides
|
||||
}
|
||||
|
||||
@@ -56,21 +58,23 @@ func (p *provider) String() string {
|
||||
return fmt.Sprintf("Provider for [%s]", p.provides)
|
||||
}
|
||||
|
||||
func MakeProvider(token Token, fct ProviderFactory) Provider {
|
||||
func MakeProvider(token Dependency, fct ProviderFactory) Provider {
|
||||
return &provider{token, fct}
|
||||
}
|
||||
|
||||
func mapFromToken(idx int, token Token) map[TokenType]map[int]int {
|
||||
func mapFromToken(idx int, token Dependency) map[TokenType]paramIndex {
|
||||
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 foldDeps = A.FoldMapWithIndex[Dependency](mergeTokenMaps)(mapFromToken)
|
||||
|
||||
var lookupMandatory = R.Lookup[map[int]int](Mandatory)
|
||||
var lookupOption = R.Lookup[map[int]int](Option)
|
||||
var lookupIdentity = R.Lookup[paramIndex](Identity)
|
||||
var lookupOption = R.Lookup[paramIndex](Option)
|
||||
var lookupIOEither = R.Lookup[paramIndex](IOEither)
|
||||
var lookupIOOption = R.Lookup[paramIndex](IOOption)
|
||||
|
||||
type Mapping = map[TokenType]map[int]int
|
||||
type Mapping = map[TokenType]paramIndex
|
||||
|
||||
func getAt[T any](ar []T) func(idx int) T {
|
||||
return func(idx int) T {
|
||||
@@ -78,14 +82,16 @@ func getAt[T any](ar []T) func(idx int) T {
|
||||
}
|
||||
}
|
||||
|
||||
func handleMandatory(mp Mapping) func(res []IOE.IOEither[error, any]) IOE.IOEither[error, map[int]any] {
|
||||
type identityResult = IOE.IOEither[error, map[int]any]
|
||||
|
||||
func handleIdentity(mp Mapping) func(res []IOE.IOEither[error, any]) identityResult {
|
||||
|
||||
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 func(res []IOE.IOEither[error, any]) identityResult {
|
||||
return F.Pipe2(
|
||||
mp,
|
||||
lookupMandatory,
|
||||
lookupIdentity,
|
||||
O.Fold(
|
||||
onNone,
|
||||
IOE.TraverseRecord[int](getAt(res)),
|
||||
@@ -94,11 +100,13 @@ func handleMandatory(mp Mapping) func(res []IOE.IOEither[error, any]) IOE.IOEith
|
||||
}
|
||||
}
|
||||
|
||||
func handleOption(mp Mapping) func(res []IOE.IOEither[error, any]) IO.IO[map[int]O.Option[any]] {
|
||||
type optionResult = IO.IO[map[int]O.Option[any]]
|
||||
|
||||
func handleOption(mp Mapping) func(res []IOE.IOEither[error, any]) optionResult {
|
||||
|
||||
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 func(res []IOE.IOEither[error, any]) optionResult {
|
||||
|
||||
return F.Pipe2(
|
||||
mp,
|
||||
@@ -106,7 +114,7 @@ func handleOption(mp Mapping) func(res []IOE.IOEither[error, any]) IO.IO[map[int
|
||||
O.Fold(
|
||||
onNone,
|
||||
F.Flow2(
|
||||
IOG.TraverseRecord[IO.IO[map[int]E.Either[error, any]], map[int]int](getAt(res)),
|
||||
IOG.TraverseRecord[IO.IO[map[int]E.Either[error, any]], paramIndex](getAt(res)),
|
||||
IO.Map(R.Map[int](E.ToOption[error, any])),
|
||||
),
|
||||
),
|
||||
@@ -114,44 +122,103 @@ func handleOption(mp Mapping) func(res []IOE.IOEither[error, any]) IO.IO[map[int
|
||||
}
|
||||
}
|
||||
|
||||
func mergeArguments(count int) func(
|
||||
mandatory IOE.IOEither[error, map[int]any],
|
||||
optonal IO.IO[map[int]O.Option[any]],
|
||||
) IOE.IOEither[error, []any] {
|
||||
type ioeitherResult = IO.IO[map[int]IOE.IOEither[error, any]]
|
||||
|
||||
optMapToAny := R.Map[int](F.ToAny[O.Option[any]])
|
||||
mergeMaps := R.UnionLastMonoid[int, any]()
|
||||
func handleIOEither(mp Mapping) func(res []IOE.IOEither[error, any]) ioeitherResult {
|
||||
|
||||
return func(
|
||||
mandatory IOE.IOEither[error, map[int]any],
|
||||
optional IO.IO[map[int]O.Option[any]],
|
||||
) IOE.IOEither[error, []any] {
|
||||
onNone := F.Nullary2(R.Empty[int, IOE.IOEither[error, any]], IO.Of[map[int]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 func(res []IOE.IOEither[error, any]) ioeitherResult {
|
||||
|
||||
return R.ReduceWithIndex(func(idx int, res []any, value any) []any {
|
||||
res[idx] = value
|
||||
return res
|
||||
}, make([]any, count))(merged)
|
||||
})),
|
||||
return F.Pipe2(
|
||||
mp,
|
||||
lookupIOEither,
|
||||
O.Fold(
|
||||
onNone,
|
||||
F.Flow2(
|
||||
R.Map[int](getAt(res)),
|
||||
IO.Of[map[int]IOE.IOEither[error, any]],
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
type iooptionResult = IO.IO[map[int]IOO.IOOption[any]]
|
||||
|
||||
func handleIOOption(mp Mapping) func(res []IOE.IOEither[error, any]) iooptionResult {
|
||||
|
||||
onNone := F.Nullary2(R.Empty[int, IOO.IOOption[any]], IO.Of[map[int]IOO.IOOption[any]])
|
||||
|
||||
return func(res []IOE.IOEither[error, any]) iooptionResult {
|
||||
|
||||
return F.Pipe2(
|
||||
mp,
|
||||
lookupIOOption,
|
||||
O.Fold(
|
||||
onNone,
|
||||
F.Flow2(
|
||||
R.Map[int](F.Flow2(
|
||||
getAt(res),
|
||||
IOO.FromIOEither[error, any],
|
||||
)),
|
||||
IO.Of[map[int]IOO.IOOption[any]],
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
var optionMapToAny = R.Map[int](F.ToAny[O.Option[any]])
|
||||
var ioeitherMapToAny = R.Map[int](F.ToAny[IOE.IOEither[error, any]])
|
||||
var iooptionMapToAny = R.Map[int](F.ToAny[IOO.IOOption[any]])
|
||||
var mergeMaps = R.UnionLastMonoid[int, any]()
|
||||
var collectParams = R.CollectOrd[any, any](Int.Ord)(F.SK[int, any])
|
||||
|
||||
func mergeArguments(
|
||||
identity identityResult,
|
||||
option optionResult,
|
||||
ioeither ioeitherResult,
|
||||
iooption iooptionResult,
|
||||
) IOE.IOEither[error, []any] {
|
||||
|
||||
return F.Pipe2(
|
||||
A.From(
|
||||
identity,
|
||||
F.Pipe2(
|
||||
option,
|
||||
IO.Map(optionMapToAny),
|
||||
IOE.FromIO[error, map[int]any],
|
||||
),
|
||||
F.Pipe2(
|
||||
ioeither,
|
||||
IO.Map(ioeitherMapToAny),
|
||||
IOE.FromIO[error, map[int]any],
|
||||
),
|
||||
F.Pipe2(
|
||||
iooption,
|
||||
IO.Map(iooptionMapToAny),
|
||||
IOE.FromIO[error, map[int]any],
|
||||
),
|
||||
),
|
||||
IOE.SequenceArray[error, map[int]any],
|
||||
IOE.Map[error](F.Flow2(
|
||||
A.Fold(mergeMaps),
|
||||
collectParams,
|
||||
)),
|
||||
)
|
||||
}
|
||||
|
||||
func MakeProviderFactory(
|
||||
deps []Token,
|
||||
deps []Dependency,
|
||||
fct func(param ...any) IOE.IOEither[error, any]) ProviderFactory {
|
||||
|
||||
mapping := foldDeps(deps)
|
||||
|
||||
mandatory := handleMandatory(mapping)
|
||||
identity := handleIdentity(mapping)
|
||||
optional := handleOption(mapping)
|
||||
|
||||
merge := mergeArguments(A.Size(deps))
|
||||
ioeither := handleIOEither(mapping)
|
||||
iooption := handleIOOption(mapping)
|
||||
|
||||
f := F.Unvariadic0(fct)
|
||||
|
||||
@@ -160,7 +227,12 @@ func MakeProviderFactory(
|
||||
resolved := A.MonadMap(deps, inj)
|
||||
// resolve dependencies
|
||||
return F.Pipe1(
|
||||
merge(mandatory(resolved), optional(resolved)),
|
||||
mergeArguments(
|
||||
identity(resolved),
|
||||
optional(resolved),
|
||||
ioeither(resolved),
|
||||
iooption(resolved),
|
||||
),
|
||||
IOE.Chain(f),
|
||||
)
|
||||
}
|
||||
|
@@ -20,14 +20,20 @@ import "fmt"
|
||||
type TokenType int
|
||||
|
||||
const (
|
||||
Mandatory TokenType = iota
|
||||
Identity TokenType = iota
|
||||
Option
|
||||
IOEither
|
||||
IOOption
|
||||
)
|
||||
|
||||
type Token interface {
|
||||
type Dependency interface {
|
||||
fmt.Stringer
|
||||
// Id returns a unique identifier for a token that can be used as a cache key
|
||||
Id() string
|
||||
// Type returns a tag that identifies the behaviour of the dependency
|
||||
Type() TokenType
|
||||
}
|
||||
|
||||
func AsDependency[T Dependency](t T) Dependency {
|
||||
return t
|
||||
}
|
||||
|
33
di/injector.go
Normal file
33
di/injector.go
Normal file
@@ -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 di
|
||||
|
||||
import (
|
||||
DIE "github.com/IBM/fp-go/di/erasure"
|
||||
F "github.com/IBM/fp-go/function"
|
||||
IG "github.com/IBM/fp-go/identity/generic"
|
||||
IOE "github.com/IBM/fp-go/ioeither"
|
||||
RIOE "github.com/IBM/fp-go/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(
|
||||
IG.Ap[DIE.InjectableFactory](DIE.AsDependency(token)),
|
||||
IOE.ChainEitherK(toType[T]()),
|
||||
)
|
||||
}
|
@@ -18,18 +18,17 @@ 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] {
|
||||
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(ER.SafeUnerase[T]),
|
||||
E.Chain(token.Unerase),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -37,21 +36,39 @@ func eraseProviderFactory0[R any](f func() IOE.IOEither[error, R]) func(params .
|
||||
return func(params ...any) IOE.IOEither[error, any] {
|
||||
return F.Pipe1(
|
||||
f(),
|
||||
IOE.Map[error](ER.Erase[R]),
|
||||
IOE.Map[error](F.ToAny[R]),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func eraseProviderFactory1[T1 any, R any](
|
||||
d1 Dependency[T1],
|
||||
f func(T1) IOE.IOEither[error, R]) func(params ...any) IOE.IOEither[error, any] {
|
||||
ft := T.Tupled1(f)
|
||||
t1 := lookupAt[T1](0)
|
||||
t1 := lookupAt[T1](0, d1)
|
||||
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]),
|
||||
IOE.Map[error](F.ToAny[R]),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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 := T.Tupled2(f)
|
||||
t1 := lookupAt[T1](0, d1)
|
||||
t2 := lookupAt[T2](1, d2)
|
||||
return func(params ...any) IOE.IOEither[error, any] {
|
||||
return F.Pipe3(
|
||||
E.SequenceT2(t1(params), t2(params)),
|
||||
IOE.FromEither[error, T.Tuple2[T1, T2]],
|
||||
IOE.Chain(ft),
|
||||
IOE.Map[error](F.ToAny[R]),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -63,7 +80,7 @@ func MakeProvider0[R any](
|
||||
return DIE.MakeProvider(
|
||||
token,
|
||||
DIE.MakeProviderFactory(
|
||||
A.Empty[DIE.Token](),
|
||||
A.Empty[DIE.Dependency](),
|
||||
eraseProviderFactory0(fct),
|
||||
),
|
||||
)
|
||||
@@ -71,15 +88,31 @@ func MakeProvider0[R any](
|
||||
|
||||
func MakeProvider1[T1, R any](
|
||||
token InjectionToken[R],
|
||||
d1 InjectionToken[T1],
|
||||
d1 Dependency[T1],
|
||||
fct func(T1) IOE.IOEither[error, R],
|
||||
) DIE.Provider {
|
||||
|
||||
return DIE.MakeProvider(
|
||||
token,
|
||||
DIE.MakeProviderFactory(
|
||||
A.From[DIE.Token](d1),
|
||||
eraseProviderFactory1(fct),
|
||||
A.From[DIE.Dependency](d1),
|
||||
eraseProviderFactory1(d1, fct),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func MakeProvider2[T1, T2, R any](
|
||||
token InjectionToken[R],
|
||||
d1 Dependency[T1],
|
||||
d2 Dependency[T2],
|
||||
fct func(T1, T2) IOE.IOEither[error, R],
|
||||
) DIE.Provider {
|
||||
|
||||
return DIE.MakeProvider(
|
||||
token,
|
||||
DIE.MakeProviderFactory(
|
||||
A.From[DIE.Dependency](d1, d2),
|
||||
eraseProviderFactory2(d1, d2, fct),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
@@ -17,22 +17,217 @@ package di
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
A "github.com/IBM/fp-go/array"
|
||||
DIE "github.com/IBM/fp-go/di/erasure"
|
||||
E "github.com/IBM/fp-go/either"
|
||||
F "github.com/IBM/fp-go/function"
|
||||
IOE "github.com/IBM/fp-go/ioeither"
|
||||
O "github.com/IBM/fp-go/option"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
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")
|
||||
INJ_KEY1 = MakeToken[string]("INJ_KEY1")
|
||||
INJ_KEY3 = MakeToken[string]("INJ_KEY3")
|
||||
)
|
||||
|
||||
func TestSimpleProvider(t *testing.T) {
|
||||
p1 := MakeProvider0(INJ_KEY1, staticValue("Carsten"))
|
||||
|
||||
fmt.Println(p1)
|
||||
var staticCount int
|
||||
|
||||
staticValue := func(value string) func() IOE.IOEither[error, string] {
|
||||
return func() 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) func() IOE.IOEither[error, string] {
|
||||
return func() 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) func() IOE.IOEither[error, string] {
|
||||
return func() 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)
|
||||
}
|
||||
|
85
di/token.go
85
di/token.go
@@ -21,37 +21,56 @@ import (
|
||||
"sync/atomic"
|
||||
|
||||
DIE "github.com/IBM/fp-go/di/erasure"
|
||||
E "github.com/IBM/fp-go/either"
|
||||
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]
|
||||
// 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 {
|
||||
Token[T]
|
||||
Option() Token[O.Option[T]]
|
||||
IOEither() Token[IOE.IOEither[error, T]]
|
||||
IOOption() Token[IOO.IOOption[T]]
|
||||
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]]
|
||||
}
|
||||
|
||||
// makeID creates a generator of unique string IDs
|
||||
func makeId() IO.IO[string] {
|
||||
var count int64
|
||||
return func() string {
|
||||
return strconv.FormatInt(atomic.AddInt64(&count, 1), 16)
|
||||
}
|
||||
var count atomic.Int64
|
||||
return IO.MakeIO(func() string {
|
||||
return strconv.FormatInt(count.Add(1), 16)
|
||||
})
|
||||
}
|
||||
|
||||
// genId is the common generator of unique string IDs
|
||||
var genId = makeId()
|
||||
|
||||
type token[T any] struct {
|
||||
name string
|
||||
id string
|
||||
typ DIE.TokenType
|
||||
name string
|
||||
id string
|
||||
typ DIE.TokenType
|
||||
toType func(val any) E.Either[error, T]
|
||||
}
|
||||
|
||||
func (t *token[T]) Id() string {
|
||||
@@ -62,44 +81,48 @@ 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}
|
||||
func (t *token[T]) Unerase(val any) E.Either[error, T] {
|
||||
return t.toType(val)
|
||||
}
|
||||
|
||||
func makeToken[T any](name string, id string, typ DIE.TokenType, unerase func(val any) E.Either[error, T]) Dependency[T] {
|
||||
return &token[T]{name, id, typ, unerase}
|
||||
}
|
||||
|
||||
type injectionToken[T any] struct {
|
||||
token[T]
|
||||
option Token[O.Option[T]]
|
||||
ioeither Token[IOE.IOEither[error, T]]
|
||||
iooption Token[IOO.IOOption[T]]
|
||||
option Dependency[O.Option[T]]
|
||||
ioeither Dependency[IOE.IOEither[error, T]]
|
||||
iooption Dependency[IOO.IOOption[T]]
|
||||
}
|
||||
|
||||
func (i *injectionToken[T]) Option() Token[O.Option[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() Token[IOE.IOEither[error, T]] {
|
||||
func (i *injectionToken[T]) IOEither() Dependency[IOE.IOEither[error, T]] {
|
||||
return i.ioeither
|
||||
}
|
||||
|
||||
func (i *injectionToken[T]) IOOption() Token[IOO.IOOption[T]] {
|
||||
func (i *injectionToken[T]) IOOption() Dependency[IOO.IOOption[T]] {
|
||||
return i.iooption
|
||||
}
|
||||
|
||||
// MakeToken create a unique injection token for a specific type
|
||||
// MakeToken create a unique `InjectionToken` 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),
|
||||
token[T]{name, id, DIE.Identity, toType[T]()},
|
||||
makeToken[O.Option[T]](fmt.Sprintf("Option[%s]", name), id, DIE.Option, toOptionType[T]()),
|
||||
makeToken[IOE.IOEither[error, T]](fmt.Sprintf("IOEither[%s]", name), id, DIE.IOEither, toIOEitherType[T]()),
|
||||
makeToken[IOO.IOOption[T]](fmt.Sprintf("IOOption[%s]", name), id, DIE.IOOption, toIOOptionType[T]()),
|
||||
}
|
||||
}
|
||||
|
62
di/utils.go
Normal file
62
di/utils.go
Normal file
@@ -0,0 +1,62 @@
|
||||
// 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 (
|
||||
E "github.com/IBM/fp-go/either"
|
||||
"github.com/IBM/fp-go/errors"
|
||||
F "github.com/IBM/fp-go/function"
|
||||
IOE "github.com/IBM/fp-go/ioeither"
|
||||
IOO "github.com/IBM/fp-go/iooption"
|
||||
O "github.com/IBM/fp-go/option"
|
||||
)
|
||||
|
||||
// toType converts and 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]() func(t any) E.Either[error, O.Option[T]] {
|
||||
return F.Flow2(
|
||||
toType[O.Option[any]](),
|
||||
E.Chain(O.Fold(
|
||||
F.Nullary2(O.None[T], E.Of[error, O.Option[T]]),
|
||||
F.Flow2(
|
||||
toType[T](),
|
||||
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]() func(t any) E.Either[error, IOE.IOEither[error, T]] {
|
||||
return F.Flow2(
|
||||
toType[IOE.IOEither[error, any]](),
|
||||
E.Map[error](IOE.ChainEitherK(toType[T]())),
|
||||
)
|
||||
}
|
||||
|
||||
// toIOOptionType converts an any to an IOOption[any] and then to an IOOption[T]
|
||||
func toIOOptionType[T any]() func(t any) E.Either[error, IOO.IOOption[T]] {
|
||||
return F.Flow2(
|
||||
toType[IOO.IOOption[any]](),
|
||||
E.Map[error](IOO.ChainOptionK(F.Flow2(
|
||||
toType[T](),
|
||||
E.ToOption[error, T],
|
||||
))),
|
||||
)
|
||||
}
|
64
di/utils_test.go
Normal file
64
di/utils_test.go
Normal file
@@ -0,0 +1,64 @@
|
||||
// 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 (
|
||||
"testing"
|
||||
|
||||
E "github.com/IBM/fp-go/either"
|
||||
F "github.com/IBM/fp-go/function"
|
||||
IOE "github.com/IBM/fp-go/ioeither"
|
||||
O "github.com/IBM/fp-go/option"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestToType(t *testing.T) {
|
||||
// good cases
|
||||
assert.Equal(t, E.Of[error](10), toType[int]()(any(10)))
|
||||
assert.Equal(t, E.Of[error]("Carsten"), toType[string]()(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(toType[int]()(any("Carsten"))))
|
||||
assert.False(t, E.IsRight(toType[O.Option[string]]()(O.Of(any("Carsten")))))
|
||||
}
|
||||
|
||||
func TestToOptionType(t *testing.T) {
|
||||
// good cases
|
||||
assert.Equal(t, E.Of[error](O.Of(10)), toOptionType[int]()(any(O.Of(any(10)))))
|
||||
assert.Equal(t, E.Of[error](O.Of("Carsten")), toOptionType[string]()(any(O.Of(any("Carsten")))))
|
||||
// bad cases
|
||||
assert.False(t, E.IsRight(toOptionType[int]()(any(10))))
|
||||
assert.False(t, E.IsRight(toOptionType[int]()(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) {
|
||||
// good cases
|
||||
assert.Equal(t, E.Of[error](10), invokeIOEither(toIOEitherType[int]()(any(IOE.Of[error](any(10))))))
|
||||
assert.Equal(t, E.Of[error]("Carsten"), invokeIOEither(toIOEitherType[string]()(any(IOE.Of[error](any("Carsten"))))))
|
||||
// bad cases
|
||||
assert.False(t, E.IsRight(invokeIOEither(toIOEitherType[string]()(any(IOE.Of[error](any(10)))))))
|
||||
assert.False(t, E.IsRight(invokeIOEither(toIOEitherType[string]()(any(IOE.Of[error]("Carsten"))))))
|
||||
assert.False(t, E.IsRight(invokeIOEither(toIOEitherType[string]()(any("Carsten")))))
|
||||
}
|
@@ -35,7 +35,7 @@ func Unerase[T any](t any) T {
|
||||
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.ToType[*T](errors.OnSome[any]("Value of type [%T] is not erased")),
|
||||
E.Map[error](F.Deref[T]),
|
||||
)
|
||||
}
|
||||
|
@@ -82,6 +82,10 @@ func Fold[A, B any](onNone func() B, onSome func(a A) B) func(ma Option[A]) B {
|
||||
}
|
||||
}
|
||||
|
||||
func MonadGetOrElse[A any](fa Option[A], onNone func() A) A {
|
||||
return MonadFold(fa, onNone, F.Identity[A])
|
||||
}
|
||||
|
||||
func GetOrElse[A any](onNone func() A) func(Option[A]) A {
|
||||
return Fold(onNone, F.Identity[A])
|
||||
}
|
||||
|
@@ -99,6 +99,14 @@ func Collect[M ~map[K]V, GR ~[]R, K comparable, V, R any](f func(K, V) R) func(M
|
||||
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)
|
||||
|
@@ -49,6 +49,11 @@ 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)
|
||||
}
|
||||
|
Reference in New Issue
Block a user