mirror of
https://github.com/IBM/fp-go.git
synced 2025-12-09 23:11:40 +02:00
Compare commits
9 Commits
v1.0.112
...
cleue-swit
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5899c5d95f | ||
|
|
7daf65effc | ||
|
|
909f7c3bce | ||
|
|
5f0c644c6d | ||
|
|
9b3d9c6930 | ||
|
|
59381c1e50 | ||
|
|
358573cc20 | ||
|
|
e166806d1b | ||
|
|
02ec50c91d |
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -60,7 +60,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Node.js ${{ env.NODE_VERSION }}
|
||||
uses: actions/setup-node@b39b52d1213e96004bfcb1c61a8a6fa8ab84f3e8 # v4.0.1
|
||||
uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2
|
||||
with:
|
||||
node-version: ${{ env.NODE_VERSION }}
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ func MapWithIndex[A, B any](f func(int, A) B) func([]A) []B {
|
||||
}
|
||||
|
||||
func Map[A, B any](f func(a A) B) func([]A) []B {
|
||||
return F.Bind2nd(MonadMap[A, B], f)
|
||||
return G.Map[[]A, []B, A, B](f)
|
||||
}
|
||||
|
||||
func MapRef[A, B any](f func(a *A) B) func([]A) []B {
|
||||
|
||||
@@ -147,7 +147,7 @@ func MonadMap[GA ~[]A, GB ~[]B, A, B any](as GA, f func(a A) B) GB {
|
||||
}
|
||||
|
||||
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)
|
||||
return array.Map[GA, GB](f)
|
||||
}
|
||||
|
||||
func MonadMapWithIndex[GA ~[]A, GB ~[]B, A, B any](as GA, f func(int, A) B) GB {
|
||||
|
||||
43
array/generic/monad.go
Normal file
43
array/generic/monad.go
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright (c) 2024 IBM Corp.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES 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/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]{}
|
||||
}
|
||||
26
array/monad.go
Normal file
26
array/monad.go
Normal file
@@ -0,0 +1,26 @@
|
||||
// Copyright (c) 2024 IBM Corp.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES 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/array/generic"
|
||||
"github.com/IBM/fp-go/internal/monad"
|
||||
)
|
||||
|
||||
// Monad returns the monadic operations for an array
|
||||
func Monad[A, B any]() monad.Monad[A, B, []A, []B, []func(A) B] {
|
||||
return G.Monad[A, B, []A, []B, []func(A) B]()
|
||||
}
|
||||
@@ -44,11 +44,11 @@ func From[A any](first A, data ...A) NonEmptyArray[A] {
|
||||
return buffer
|
||||
}
|
||||
|
||||
func IsEmpty[A any](as NonEmptyArray[A]) bool {
|
||||
func IsEmpty[A any](_ NonEmptyArray[A]) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func IsNonEmpty[A any](as NonEmptyArray[A]) bool {
|
||||
func IsNonEmpty[A any](_ NonEmptyArray[A]) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -32,20 +32,20 @@ type bounded[T any] struct {
|
||||
b T
|
||||
}
|
||||
|
||||
func (self bounded[T]) Equals(x, y T) bool {
|
||||
return self.e(x, y)
|
||||
func (b bounded[T]) Equals(x, y T) bool {
|
||||
return b.e(x, y)
|
||||
}
|
||||
|
||||
func (self bounded[T]) Compare(x, y T) int {
|
||||
return self.c(x, y)
|
||||
func (b bounded[T]) Compare(x, y T) int {
|
||||
return b.c(x, y)
|
||||
}
|
||||
|
||||
func (self bounded[T]) Top() T {
|
||||
return self.t
|
||||
func (b bounded[T]) Top() T {
|
||||
return b.t
|
||||
}
|
||||
|
||||
func (self bounded[T]) Bottom() T {
|
||||
return self.b
|
||||
func (b bounded[T]) Bottom() T {
|
||||
return b.b
|
||||
}
|
||||
|
||||
// MakeBounded creates an instance of a bounded type
|
||||
|
||||
@@ -37,7 +37,7 @@ 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], f func(A) B) Const[E, B] {
|
||||
func MonadMap[E, A, B any](fa Const[E, A], _ func(A) B) Const[E, B] {
|
||||
return Make[E, B](fa.value)
|
||||
}
|
||||
|
||||
|
||||
@@ -47,5 +47,5 @@ func ExampleReadFile() {
|
||||
fmt.Println(result())
|
||||
|
||||
// Output:
|
||||
// Right[<nil>, string](Carsten)
|
||||
// Right[string](Carsten)
|
||||
}
|
||||
|
||||
@@ -182,10 +182,7 @@ func withCancelCauseFunc[
|
||||
ma,
|
||||
IOE.Swap[GIOA, func() E.Either[A, error]],
|
||||
IOE.ChainFirstIOK[func() E.Either[A, error], func() any](func(err error) func() any {
|
||||
return IO.MakeIO[func() any](func() any {
|
||||
cancel(err)
|
||||
return nil
|
||||
})
|
||||
return IO.FromImpure[func() any](func() { cancel(err) })
|
||||
}),
|
||||
IOE.Swap[func() E.Either[A, error], GIOA],
|
||||
)
|
||||
|
||||
@@ -61,7 +61,7 @@ func Requester(builder *R.Builder) RIOEH.Requester {
|
||||
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.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 {
|
||||
|
||||
@@ -32,12 +32,12 @@ import (
|
||||
func TestBuilderWithQuery(t *testing.T) {
|
||||
// add some query
|
||||
withLimit := R.WithQueryArg("limit")("10")
|
||||
withUrl := R.WithUrl("http://www.example.org?a=b")
|
||||
withURL := R.WithUrl("http://www.example.org?a=b")
|
||||
|
||||
b := F.Pipe2(
|
||||
R.Default,
|
||||
withLimit,
|
||||
withUrl,
|
||||
withURL,
|
||||
)
|
||||
|
||||
req := F.Pipe3(
|
||||
|
||||
@@ -103,12 +103,19 @@ func ReadText(client Client) func(Requester) RIOE.ReaderIOEither[string] {
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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.Flow3(
|
||||
ReadFullResponse(client),
|
||||
RIOE.ChainFirstEitherK(F.Flow2(
|
||||
H.Response,
|
||||
H.ValidateJsonResponse,
|
||||
H.ValidateJSONResponse,
|
||||
)),
|
||||
RIOE.ChainEitherK(F.Flow2(
|
||||
H.Body,
|
||||
|
||||
@@ -31,7 +31,7 @@ import (
|
||||
)
|
||||
|
||||
type PostItem struct {
|
||||
UserId uint `json:"userId"`
|
||||
UserID uint `json:"userId"`
|
||||
Id uint `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Body string `json:"body"`
|
||||
@@ -84,7 +84,7 @@ func TestSendSingleRequest(t *testing.T) {
|
||||
|
||||
req1 := MakeGetRequest("https://jsonplaceholder.typicode.com/posts/1")
|
||||
|
||||
readItem := ReadJson[PostItem](client)
|
||||
readItem := ReadJSON[PostItem](client)
|
||||
|
||||
resp1 := readItem(req1)
|
||||
|
||||
@@ -112,7 +112,7 @@ func TestSendSingleRequestWithHeaderUnsafe(t *testing.T) {
|
||||
R.Map(setHeaderUnsafe("Content-Type", "text/html")),
|
||||
)
|
||||
|
||||
readItem := ReadJson[PostItem](client)
|
||||
readItem := ReadJSON[PostItem](client)
|
||||
|
||||
resp1 := F.Pipe2(
|
||||
req1,
|
||||
@@ -140,7 +140,7 @@ func TestSendSingleRequestWithHeaderSafe(t *testing.T) {
|
||||
WithHeader("Content-Type", "text/html").
|
||||
Build()
|
||||
|
||||
readItem := ReadJson[PostItem](client)
|
||||
readItem := ReadJSON[PostItem](client)
|
||||
|
||||
response := F.Pipe2(
|
||||
request,
|
||||
|
||||
@@ -127,7 +127,7 @@ func MakeInjector(providers []Provider) InjectableFactory {
|
||||
var resolved sync.Map
|
||||
|
||||
// provide a mapping for all providers
|
||||
factoryById := assembleProviders(providers)
|
||||
factoryByID := assembleProviders(providers)
|
||||
|
||||
// the actual factory, we need lazy initialization
|
||||
var injFct InjectableFactory
|
||||
@@ -149,7 +149,7 @@ func MakeInjector(providers []Provider) InjectableFactory {
|
||||
T.Map2(F.Flow3(
|
||||
Dependency.Id,
|
||||
R.Lookup[ProviderFactory, string],
|
||||
I.Ap[O.Option[ProviderFactory]](factoryById),
|
||||
I.Ap[O.Option[ProviderFactory]](factoryByID),
|
||||
), handleMissingProvider),
|
||||
T.Tupled2(O.MonadGetOrElse[ProviderFactory]),
|
||||
IG.Ap[ProviderFactory](injFct),
|
||||
|
||||
39
di/token.go
39
di/token.go
@@ -65,34 +65,38 @@ type MultiInjectionToken[T any] interface {
|
||||
}
|
||||
|
||||
// makeID creates a generator of unique string IDs
|
||||
func makeId() IO.IO[string] {
|
||||
func makeID() IO.IO[string] {
|
||||
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()
|
||||
// genID is the common generator of unique string IDs
|
||||
var genID = makeID()
|
||||
|
||||
type token[T any] struct {
|
||||
type tokenBase struct {
|
||||
name string
|
||||
id string
|
||||
flag int
|
||||
toType func(val any) E.Either[error, T]
|
||||
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.id
|
||||
return t.base.id
|
||||
}
|
||||
|
||||
func (t *token[T]) Flag() int {
|
||||
return t.flag
|
||||
return t.base.flag
|
||||
}
|
||||
|
||||
func (t *token[T]) String() string {
|
||||
return t.name
|
||||
return t.base.name
|
||||
}
|
||||
|
||||
func (t *token[T]) Unerase(val any) E.Either[error, T] {
|
||||
@@ -100,11 +104,14 @@ func (t *token[T]) Unerase(val any) E.Either[error, T] {
|
||||
}
|
||||
|
||||
func (t *token[T]) ProviderFactory() O.Option[DIE.ProviderFactory] {
|
||||
return t.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]{name, id, typ, unerase, providerFactory}
|
||||
return &token[T]{makeTokenBase(name, id, typ, providerFactory), unerase}
|
||||
}
|
||||
|
||||
type injectionToken[T any] struct {
|
||||
@@ -136,7 +143,7 @@ func (i *injectionToken[T]) IOOption() Dependency[IOO.IOOption[T]] {
|
||||
}
|
||||
|
||||
func (i *injectionToken[T]) ProviderFactory() O.Option[DIE.ProviderFactory] {
|
||||
return i.providerFactory
|
||||
return i.base.providerFactory
|
||||
}
|
||||
|
||||
func (m *multiInjectionToken[T]) Container() InjectionToken[[]T] {
|
||||
@@ -149,10 +156,10 @@ func (m *multiInjectionToken[T]) Item() InjectionToken[T] {
|
||||
|
||||
// makeToken create a unique [InjectionToken] for a specific type
|
||||
func makeInjectionToken[T any](name string, providerFactory O.Option[DIE.ProviderFactory]) InjectionToken[T] {
|
||||
id := genId()
|
||||
id := genID()
|
||||
toIdentity := toType[T]()
|
||||
return &injectionToken[T]{
|
||||
token[T]{name, id, DIE.Identity, toIdentity, providerFactory},
|
||||
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),
|
||||
@@ -171,7 +178,7 @@ func MakeTokenWithDefault[T any](name string, providerFactory DIE.ProviderFactor
|
||||
|
||||
// MakeMultiToken creates a [MultiInjectionToken]
|
||||
func MakeMultiToken[T any](name string) MultiInjectionToken[T] {
|
||||
id := genId()
|
||||
id := genID()
|
||||
toItem := toType[T]()
|
||||
toContainer := toArrayType(toItem)
|
||||
containerName := fmt.Sprintf("Container[%s]", name)
|
||||
@@ -180,14 +187,14 @@ func MakeMultiToken[T any](name string) MultiInjectionToken[T] {
|
||||
providerFactory := O.None[DIE.ProviderFactory]()
|
||||
// container
|
||||
container := &injectionToken[[]T]{
|
||||
token[[]T]{containerName, id, DIE.Multi | DIE.Identity, toContainer, providerFactory},
|
||||
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]{itemName, id, DIE.Item | DIE.Identity, toItem, providerFactory},
|
||||
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),
|
||||
|
||||
15
di/utils.go
15
di/utils.go
@@ -25,6 +25,13 @@ import (
|
||||
O "github.com/IBM/fp-go/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
|
||||
@@ -38,7 +45,7 @@ func toType[T any]() func(t any) E.Either[error, T] {
|
||||
// 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(
|
||||
toType[O.Option[any]](),
|
||||
toOptionAny,
|
||||
E.Chain(O.Fold(
|
||||
F.Nullary2(O.None[T], E.Of[error, O.Option[T]]),
|
||||
F.Flow2(
|
||||
@@ -52,7 +59,7 @@ func toOptionType[T any](item func(any) E.Either[error, T]) func(t any) E.Either
|
||||
// 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(
|
||||
toType[IOE.IOEither[error, any]](),
|
||||
toIOEitherAny,
|
||||
E.Map[error](IOE.ChainEitherK(item)),
|
||||
)
|
||||
}
|
||||
@@ -60,7 +67,7 @@ func toIOEitherType[T any](item func(any) E.Either[error, T]) func(t any) E.Eith
|
||||
// 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(
|
||||
toType[IOO.IOOption[any]](),
|
||||
toIOOptionAny,
|
||||
E.Map[error](IOO.ChainOptionK(F.Flow2(
|
||||
item,
|
||||
E.ToOption[error, T],
|
||||
@@ -71,7 +78,7 @@ func toIOOptionType[T any](item func(any) E.Either[error, T]) func(t any) E.Eith
|
||||
// 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(
|
||||
toType[[]any](),
|
||||
toArrayAny,
|
||||
E.Chain(E.TraverseArray(item)),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -23,17 +23,16 @@ type (
|
||||
// Either defines a data structure that logically holds either an E or an A. The flag discriminates the cases
|
||||
Either[E, A any] struct {
|
||||
isLeft bool
|
||||
left E
|
||||
right A
|
||||
value any
|
||||
}
|
||||
)
|
||||
|
||||
// String prints some debug info for the object
|
||||
func (s Either[E, A]) String() string {
|
||||
if s.isLeft {
|
||||
return fmt.Sprintf("Left[%T, %T](%v)", s.left, s.right, s.left)
|
||||
return fmt.Sprintf("Left[%T](%v)", s.value, s.value)
|
||||
}
|
||||
return fmt.Sprintf("Right[%T, %T](%v)", s.left, s.right, s.right)
|
||||
return fmt.Sprintf("Right[%T](%v)", s.value, s.value)
|
||||
}
|
||||
|
||||
// Format prints some debug info for the object
|
||||
@@ -58,23 +57,29 @@ func IsRight[E, A any](val Either[E, A]) bool {
|
||||
|
||||
// Left creates a new instance of an [Either] representing the left value.
|
||||
func Left[A, E any](value E) Either[E, A] {
|
||||
return Either[E, A]{isLeft: true, left: value}
|
||||
return Either[E, A]{true, value}
|
||||
}
|
||||
|
||||
// Right creates a new instance of an [Either] representing the right value.
|
||||
func Right[E, A any](value A) Either[E, A] {
|
||||
return Either[E, A]{isLeft: false, right: value}
|
||||
return Either[E, A]{false, value}
|
||||
}
|
||||
|
||||
// MonadFold extracts the values from an [Either] by invoking the [onLeft] callback or the [onRight] callback depending on the case
|
||||
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.left)
|
||||
return onLeft(ma.value.(E))
|
||||
}
|
||||
return onRight(ma.right)
|
||||
return onRight(ma.value.(A))
|
||||
}
|
||||
|
||||
// Unwrap converts an [Either] into the idiomatic tuple
|
||||
func Unwrap[E, A any](ma Either[E, A]) (A, E) {
|
||||
return ma.right, ma.left
|
||||
if ma.isLeft {
|
||||
var a A
|
||||
return a, ma.value.(E)
|
||||
} else {
|
||||
var e E
|
||||
return ma.value.(A), e
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ package either
|
||||
import (
|
||||
E "github.com/IBM/fp-go/errors"
|
||||
F "github.com/IBM/fp-go/function"
|
||||
C "github.com/IBM/fp-go/internal/chain"
|
||||
FC "github.com/IBM/fp-go/internal/functor"
|
||||
L "github.com/IBM/fp-go/lazy"
|
||||
O "github.com/IBM/fp-go/option"
|
||||
@@ -85,12 +86,15 @@ func MonadChain[E, A, B any](fa Either[E, A], f func(a A) Either[E, B]) Either[E
|
||||
}
|
||||
|
||||
func MonadChainFirst[E, A, B any](ma Either[E, A], f func(a A) Either[E, B]) Either[E, A] {
|
||||
return MonadChain(ma, func(a A) Either[E, A] {
|
||||
return MonadMap(f(a), F.Constant1[B](a))
|
||||
})
|
||||
return C.MonadChainFirst(
|
||||
MonadChain[E, A, A],
|
||||
MonadMap[E, B, A],
|
||||
ma,
|
||||
f,
|
||||
)
|
||||
}
|
||||
|
||||
func MonadChainTo[A, E, B any](ma Either[E, A], mb Either[E, B]) Either[E, B] {
|
||||
func MonadChainTo[A, E, B any](_ Either[E, A], mb Either[E, B]) Either[E, B] {
|
||||
return mb
|
||||
}
|
||||
|
||||
@@ -114,7 +118,11 @@ func Chain[E, A, B any](f func(a A) Either[E, B]) func(Either[E, A]) Either[E, B
|
||||
}
|
||||
|
||||
func ChainFirst[E, A, B any](f func(a A) Either[E, B]) func(Either[E, A]) Either[E, A] {
|
||||
return F.Bind2nd(MonadChainFirst[E, A, B], f)
|
||||
return C.ChainFirst(
|
||||
Chain[E, A, A],
|
||||
Map[E, B, A],
|
||||
f,
|
||||
)
|
||||
}
|
||||
|
||||
func Flatten[E, A any](mma Either[E, Either[E, A]]) Either[E, A] {
|
||||
|
||||
@@ -26,12 +26,6 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestDefault(t *testing.T) {
|
||||
var e Either[error, string]
|
||||
|
||||
assert.Equal(t, Of[error](""), e)
|
||||
}
|
||||
|
||||
func TestIsLeft(t *testing.T) {
|
||||
err := errors.New("Some error")
|
||||
withError := Left[string](err)
|
||||
|
||||
@@ -48,11 +48,11 @@ func ExampleEither_creation() {
|
||||
fmt.Println(rightFromPred)
|
||||
|
||||
// Output:
|
||||
// Left[*errors.errorString, string](some error)
|
||||
// Right[<nil>, string](value)
|
||||
// Left[*errors.errorString, *string](value was nil)
|
||||
// Left[*errors.errorString](some error)
|
||||
// Right[string](value)
|
||||
// Left[*errors.errorString](value was nil)
|
||||
// true
|
||||
// Left[*errors.errorString, int](3 is an odd number)
|
||||
// Right[<nil>, int](4)
|
||||
// Left[*errors.errorString](3 is an odd number)
|
||||
// Right[int](4)
|
||||
|
||||
}
|
||||
|
||||
@@ -53,8 +53,8 @@ func ExampleEither_extraction() {
|
||||
fmt.Println(doubleFromRightBis)
|
||||
|
||||
// Output:
|
||||
// Left[*errors.errorString, int](Division by Zero!)
|
||||
// Right[<nil>, int](10)
|
||||
// Left[*errors.errorString](Division by Zero!)
|
||||
// Right[int](10)
|
||||
// 0
|
||||
// 10
|
||||
// 0
|
||||
|
||||
43
either/monad.go
Normal file
43
either/monad.go
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright (c) 2024 IBM Corp.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES 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/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]
|
||||
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]{}
|
||||
}
|
||||
@@ -19,38 +19,17 @@ import (
|
||||
F "github.com/IBM/fp-go/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<Either[B]>
|
||||
HKTA = HKT<A>
|
||||
HKTB = HKT<B>
|
||||
*/
|
||||
func traverse[E, A, B, HKTB, HKTRB any](
|
||||
mof func(Either[E, B]) HKTRB,
|
||||
mmap func(func(B) Either[E, B]) func(HKTB) HKTRB,
|
||||
) func(Either[E, A], func(A) HKTB) HKTRB {
|
||||
|
||||
left := F.Flow2(Left[B, E], mof)
|
||||
right := mmap(Right[E, B])
|
||||
|
||||
return func(ta Either[E, A], f func(A) HKTB) HKTRB {
|
||||
return MonadFold(ta,
|
||||
left,
|
||||
F.Flow2(f, right),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Traverse converts an [Either] of some higher kinded type into the higher kinded type of an [Either]
|
||||
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 {
|
||||
delegate := traverse[E, A, B](mof, mmap)
|
||||
|
||||
left := F.Flow2(Left[B, E], mof)
|
||||
right := mmap(Right[E, B])
|
||||
|
||||
return func(f func(A) HKTB) func(Either[E, A]) HKTRB {
|
||||
return F.Bind2nd(delegate, f)
|
||||
return Fold(left, F.Flow2(f, right))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
4
eq/eq.go
4
eq/eq.go
@@ -27,8 +27,8 @@ type eq[T any] struct {
|
||||
c func(x, y T) bool
|
||||
}
|
||||
|
||||
func (self eq[T]) Equals(x, y T) bool {
|
||||
return self.c(x, y)
|
||||
func (e eq[T]) Equals(x, y T) bool {
|
||||
return e.c(x, y)
|
||||
}
|
||||
|
||||
func strictEq[A comparable](a, b A) bool {
|
||||
|
||||
@@ -56,7 +56,11 @@ var (
|
||||
Monoid = ENDO.Monoid[*Builder]()
|
||||
|
||||
// Url is a [L.Lens] for the URL
|
||||
Url = L.MakeLensRef((*Builder).GetUrl, (*Builder).SetUrl)
|
||||
//
|
||||
// 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
|
||||
@@ -76,15 +80,19 @@ var (
|
||||
noBody = O.None[E.Either[error, []byte]]()
|
||||
noQueryArg = O.None[string]()
|
||||
|
||||
parseUrl = E.Eitherize1(url.Parse)
|
||||
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 a certain method
|
||||
WithUrl = Url.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
|
||||
@@ -148,12 +156,19 @@ func (builder *Builder) clone() *Builder {
|
||||
}
|
||||
|
||||
// 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,
|
||||
parseURL,
|
||||
E.Chain(F.Flow4(
|
||||
T.Replicate2[*url.URL],
|
||||
T.Map2(
|
||||
@@ -176,10 +191,15 @@ func (builder *Builder) GetTargetUrl() E.Either[error, 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,
|
||||
@@ -209,11 +229,17 @@ func (builder *Builder) SetMethod(method string) *Builder {
|
||||
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
|
||||
@@ -278,14 +304,21 @@ func WithoutHeader(name string) Endomorphism {
|
||||
}
|
||||
|
||||
// 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),
|
||||
WithContentType(C.JSON),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ func TestBuilder(t *testing.T) {
|
||||
|
||||
b1 := F.Pipe1(
|
||||
Default,
|
||||
withContentType(C.Json),
|
||||
withContentType(C.JSON),
|
||||
)
|
||||
|
||||
b2 := F.Pipe1(
|
||||
@@ -48,7 +48,7 @@ func TestBuilder(t *testing.T) {
|
||||
)
|
||||
|
||||
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.JSON), b1.GetHeader(name))
|
||||
assert.Equal(t, O.Of(C.TextPlain), b2.GetHeader(name))
|
||||
assert.Equal(t, O.None[string](), b3.GetHeader(name))
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ package content
|
||||
|
||||
const (
|
||||
TextPlain = "text/plain"
|
||||
Json = "application/json"
|
||||
JSON = "application/json"
|
||||
Json = JSON // Deprecated: use [JSON] instead
|
||||
FormEncoded = "application/x-www-form-urlencoded"
|
||||
)
|
||||
|
||||
@@ -45,29 +45,33 @@ type (
|
||||
|
||||
var (
|
||||
// mime type to check if a media type matches
|
||||
reJsonMimeType = regexp.MustCompile(`application/(?:\w+\+)?json`)
|
||||
reJSONMimeType = regexp.MustCompile(`application/(?:\w+\+)?json`)
|
||||
// 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(
|
||||
validateJSONContentTypeString = F.Flow2(
|
||||
ParseMediaType,
|
||||
E.ChainFirst(F.Flow2(
|
||||
T.First[string, map[string]string],
|
||||
E.FromPredicate(reJsonMimeType.MatchString, func(mimeType string) error {
|
||||
E.FromPredicate(reJSONMimeType.MatchString, func(mimeType string) error {
|
||||
return fmt.Errorf("mimetype [%s] is not a valid JSON content type", mimeType)
|
||||
}),
|
||||
)),
|
||||
)
|
||||
// ValidateJsonResponse checks if an HTTP response is a valid JSON response
|
||||
ValidateJsonResponse = F.Flow2(
|
||||
// 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),
|
||||
E.ChainFirst(validateJSONContentTypeString),
|
||||
)))
|
||||
// ValidateJsonResponse checks if an HTTP response is a valid JSON response
|
||||
//
|
||||
// Deprecated: use [ValidateJSONResponse] instead
|
||||
ValidateJsonResponse = ValidateJSONResponse
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -39,7 +39,7 @@ func Error[A any](t *testing.T) func(E.Either[error, A]) bool {
|
||||
func TestValidateJsonContentTypeString(t *testing.T) {
|
||||
|
||||
res := F.Pipe1(
|
||||
validateJsonContentTypeString(C.Json),
|
||||
validateJSONContentTypeString(C.JSON),
|
||||
NoError[ParsedMediaType](t),
|
||||
)
|
||||
|
||||
@@ -49,7 +49,7 @@ func TestValidateJsonContentTypeString(t *testing.T) {
|
||||
func TestValidateInvalidJsonContentTypeString(t *testing.T) {
|
||||
|
||||
res := F.Pipe1(
|
||||
validateJsonContentTypeString("application/xml"),
|
||||
validateJSONContentTypeString("application/xml"),
|
||||
Error[ParsedMediaType](t),
|
||||
)
|
||||
|
||||
|
||||
43
identity/monad.go
Normal file
43
identity/monad.go
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright (c) 2024 IBM Corp.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES 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/internal/monad"
|
||||
)
|
||||
|
||||
type identityMonad[A, B any] struct{}
|
||||
|
||||
func (o *identityMonad[A, B]) Of(a A) A {
|
||||
return Of[A](a)
|
||||
}
|
||||
|
||||
func (o *identityMonad[A, B]) Map(f func(A) B) func(A) B {
|
||||
return Map[A, B](f)
|
||||
}
|
||||
|
||||
func (o *identityMonad[A, B]) Chain(f func(A) B) func(A) B {
|
||||
return Chain[A, B](f)
|
||||
}
|
||||
|
||||
func (o *identityMonad[A, B]) Ap(fa A) func(func(A) B) B {
|
||||
return Ap[B, A](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]{}
|
||||
}
|
||||
@@ -34,6 +34,9 @@ func AssertIdentity[HKTA, HKTAA, A any](t *testing.T,
|
||||
|
||||
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)
|
||||
@@ -57,6 +60,9 @@ func AssertHomomorphism[HKTA, HKTB, HKTAB, A, B any](t *testing.T,
|
||||
|
||||
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))
|
||||
@@ -73,7 +79,6 @@ func AssertInterchange[HKTA, HKTB, HKTAB, HKTABB, A, B any](t *testing.T,
|
||||
eq E.Eq[HKTB],
|
||||
|
||||
fofa func(A) HKTA,
|
||||
fofb func(B) HKTB,
|
||||
fofab func(func(A) B) HKTAB,
|
||||
fofabb func(func(func(A) B) B) HKTABB,
|
||||
|
||||
@@ -82,6 +87,9 @@ func AssertInterchange[HKTA, HKTB, HKTAB, HKTABB, A, B any](t *testing.T,
|
||||
|
||||
ab func(A) B,
|
||||
) func(a A) bool {
|
||||
// mark as test helper
|
||||
t.Helper()
|
||||
|
||||
return func(a A) bool {
|
||||
|
||||
fab := fofab(ab)
|
||||
@@ -127,12 +135,15 @@ func AssertLaws[HKTA, HKTB, HKTC, HKTAA, HKTAB, HKTBC, HKTAC, HKTABB, HKTABAC, A
|
||||
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, fofb, fofab, fofabb, fapab, fapabb, ab)
|
||||
interchange := AssertInterchange(t, eqb, fofa, fofab, fofabb, fapab, fapabb, ab)
|
||||
|
||||
return func(a A) bool {
|
||||
fa := fofa(a)
|
||||
|
||||
26
internal/applicative/types.go
Normal file
26
internal/applicative/types.go
Normal file
@@ -0,0 +1,26 @@
|
||||
// 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 applicative
|
||||
|
||||
import (
|
||||
"github.com/IBM/fp-go/internal/apply"
|
||||
"github.com/IBM/fp-go/internal/pointed"
|
||||
)
|
||||
|
||||
type Applicative[A, B, HKTA, HKTB, HKTFAB any] interface {
|
||||
apply.Apply[A, B, HKTA, HKTB, HKTFAB]
|
||||
pointed.Pointed[A, HKTA]
|
||||
}
|
||||
@@ -43,6 +43,7 @@ func AssertAssociativeComposition[HKTA, HKTB, HKTC, HKTAB, HKTBC, HKTAC, HKTABAC
|
||||
ab func(A) B,
|
||||
bc func(B) C,
|
||||
) func(fa HKTA) bool {
|
||||
t.Helper()
|
||||
return func(fa HKTA) bool {
|
||||
|
||||
fab := fofab(ab)
|
||||
@@ -86,6 +87,8 @@ func AssertLaws[HKTA, HKTB, HKTC, HKTAB, HKTBC, HKTAC, HKTABAC, A, B, C any](t *
|
||||
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
|
||||
|
||||
25
internal/apply/types.go
Normal file
25
internal/apply/types.go
Normal file
@@ -0,0 +1,25 @@
|
||||
// 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 apply
|
||||
|
||||
import (
|
||||
"github.com/IBM/fp-go/internal/functor"
|
||||
)
|
||||
|
||||
type Apply[A, B, HKTA, HKTB, HKTFAB any] interface {
|
||||
functor.Functor[A, B, HKTA, HKTB]
|
||||
Ap(HKTA) func(HKTFAB) HKTB
|
||||
}
|
||||
@@ -106,6 +106,12 @@ func MonadMap[GA ~[]A, GB ~[]B, A, B any](as GA, f func(a A) B) GB {
|
||||
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)
|
||||
|
||||
@@ -30,7 +30,6 @@ import (
|
||||
func AssertAssociativity[HKTA, HKTB, HKTC, A, B, C any](t *testing.T,
|
||||
eq E.Eq[HKTC],
|
||||
|
||||
fofa func(A) HKTA,
|
||||
fofb func(B) HKTB,
|
||||
fofc func(C) HKTC,
|
||||
|
||||
@@ -61,7 +60,6 @@ func AssertLaws[HKTA, HKTB, HKTC, HKTAB, HKTBC, HKTAC, HKTABAC, A, B, C any](t *
|
||||
eqa E.Eq[HKTA],
|
||||
eqc E.Eq[HKTC],
|
||||
|
||||
fofa func(A) HKTA,
|
||||
fofb func(B) HKTB,
|
||||
fofc func(C) HKTC,
|
||||
|
||||
@@ -91,7 +89,7 @@ func AssertLaws[HKTA, HKTB, HKTC, HKTAB, HKTBC, HKTAC, HKTABAC, A, B, C any](t *
|
||||
// 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, fofa, fofb, fofc, chainab, chainac, chainbc, ab, bc)
|
||||
associativity := AssertAssociativity(t, eqc, fofb, fofc, chainab, chainac, chainbc, ab, bc)
|
||||
|
||||
return func(fa HKTA) bool {
|
||||
return apply(fa) && associativity(fa)
|
||||
|
||||
25
internal/chain/types.go
Normal file
25
internal/chain/types.go
Normal file
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) 2024 IBM Corp.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES 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/internal/apply"
|
||||
)
|
||||
|
||||
type Chainable[A, B, HKTA, HKTB, HKTFAB any] interface {
|
||||
apply.Apply[A, B, HKTA, HKTB, HKTFAB]
|
||||
Chain(func(A) HKTB) func(HKTA) HKTB
|
||||
}
|
||||
@@ -159,3 +159,7 @@ func OrLeft[E1, E2, A, HKTE1A, HKTE2, HKTE2A any](
|
||||
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)
|
||||
}
|
||||
|
||||
26
internal/foldable/types.go
Normal file
26
internal/foldable/types.go
Normal file
@@ -0,0 +1,26 @@
|
||||
// Copyright (c) 2024 IBM Corp.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES 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/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
|
||||
}
|
||||
24
internal/fromeither/types.go
Normal file
24
internal/fromeither/types.go
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) 2024 IBM Corp.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES 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/either"
|
||||
)
|
||||
|
||||
type FromEither[E, A, HKTA any] interface {
|
||||
FromEither(ET.Either[E, A]) HKTA
|
||||
}
|
||||
20
internal/fromio/types.go
Normal file
20
internal/fromio/types.go
Normal file
@@ -0,0 +1,20 @@
|
||||
// Copyright (c) 2024 IBM Corp.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES 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
|
||||
}
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
//
|
||||
// F.map(fa, a => a) <-> fa
|
||||
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")
|
||||
}
|
||||
@@ -46,6 +47,7 @@ func AssertComposition[HKTA, HKTB, HKTC, A, B, C any](
|
||||
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")
|
||||
}
|
||||
@@ -63,6 +65,7 @@ func AssertLaws[HKTA, HKTB, HKTC, A, B, C any](t *testing.T,
|
||||
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)
|
||||
|
||||
|
||||
20
internal/functor/types.go
Normal file
20
internal/functor/types.go
Normal file
@@ -0,0 +1,20 @@
|
||||
// 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 functor
|
||||
|
||||
type Functor[A, B, HKTA, HKTB any] interface {
|
||||
Map(func(A) B) func(HKTA) HKTB
|
||||
}
|
||||
26
internal/monad/monad.go
Normal file
26
internal/monad/monad.go
Normal file
@@ -0,0 +1,26 @@
|
||||
// Copyright (c) 2024 IBM Corp.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES 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/internal/applicative"
|
||||
"github.com/IBM/fp-go/internal/chain"
|
||||
)
|
||||
|
||||
type Monad[A, B, HKTA, HKTB, HKTFAB any] interface {
|
||||
applicative.Applicative[A, B, HKTA, HKTB, HKTFAB]
|
||||
chain.Chainable[A, B, HKTA, HKTB, HKTFAB]
|
||||
}
|
||||
@@ -110,7 +110,7 @@ func AssertLaws[HKTA, HKTB, HKTC, HKTAA, HKTAB, HKTBC, HKTAC, HKTABB, HKTABAC, A
|
||||
// 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, fofa, fofb, fofc, fofab, fofbc, faa, fab, fac, fbc, fmap, chainab, chainac, chainbc, fapab, fapbc, fapac, fapabac, ab, bc)
|
||||
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)
|
||||
|
||||
@@ -40,6 +40,12 @@ func MonadMap[A, B, HKTFA, HKTFB any](fmap func(HKTFA, func(O.Option[A]) O.Optio
|
||||
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,
|
||||
|
||||
21
internal/pointed/types.go
Normal file
21
internal/pointed/types.go
Normal file
@@ -0,0 +1,21 @@
|
||||
// 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 pointed
|
||||
|
||||
type Pointed[A, HKTA any] interface {
|
||||
// Of lifts a value into its higher kinded type
|
||||
Of(A) HKTA
|
||||
}
|
||||
@@ -25,6 +25,11 @@ import (
|
||||
T "github.com/IBM/fp-go/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 {
|
||||
@@ -43,7 +48,7 @@ func FromIO[GA ~func() A, A any](a GA) GA {
|
||||
func FromImpure[GA ~func() any, IMP ~func()](f IMP) GA {
|
||||
return MakeIO[GA](func() any {
|
||||
f()
|
||||
return nil
|
||||
return undefined
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
43
io/generic/monad.go
Normal file
43
io/generic/monad.go
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright (c) 2024 IBM Corp.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES 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/internal/monad"
|
||||
)
|
||||
|
||||
type ioMonad[A, B any, GA ~func() A, GB ~func() B, GAB ~func() func(A) B] struct{}
|
||||
|
||||
func (o *ioMonad[A, B, GA, GB, GAB]) Of(a A) GA {
|
||||
return Of[GA, A](a)
|
||||
}
|
||||
|
||||
func (o *ioMonad[A, B, GA, GB, GAB]) Map(f func(A) B) func(GA) GB {
|
||||
return Map[GA, GB, A, B](f)
|
||||
}
|
||||
|
||||
func (o *ioMonad[A, B, GA, GB, GAB]) Chain(f func(A) GB) func(GA) GB {
|
||||
return Chain[GA, GB, A, B](f)
|
||||
}
|
||||
|
||||
func (o *ioMonad[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 [Option]
|
||||
func Monad[A, B any, GA ~func() A, GB ~func() B, GAB ~func() func(A) B]() monad.Monad[A, B, GA, GB, GAB] {
|
||||
return &ioMonad[A, B, GA, GB, GAB]{}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ func TestLogger(t *testing.T) {
|
||||
|
||||
lio := l("out")
|
||||
|
||||
assert.Equal(t, nil, lio(10)())
|
||||
assert.NotPanics(t, func() { lio(10)() })
|
||||
}
|
||||
|
||||
func TestLogf(t *testing.T) {
|
||||
@@ -36,5 +36,5 @@ func TestLogf(t *testing.T) {
|
||||
|
||||
lio := l("Value is %d")
|
||||
|
||||
assert.Equal(t, nil, lio(10)())
|
||||
assert.NotPanics(t, func() { lio(10)() })
|
||||
}
|
||||
|
||||
26
io/monad.go
Normal file
26
io/monad.go
Normal file
@@ -0,0 +1,26 @@
|
||||
// Copyright (c) 2024 IBM Corp.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES 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/internal/monad"
|
||||
G "github.com/IBM/fp-go/io/generic"
|
||||
)
|
||||
|
||||
// Monad returns the monadic operations for [IO]
|
||||
func Monad[A, B any]() monad.Monad[A, B, IO[A], IO[B], IO[func(A) B]] {
|
||||
return G.Monad[A, B, IO[A], IO[B], IO[func(A) B]]()
|
||||
}
|
||||
@@ -48,10 +48,10 @@ func ExampleIOEither_creation() {
|
||||
fmt.Println(rightFromPred())
|
||||
|
||||
// Output:
|
||||
// Left[*errors.errorString, string](some error)
|
||||
// Right[<nil>, string](value)
|
||||
// Right[<nil>, int](42)
|
||||
// Left[*errors.errorString, int](3 is an odd number)
|
||||
// Right[<nil>, int](4)
|
||||
// Left[*errors.errorString](some error)
|
||||
// Right[string](value)
|
||||
// Right[int](42)
|
||||
// Left[*errors.errorString](3 is an odd number)
|
||||
// Right[int](4)
|
||||
|
||||
}
|
||||
|
||||
@@ -53,5 +53,5 @@ func ExampleIOEither_do() {
|
||||
fmt.Println(b())
|
||||
|
||||
// Output:
|
||||
// Right[<nil>, int](8)
|
||||
// Right[int](8)
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ func ExampleIOEither_extraction() {
|
||||
fmt.Println(valueFromIO)
|
||||
|
||||
// Output:
|
||||
// Right[<nil>, int](42)
|
||||
// Right[int](42)
|
||||
// 42
|
||||
// 42
|
||||
|
||||
|
||||
@@ -156,7 +156,10 @@ func MonadAp[GB ~func() ET.Either[E, B], GAB ~func() ET.Either[E, func(A) B], GA
|
||||
}
|
||||
|
||||
func Ap[GB ~func() ET.Either[E, B], GAB ~func() ET.Either[E, func(A) B], GA ~func() ET.Either[E, A], E, A, B any](ma GA) func(GAB) GB {
|
||||
return F.Bind2nd(MonadAp[GB, GAB, GA], ma)
|
||||
return eithert.Ap(
|
||||
IO.Ap[GB, func() func(ET.Either[E, A]) ET.Either[E, B], GA, ET.Either[E, B], ET.Either[E, A]],
|
||||
IO.Map[GAB, func() func(ET.Either[E, A]) ET.Either[E, B], ET.Either[E, func(A) B], func(ET.Either[E, A]) ET.Either[E, B]],
|
||||
ma)
|
||||
}
|
||||
|
||||
func MonadApSeq[GB ~func() ET.Either[E, B], GAB ~func() ET.Either[E, func(A) B], GA ~func() ET.Either[E, A], E, A, B any](mab GAB, ma GA) GB {
|
||||
@@ -167,7 +170,10 @@ func MonadApSeq[GB ~func() ET.Either[E, B], GAB ~func() ET.Either[E, func(A) B],
|
||||
}
|
||||
|
||||
func ApSeq[GB ~func() ET.Either[E, B], GAB ~func() ET.Either[E, func(A) B], GA ~func() ET.Either[E, A], E, A, B any](ma GA) func(GAB) GB {
|
||||
return F.Bind2nd(MonadApSeq[GB, GAB, GA], ma)
|
||||
return eithert.Ap(
|
||||
IO.ApSeq[GB, func() func(ET.Either[E, A]) ET.Either[E, B], GA, ET.Either[E, B], ET.Either[E, A]],
|
||||
IO.Map[GAB, func() func(ET.Either[E, A]) ET.Either[E, B], ET.Either[E, func(A) B], func(ET.Either[E, A]) ET.Either[E, B]],
|
||||
ma)
|
||||
}
|
||||
|
||||
func MonadApPar[GB ~func() ET.Either[E, B], GAB ~func() ET.Either[E, func(A) B], GA ~func() ET.Either[E, A], E, A, B any](mab GAB, ma GA) GB {
|
||||
@@ -178,7 +184,10 @@ func MonadApPar[GB ~func() ET.Either[E, B], GAB ~func() ET.Either[E, func(A) B],
|
||||
}
|
||||
|
||||
func ApPar[GB ~func() ET.Either[E, B], GAB ~func() ET.Either[E, func(A) B], GA ~func() ET.Either[E, A], E, A, B any](ma GA) func(GAB) GB {
|
||||
return F.Bind2nd(MonadApPar[GB, GAB, GA], ma)
|
||||
return eithert.Ap(
|
||||
IO.ApPar[GB, func() func(ET.Either[E, A]) ET.Either[E, B], GA, ET.Either[E, B], ET.Either[E, A]],
|
||||
IO.Map[GAB, func() func(ET.Either[E, A]) ET.Either[E, B], ET.Either[E, func(A) B], func(ET.Either[E, A]) ET.Either[E, B]],
|
||||
ma)
|
||||
}
|
||||
|
||||
func Flatten[GA ~func() ET.Either[E, A], GAA ~func() ET.Either[E, GA], E, A any](mma GAA) GA {
|
||||
@@ -204,11 +213,18 @@ func Memoize[GA ~func() ET.Either[E, A], E, A any](ma GA) GA {
|
||||
}
|
||||
|
||||
func MonadMapLeft[GA1 ~func() ET.Either[E1, A], GA2 ~func() ET.Either[E2, A], E1, E2, A any](fa GA1, f func(E1) E2) GA2 {
|
||||
return eithert.MonadMapLeft(IO.MonadMap[GA1, GA2, ET.Either[E1, A], ET.Either[E2, A]], fa, f)
|
||||
return eithert.MonadMapLeft(
|
||||
IO.MonadMap[GA1, GA2, ET.Either[E1, A], ET.Either[E2, A]],
|
||||
fa,
|
||||
f,
|
||||
)
|
||||
}
|
||||
|
||||
func MapLeft[GA1 ~func() ET.Either[E1, A], GA2 ~func() ET.Either[E2, A], E1, E2, A any](f func(E1) E2) func(GA1) GA2 {
|
||||
return F.Bind2nd(MonadMapLeft[GA1, GA2, E1, E2, A], f)
|
||||
return eithert.MapLeft(
|
||||
IO.Map[GA1, GA2, ET.Either[E1, A], ET.Either[E2, A]],
|
||||
f,
|
||||
)
|
||||
}
|
||||
|
||||
// Delay creates an operation that passes in the value after some [time.Duration]
|
||||
|
||||
@@ -24,9 +24,9 @@ import (
|
||||
F "github.com/IBM/fp-go/function"
|
||||
)
|
||||
|
||||
// LogJson converts the argument to JSON and then logs it via the format string
|
||||
// LogJSON converts the argument to JSON and then logs it via the format string
|
||||
// Can be used with [ChainFirst]
|
||||
func LogJson[GA ~func() ET.Either[error, any], A any](prefix string) func(A) GA {
|
||||
func LogJSON[GA ~func() ET.Either[error, any], A any](prefix string) func(A) GA {
|
||||
return func(a A) GA {
|
||||
// log this
|
||||
return F.Pipe3(
|
||||
@@ -41,3 +41,11 @@ func LogJson[GA ~func() ET.Either[error, any], A any](prefix string) func(A) GA
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// LogJson converts the argument to JSON and then logs it via the format string
|
||||
// Can be used with [ChainFirst]
|
||||
//
|
||||
// Deprecated: use [LogJSON] instead
|
||||
func LogJson[GA ~func() ET.Either[error, any], A any](prefix string) func(A) GA {
|
||||
return LogJSON[GA, A](prefix)
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ func Requester(builder *R.Builder) IOEH.Requester {
|
||||
return F.Pipe5(
|
||||
builder.GetBody(),
|
||||
O.Fold(LZ.Of(E.Of[error](withoutBody)), E.Map[error](withBody)),
|
||||
E.Ap[func(string) IOE.IOEither[error, *http.Request]](builder.GetTargetUrl()),
|
||||
E.Ap[func(string) IOE.IOEither[error, *http.Request]](builder.GetTargetURL()),
|
||||
E.Flap[error, IOE.IOEither[error, *http.Request]](builder.GetMethod()),
|
||||
E.GetOrElse(IOE.Left[*http.Request, error]),
|
||||
IOE.Map[error](func(req *http.Request) *http.Request {
|
||||
|
||||
@@ -31,12 +31,12 @@ import (
|
||||
func TestBuilderWithQuery(t *testing.T) {
|
||||
// add some query
|
||||
withLimit := R.WithQueryArg("limit")("10")
|
||||
withUrl := R.WithUrl("http://www.example.org?a=b")
|
||||
withURL := R.WithUrl("http://www.example.org?a=b")
|
||||
|
||||
b := F.Pipe2(
|
||||
R.Default,
|
||||
withLimit,
|
||||
withUrl,
|
||||
withURL,
|
||||
)
|
||||
|
||||
req := F.Pipe3(
|
||||
|
||||
@@ -118,12 +118,19 @@ func ReadText(client Client) func(Requester) IOE.IOEither[error, string] {
|
||||
}
|
||||
|
||||
// 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) IOE.IOEither[error, A] {
|
||||
return ReadJSON[A](client)
|
||||
}
|
||||
|
||||
// ReadJSON sends a request, reads the response and parses the response as JSON
|
||||
func ReadJSON[A any](client Client) func(Requester) IOE.IOEither[error, A] {
|
||||
return F.Flow3(
|
||||
ReadFullResponse(client),
|
||||
IOE.ChainFirstEitherK(F.Flow2(
|
||||
H.Response,
|
||||
H.ValidateJsonResponse,
|
||||
H.ValidateJSONResponse,
|
||||
)),
|
||||
IOE.ChainEitherK(F.Flow2(
|
||||
H.Body,
|
||||
|
||||
@@ -40,7 +40,7 @@ var testLogPolicy = R.CapDelay(
|
||||
)
|
||||
|
||||
type PostItem struct {
|
||||
UserId uint `json:"userId"`
|
||||
UserID uint `json:"userId"`
|
||||
Id uint `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Body string `json:"body"`
|
||||
@@ -54,7 +54,7 @@ func TestRetryHttp(t *testing.T) {
|
||||
action := func(status R.RetryStatus) IOE.IOEither[error, *PostItem] {
|
||||
return F.Pipe1(
|
||||
MakeGetRequest(urls[status.IterNumber]),
|
||||
ReadJson[*PostItem](client),
|
||||
ReadJSON[*PostItem](client),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,14 @@ import (
|
||||
|
||||
// LogJson converts the argument to pretty printed JSON and then logs it via the format string
|
||||
// Can be used with [ChainFirst]
|
||||
//
|
||||
// Deprecated: use [LogJSON] instead
|
||||
func LogJson[A any](prefix string) func(A) IOEither[error, any] {
|
||||
return G.LogJson[IOEither[error, any], A](prefix)
|
||||
}
|
||||
|
||||
// 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 G.LogJSON[IOEither[error, any], A](prefix)
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ func TestLogging(t *testing.T) {
|
||||
|
||||
res := F.Pipe1(
|
||||
Of[error](src),
|
||||
ChainFirst(LogJson[*SomeData]("Data: \n%s")),
|
||||
ChainFirst(LogJSON[*SomeData]("Data: \n%s")),
|
||||
)
|
||||
|
||||
dst := res()
|
||||
|
||||
@@ -70,7 +70,7 @@ func MonadMap[GA ~func() O.Option[A], GB ~func() O.Option[B], A, B any](fa GA, f
|
||||
}
|
||||
|
||||
func Map[GA ~func() O.Option[A], GB ~func() O.Option[B], A, B any](f func(A) B) func(GA) GB {
|
||||
return F.Bind2nd(MonadMap[GA, GB, A, B], f)
|
||||
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 {
|
||||
|
||||
@@ -18,6 +18,7 @@ package generic
|
||||
import (
|
||||
A "github.com/IBM/fp-go/array/generic"
|
||||
F "github.com/IBM/fp-go/function"
|
||||
C "github.com/IBM/fp-go/internal/chain"
|
||||
"github.com/IBM/fp-go/internal/utils"
|
||||
IO "github.com/IBM/fp-go/iooption/generic"
|
||||
M "github.com/IBM/fp-go/monoid"
|
||||
@@ -146,6 +147,23 @@ func MonadChain[GV ~func() O.Option[T.Tuple2[GV, V]], GU ~func() O.Option[T.Tupl
|
||||
return Chain[GV, GU](f)(ma)
|
||||
}
|
||||
|
||||
func MonadChainFirst[GV ~func() O.Option[T.Tuple2[GV, V]], GU ~func() O.Option[T.Tuple2[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[T.Tuple2[GV, V]], GU ~func() O.Option[T.Tuple2[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[T.Tuple2[GV, GU]], GU ~func() O.Option[T.Tuple2[GU, U]], U any](ma GV) GU {
|
||||
return MonadChain(ma, F.Identity[GU])
|
||||
}
|
||||
|
||||
45
iterator/stateless/generic/monad.go
Normal file
45
iterator/stateless/generic/monad.go
Normal file
@@ -0,0 +1,45 @@
|
||||
// Copyright (c) 2024 IBM Corp.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES 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/internal/monad"
|
||||
O "github.com/IBM/fp-go/option"
|
||||
T "github.com/IBM/fp-go/tuple"
|
||||
)
|
||||
|
||||
type iteratorMonad[A, B any, GA ~func() O.Option[T.Tuple2[GA, A]], GB ~func() O.Option[T.Tuple2[GB, B]], GAB ~func() O.Option[T.Tuple2[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[T.Tuple2[GA, A]], GB ~func() O.Option[T.Tuple2[GB, B]], GAB ~func() O.Option[T.Tuple2[GAB, func(A) B]]]() monad.Monad[A, B, GA, GB, GAB] {
|
||||
return &iteratorMonad[A, B, GA, GB, GAB]{}
|
||||
}
|
||||
@@ -144,3 +144,11 @@ func FoldMap[U, V any](m M.Monoid[V]) func(func(U) V) func(ma Iterator[U]) V {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -67,8 +67,8 @@ func isPrimeNumber(num int) bool {
|
||||
if num <= 2 {
|
||||
return true
|
||||
}
|
||||
sq_root := int(math.Sqrt(float64(num)))
|
||||
for i := 2; i <= sq_root; i++ {
|
||||
sqRoot := int(math.Sqrt(float64(num)))
|
||||
for i := 2; i <= sqRoot; i++ {
|
||||
if num%i == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
26
iterator/stateless/monad.go
Normal file
26
iterator/stateless/monad.go
Normal file
@@ -0,0 +1,26 @@
|
||||
// Copyright (c) 2024 IBM Corp.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES 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/internal/monad"
|
||||
G "github.com/IBM/fp-go/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]]()
|
||||
}
|
||||
@@ -23,8 +23,8 @@ type magma[A any] struct {
|
||||
c func(A, A) A
|
||||
}
|
||||
|
||||
func (self magma[A]) Concat(x A, y A) A {
|
||||
return self.c(x, y)
|
||||
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] {
|
||||
@@ -63,17 +63,17 @@ func FilterFirst[A any](p func(A) bool) func(Magma[A]) Magma[A] {
|
||||
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 A, y A) A {
|
||||
return MakeMagma(func(x, y A) A {
|
||||
return filterSecond(p, c, x, y)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func first[A any](x A, y A) A {
|
||||
func first[A any](x, _ A) A {
|
||||
return x
|
||||
}
|
||||
|
||||
func second[A any](x A, y A) A {
|
||||
func second[A any](_, y A) A {
|
||||
return y
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ func Second[A any]() Magma[A] {
|
||||
return MakeMagma(second[A])
|
||||
}
|
||||
|
||||
func endo[A any](f func(A) A, c func(A, A) A, x A, y A) A {
|
||||
func endo[A any](f func(A) A, c func(A, A) A, x, y A) A {
|
||||
return c(f(x), f(y))
|
||||
}
|
||||
|
||||
|
||||
@@ -21,9 +21,9 @@ import (
|
||||
)
|
||||
|
||||
// FunctionMonoid forms a monoid as long as you can provide a monoid for the codomain.
|
||||
func FunctionMonoid[A, B any](M Monoid[B]) Monoid[func(A) B] {
|
||||
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()),
|
||||
S.FunctionSemigroup[A, B](m).Concat,
|
||||
F.Constant1[A](m.Empty()),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -29,12 +29,12 @@ type monoid[A any] struct {
|
||||
e A
|
||||
}
|
||||
|
||||
func (self monoid[A]) Concat(x A, y A) A {
|
||||
return self.c(x, y)
|
||||
func (m monoid[A]) Concat(x, y A) A {
|
||||
return m.c(x, y)
|
||||
}
|
||||
|
||||
func (self monoid[A]) Empty() A {
|
||||
return self.e
|
||||
func (m monoid[A]) Empty() A {
|
||||
return m.e
|
||||
}
|
||||
|
||||
// MakeMonoid creates a monoid given a concat function and an empty element
|
||||
|
||||
@@ -34,8 +34,8 @@ type (
|
||||
// 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 {
|
||||
copy := *s
|
||||
return setter(©, a)
|
||||
cpy := *s
|
||||
return setter(&cpy, a)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,8 +45,8 @@ func setCopyCurried[SET ~func(A) EM.Endomorphism[*S], S, A any](setter SET) func
|
||||
return func(a A) EM.Endomorphism[*S] {
|
||||
seta := setter(a)
|
||||
return func(s *S) *S {
|
||||
copy := *s
|
||||
return seta(©)
|
||||
cpy := *s
|
||||
return seta(&cpy)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,8 +36,8 @@ func (inner *Inner) getA() int {
|
||||
return inner.A
|
||||
}
|
||||
|
||||
func (inner *Inner) setA(A int) *Inner {
|
||||
inner.A = A
|
||||
func (inner *Inner) setA(a int) *Inner {
|
||||
inner.A = a
|
||||
return inner
|
||||
}
|
||||
|
||||
|
||||
@@ -33,8 +33,8 @@ type Optional[S, A any] struct {
|
||||
// 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 {
|
||||
copy := *s
|
||||
return setter(©, a)
|
||||
cpy := *s
|
||||
return setter(&cpy, a)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,8 +22,8 @@ import (
|
||||
O "github.com/IBM/fp-go/option"
|
||||
)
|
||||
|
||||
// PrismAsOptional converts a prism into an optional
|
||||
func PrismAsOptional[S, A any](sa P.Prism[S, A]) OPT.Optional[S, A] {
|
||||
// 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 {
|
||||
@@ -38,5 +38,5 @@ func PrismSome[A any]() P.Prism[O.Option[A], 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](PrismAsOptional(PrismSome[A]()))(soa)
|
||||
return OPT.Compose[S](AsOptional(PrismSome[A]()))(soa)
|
||||
}
|
||||
|
||||
43
option/monad.go
Normal file
43
option/monad.go
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright (c) 2024 IBM Corp.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES 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/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]
|
||||
func Monad[A, B any]() monad.Monad[A, B, Option[A], Option[B], Option[func(A) B]] {
|
||||
return &optionMonad[A, B]{}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ package option
|
||||
|
||||
import (
|
||||
F "github.com/IBM/fp-go/function"
|
||||
C "github.com/IBM/fp-go/internal/chain"
|
||||
FC "github.com/IBM/fp-go/internal/functor"
|
||||
)
|
||||
|
||||
@@ -95,10 +96,10 @@ func MonadChain[A, B any](fa Option[A], f func(A) Option[B]) Option[B] {
|
||||
}
|
||||
|
||||
func Chain[A, B any](f func(A) Option[B]) func(Option[A]) Option[B] {
|
||||
return F.Bind2nd(MonadChain[A, B], f)
|
||||
return Fold(None[B], f)
|
||||
}
|
||||
|
||||
func MonadChainTo[A, B any](ma Option[A], mb Option[B]) Option[B] {
|
||||
func MonadChainTo[A, B any](_ Option[A], mb Option[B]) Option[B] {
|
||||
return mb
|
||||
}
|
||||
|
||||
@@ -107,13 +108,20 @@ func ChainTo[A, B any](mb Option[B]) func(Option[A]) Option[B] {
|
||||
}
|
||||
|
||||
func MonadChainFirst[A, B any](ma Option[A], f func(A) Option[B]) Option[A] {
|
||||
return MonadChain(ma, func(a A) Option[A] {
|
||||
return MonadMap(f(a), F.Constant1[B](a))
|
||||
})
|
||||
return C.MonadChainFirst(
|
||||
MonadChain[A, A],
|
||||
MonadMap[B, A],
|
||||
ma,
|
||||
f,
|
||||
)
|
||||
}
|
||||
|
||||
func ChainFirst[A, B any](f func(A) Option[B]) func(Option[A]) Option[A] {
|
||||
return F.Bind2nd(MonadChainFirst[A, B], f)
|
||||
return C.ChainFirst(
|
||||
Chain[A, A],
|
||||
Map[B, A],
|
||||
f,
|
||||
)
|
||||
}
|
||||
|
||||
func Flatten[A any](mma Option[Option[A]]) Option[A] {
|
||||
|
||||
@@ -42,11 +42,11 @@ func Monoid[A any]() M.Monoid[Ord[A]] {
|
||||
}
|
||||
|
||||
// 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))
|
||||
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))
|
||||
func MinSemigroup[A any](o Ord[A]) S.Semigroup[A] {
|
||||
return S.MakeSemigroup(Min(o))
|
||||
}
|
||||
|
||||
@@ -132,10 +132,10 @@ func FromStrictCompare[A C.Ordered]() Ord[A] {
|
||||
}
|
||||
|
||||
// Lt tests whether one value is strictly less than another
|
||||
func Lt[A any](O Ord[A]) func(A) func(A) bool {
|
||||
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
|
||||
return o.Compare(first, second) < 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
)
|
||||
|
||||
// SemigroupAny combines predicates via ||
|
||||
func SemigroupAny[A any](predicate func(A) bool) S.Semigroup[func(A) bool] {
|
||||
func SemigroupAny[A any]() S.Semigroup[func(A) bool] {
|
||||
return S.MakeSemigroup(func(first func(A) bool, second func(A) bool) func(A) bool {
|
||||
return F.Pipe1(
|
||||
first,
|
||||
@@ -32,7 +32,7 @@ func SemigroupAny[A any](predicate func(A) bool) S.Semigroup[func(A) bool] {
|
||||
}
|
||||
|
||||
// SemigroupAll combines predicates via &&
|
||||
func SemigroupAll[A any](predicate func(A) bool) S.Semigroup[func(A) bool] {
|
||||
func SemigroupAll[A any]() S.Semigroup[func(A) bool] {
|
||||
return S.MakeSemigroup(func(first func(A) bool, second func(A) bool) func(A) bool {
|
||||
return F.Pipe1(
|
||||
first,
|
||||
@@ -42,17 +42,17 @@ func SemigroupAll[A any](predicate func(A) bool) S.Semigroup[func(A) bool] {
|
||||
}
|
||||
|
||||
// MonoidAny combines predicates via ||
|
||||
func MonoidAny[A any](predicate func(A) bool) S.Semigroup[func(A) bool] {
|
||||
func MonoidAny[A any]() S.Semigroup[func(A) bool] {
|
||||
return M.MakeMonoid(
|
||||
SemigroupAny(predicate).Concat,
|
||||
SemigroupAny[A]().Concat,
|
||||
F.Constant1[A](false),
|
||||
)
|
||||
}
|
||||
|
||||
// MonoidAll combines predicates via &&
|
||||
func MonoidAll[A any](predicate func(A) bool) S.Semigroup[func(A) bool] {
|
||||
func MonoidAll[A any]() S.Semigroup[func(A) bool] {
|
||||
return M.MakeMonoid(
|
||||
SemigroupAll(predicate).Concat,
|
||||
SemigroupAll[A]().Concat,
|
||||
F.Constant1[A](true),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ const emptyDuration = time.Duration(0)
|
||||
|
||||
var ordDuration = ord.FromStrictCompare[time.Duration]()
|
||||
|
||||
// 'RetryPolicy' is a 'Monoid'. You can collapse multiple strategies into one using 'concat'.
|
||||
// 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
|
||||
@@ -98,10 +98,8 @@ func ExponentialBackoff(delay time.Duration) RetryPolicy {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initial, default retry status. Exported mostly to allow user code
|
||||
* to test their handlers and retry policies.
|
||||
*/
|
||||
// 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,
|
||||
|
||||
@@ -33,8 +33,8 @@ import (
|
||||
)
|
||||
|
||||
type PostItem struct {
|
||||
UserId uint `json:"userId"`
|
||||
Id uint `json:"id"`
|
||||
UserID uint `json:"userId"`
|
||||
ID uint `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Body string `json:"body"`
|
||||
}
|
||||
@@ -43,7 +43,7 @@ type CatFact struct {
|
||||
Fact string `json:"fact"`
|
||||
}
|
||||
|
||||
func idxToUrl(idx int) string {
|
||||
func idxToURL(idx int) string {
|
||||
return fmt.Sprintf("https://jsonplaceholder.typicode.com/posts/%d", idx+1)
|
||||
}
|
||||
|
||||
@@ -53,13 +53,13 @@ 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)
|
||||
readSinglePost := H.ReadJSON[PostItem](client)
|
||||
|
||||
// total number of http requests
|
||||
count := 10
|
||||
|
||||
data := F.Pipe3(
|
||||
A.MakeBy(count, idxToUrl),
|
||||
A.MakeBy(count, idxToURL),
|
||||
R.TraverseArray(F.Flow3(
|
||||
H.MakeGetRequest,
|
||||
readSinglePost,
|
||||
@@ -74,13 +74,13 @@ func TestMultipleHttpRequests(t *testing.T) {
|
||||
assert.Equal(t, E.Of[error](count), result())
|
||||
}
|
||||
|
||||
func heterogeneousHttpRequests() R.ReaderIOEither[T.Tuple2[PostItem, CatFact]] {
|
||||
func heterogeneousHTTPRequests() 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)
|
||||
readSinglePost := H.ReadJSON[PostItem](client)
|
||||
// readSingleCatFact sends a GET request and parses the response as [CatFact]
|
||||
readSingleCatFact := H.ReadJson[CatFact](client)
|
||||
readSingleCatFact := H.ReadJSON[CatFact](client)
|
||||
|
||||
return F.Pipe3(
|
||||
T.MakeTuple2("https://jsonplaceholder.typicode.com/posts/1", "https://catfact.ninja/fact"),
|
||||
@@ -97,7 +97,7 @@ func heterogeneousHttpRequests() R.ReaderIOEither[T.Tuple2[PostItem, CatFact]] {
|
||||
// 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()
|
||||
data := heterogeneousHTTPRequests()
|
||||
|
||||
result := data(context.Background())
|
||||
|
||||
@@ -108,6 +108,6 @@ func TestHeterogeneousHttpRequests(t *testing.T) {
|
||||
// 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())()
|
||||
heterogeneousHTTPRequests()(context.Background())()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,26 +67,26 @@ func Example_application() {
|
||||
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")
|
||||
sanitizeJSONP := Replace(regexp.MustCompile(`(?s)^\s*\((.*)\)\s*$`))("$1")
|
||||
// parse jsonP
|
||||
parseJsonP := F.Flow3(
|
||||
sanitizeJsonP,
|
||||
parseJSONP := F.Flow3(
|
||||
sanitizeJSONP,
|
||||
S.ToBytes,
|
||||
J.Unmarshal[FlickrFeed],
|
||||
)
|
||||
// markup
|
||||
img := S.Format[string]("<img src='%s'/>")
|
||||
// lenses
|
||||
mediaUrl := F.Flow2(
|
||||
mediaURL := F.Flow2(
|
||||
FlickrItem.getMedia,
|
||||
FlickrMedia.getLink,
|
||||
)
|
||||
mediaUrls := F.Flow2(
|
||||
mediaURLs := F.Flow2(
|
||||
FlickrFeed.getItems,
|
||||
A.Map(mediaUrl),
|
||||
A.Map(mediaURL),
|
||||
)
|
||||
images := F.Flow2(
|
||||
mediaUrls,
|
||||
mediaURLs,
|
||||
A.Map(img),
|
||||
)
|
||||
|
||||
@@ -97,7 +97,7 @@ func Example_application() {
|
||||
url,
|
||||
H.MakeGetRequest,
|
||||
H.ReadText(client),
|
||||
R.ChainEitherK(parseJsonP),
|
||||
R.ChainEitherK(parseJSONP),
|
||||
R.Map(images),
|
||||
)
|
||||
|
||||
|
||||
@@ -196,8 +196,8 @@ func Example_getAge() {
|
||||
fmt.Println(zoltar(MakeUser("2005-12-12")))
|
||||
|
||||
// Output:
|
||||
// Right[<nil>, float64](6472)
|
||||
// Left[*time.ParseError, float64](parsing time "July 4, 2001" as "2006-01-02": cannot parse "July 4, 2001" as "2006")
|
||||
// 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
|
||||
}
|
||||
|
||||
@@ -235,8 +235,8 @@ func Example_solution08C() {
|
||||
fmt.Println(eitherWelcome(theresa08))
|
||||
|
||||
// Output:
|
||||
// Left[*errors.errorString, string](your account is not active)
|
||||
// Right[<nil>, string](Welcome Theresa)
|
||||
// Left[*errors.errorString](your account is not active)
|
||||
// Right[string](Welcome Theresa)
|
||||
}
|
||||
|
||||
func Example_solution08D() {
|
||||
@@ -269,8 +269,8 @@ func Example_solution08D() {
|
||||
fmt.Println(register(yi08)())
|
||||
|
||||
// Output:
|
||||
// Right[<nil>, string](Gary)
|
||||
// Left[*errors.errorString, <nil>](Your name Yi is larger than 3 characters)
|
||||
// Right[<nil>, string](Welcome Albert)
|
||||
// Left[*errors.errorString, string](Your name Yi is larger than 3 characters)
|
||||
// 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)
|
||||
}
|
||||
|
||||
@@ -181,6 +181,6 @@ func Example_solution09C() {
|
||||
fmt.Println(joinMailingList("notanemail")())
|
||||
|
||||
// Output:
|
||||
// Right[<nil>, string](sleepy@grandpa.net)
|
||||
// Left[*errors.errorString, string](email notanemail is invalid)
|
||||
// Right[string](sleepy@grandpa.net)
|
||||
// Left[*errors.errorString](email notanemail is invalid)
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ import (
|
||||
|
||||
type (
|
||||
PostItem struct {
|
||||
UserId uint `json:"userId"`
|
||||
UserID uint `json:"userId"`
|
||||
Id uint `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Body string `json:"body"`
|
||||
@@ -77,7 +77,7 @@ func (player Player) getName() string {
|
||||
return player.Name
|
||||
}
|
||||
|
||||
func (player Player) getId() int {
|
||||
func (player Player) getID() int {
|
||||
return player.Id
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ func (item PostItem) getTitle() string {
|
||||
return item.Title
|
||||
}
|
||||
|
||||
func idxToUrl(idx int) string {
|
||||
func idxToURL(idx int) string {
|
||||
return fmt.Sprintf("https://jsonplaceholder.typicode.com/posts/%d", idx+1)
|
||||
}
|
||||
|
||||
@@ -101,9 +101,9 @@ func Example_renderPage() {
|
||||
|
||||
// get returns the title of the nth item from the REST service
|
||||
get := F.Flow4(
|
||||
idxToUrl,
|
||||
idxToURL,
|
||||
H.MakeGetRequest,
|
||||
H.ReadJson[PostItem](client),
|
||||
H.ReadJSON[PostItem](client),
|
||||
R.Map(PostItem.getTitle),
|
||||
)
|
||||
|
||||
@@ -117,7 +117,7 @@ func Example_renderPage() {
|
||||
fmt.Println(res(context.TODO())())
|
||||
|
||||
// Output:
|
||||
// Right[<nil>, string](<div>Destinations: [qui est esse], Events: [ea molestias quasi exercitationem repellat qui ipsa sit aut]</div>)
|
||||
// Right[string](<div>Destinations: [qui est esse], Events: [ea molestias quasi exercitationem repellat qui ipsa sit aut]</div>)
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ import (
|
||||
S "github.com/IBM/fp-go/string"
|
||||
)
|
||||
|
||||
func findUserById(id int) IOE.IOEither[error, Chapter08User] {
|
||||
func findUserByID(id int) IOE.IOEither[error, Chapter08User] {
|
||||
switch id {
|
||||
case 1:
|
||||
return IOE.Of[error](albert08)
|
||||
@@ -52,21 +52,21 @@ func Example_solution11A() {
|
||||
}
|
||||
|
||||
func Example_solution11B() {
|
||||
findByNameId := F.Flow2(
|
||||
findUserById,
|
||||
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)())
|
||||
fmt.Println(findByNameID(1)())
|
||||
fmt.Println(findByNameID(2)())
|
||||
fmt.Println(findByNameID(3)())
|
||||
fmt.Println(findByNameID(4)())
|
||||
|
||||
// Output:
|
||||
// Right[<nil>, string](Albert)
|
||||
// Right[<nil>, string](Gary)
|
||||
// Right[<nil>, string](Theresa)
|
||||
// Left[*errors.errorString, string](user 4 not found)
|
||||
// Right[string](Albert)
|
||||
// Right[string](Gary)
|
||||
// Right[string](Theresa)
|
||||
// Left[*errors.errorString](user 4 not found)
|
||||
}
|
||||
|
||||
func Example_solution11C() {
|
||||
|
||||
@@ -42,7 +42,7 @@ var (
|
||||
}
|
||||
|
||||
// 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")))
|
||||
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) IOE.IOEither[error, string] {
|
||||
@@ -60,7 +60,7 @@ func Example_solution12A() {
|
||||
fmt.Println(getJsons(routes)())
|
||||
|
||||
// Output:
|
||||
// Right[<nil>, map[string]string](map[/:json for / /about:json for /about])
|
||||
// Right[map[string]string](map[/:json for / /about:json for /about])
|
||||
}
|
||||
|
||||
func Example_solution12B() {
|
||||
@@ -74,8 +74,8 @@ func Example_solution12B() {
|
||||
fmt.Println(startGame(A.From(playerAlbert, Player{Id: 4})))
|
||||
|
||||
// Output:
|
||||
// Right[<nil>, string](Game started)
|
||||
// Left[*errors.errorString, string](player 4 must have a name)
|
||||
// Right[string](Game started)
|
||||
// Left[*errors.errorString](player 4 must have a name)
|
||||
}
|
||||
|
||||
func Example_solution12C() {
|
||||
@@ -94,5 +94,5 @@ func Example_solution12C() {
|
||||
fmt.Println(readFirst())
|
||||
|
||||
// Output:
|
||||
// Right[<nil>, option.Option[string]](Some[string](content of file1 (utf-8)))
|
||||
// Right[option.Option[string]](Some[string](content of file1 (utf-8)))
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ import (
|
||||
)
|
||||
|
||||
type PostItem struct {
|
||||
UserId uint `json:"userId"`
|
||||
UserID uint `json:"userId"`
|
||||
Id uint `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Body string `json:"body"`
|
||||
@@ -41,13 +41,13 @@ type CatFact struct {
|
||||
Fact string `json:"fact"`
|
||||
}
|
||||
|
||||
func heterogeneousHttpRequests(count int) R.ReaderIOEither[[]T.Tuple2[PostItem, CatFact]] {
|
||||
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)
|
||||
readSinglePost := H.ReadJSON[PostItem](client)
|
||||
// readSingleCatFact sends a GET request and parses the response as [CatFact]
|
||||
readSingleCatFact := H.ReadJson[CatFact](client)
|
||||
readSingleCatFact := H.ReadJSON[CatFact](client)
|
||||
|
||||
single := F.Pipe2(
|
||||
T.MakeTuple2("https://jsonplaceholder.typicode.com/posts/1", "https://catfact.ninja/fact"),
|
||||
@@ -64,7 +64,7 @@ func heterogeneousHttpRequests(count int) R.ReaderIOEither[[]T.Tuple2[PostItem,
|
||||
)
|
||||
}
|
||||
|
||||
func heterogeneousHttpRequestsIdiomatic(count int) ([]T.Tuple2[PostItem, CatFact], error) {
|
||||
func heterogeneousHTTPRequestsIdiomatic(count int) ([]T.Tuple2[PostItem, CatFact], error) {
|
||||
// prepare the http client
|
||||
var result []T.Tuple2[PostItem, CatFact]
|
||||
|
||||
@@ -109,13 +109,13 @@ func BenchmarkHeterogeneousHttpRequests(b *testing.B) {
|
||||
|
||||
b.Run("functional", func(b *testing.B) {
|
||||
for n := 0; n < b.N; n++ {
|
||||
benchResults = heterogeneousHttpRequests(count)(context.Background())()
|
||||
benchResults = heterogeneousHTTPRequests(count)(context.Background())()
|
||||
}
|
||||
})
|
||||
|
||||
b.Run("idiomatic", func(b *testing.B) {
|
||||
for n := 0; n < b.N; n++ {
|
||||
benchResults, _ = heterogeneousHttpRequestsIdiomatic(count)
|
||||
benchResults, _ = heterogeneousHTTPRequestsIdiomatic(count)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ func Example_either_monad() {
|
||||
fmt.Println(makeUrl("8080"))
|
||||
|
||||
// Output:
|
||||
// Right[<nil>, string](http://localhost:8080)
|
||||
// Right[string](http://localhost:8080)
|
||||
}
|
||||
|
||||
func Example_either_idiomatic() {
|
||||
|
||||
@@ -64,7 +64,7 @@ func Example_io_flow() {
|
||||
fmt.Println(result())
|
||||
|
||||
// Output:
|
||||
// Right[<nil>, string](Text: Some data, Number: 10)
|
||||
// Right[string](Text: Some data, Number: 10)
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -42,10 +42,10 @@ func Reverse[A any](m Semigroup[A]) Semigroup[A] {
|
||||
}
|
||||
|
||||
// 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] {
|
||||
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))
|
||||
return s.Concat(f(a), g(a))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -35,6 +35,12 @@ var (
|
||||
|
||||
// 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 {
|
||||
@@ -61,16 +67,6 @@ func Size(s string) int {
|
||||
return len(s)
|
||||
}
|
||||
|
||||
// Includes returns a predicate that tests for the existence of the search string
|
||||
func Includes(searchString string) func(string) bool {
|
||||
return F.Bind2nd(strings.Contains, searchString)
|
||||
}
|
||||
|
||||
// Equals returns a predicate that tests if a string is equal
|
||||
func Equals(other string) func(string) bool {
|
||||
return F.Bind2nd(Eq, other)
|
||||
}
|
||||
|
||||
// Format applies a format string to an arbitrary value
|
||||
func Format[T any](format string) func(t T) string {
|
||||
return func(t T) string {
|
||||
|
||||
@@ -36,3 +36,15 @@ func TestJoin(t *testing.T) {
|
||||
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"))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user