mirror of
https://github.com/IBM/fp-go.git
synced 2025-08-28 19:49:07 +02:00
Compare commits
20 Commits
cleue-most
...
cleue-add-
Author | SHA1 | Date | |
---|---|---|---|
|
ff1b6faf84 | ||
|
38120764e7 | ||
|
a83c2aec49 | ||
|
03debd37c8 | ||
|
4f04344cda | ||
|
cf1c886f48 | ||
|
3e0eb99b88 | ||
|
cb15a3d9fc | ||
|
16535605f5 | ||
|
53f4e5ebd7 | ||
|
fb91fd5dc8 | ||
|
3ccafb5302 | ||
|
52b71ef4f3 | ||
|
5d77d5bb3d | ||
|
8ba8f852fa | ||
|
29d9882d2a | ||
|
f80ca31e14 | ||
|
8692078972 | ||
|
12a4f6801c | ||
|
8650a8a600 |
@@ -187,6 +187,14 @@ func IsEmpty[AS ~[]A, A any](as AS) bool {
|
||||
return array.IsEmpty(as)
|
||||
}
|
||||
|
||||
func IsNil[GA ~[]A, A any](as GA) bool {
|
||||
return array.IsNil(as)
|
||||
}
|
||||
|
||||
func IsNonNil[GA ~[]A, A any](as GA) bool {
|
||||
return array.IsNonNil(as)
|
||||
}
|
||||
|
||||
func Match[AS ~[]A, A, B any](onEmpty func() B, onNonEmpty func(AS) B) func(AS) B {
|
||||
return func(as AS) B {
|
||||
if IsEmpty(as) {
|
||||
|
@@ -21,6 +21,13 @@ import (
|
||||
"testing"
|
||||
|
||||
H "net/http"
|
||||
|
||||
R "github.com/IBM/fp-go/context/readerioeither"
|
||||
E "github.com/IBM/fp-go/either"
|
||||
"github.com/IBM/fp-go/errors"
|
||||
F "github.com/IBM/fp-go/function"
|
||||
IOE "github.com/IBM/fp-go/ioeither"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type PostItem struct {
|
||||
@@ -30,6 +37,47 @@ type PostItem struct {
|
||||
Body string `json:"body"`
|
||||
}
|
||||
|
||||
func getTitle(item PostItem) string {
|
||||
return item.Title
|
||||
}
|
||||
|
||||
type simpleRequestBuilder struct {
|
||||
method string
|
||||
url string
|
||||
headers H.Header
|
||||
}
|
||||
|
||||
func requestBuilder() simpleRequestBuilder {
|
||||
return simpleRequestBuilder{method: "GET"}
|
||||
}
|
||||
|
||||
func (b simpleRequestBuilder) WithURL(url string) simpleRequestBuilder {
|
||||
b.url = url
|
||||
return b
|
||||
}
|
||||
|
||||
func (b simpleRequestBuilder) WithHeader(key, value string) simpleRequestBuilder {
|
||||
if b.headers == nil {
|
||||
b.headers = make(H.Header)
|
||||
} else {
|
||||
b.headers = b.headers.Clone()
|
||||
}
|
||||
b.headers.Set(key, value)
|
||||
return b
|
||||
}
|
||||
|
||||
func (b simpleRequestBuilder) Build() R.ReaderIOEither[*H.Request] {
|
||||
return func(ctx context.Context) IOE.IOEither[error, *H.Request] {
|
||||
return IOE.TryCatchError(func() (*H.Request, error) {
|
||||
req, err := H.NewRequestWithContext(ctx, b.method, b.url, nil)
|
||||
if err == nil {
|
||||
req.Header = b.headers
|
||||
}
|
||||
return req, err
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSendSingleRequest(t *testing.T) {
|
||||
|
||||
client := MakeClient(H.DefaultClient)
|
||||
@@ -40,7 +88,70 @@ func TestSendSingleRequest(t *testing.T) {
|
||||
|
||||
resp1 := readItem(req1)
|
||||
|
||||
resE := resp1(context.Background())()
|
||||
resE := resp1(context.TODO())()
|
||||
|
||||
fmt.Println(resE)
|
||||
}
|
||||
|
||||
// setHeaderUnsafe updates a header value in a request object by mutating the request object
|
||||
func setHeaderUnsafe(key, value string) func(*H.Request) *H.Request {
|
||||
return func(req *H.Request) *H.Request {
|
||||
req.Header.Set(key, value)
|
||||
return req
|
||||
}
|
||||
}
|
||||
|
||||
func TestSendSingleRequestWithHeaderUnsafe(t *testing.T) {
|
||||
|
||||
client := MakeClient(H.DefaultClient)
|
||||
|
||||
// this is not safe from a puristic perspective, because the map call mutates the request object
|
||||
req1 := F.Pipe2(
|
||||
"https://jsonplaceholder.typicode.com/posts/1",
|
||||
MakeGetRequest,
|
||||
R.Map(setHeaderUnsafe("Content-Type", "text/html")),
|
||||
)
|
||||
|
||||
readItem := ReadJson[PostItem](client)
|
||||
|
||||
resp1 := F.Pipe2(
|
||||
req1,
|
||||
readItem,
|
||||
R.Map(getTitle),
|
||||
)
|
||||
|
||||
res := F.Pipe1(
|
||||
resp1(context.TODO())(),
|
||||
E.GetOrElse(errors.ToString),
|
||||
)
|
||||
|
||||
assert.Equal(t, "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", res)
|
||||
}
|
||||
|
||||
func TestSendSingleRequestWithHeaderSafe(t *testing.T) {
|
||||
|
||||
client := MakeClient(H.DefaultClient)
|
||||
|
||||
// the request builder assembles config values to construct
|
||||
// the final http request. Each `With` step creates a copy of the settings
|
||||
// so the flow is pure
|
||||
request := requestBuilder().
|
||||
WithURL("https://jsonplaceholder.typicode.com/posts/1").
|
||||
WithHeader("Content-Type", "text/html").
|
||||
Build()
|
||||
|
||||
readItem := ReadJson[PostItem](client)
|
||||
|
||||
response := F.Pipe2(
|
||||
request,
|
||||
readItem,
|
||||
R.Map(getTitle),
|
||||
)
|
||||
|
||||
res := F.Pipe1(
|
||||
response(context.TODO())(),
|
||||
E.GetOrElse(errors.ToString),
|
||||
)
|
||||
|
||||
assert.Equal(t, "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", res)
|
||||
}
|
||||
|
@@ -44,3 +44,15 @@ func SequenceArrayG[GA ~[]A, GOA ~[]Either[E, A], E, A any](ma GOA) Either[E, GA
|
||||
func SequenceArray[E, A any](ma []Either[E, A]) Either[E, []A] {
|
||||
return SequenceArrayG[[]A](ma)
|
||||
}
|
||||
|
||||
// CompactArrayG discards the none values and keeps the some values
|
||||
func CompactArrayG[A1 ~[]Either[E, A], A2 ~[]A, E, A any](fa A1) A2 {
|
||||
return RA.Reduce(fa, func(out A2, value Either[E, A]) A2 {
|
||||
return MonadFold(value, F.Constant1[E](out), F.Bind1st(RA.Append[A2, A], out))
|
||||
}, make(A2, len(fa)))
|
||||
}
|
||||
|
||||
// CompactArray discards the none values and keeps the some values
|
||||
func CompactArray[E, A any](fa []Either[E, A]) []A {
|
||||
return CompactArrayG[[]Either[E, A], []A](fa)
|
||||
}
|
||||
|
@@ -91,11 +91,11 @@ func MonadChainTo[E, A, B any](ma Either[E, A], mb Either[E, B]) Either[E, B] {
|
||||
return mb
|
||||
}
|
||||
|
||||
func MonadChainOptionK[E, A, B any](onNone func() E, ma Either[E, A], f func(A) O.Option[B]) Either[E, B] {
|
||||
func MonadChainOptionK[A, B, E any](onNone func() E, ma Either[E, A], f func(A) O.Option[B]) Either[E, B] {
|
||||
return MonadChain(ma, F.Flow2(f, FromOption[E, B](onNone)))
|
||||
}
|
||||
|
||||
func ChainOptionK[E, A, B any](onNone func() E) func(func(A) O.Option[B]) func(Either[E, A]) Either[E, B] {
|
||||
func ChainOptionK[A, B, E any](onNone func() E) func(func(A) O.Option[B]) func(Either[E, A]) Either[E, B] {
|
||||
from := FromOption[E, B](onNone)
|
||||
return func(f func(A) O.Option[B]) func(Either[E, A]) Either[E, B] {
|
||||
return Chain(F.Flow2(f, from))
|
||||
@@ -209,7 +209,7 @@ func OrElse[E, A any](onLeft func(e E) Either[E, A]) func(Either[E, A]) Either[E
|
||||
return Fold(onLeft, Of[E, A])
|
||||
}
|
||||
|
||||
func ToType[E, A any](onError func(any) E) func(any) Either[E, A] {
|
||||
func ToType[A, E any](onError func(any) E) func(any) Either[E, A] {
|
||||
return func(value any) Either[E, A] {
|
||||
return F.Pipe2(
|
||||
value,
|
||||
|
@@ -100,7 +100,7 @@ func TestChainFirst(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestChainOptionK(t *testing.T) {
|
||||
f := ChainOptionK[string, int, int](F.Constant("a"))(func(n int) O.Option[int] {
|
||||
f := ChainOptionK[int, int](F.Constant("a"))(func(n int) O.Option[int] {
|
||||
if n > 0 {
|
||||
return O.Some(n)
|
||||
}
|
||||
|
@@ -43,3 +43,24 @@ func SequenceRecordG[GA ~map[K]A, GOA ~map[K]Either[E, A], K comparable, E, A an
|
||||
func SequenceRecord[K comparable, E, A any](ma map[K]Either[E, A]) Either[E, map[K]A] {
|
||||
return SequenceRecordG[map[K]A](ma)
|
||||
}
|
||||
|
||||
func upsertAtReadWrite[M ~map[K]V, K comparable, V any](r M, k K, v V) M {
|
||||
r[k] = v
|
||||
return r
|
||||
}
|
||||
|
||||
// CompactRecordG discards the noe values and keeps the some values
|
||||
func CompactRecordG[M1 ~map[K]Either[E, A], M2 ~map[K]A, K comparable, E, A any](m M1) M2 {
|
||||
out := make(M2)
|
||||
onLeft := F.Constant1[E](out)
|
||||
return RR.ReduceWithIndex(m, func(key K, _ M2, value Either[E, A]) M2 {
|
||||
return MonadFold(value, onLeft, func(v A) M2 {
|
||||
return upsertAtReadWrite(out, key, v)
|
||||
})
|
||||
}, out)
|
||||
}
|
||||
|
||||
// CompactRecord discards all none values and keeps the somes
|
||||
func CompactRecord[K comparable, E, A any](m map[K]Either[E, A]) map[K]A {
|
||||
return CompactRecordG[map[K]Either[E, A], map[K]A](m)
|
||||
}
|
||||
|
37
either/record_test.go
Normal file
37
either/record_test.go
Normal file
@@ -0,0 +1,37 @@
|
||||
// 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 either
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCompactRecord(t *testing.T) {
|
||||
// make the map
|
||||
m := make(map[string]Either[string, int])
|
||||
m["foo"] = Left[int]("error")
|
||||
m["bar"] = Right[string](1)
|
||||
// compact it
|
||||
m1 := CompactRecord(m)
|
||||
// check expected
|
||||
exp := map[string]int{
|
||||
"bar": 1,
|
||||
}
|
||||
|
||||
assert.Equal(t, exp, m1)
|
||||
}
|
@@ -27,13 +27,13 @@ HKTRB = HKT<Either[B]>
|
||||
HKTA = HKT<A>
|
||||
HKTB = HKT<B>
|
||||
*/
|
||||
func traverse[E, A, B, HKTA, HKTB, HKTRB any](
|
||||
_of func(Either[E, B]) HKTRB,
|
||||
_map func(HKTB, func(B) Either[E, B]) HKTRB,
|
||||
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], _of)
|
||||
right := F.Bind2nd(_map, Right[E, B])
|
||||
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,
|
||||
@@ -43,27 +43,21 @@ func traverse[E, A, B, HKTA, HKTB, HKTRB any](
|
||||
}
|
||||
}
|
||||
|
||||
func Traverse[E, A, B, HKTA, HKTB, HKTRB any](
|
||||
_of func(Either[E, B]) HKTRB,
|
||||
_map func(HKTB, func(B) Either[E, B]) HKTRB,
|
||||
// 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, HKTA](_of, _map)
|
||||
delegate := traverse[E, A, B](mof, mmap)
|
||||
return func(f func(A) HKTB) func(Either[E, A]) HKTRB {
|
||||
return F.Bind2nd(delegate, f)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
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
|
||||
|
||||
HKTRA = HKT<Either[A]>
|
||||
HKTA = HKT<A>
|
||||
HKTB = HKT<B>
|
||||
*/
|
||||
// Sequence converts an [Either] of some higher kinded type into the higher kinded type of an [Either]
|
||||
func Sequence[E, A, HKTA, HKTRA any](
|
||||
_of func(Either[E, A]) HKTRA,
|
||||
_map func(HKTA, func(A) Either[E, A]) HKTRA,
|
||||
mof func(Either[E, A]) HKTRA,
|
||||
mmap func(func(A) Either[E, A]) func(HKTA) HKTRA,
|
||||
) func(Either[E, HKTA]) HKTRA {
|
||||
return Fold(F.Flow2(Left[A, E], _of), F.Bind2nd(_map, Right[E, A]))
|
||||
return Fold(F.Flow2(Left[A, E], mof), mmap(Right[E, A]))
|
||||
}
|
||||
|
@@ -30,9 +30,9 @@ func TestTraverse(t *testing.T) {
|
||||
}
|
||||
return O.None[int]()
|
||||
}
|
||||
trav := Traverse[string, int, int, O.Option[Either[string, int]]](
|
||||
trav := Traverse[int](
|
||||
O.Of[Either[string, int]],
|
||||
O.MonadMap[int, Either[string, int]],
|
||||
O.Map[int, Either[string, int]],
|
||||
)(f)
|
||||
|
||||
assert.Equal(t, O.Of(Left[int]("a")), F.Pipe1(Left[int]("a"), trav))
|
||||
@@ -44,7 +44,7 @@ func TestSequence(t *testing.T) {
|
||||
|
||||
seq := Sequence(
|
||||
O.Of[Either[string, int]],
|
||||
O.MonadMap[int, Either[string, int]],
|
||||
O.Map[int, Either[string, int]],
|
||||
)
|
||||
|
||||
assert.Equal(t, O.Of(Right[string](1)), seq(Right[string](O.Of(1))))
|
||||
|
@@ -19,12 +19,12 @@ import (
|
||||
G "github.com/IBM/fp-go/function/generic"
|
||||
)
|
||||
|
||||
// Cache converts a unary function into a unary function that caches the value depending on the parameter
|
||||
func Cache[K comparable, T any](f func(K) T) func(K) T {
|
||||
return G.Cache(f)
|
||||
// Memoize converts a unary function into a unary function that caches the value depending on the parameter
|
||||
func Memoize[K comparable, T any](f func(K) T) func(K) T {
|
||||
return G.Memoize(f)
|
||||
}
|
||||
|
||||
// ContramapCache converts a unary function into a unary function that caches the value depending on the parameter
|
||||
func ContramapCache[A any, K comparable, T any](kf func(A) K) func(func(A) T) func(A) T {
|
||||
return G.ContramapCache[func(A) T](kf)
|
||||
// ContramapMemoize converts a unary function into a unary function that caches the value depending on the parameter
|
||||
func ContramapMemoize[A any, K comparable, T any](kf func(A) K) func(func(A) T) func(A) T {
|
||||
return G.ContramapMemoize[func(A) T](kf)
|
||||
}
|
||||
|
@@ -29,7 +29,7 @@ func TestCache(t *testing.T) {
|
||||
return n
|
||||
}
|
||||
|
||||
cached := Cache(withSideEffect)
|
||||
cached := Memoize(withSideEffect)
|
||||
|
||||
assert.Equal(t, 0, count)
|
||||
|
||||
|
@@ -21,13 +21,13 @@ import (
|
||||
L "github.com/IBM/fp-go/internal/lazy"
|
||||
)
|
||||
|
||||
// Cache converts a unary function into a unary function that caches the value depending on the parameter
|
||||
func Cache[F ~func(K) T, K comparable, T any](f F) F {
|
||||
return ContramapCache[F](func(k K) K { return k })(f)
|
||||
// Memoize converts a unary function into a unary function that caches the value depending on the parameter
|
||||
func Memoize[F ~func(K) T, K comparable, T any](f F) F {
|
||||
return ContramapMemoize[F](func(k K) K { return k })(f)
|
||||
}
|
||||
|
||||
// ContramapCache converts a unary function into a unary function that caches the value depending on the parameter
|
||||
func ContramapCache[F ~func(A) T, KF func(A) K, A any, K comparable, T any](kf KF) func(F) F {
|
||||
// ContramapMemoize converts a unary function into a unary function that caches the value depending on the parameter
|
||||
func ContramapMemoize[F ~func(A) T, KF func(A) K, A any, K comparable, T any](kf KF) func(F) F {
|
||||
return CacheCallback[F](kf, getOrCreate[K, T]())
|
||||
}
|
||||
|
||||
|
@@ -24,3 +24,9 @@ func MagmaSub[A Number]() M.Magma[A] {
|
||||
return first - second
|
||||
})
|
||||
}
|
||||
|
||||
func MagmaDiv[A Number]() M.Magma[A] {
|
||||
return M.MakeMagma(func(first A, second A) A {
|
||||
return first / second
|
||||
})
|
||||
}
|
||||
|
@@ -19,6 +19,7 @@ import (
|
||||
M "github.com/IBM/fp-go/monoid"
|
||||
)
|
||||
|
||||
// MonoidSum is the [Monoid] that adds elements with a zero empty element
|
||||
func MonoidSum[A Number]() M.Monoid[A] {
|
||||
s := SemigroupSum[A]()
|
||||
return M.MakeMonoid(
|
||||
@@ -26,3 +27,12 @@ func MonoidSum[A Number]() M.Monoid[A] {
|
||||
0,
|
||||
)
|
||||
}
|
||||
|
||||
// MonoidProduct is the [Monoid] that multiplies elements with a one empty element
|
||||
func MonoidProduct[A Number]() M.Monoid[A] {
|
||||
s := SemigroupProduct[A]()
|
||||
return M.MakeMonoid(
|
||||
s.Concat,
|
||||
1,
|
||||
)
|
||||
}
|
||||
|
@@ -24,3 +24,9 @@ func SemigroupSum[A Number]() S.Semigroup[A] {
|
||||
return first + second
|
||||
})
|
||||
}
|
||||
|
||||
func SemigroupProduct[A Number]() S.Semigroup[A] {
|
||||
return S.MakeSemigroup(func(first A, second A) A {
|
||||
return first * second
|
||||
})
|
||||
}
|
||||
|
@@ -24,16 +24,30 @@ type Number interface {
|
||||
}
|
||||
|
||||
// Add is a curried function used to add two numbers
|
||||
func Add[T Number](left T) func(T) T {
|
||||
return func(right T) T {
|
||||
func Add[T Number](right T) func(T) T {
|
||||
return func(left T) T {
|
||||
return left + right
|
||||
}
|
||||
}
|
||||
|
||||
// Mul is a curried function used to add two numbers
|
||||
func Mul[T Number](coeff T) func(T) T {
|
||||
return func(value T) T {
|
||||
return coeff * value
|
||||
// Sub is a curried function used to subtract two numbers
|
||||
func Sub[T Number](right T) func(T) T {
|
||||
return func(left T) T {
|
||||
return left - right
|
||||
}
|
||||
}
|
||||
|
||||
// Mul is a curried function used to multiply two numbers
|
||||
func Mul[T Number](right T) func(T) T {
|
||||
return func(left T) T {
|
||||
return left * right
|
||||
}
|
||||
}
|
||||
|
||||
// Div is a curried function used to divide two numbers
|
||||
func Div[T Number](right T) func(T) T {
|
||||
return func(left T) T {
|
||||
return left / right
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -67,7 +67,7 @@ func Modify[S, A any](f func(A) A) func(Iso[S, A]) func(S) S {
|
||||
}
|
||||
|
||||
// Wrap wraps the value
|
||||
func Unwrap[S, A any](s S) func(Iso[S, A]) A {
|
||||
func Unwrap[A, S any](s S) func(Iso[S, A]) A {
|
||||
return func(sa Iso[S, A]) A {
|
||||
return sa.Get(s)
|
||||
}
|
||||
@@ -81,8 +81,8 @@ func Wrap[S, A any](a A) func(Iso[S, A]) S {
|
||||
}
|
||||
|
||||
// From wraps the value
|
||||
func To[S, A any](s S) func(Iso[S, A]) A {
|
||||
return Unwrap[S, A](s)
|
||||
func To[A, S any](s S) func(Iso[S, A]) A {
|
||||
return Unwrap[A, S](s)
|
||||
}
|
||||
|
||||
// To unwraps the value
|
||||
|
@@ -121,7 +121,7 @@ func Compose[S, A, B any](ab Lens[A, B]) func(Lens[S, A]) Lens[S, B] {
|
||||
// the getter returns an `Option[B]` because the container `A` could already be an option
|
||||
// if the setter is invoked with `Some[B]` then the value of `B` will be set, potentially on a default value of `A` if `A` did not exist
|
||||
// if the setter is invoked with `None[B]` then the container `A` is reset to `None[A]` because this is the only way to remove `B`
|
||||
func ComposeOption[S, A, B any](defaultA A) func(ab Lens[A, B]) func(Lens[S, O.Option[A]]) Lens[S, O.Option[B]] {
|
||||
func ComposeOption[S, B, A any](defaultA A) func(ab Lens[A, B]) func(Lens[S, O.Option[A]]) Lens[S, O.Option[B]] {
|
||||
defa := F.Constant(defaultA)
|
||||
return func(ab Lens[A, B]) func(Lens[S, O.Option[A]]) Lens[S, O.Option[B]] {
|
||||
foldab := O.Fold(O.None[B], F.Flow2(ab.Get, O.Some[B]))
|
||||
@@ -172,7 +172,7 @@ func ComposeOption[S, A, B any](defaultA A) func(ab Lens[A, B]) func(Lens[S, O.O
|
||||
// if the setter is called with `Some[B]` and `A` does not exist, the default of 'A' is updated with `B`
|
||||
// if the setter is called with `None[B]` and `A` does not exist this is the identity operation on 'S'
|
||||
// if the setter is called with `None[B]` and `A` does exist, 'B' is removed from 'A'
|
||||
func ComposeOptions[S, A, B any](defaultA A) func(ab Lens[A, O.Option[B]]) func(Lens[S, O.Option[A]]) Lens[S, O.Option[B]] {
|
||||
func ComposeOptions[S, B, A any](defaultA A) func(ab Lens[A, O.Option[B]]) func(Lens[S, O.Option[A]]) Lens[S, O.Option[B]] {
|
||||
defa := F.Constant(defaultA)
|
||||
noops := F.Constant(F.Identity[S])
|
||||
noneb := O.None[B]()
|
||||
|
@@ -209,7 +209,7 @@ func TestComposeOption(t *testing.T) {
|
||||
// compose lenses
|
||||
lens := F.Pipe1(
|
||||
inner,
|
||||
ComposeOption[Outer, *Inner, int](defaultInner)(value),
|
||||
ComposeOption[Outer, int](defaultInner)(value),
|
||||
)
|
||||
outer1 := Outer{inner: &Inner{Value: 1, Foo: "a"}}
|
||||
// the checks
|
||||
@@ -235,7 +235,7 @@ func TestComposeOptions(t *testing.T) {
|
||||
// compose lenses
|
||||
lens := F.Pipe1(
|
||||
inner,
|
||||
ComposeOptions[OuterOpt, *InnerOpt, *int](defaultInner)(value),
|
||||
ComposeOptions[OuterOpt, *int](defaultInner)(value),
|
||||
)
|
||||
// additional settings
|
||||
defaultValue2 := 2
|
||||
|
@@ -24,7 +24,7 @@ import (
|
||||
)
|
||||
|
||||
// AtRecord returns a lens that focusses on a value in a record
|
||||
func AtRecord[M ~map[K]V, K comparable, V any](key K) L.Lens[M, O.Option[V]] {
|
||||
func AtRecord[M ~map[K]V, V any, K comparable](key K) L.Lens[M, O.Option[V]] {
|
||||
addKey := F.Bind1of2(RR.UpsertAt[M, K, V])(key)
|
||||
delKey := F.Bind1of1(RR.DeleteAt[M, K, V])(key)
|
||||
fold := O.Fold(
|
||||
@@ -44,6 +44,6 @@ func AtRecord[M ~map[K]V, K comparable, V any](key K) L.Lens[M, O.Option[V]] {
|
||||
}
|
||||
|
||||
// AtKey returns a `Lens` focused on a required key of a `ReadonlyRecord`
|
||||
func AtKey[M ~map[K]V, S any, K comparable, V any](key K) func(sa L.Lens[S, M]) L.Lens[S, O.Option[V]] {
|
||||
func AtKey[M ~map[K]V, S any, V any, K comparable](key K) func(sa L.Lens[S, M]) L.Lens[S, O.Option[V]] {
|
||||
return L.Compose[S](AtRecord[M](key))
|
||||
}
|
||||
|
@@ -22,11 +22,11 @@ import (
|
||||
)
|
||||
|
||||
// AtRecord returns a lens that focusses on a value in a record
|
||||
func AtRecord[K comparable, V any](key K) L.Lens[map[K]V, O.Option[V]] {
|
||||
func AtRecord[V any, K comparable](key K) L.Lens[map[K]V, O.Option[V]] {
|
||||
return G.AtRecord[map[K]V](key)
|
||||
}
|
||||
|
||||
// AtKey returns a `Lens` focused on a required key of a `ReadonlyRecord`
|
||||
func AtKey[S any, K comparable, V any](key K) func(sa L.Lens[S, map[K]V]) L.Lens[S, O.Option[V]] {
|
||||
func AtKey[S any, V any, K comparable](key K) func(sa L.Lens[S, map[K]V]) L.Lens[S, O.Option[V]] {
|
||||
return G.AtKey[map[K]V, S](key)
|
||||
}
|
||||
|
@@ -31,7 +31,7 @@ type (
|
||||
func TestAtKey(t *testing.T) {
|
||||
sa := F.Pipe1(
|
||||
L.Id[S](),
|
||||
AtKey[S, string, int]("a"),
|
||||
AtKey[S, int]("a"),
|
||||
)
|
||||
|
||||
assert.Equal(t, O.Some(1), sa.Get(S{"a": 1}))
|
||||
|
@@ -198,7 +198,7 @@ func TestOuterLensLaws(t *testing.T) {
|
||||
eqValue := EQT.Eq[int]()
|
||||
eqOptValue := O.Eq(eqValue)
|
||||
// lens to access a value from outer
|
||||
valueFromOuter := L.ComposeOption[*Outer, *Inner, int](&defaultInner)(valueLens)(outerLens)
|
||||
valueFromOuter := L.ComposeOption[*Outer, int](&defaultInner)(valueLens)(outerLens)
|
||||
// try to access the value, this should get an option
|
||||
assert.True(t, eqOptValue.Equals(valueFromOuter.Get(&emptyOuter), O.None[int]()))
|
||||
// update the object
|
||||
@@ -234,7 +234,7 @@ func TestOuterOptLensLaws(t *testing.T) {
|
||||
valueFromOuter := F.Pipe3(
|
||||
valueOptLens,
|
||||
LI.Compose[*InnerOpt](intIso),
|
||||
L.ComposeOptions[*OuterOpt, *InnerOpt, int](&defaultInnerOpt),
|
||||
L.ComposeOptions[*OuterOpt, int](&defaultInnerOpt),
|
||||
I.Ap[L.Lens[*OuterOpt, O.Option[int]]](outerOptLens),
|
||||
)
|
||||
|
||||
|
@@ -44,3 +44,15 @@ func SequenceArrayG[GA ~[]A, GOA ~[]Option[A], A any](ma GOA) Option[GA] {
|
||||
func SequenceArray[A any](ma []Option[A]) Option[[]A] {
|
||||
return SequenceArrayG[[]A](ma)
|
||||
}
|
||||
|
||||
// CompactArrayG discards the none values and keeps the some values
|
||||
func CompactArrayG[A1 ~[]Option[A], A2 ~[]A, A any](fa A1) A2 {
|
||||
return RA.Reduce(fa, func(out A2, value Option[A]) A2 {
|
||||
return MonadFold(value, F.Constant(out), F.Bind1st(RA.Append[A2, A], out))
|
||||
}, make(A2, len(fa)))
|
||||
}
|
||||
|
||||
// CompactArray discards the none values and keeps the some values
|
||||
func CompactArray[A any](fa []Option[A]) []A {
|
||||
return CompactArrayG[[]Option[A], []A](fa)
|
||||
}
|
||||
|
@@ -44,3 +44,21 @@ func SequenceRecordG[GA ~map[K]A, GOA ~map[K]Option[A], K comparable, A any](ma
|
||||
func SequenceRecord[K comparable, A any](ma map[K]Option[A]) Option[map[K]A] {
|
||||
return SequenceRecordG[map[K]A](ma)
|
||||
}
|
||||
|
||||
func upsertAtReadWrite[M ~map[K]V, K comparable, V any](r M, k K, v V) M {
|
||||
r[k] = v
|
||||
return r
|
||||
}
|
||||
|
||||
// CompactRecordG discards the noe values and keeps the some values
|
||||
func CompactRecordG[M1 ~map[K]Option[A], M2 ~map[K]A, K comparable, A any](m M1) M2 {
|
||||
bnd := F.Bind12of3(upsertAtReadWrite[M2])
|
||||
return RR.ReduceWithIndex(m, func(key K, m M2, value Option[A]) M2 {
|
||||
return MonadFold(value, F.Constant(m), bnd(m, key))
|
||||
}, make(M2))
|
||||
}
|
||||
|
||||
// CompactRecord discards the noe values and keeps the some values
|
||||
func CompactRecord[K comparable, A any](m map[K]Option[A]) map[K]A {
|
||||
return CompactRecordG[map[K]Option[A], map[K]A](m)
|
||||
}
|
||||
|
@@ -30,3 +30,18 @@ func TestSequenceRecord(t *testing.T) {
|
||||
"b": Of("B"),
|
||||
}))
|
||||
}
|
||||
|
||||
func TestCompactRecord(t *testing.T) {
|
||||
// make the map
|
||||
m := make(map[string]Option[int])
|
||||
m["foo"] = None[int]()
|
||||
m["bar"] = Some(1)
|
||||
// compact it
|
||||
m1 := CompactRecord(m)
|
||||
// check expected
|
||||
exp := map[string]int{
|
||||
"bar": 1,
|
||||
}
|
||||
|
||||
assert.Equal(t, exp, m1)
|
||||
}
|
||||
|
@@ -19,13 +19,22 @@ import (
|
||||
F "github.com/IBM/fp-go/function"
|
||||
)
|
||||
|
||||
// HKTA = HKT<A>
|
||||
// HKTOA = HKT<Option<A>>
|
||||
//
|
||||
// Sequence converts an option of some higher kinded type into the higher kinded type of an option
|
||||
// Sequence converts an [Option] of some higher kinded type into the higher kinded type of an [Option]
|
||||
func Sequence[A, HKTA, HKTOA any](
|
||||
_of func(Option[A]) HKTOA,
|
||||
_map func(HKTA, func(A) Option[A]) HKTOA,
|
||||
mof func(Option[A]) HKTOA,
|
||||
mmap func(func(A) Option[A]) func(HKTA) HKTOA,
|
||||
) func(Option[HKTA]) HKTOA {
|
||||
return Fold(F.Nullary2(None[A], _of), F.Bind2nd(_map, Some[A]))
|
||||
return Fold(F.Nullary2(None[A], mof), mmap(Some[A]))
|
||||
}
|
||||
|
||||
// Traverse converts an [Option] of some higher kinded type into the higher kinded type of an [Option]
|
||||
func Traverse[A, B, HKTB, HKTOB any](
|
||||
mof func(Option[B]) HKTOB,
|
||||
mmap func(func(B) Option[B]) func(HKTB) HKTOB,
|
||||
) func(func(A) HKTB) func(Option[A]) HKTOB {
|
||||
onNone := F.Nullary2(None[B], mof)
|
||||
onSome := mmap(Some[B])
|
||||
return func(f func(A) HKTB) func(Option[A]) HKTOB {
|
||||
return Fold(onNone, F.Flow2(f, onSome))
|
||||
}
|
||||
}
|
||||
|
@@ -23,7 +23,7 @@ import (
|
||||
O "github.com/IBM/fp-go/option"
|
||||
)
|
||||
|
||||
// AssertLaws asserts the apply monad laws for the `Either` monad
|
||||
// AssertLaws asserts the apply monad laws for the [Option] monad
|
||||
func AssertLaws[A, B, C any](t *testing.T,
|
||||
eqa EQ.Eq[A],
|
||||
eqb EQ.Eq[B],
|
||||
|
10
ord/ord.go
10
ord/ord.go
@@ -131,7 +131,7 @@ func FromStrictCompare[A C.Ordered]() Ord[A] {
|
||||
return MakeOrd(strictCompare[A], strictEq[A])
|
||||
}
|
||||
|
||||
// Lt tests whether one value is _strictly less than_ another
|
||||
// Lt tests whether one value is strictly less than another
|
||||
func Lt[A any](O Ord[A]) func(A) func(A) bool {
|
||||
return func(second A) func(A) bool {
|
||||
return func(first A) bool {
|
||||
@@ -140,7 +140,7 @@ func Lt[A any](O Ord[A]) func(A) func(A) bool {
|
||||
}
|
||||
}
|
||||
|
||||
// Leq Tests whether one value is less or equal than_ another
|
||||
// Leq Tests whether one value is less or equal than another
|
||||
func Leq[A any](O Ord[A]) func(A) func(A) bool {
|
||||
return func(second A) func(A) bool {
|
||||
return func(first A) bool {
|
||||
@@ -150,9 +150,9 @@ func Leq[A any](O Ord[A]) func(A) func(A) bool {
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether one value is _strictly greater than_ another
|
||||
* Test whether one value is strictly greater than another
|
||||
*/
|
||||
func cc[A any](O Ord[A]) func(A) func(A) bool {
|
||||
func Gt[A any](O Ord[A]) func(A) func(A) bool {
|
||||
return func(second A) func(A) bool {
|
||||
return func(first A) bool {
|
||||
return O.Compare(first, second) > 0
|
||||
@@ -160,7 +160,7 @@ func cc[A any](O Ord[A]) func(A) func(A) bool {
|
||||
}
|
||||
}
|
||||
|
||||
// Geq tests whether one value is greater or equal than_ another
|
||||
// Geq tests whether one value is greater or equal than another
|
||||
func Geq[A any](O Ord[A]) func(A) func(A) bool {
|
||||
return func(second A) func(A) bool {
|
||||
return func(first A) bool {
|
||||
|
@@ -32,7 +32,7 @@ func FromEither[E, L, A any](e ET.Either[L, A]) ReaderEither[E, L, A] {
|
||||
return G.FromEither[ReaderEither[E, L, A]](e)
|
||||
}
|
||||
|
||||
func RightReader[E, L, A any](r R.Reader[E, A]) ReaderEither[E, L, A] {
|
||||
func RightReader[L, E, A any](r R.Reader[E, A]) ReaderEither[E, L, A] {
|
||||
return G.RightReader[R.Reader[E, A], ReaderEither[E, L, A]](r)
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ func MonadAp[E, L, A, B any](fab ReaderEither[E, L, func(A) B], fa ReaderEither[
|
||||
return G.MonadAp[ReaderEither[E, L, A], ReaderEither[E, L, B], ReaderEither[E, L, func(A) B]](fab, fa)
|
||||
}
|
||||
|
||||
func Ap[E, L, A, B any](fa ReaderEither[E, L, A]) func(ReaderEither[E, L, func(A) B]) ReaderEither[E, L, B] {
|
||||
func Ap[B, E, L, A any](fa ReaderEither[E, L, A]) func(ReaderEither[E, L, func(A) B]) ReaderEither[E, L, B] {
|
||||
return G.Ap[ReaderEither[E, L, A], ReaderEither[E, L, B], ReaderEither[E, L, func(A) B]](fa)
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ func OrElse[E, L1, A, L2 any](onLeft func(L1) ReaderEither[E, L2, A]) func(Reade
|
||||
return G.OrElse[ReaderEither[E, L1, A]](onLeft)
|
||||
}
|
||||
|
||||
func OrLeft[L1, E, L2, A any](onLeft func(L1) R.Reader[E, L2]) func(ReaderEither[E, L1, A]) ReaderEither[E, L2, A] {
|
||||
func OrLeft[A, L1, E, L2 any](onLeft func(L1) R.Reader[E, L2]) func(ReaderEither[E, L1, A]) ReaderEither[E, L2, A] {
|
||||
return G.OrLeft[ReaderEither[E, L1, A], ReaderEither[E, L2, A]](onLeft)
|
||||
}
|
||||
|
||||
@@ -104,19 +104,19 @@ func Ask[E, L any]() ReaderEither[E, L, E] {
|
||||
return G.Ask[ReaderEither[E, L, E]]()
|
||||
}
|
||||
|
||||
func Asks[E, L, A any](r R.Reader[E, A]) ReaderEither[E, L, A] {
|
||||
func Asks[L, E, A any](r R.Reader[E, A]) ReaderEither[E, L, A] {
|
||||
return G.Asks[R.Reader[E, A], ReaderEither[E, L, A]](r)
|
||||
}
|
||||
|
||||
func MonadChainEitherK[E, L, A, B any](ma ReaderEither[E, L, A], f func(A) ET.Either[L, B]) ReaderEither[E, L, B] {
|
||||
func MonadChainEitherK[A, B, L, E any](ma ReaderEither[E, L, A], f func(A) ET.Either[L, B]) ReaderEither[E, L, B] {
|
||||
return G.MonadChainEitherK[ReaderEither[E, L, A], ReaderEither[E, L, B]](ma, f)
|
||||
}
|
||||
|
||||
func ChainEitherK[E, L, A, B any](f func(A) ET.Either[L, B]) func(ma ReaderEither[E, L, A]) ReaderEither[E, L, B] {
|
||||
func ChainEitherK[A, B, L, E any](f func(A) ET.Either[L, B]) func(ma ReaderEither[E, L, A]) ReaderEither[E, L, B] {
|
||||
return G.ChainEitherK[ReaderEither[E, L, A], ReaderEither[E, L, B]](f)
|
||||
}
|
||||
|
||||
func ChainOptionK[E, L, A, B any](onNone func() L) func(func(A) O.Option[B]) func(ReaderEither[E, L, A]) ReaderEither[E, L, B] {
|
||||
func ChainOptionK[E, A, B, L any](onNone func() L) func(func(A) O.Option[B]) func(ReaderEither[E, L, A]) ReaderEither[E, L, B] {
|
||||
return G.ChainOptionK[ReaderEither[E, L, A], ReaderEither[E, L, B]](onNone)
|
||||
}
|
||||
|
||||
@@ -135,11 +135,11 @@ func BiMap[E, E1, E2, A, B any](f func(E1) E2, g func(A) B) func(ReaderEither[E,
|
||||
|
||||
// Local changes the value of the local context during the execution of the action `ma` (similar to `Contravariant`'s
|
||||
// `contramap`).
|
||||
func Local[R2, R1, E, A any](f func(R2) R1) func(ReaderEither[R1, E, A]) ReaderEither[R2, E, A] {
|
||||
func Local[E, A, R2, R1 any](f func(R2) R1) func(ReaderEither[R1, E, A]) ReaderEither[R2, E, A] {
|
||||
return G.Local[ReaderEither[R1, E, A], ReaderEither[R2, E, A]](f)
|
||||
}
|
||||
|
||||
// Read applies a context to a reader to obtain its value
|
||||
func Read[E, E1, A any](e E) func(ReaderEither[E, E1, A]) ET.Either[E1, A] {
|
||||
func Read[E1, A, E any](e E) func(ReaderEither[E, E1, A]) ET.Either[E1, A] {
|
||||
return G.Read[ReaderEither[E, E1, A]](e)
|
||||
}
|
||||
|
@@ -42,7 +42,7 @@ func TestMap(t *testing.T) {
|
||||
func TestAp(t *testing.T) {
|
||||
g := F.Pipe1(
|
||||
Of[MyContext, error](utils.Double),
|
||||
Ap[MyContext, error, int, int](Of[MyContext, error](1)),
|
||||
Ap[int](Of[MyContext, error](1)),
|
||||
)
|
||||
assert.Equal(t, ET.Of[error](2), g(defaultContext))
|
||||
|
||||
|
@@ -93,7 +93,7 @@ func MonadChainReaderK[R, E, A, B any](ma ReaderIOEither[R, E, A], f func(A) RD.
|
||||
return G.MonadChainReaderK[ReaderIOEither[R, E, A], ReaderIOEither[R, E, B]](ma, f)
|
||||
}
|
||||
|
||||
func ChainReaderK[R, E, A, B any](f func(A) RD.Reader[R, B]) func(ReaderIOEither[R, E, A]) ReaderIOEither[R, E, B] {
|
||||
func ChainReaderK[E, R, A, B any](f func(A) RD.Reader[R, B]) func(ReaderIOEither[R, E, A]) ReaderIOEither[R, E, B] {
|
||||
return G.ChainReaderK[ReaderIOEither[R, E, A], ReaderIOEither[R, E, B]](f)
|
||||
}
|
||||
|
||||
@@ -121,7 +121,7 @@ func ChainFirstIOK[R, E, A, B any](f func(A) io.IO[B]) func(ma ReaderIOEither[R,
|
||||
return G.ChainFirstIOK[ReaderIOEither[R, E, A]](f)
|
||||
}
|
||||
|
||||
func ChainOptionK[R, E, A, B any](onNone func() E) func(func(A) O.Option[B]) func(ReaderIOEither[R, E, A]) ReaderIOEither[R, E, B] {
|
||||
func ChainOptionK[R, A, B, E any](onNone func() E) func(func(A) O.Option[B]) func(ReaderIOEither[R, E, A]) ReaderIOEither[R, E, B] {
|
||||
return G.ChainOptionK[ReaderIOEither[R, E, A], ReaderIOEither[R, E, B]](onNone)
|
||||
}
|
||||
|
||||
@@ -149,7 +149,7 @@ func Left[R, A, E any](e E) ReaderIOEither[R, E, A] {
|
||||
return G.Left[ReaderIOEither[R, E, A]](e)
|
||||
}
|
||||
|
||||
func ThrowError[R, E, A any](e E) ReaderIOEither[R, E, A] {
|
||||
func ThrowError[R, A, E any](e E) ReaderIOEither[R, E, A] {
|
||||
return G.ThrowError[ReaderIOEither[R, E, A]](e)
|
||||
}
|
||||
|
||||
@@ -166,7 +166,7 @@ func FromEither[R, E, A any](t ET.Either[E, A]) ReaderIOEither[R, E, A] {
|
||||
return G.FromEither[ReaderIOEither[R, E, A]](t)
|
||||
}
|
||||
|
||||
func RightReader[R, E, A any](ma RD.Reader[R, A]) ReaderIOEither[R, E, A] {
|
||||
func RightReader[E, R, A any](ma RD.Reader[R, A]) ReaderIOEither[R, E, A] {
|
||||
return G.RightReader[RD.Reader[R, A], ReaderIOEither[R, E, A]](ma)
|
||||
}
|
||||
|
||||
@@ -174,7 +174,7 @@ func LeftReader[A, R, E any](ma RD.Reader[R, E]) ReaderIOEither[R, E, A] {
|
||||
return G.LeftReader[RD.Reader[R, E], ReaderIOEither[R, E, A]](ma)
|
||||
}
|
||||
|
||||
func FromReader[R, E, A any](ma RD.Reader[R, A]) ReaderIOEither[R, E, A] {
|
||||
func FromReader[E, R, A any](ma RD.Reader[R, A]) ReaderIOEither[R, E, A] {
|
||||
return G.FromReader[RD.Reader[R, A], ReaderIOEither[R, E, A]](ma)
|
||||
}
|
||||
|
||||
@@ -202,11 +202,11 @@ func Ask[R, E any]() ReaderIOEither[R, E, R] {
|
||||
return G.Ask[ReaderIOEither[R, E, R]]()
|
||||
}
|
||||
|
||||
func Asks[R, E, A any](r RD.Reader[R, A]) ReaderIOEither[R, E, A] {
|
||||
func Asks[E, R, A any](r RD.Reader[R, A]) ReaderIOEither[R, E, A] {
|
||||
return G.Asks[RD.Reader[R, A], ReaderIOEither[R, E, A]](r)
|
||||
}
|
||||
|
||||
func FromOption[R, E, A any](onNone func() E) func(O.Option[A]) ReaderIOEither[R, E, A] {
|
||||
func FromOption[R, A, E any](onNone func() E) func(O.Option[A]) ReaderIOEither[R, E, A] {
|
||||
return G.FromOption[ReaderIOEither[R, E, A]](onNone)
|
||||
}
|
||||
|
||||
@@ -226,7 +226,7 @@ func OrElse[R, E1, A, E2 any](onLeft func(E1) ReaderIOEither[R, E2, A]) func(Rea
|
||||
return G.OrElse[ReaderIOEither[R, E1, A]](onLeft)
|
||||
}
|
||||
|
||||
func OrLeft[E1, R, E2, A any](onLeft func(E1) RIO.ReaderIO[R, E2]) func(ReaderIOEither[R, E1, A]) ReaderIOEither[R, E2, A] {
|
||||
func OrLeft[A, E1, R, E2 any](onLeft func(E1) RIO.ReaderIO[R, E2]) func(ReaderIOEither[R, E1, A]) ReaderIOEither[R, E2, A] {
|
||||
return G.OrLeft[ReaderIOEither[R, E1, A], RIO.ReaderIO[R, E2], ReaderIOEither[R, E2, A]](onLeft)
|
||||
}
|
||||
|
||||
|
@@ -39,7 +39,7 @@ func TestMap(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestOrLeft(t *testing.T) {
|
||||
f := OrLeft[string, context.Context, string, int](func(s string) RIO.ReaderIO[context.Context, string] {
|
||||
f := OrLeft[int](func(s string) RIO.ReaderIO[context.Context, string] {
|
||||
return RIO.Of[context.Context](s + "!")
|
||||
})
|
||||
|
||||
@@ -70,7 +70,7 @@ func TestChainReaderK(t *testing.T) {
|
||||
|
||||
g := F.Pipe1(
|
||||
Of[context.Context, error](1),
|
||||
ChainReaderK[context.Context, error](func(v int) R.Reader[context.Context, string] {
|
||||
ChainReaderK[error](func(v int) R.Reader[context.Context, string] {
|
||||
return R.Of[context.Context](fmt.Sprintf("%d", v))
|
||||
}),
|
||||
)
|
||||
|
@@ -186,6 +186,13 @@ func MapRefWithIndex[M ~map[K]V, N ~map[K]R, K comparable, V, R any](f func(K, *
|
||||
return F.Bind2nd(MonadMapRefWithIndex[M, N, K, V, R], f)
|
||||
}
|
||||
|
||||
func MonadLookup[M ~map[K]V, K comparable, V any](m M, k K) O.Option[V] {
|
||||
if val, ok := m[k]; ok {
|
||||
return O.Some(val)
|
||||
}
|
||||
return O.None[V]()
|
||||
}
|
||||
|
||||
func Lookup[M ~map[K]V, K comparable, V any](k K) func(M) O.Option[V] {
|
||||
n := O.None[V]()
|
||||
return func(m M) O.Option[V] {
|
||||
|
@@ -102,6 +102,11 @@ func Lookup[V any, K comparable](k K) func(map[K]V) O.Option[V] {
|
||||
return G.Lookup[map[K]V](k)
|
||||
}
|
||||
|
||||
// MonadLookup returns the entry for a key in a map if it exists
|
||||
func MonadLookup[V any, K comparable](m map[K]V, k K) O.Option[V] {
|
||||
return G.MonadLookup[map[K]V](m, k)
|
||||
}
|
||||
|
||||
// Has tests if a key is contained in a map
|
||||
func Has[K comparable, V any](k K, r map[K]V) bool {
|
||||
return G.Has(k, r)
|
||||
|
@@ -0,0 +1,38 @@
|
||||
// Copyright (c) 2023 IBM Corp.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package mostlyadequate
|
||||
|
||||
import "fmt"
|
||||
|
||||
func Hi(name string) string {
|
||||
return fmt.Sprintf("Hi %s", name)
|
||||
}
|
||||
|
||||
func Greeting(name string) string {
|
||||
return Hi(name)
|
||||
}
|
||||
|
||||
func Example_greeting() {
|
||||
// functions are first class objects
|
||||
greet := Hi
|
||||
|
||||
fmt.Println(Greeting("times"))
|
||||
fmt.Println(greet("times"))
|
||||
|
||||
// Output:
|
||||
// Hi times
|
||||
// Hi times
|
||||
}
|
@@ -16,9 +16,12 @@
|
||||
package mostlyadequate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
A "github.com/IBM/fp-go/array"
|
||||
F "github.com/IBM/fp-go/function"
|
||||
N "github.com/IBM/fp-go/number"
|
||||
I "github.com/IBM/fp-go/number/integer"
|
||||
@@ -26,8 +29,9 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
Match = F.Curry2((*regexp.Regexp).FindStringSubmatch)
|
||||
Split = F.Curry2(F.Bind3of3((*regexp.Regexp).Split)(-1))
|
||||
Match = F.Curry2((*regexp.Regexp).FindStringSubmatch)
|
||||
Matches = F.Curry2((*regexp.Regexp).MatchString)
|
||||
Split = F.Curry2(F.Bind3of3((*regexp.Regexp).Split)(-1))
|
||||
|
||||
Add = N.Add[int]
|
||||
ToString = I.ToString
|
||||
@@ -44,3 +48,36 @@ func Replace(search *regexp.Regexp) func(replace string) func(s string) string {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Example_solution04A() {
|
||||
// words :: String -> [String]
|
||||
words := Split(regexp.MustCompile(` `))
|
||||
|
||||
fmt.Println(words("Jingle bells Batman smells"))
|
||||
|
||||
// Output:
|
||||
// [Jingle bells Batman smells]
|
||||
}
|
||||
|
||||
func Example_solution04B() {
|
||||
// filterQs :: [String] -> [String]
|
||||
filterQs := A.Filter(Matches(regexp.MustCompile(`q`)))
|
||||
|
||||
fmt.Println(filterQs(A.From("quick", "camels", "quarry", "over", "quails")))
|
||||
|
||||
// Output:
|
||||
// [quick quarry quails]
|
||||
}
|
||||
|
||||
func Example_solution04C() {
|
||||
|
||||
keepHighest := N.Max[int]
|
||||
|
||||
// max :: [Number] -> Number
|
||||
max := A.Reduce(keepHighest, math.MinInt)
|
||||
|
||||
fmt.Println(max(A.From(323, 523, 554, 123, 5234)))
|
||||
|
||||
// Output:
|
||||
// 5234
|
||||
}
|
||||
|
@@ -21,6 +21,9 @@ import (
|
||||
|
||||
A "github.com/IBM/fp-go/array"
|
||||
F "github.com/IBM/fp-go/function"
|
||||
I "github.com/IBM/fp-go/number/integer"
|
||||
O "github.com/IBM/fp-go/option"
|
||||
"github.com/IBM/fp-go/ord"
|
||||
S "github.com/IBM/fp-go/string"
|
||||
)
|
||||
|
||||
@@ -58,3 +61,50 @@ func Example_pipe() {
|
||||
|
||||
// Output: SEND IN THE CLOWNS!
|
||||
}
|
||||
|
||||
func Example_solution05A() {
|
||||
IsLastInStock := F.Flow2(
|
||||
A.Last[Car],
|
||||
O.Map(Car.getInStock),
|
||||
)
|
||||
|
||||
fmt.Println(IsLastInStock(Cars[0:3]))
|
||||
fmt.Println(IsLastInStock(Cars[3:]))
|
||||
|
||||
// Output:
|
||||
// Some[bool](true)
|
||||
// Some[bool](false)
|
||||
}
|
||||
|
||||
func Example_solution05B() {
|
||||
// averageDollarValue :: [Car] -> Int
|
||||
averageDollarValue := F.Flow2(
|
||||
A.Map(Car.getDollarValue),
|
||||
average,
|
||||
)
|
||||
|
||||
fmt.Println(averageDollarValue(Cars))
|
||||
|
||||
// Output:
|
||||
// 790700
|
||||
}
|
||||
|
||||
func Example_solution05C() {
|
||||
// order by horsepower
|
||||
ordByHorsepower := ord.Contramap(Car.getHorsepower)(I.Ord)
|
||||
|
||||
// fastestCar :: [Car] -> Option[String]
|
||||
fastestCar := F.Flow3(
|
||||
A.Sort(ordByHorsepower),
|
||||
A.Last[Car],
|
||||
O.Map(F.Flow2(
|
||||
Car.getName,
|
||||
S.Format[string]("%s is the fastest"),
|
||||
)),
|
||||
)
|
||||
|
||||
fmt.Println(fastestCar(Cars))
|
||||
|
||||
// Output:
|
||||
// Some[string](Aston Martin One-77 is the fastest)
|
||||
}
|
||||
|
115
samples/mostly-adequate/chapter06_exampleapplication_test.go
Normal file
115
samples/mostly-adequate/chapter06_exampleapplication_test.go
Normal file
@@ -0,0 +1,115 @@
|
||||
// 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 mostlyadequate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
|
||||
A "github.com/IBM/fp-go/array"
|
||||
E "github.com/IBM/fp-go/either"
|
||||
F "github.com/IBM/fp-go/function"
|
||||
J "github.com/IBM/fp-go/json"
|
||||
S "github.com/IBM/fp-go/string"
|
||||
|
||||
R "github.com/IBM/fp-go/context/readerioeither"
|
||||
H "github.com/IBM/fp-go/context/readerioeither/http"
|
||||
)
|
||||
|
||||
type (
|
||||
FlickrMedia struct {
|
||||
Link string `json:"m"`
|
||||
}
|
||||
|
||||
FlickrItem struct {
|
||||
Media FlickrMedia `json:"media"`
|
||||
}
|
||||
|
||||
FlickrFeed struct {
|
||||
Items []FlickrItem `json:"items"`
|
||||
}
|
||||
)
|
||||
|
||||
func (f FlickrMedia) getLink() string {
|
||||
return f.Link
|
||||
}
|
||||
|
||||
func (f FlickrItem) getMedia() FlickrMedia {
|
||||
return f.Media
|
||||
}
|
||||
|
||||
func (f FlickrFeed) getItems() []FlickrItem {
|
||||
return f.Items
|
||||
}
|
||||
|
||||
func Example_application() {
|
||||
// pure
|
||||
host := "api.flickr.com"
|
||||
path := "/services/feeds/photos_public.gne"
|
||||
query := S.Format[string]("?tags=%s&format=json&jsoncallback=?")
|
||||
url := F.Flow2(
|
||||
query,
|
||||
S.Format[string](fmt.Sprintf("https://%s%s%%s", host, path)),
|
||||
)
|
||||
// flick returns jsonP, we extract the JSON body, this is handled by jquery in the original code
|
||||
sanitizeJsonP := Replace(regexp.MustCompile(`(?s)^\s*\((.*)\)\s*$`))("$1")
|
||||
// parse jsonP
|
||||
parseJsonP := F.Flow3(
|
||||
sanitizeJsonP,
|
||||
S.ToBytes,
|
||||
J.Unmarshal[FlickrFeed],
|
||||
)
|
||||
// markup
|
||||
img := S.Format[string]("<img src='%s'/>")
|
||||
// lenses
|
||||
mediaUrl := F.Flow2(
|
||||
FlickrItem.getMedia,
|
||||
FlickrMedia.getLink,
|
||||
)
|
||||
mediaUrls := F.Flow2(
|
||||
FlickrFeed.getItems,
|
||||
A.Map(mediaUrl),
|
||||
)
|
||||
images := F.Flow2(
|
||||
mediaUrls,
|
||||
A.Map(img),
|
||||
)
|
||||
|
||||
client := H.MakeClient(http.DefaultClient)
|
||||
|
||||
// func(string) R.ReaderIOEither[[]string]
|
||||
app := F.Flow5(
|
||||
url,
|
||||
H.MakeGetRequest,
|
||||
H.ReadText(client),
|
||||
R.ChainEitherK(parseJsonP),
|
||||
R.Map(images),
|
||||
)
|
||||
|
||||
// R.ReaderIOEither[[]string]
|
||||
// this is the managed effect that can be called to download and render the images
|
||||
catImageEffect := app("cats")
|
||||
|
||||
// impure, actually executes the effect
|
||||
catImages := catImageEffect(context.TODO())()
|
||||
fmt.Println(E.IsRight(catImages))
|
||||
|
||||
// Output:
|
||||
// true
|
||||
|
||||
}
|
@@ -19,9 +19,12 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
A "github.com/IBM/fp-go/array"
|
||||
E "github.com/IBM/fp-go/either"
|
||||
"github.com/IBM/fp-go/errors"
|
||||
F "github.com/IBM/fp-go/function"
|
||||
I "github.com/IBM/fp-go/identity"
|
||||
IOE "github.com/IBM/fp-go/ioeither"
|
||||
N "github.com/IBM/fp-go/number"
|
||||
O "github.com/IBM/fp-go/option"
|
||||
"github.com/IBM/fp-go/ord"
|
||||
@@ -40,6 +43,45 @@ func getBalance(a Account) float32 {
|
||||
return a.Balance
|
||||
}
|
||||
|
||||
type (
|
||||
Chapter08User struct {
|
||||
Id int
|
||||
Name string
|
||||
Active bool
|
||||
Saved bool
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
albert08 = Chapter08User{
|
||||
Id: 1,
|
||||
Active: true,
|
||||
Name: "Albert",
|
||||
}
|
||||
|
||||
gary08 = Chapter08User{
|
||||
Id: 2,
|
||||
Active: false,
|
||||
Name: "Gary",
|
||||
}
|
||||
|
||||
theresa08 = Chapter08User{
|
||||
Id: 3,
|
||||
Active: true,
|
||||
Name: "Theresa",
|
||||
}
|
||||
|
||||
yi08 = Chapter08User{Id: 4, Name: "Yi", Active: true}
|
||||
)
|
||||
|
||||
func (u Chapter08User) getName() string {
|
||||
return u.Name
|
||||
}
|
||||
|
||||
func (u Chapter08User) isActive() bool {
|
||||
return u.Active
|
||||
}
|
||||
|
||||
var (
|
||||
ordFloat32 = ord.FromStrictCompare[float32]()
|
||||
UpdateLedger = F.Identity[Account]
|
||||
@@ -55,6 +97,33 @@ var (
|
||||
Withdraw(20),
|
||||
O.Fold(F.Constant("You're broke!"), FinishTransaction),
|
||||
)
|
||||
|
||||
// showWelcome :: User -> String
|
||||
showWelcome = F.Flow2(
|
||||
Chapter08User.getName,
|
||||
S.Format[string]("Welcome %s"),
|
||||
)
|
||||
|
||||
// checkActive :: User -> Either error User
|
||||
checkActive = E.FromPredicate(Chapter08User.isActive, F.Constant1[Chapter08User](fmt.Errorf("Your account is not active")))
|
||||
|
||||
// validateUser :: (User -> Either String ()) -> User -> Either String User
|
||||
validateUser = F.Curry2(func(validate func(Chapter08User) E.Either[error, any], user Chapter08User) E.Either[error, Chapter08User] {
|
||||
return F.Pipe2(
|
||||
user,
|
||||
validate,
|
||||
E.MapTo[error, any](user),
|
||||
)
|
||||
})
|
||||
|
||||
// save :: User -> IOEither error User
|
||||
save = func(user Chapter08User) IOE.IOEither[error, Chapter08User] {
|
||||
return IOE.FromIO[error](func() Chapter08User {
|
||||
var u = user
|
||||
u.Saved = true
|
||||
return u
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
func Withdraw(amount float32) func(account Account) O.Option[Account] {
|
||||
@@ -131,3 +200,77 @@ func Example_getAge() {
|
||||
// Left[*time.ParseError, float64](parsing time "July 4, 2001" as "2006-01-02": cannot parse "July 4, 2001" as "2006")
|
||||
// If you survive, you will be 6837
|
||||
}
|
||||
|
||||
func Example_solution08A() {
|
||||
incrF := I.Map(N.Add(1))
|
||||
|
||||
fmt.Println(incrF(I.Of(2)))
|
||||
|
||||
// Output: 3
|
||||
}
|
||||
|
||||
func Example_solution08B() {
|
||||
// initial :: User -> Option rune
|
||||
initial := F.Flow3(
|
||||
Chapter08User.getName,
|
||||
S.ToRunes,
|
||||
A.Head[rune],
|
||||
)
|
||||
|
||||
fmt.Println(initial(albert08))
|
||||
|
||||
// Output:
|
||||
// Some[int32](65)
|
||||
}
|
||||
|
||||
func Example_solution08C() {
|
||||
|
||||
// eitherWelcome :: User -> Either String String
|
||||
eitherWelcome := F.Flow2(
|
||||
checkActive,
|
||||
E.Map[error](showWelcome),
|
||||
)
|
||||
|
||||
fmt.Println(eitherWelcome(gary08))
|
||||
fmt.Println(eitherWelcome(theresa08))
|
||||
|
||||
// Output:
|
||||
// Left[*errors.errorString, string](Your account is not active)
|
||||
// Right[<nil>, string](Welcome Theresa)
|
||||
}
|
||||
|
||||
func Example_solution08D() {
|
||||
|
||||
// // validateName :: User -> Either String ()
|
||||
validateName := F.Flow3(
|
||||
Chapter08User.getName,
|
||||
E.FromPredicate(F.Flow2(
|
||||
S.Size,
|
||||
ord.Gt(ord.FromStrictCompare[int]())(3),
|
||||
), errors.OnSome[string]("Your name %s is larger than 3 characters")),
|
||||
E.Map[error](F.ToAny[string]),
|
||||
)
|
||||
|
||||
saveAndWelcome := F.Flow2(
|
||||
save,
|
||||
IOE.Map[error](showWelcome),
|
||||
)
|
||||
|
||||
register := F.Flow3(
|
||||
validateUser(validateName),
|
||||
IOE.FromEither[error, Chapter08User],
|
||||
IOE.Chain(saveAndWelcome),
|
||||
)
|
||||
|
||||
fmt.Println(validateName(gary08))
|
||||
fmt.Println(validateName(yi08))
|
||||
|
||||
fmt.Println(register(albert08)())
|
||||
fmt.Println(register(yi08)())
|
||||
|
||||
// Output:
|
||||
// Right[<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)
|
||||
}
|
||||
|
@@ -17,10 +17,17 @@ package mostlyadequate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"regexp"
|
||||
|
||||
A "github.com/IBM/fp-go/array"
|
||||
E "github.com/IBM/fp-go/either"
|
||||
"github.com/IBM/fp-go/errors"
|
||||
F "github.com/IBM/fp-go/function"
|
||||
"github.com/IBM/fp-go/io"
|
||||
IOE "github.com/IBM/fp-go/ioeither"
|
||||
O "github.com/IBM/fp-go/option"
|
||||
S "github.com/IBM/fp-go/string"
|
||||
)
|
||||
|
||||
type (
|
||||
@@ -37,20 +44,85 @@ type (
|
||||
AddressBook struct {
|
||||
Addresses []Address
|
||||
}
|
||||
|
||||
Chapter09User struct {
|
||||
Id int
|
||||
Name string
|
||||
Address Address
|
||||
}
|
||||
)
|
||||
|
||||
func getAddresses(ab AddressBook) []Address {
|
||||
var (
|
||||
albert09 = Chapter09User{
|
||||
Id: 1,
|
||||
Name: "Albert",
|
||||
Address: Address{
|
||||
Street: Street{
|
||||
Number: 22,
|
||||
Name: "Walnut St",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
gary09 = Chapter09User{
|
||||
Id: 2,
|
||||
Name: "Gary",
|
||||
Address: Address{
|
||||
Street: Street{
|
||||
Number: 14,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
theresa09 = Chapter09User{
|
||||
Id: 3,
|
||||
Name: "Theresa",
|
||||
}
|
||||
)
|
||||
|
||||
func (ab AddressBook) getAddresses() []Address {
|
||||
return ab.Addresses
|
||||
}
|
||||
|
||||
func getStreet(s Address) Street {
|
||||
func (s Address) getStreet() Street {
|
||||
return s.Street
|
||||
}
|
||||
|
||||
var FirstAddressStreet = F.Flow3(
|
||||
getAddresses,
|
||||
A.Head[Address],
|
||||
O.Map(getStreet),
|
||||
func (s Street) getName() string {
|
||||
return s.Name
|
||||
}
|
||||
|
||||
func (u Chapter09User) getAddress() Address {
|
||||
return u.Address
|
||||
}
|
||||
|
||||
var (
|
||||
FirstAddressStreet = F.Flow3(
|
||||
AddressBook.getAddresses,
|
||||
A.Head[Address],
|
||||
O.Map(Address.getStreet),
|
||||
)
|
||||
|
||||
// getFile :: IO String
|
||||
getFile = io.Of("/home/mostly-adequate/ch09.md")
|
||||
|
||||
// pureLog :: String -> IO ()
|
||||
pureLog = io.Logf[string]("%s")
|
||||
|
||||
// addToMailingList :: Email -> IOEither([Email])
|
||||
addToMailingList = F.Flow2(
|
||||
A.Of[string],
|
||||
IOE.Of[error, []string],
|
||||
)
|
||||
|
||||
// validateEmail :: Email -> Either error Email
|
||||
validateEmail = E.FromPredicate(Matches(regexp.MustCompile(`\S+@\S+\.\S+`)), errors.OnSome[string]("email %s is invalid"))
|
||||
|
||||
// emailBlast :: [Email] -> IO ()
|
||||
emailBlast = F.Flow2(
|
||||
A.Intercalate(S.Monoid)(","),
|
||||
IOE.Of[error, string],
|
||||
)
|
||||
)
|
||||
|
||||
func Example_street() {
|
||||
@@ -62,3 +134,53 @@ func Example_street() {
|
||||
// Output:
|
||||
// Some[mostlyadequate.Street]({Mulburry 8402})
|
||||
}
|
||||
|
||||
func Example_solution09A() {
|
||||
// // getStreetName :: User -> Maybe String
|
||||
getStreetName := F.Flow4(
|
||||
Chapter09User.getAddress,
|
||||
Address.getStreet,
|
||||
Street.getName,
|
||||
O.FromPredicate(S.IsNonEmpty),
|
||||
)
|
||||
|
||||
fmt.Println(getStreetName(albert09))
|
||||
fmt.Println(getStreetName(gary09))
|
||||
fmt.Println(getStreetName(theresa09))
|
||||
|
||||
// Output:
|
||||
// Some[string](Walnut St)
|
||||
// None[string]
|
||||
// None[string]
|
||||
|
||||
}
|
||||
|
||||
func Example_solution09B() {
|
||||
logFilename := F.Flow2(
|
||||
io.Map(path.Base),
|
||||
io.ChainFirst(pureLog),
|
||||
)
|
||||
|
||||
fmt.Println(logFilename(getFile)())
|
||||
|
||||
// Output:
|
||||
// ch09.md
|
||||
}
|
||||
|
||||
func Example_solution09C() {
|
||||
|
||||
// // joinMailingList :: Email -> Either String (IO ())
|
||||
joinMailingList := F.Flow4(
|
||||
validateEmail,
|
||||
IOE.FromEither[error, string],
|
||||
IOE.Chain(addToMailingList),
|
||||
IOE.Chain(emailBlast),
|
||||
)
|
||||
|
||||
fmt.Println(joinMailingList("sleepy@grandpa.net")())
|
||||
fmt.Println(joinMailingList("notanemail")())
|
||||
|
||||
// Output:
|
||||
// Right[<nil>, string](sleepy@grandpa.net)
|
||||
// Left[*errors.errorString, string](email notanemail is invalid)
|
||||
}
|
||||
|
183
samples/mostly-adequate/chapter10_applicativefunctor_test.go
Normal file
183
samples/mostly-adequate/chapter10_applicativefunctor_test.go
Normal file
@@ -0,0 +1,183 @@
|
||||
// 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 mostlyadequate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
R "github.com/IBM/fp-go/context/readerioeither"
|
||||
H "github.com/IBM/fp-go/context/readerioeither/http"
|
||||
F "github.com/IBM/fp-go/function"
|
||||
IOO "github.com/IBM/fp-go/iooption"
|
||||
N "github.com/IBM/fp-go/number"
|
||||
O "github.com/IBM/fp-go/option"
|
||||
M "github.com/IBM/fp-go/record"
|
||||
T "github.com/IBM/fp-go/tuple"
|
||||
)
|
||||
|
||||
type (
|
||||
PostItem struct {
|
||||
UserId uint `json:"userId"`
|
||||
Id uint `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Body string `json:"body"`
|
||||
}
|
||||
|
||||
Player struct {
|
||||
Id int
|
||||
Name string
|
||||
}
|
||||
|
||||
LocalStorage = map[string]Player
|
||||
)
|
||||
|
||||
var (
|
||||
playerAlbert = Player{
|
||||
Id: 1,
|
||||
Name: "Albert",
|
||||
}
|
||||
playerTheresa = Player{
|
||||
Id: 2,
|
||||
Name: "Theresa",
|
||||
}
|
||||
localStorage = LocalStorage{
|
||||
"player1": playerAlbert,
|
||||
"player2": playerTheresa,
|
||||
}
|
||||
|
||||
// getFromCache :: String -> IO User
|
||||
getFromCache = func(name string) IOO.IOOption[Player] {
|
||||
return func() O.Option[Player] {
|
||||
return M.MonadLookup(localStorage, name)
|
||||
}
|
||||
}
|
||||
|
||||
// game :: User -> User -> String
|
||||
game = F.Curry2(func(a, b Player) string {
|
||||
return fmt.Sprintf("%s vs %s", a.Name, b.Name)
|
||||
})
|
||||
)
|
||||
|
||||
func (player Player) getName() string {
|
||||
return player.Name
|
||||
}
|
||||
|
||||
func (player Player) getId() int {
|
||||
return player.Id
|
||||
}
|
||||
|
||||
func (item PostItem) getTitle() string {
|
||||
return item.Title
|
||||
}
|
||||
|
||||
func idxToUrl(idx int) string {
|
||||
return fmt.Sprintf("https://jsonplaceholder.typicode.com/posts/%d", idx+1)
|
||||
}
|
||||
|
||||
func renderString(destinations string) func(string) string {
|
||||
return func(events string) string {
|
||||
return fmt.Sprintf("<div>Destinations: [%s], Events: [%s]</div>", destinations, events)
|
||||
}
|
||||
}
|
||||
|
||||
func Example_renderPage() {
|
||||
// prepare the http client
|
||||
client := H.MakeClient(http.DefaultClient)
|
||||
|
||||
// get returns the title of the nth item from the REST service
|
||||
get := F.Flow4(
|
||||
idxToUrl,
|
||||
H.MakeGetRequest,
|
||||
H.ReadJson[PostItem](client),
|
||||
R.Map(PostItem.getTitle),
|
||||
)
|
||||
|
||||
res := F.Pipe2(
|
||||
R.Of(renderString), // start with a function with 2 unresolved arguments
|
||||
R.Ap[func(string) string](get(1)), // resolve the first argument
|
||||
R.Ap[string](get(2)), // in parallel resolve the second argument
|
||||
)
|
||||
|
||||
// finally invoke in context and start
|
||||
fmt.Println(res(context.TODO())())
|
||||
|
||||
// Output:
|
||||
// Right[<nil>, string](<div>Destinations: [qui est esse], Events: [ea molestias quasi exercitationem repellat qui ipsa sit aut]</div>)
|
||||
|
||||
}
|
||||
|
||||
func Example_solution10A() {
|
||||
safeAdd := F.Curry2(func(a, b O.Option[int]) O.Option[int] {
|
||||
return F.Pipe3(
|
||||
N.Add[int],
|
||||
O.Of[func(int) func(int) int],
|
||||
O.Ap[func(int) int](a),
|
||||
O.Ap[int](b),
|
||||
)
|
||||
})
|
||||
|
||||
fmt.Println(safeAdd(O.Of(2))(O.Of(3)))
|
||||
fmt.Println(safeAdd(O.None[int]())(O.Of(3)))
|
||||
fmt.Println(safeAdd(O.Of(2))(O.None[int]()))
|
||||
|
||||
// Output:
|
||||
// Some[int](5)
|
||||
// None[int]
|
||||
// None[int]
|
||||
}
|
||||
|
||||
func Example_solution10B() {
|
||||
|
||||
safeAdd := F.Curry2(T.Untupled2(F.Flow2(
|
||||
O.SequenceTuple2[int, int],
|
||||
O.Map(T.Tupled2(N.MonoidSum[int]().Concat)),
|
||||
)))
|
||||
|
||||
fmt.Println(safeAdd(O.Of(2))(O.Of(3)))
|
||||
fmt.Println(safeAdd(O.None[int]())(O.Of(3)))
|
||||
fmt.Println(safeAdd(O.Of(2))(O.None[int]()))
|
||||
|
||||
// Output:
|
||||
// Some[int](5)
|
||||
// None[int]
|
||||
// None[int]
|
||||
}
|
||||
|
||||
func Example_solution10C() {
|
||||
// startGame :: IO String
|
||||
startGame := F.Pipe2(
|
||||
IOO.Of(game),
|
||||
IOO.Ap[func(Player) string](getFromCache("player1")),
|
||||
IOO.Ap[string](getFromCache("player2")),
|
||||
)
|
||||
|
||||
startGameTupled := F.Pipe2(
|
||||
T.MakeTuple2("player1", "player2"),
|
||||
IOO.TraverseTuple2(getFromCache, getFromCache),
|
||||
IOO.Map(T.Tupled2(func(a, b Player) string {
|
||||
return fmt.Sprintf("%s vs %s", a.Name, b.Name)
|
||||
})),
|
||||
)
|
||||
|
||||
fmt.Println(startGame())
|
||||
fmt.Println(startGameTupled())
|
||||
|
||||
// Output:
|
||||
// Some[string](Albert vs Theresa)
|
||||
// Some[string](Albert vs Theresa)
|
||||
}
|
89
samples/mostly-adequate/chapter11_transformagain_test.go
Normal file
89
samples/mostly-adequate/chapter11_transformagain_test.go
Normal file
@@ -0,0 +1,89 @@
|
||||
// Copyright (c) 2023 IBM Corp.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package mostlyadequate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
A "github.com/IBM/fp-go/array"
|
||||
E "github.com/IBM/fp-go/either"
|
||||
F "github.com/IBM/fp-go/function"
|
||||
IOE "github.com/IBM/fp-go/ioeither"
|
||||
S "github.com/IBM/fp-go/string"
|
||||
)
|
||||
|
||||
func findUserById(id int) IOE.IOEither[error, Chapter08User] {
|
||||
switch id {
|
||||
case 1:
|
||||
return IOE.Of[error](albert08)
|
||||
case 2:
|
||||
return IOE.Of[error](gary08)
|
||||
case 3:
|
||||
return IOE.Of[error](theresa08)
|
||||
default:
|
||||
return IOE.Left[Chapter08User](fmt.Errorf("user %d not found", id))
|
||||
}
|
||||
}
|
||||
|
||||
func Example_solution11A() {
|
||||
// eitherToMaybe :: Either b a -> Maybe a
|
||||
eitherToMaybe := E.ToOption[error, string]
|
||||
|
||||
fmt.Println(eitherToMaybe(E.Of[error]("one eyed willy")))
|
||||
fmt.Println(eitherToMaybe(E.Left[string](fmt.Errorf("some error"))))
|
||||
|
||||
// Output:
|
||||
// Some[string](one eyed willy)
|
||||
// None[string]
|
||||
}
|
||||
|
||||
func Example_solution11B() {
|
||||
findByNameId := F.Flow2(
|
||||
findUserById,
|
||||
IOE.Map[error](Chapter08User.getName),
|
||||
)
|
||||
|
||||
fmt.Println(findByNameId(1)())
|
||||
fmt.Println(findByNameId(2)())
|
||||
fmt.Println(findByNameId(3)())
|
||||
fmt.Println(findByNameId(4)())
|
||||
|
||||
// Output:
|
||||
// Right[<nil>, string](Albert)
|
||||
// Right[<nil>, string](Gary)
|
||||
// Right[<nil>, string](Theresa)
|
||||
// Left[*errors.errorString, string](user 4 not found)
|
||||
}
|
||||
|
||||
func Example_solution11C() {
|
||||
// strToList :: String -> [Char
|
||||
strToList := Split(regexp.MustCompile(``))
|
||||
|
||||
// listToStr :: [Char] -> String
|
||||
listToStr := A.Intercalate(S.Monoid)("")
|
||||
|
||||
sortLetters := F.Flow3(
|
||||
strToList,
|
||||
A.Sort(S.Ord),
|
||||
listToStr,
|
||||
)
|
||||
|
||||
fmt.Println(sortLetters("sortme"))
|
||||
|
||||
// Output:
|
||||
// emorst
|
||||
}
|
98
samples/mostly-adequate/chapter12_traversing_test.go
Normal file
98
samples/mostly-adequate/chapter12_traversing_test.go
Normal file
@@ -0,0 +1,98 @@
|
||||
// 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 mostlyadequate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
A "github.com/IBM/fp-go/array"
|
||||
E "github.com/IBM/fp-go/either"
|
||||
"github.com/IBM/fp-go/errors"
|
||||
F "github.com/IBM/fp-go/function"
|
||||
IOE "github.com/IBM/fp-go/ioeither"
|
||||
O "github.com/IBM/fp-go/option"
|
||||
P "github.com/IBM/fp-go/predicate"
|
||||
S "github.com/IBM/fp-go/string"
|
||||
)
|
||||
|
||||
var (
|
||||
// httpGet :: Route -> Task Error JSON
|
||||
httpGet = F.Flow2(
|
||||
S.Format[string]("json for %s"),
|
||||
IOE.Of[error, string],
|
||||
)
|
||||
|
||||
// routes :: Map Route Route
|
||||
routes = map[string]string{
|
||||
"/": "/",
|
||||
"/about": "/about",
|
||||
}
|
||||
|
||||
// validate :: Player -> Either error Player
|
||||
validatePlayer = E.FromPredicate(P.ContraMap(Player.getName)(S.IsNonEmpty), F.Flow2(Player.getId, errors.OnSome[int]("player %d must have a name")))
|
||||
|
||||
// readfile :: String -> String -> Task Error String
|
||||
readfile = F.Curry2(func(encoding, file string) IOE.IOEither[error, string] {
|
||||
return IOE.Of[error](fmt.Sprintf("content of %s (%s)", file, encoding))
|
||||
})
|
||||
|
||||
// readdir :: String -> Task Error [String]
|
||||
readdir = IOE.Of[error](A.From("file1", "file2", "file3"))
|
||||
)
|
||||
|
||||
func Example_solution12A() {
|
||||
// getJsons :: Map Route Route -> Task Error (Map Route JSON)
|
||||
getJsons := IOE.TraverseRecord[string](httpGet)
|
||||
|
||||
fmt.Println(getJsons(routes)())
|
||||
|
||||
// Output:
|
||||
// Right[<nil>, map[string]string](map[/:json for / /about:json for /about])
|
||||
}
|
||||
|
||||
func Example_solution12B() {
|
||||
// startGame :: [Player] -> [Either Error String]
|
||||
startGame := F.Flow2(
|
||||
E.TraverseArray(validatePlayer),
|
||||
E.MapTo[error, []Player]("Game started"),
|
||||
)
|
||||
|
||||
fmt.Println(startGame(A.From(playerAlbert, playerTheresa)))
|
||||
fmt.Println(startGame(A.From(playerAlbert, Player{Id: 4})))
|
||||
|
||||
// Output:
|
||||
// Right[<nil>, string](Game started)
|
||||
// Left[*errors.errorString, string](player 4 must have a name)
|
||||
}
|
||||
|
||||
func Example_solution12C() {
|
||||
traverseO := O.Traverse[string](
|
||||
IOE.Of[error, O.Option[string]],
|
||||
IOE.Map[error, string, O.Option[string]],
|
||||
)
|
||||
|
||||
// readFirst :: String -> Task Error (Maybe String)
|
||||
readFirst := F.Pipe2(
|
||||
readdir,
|
||||
IOE.Map[error](A.Head[string]),
|
||||
IOE.Chain(traverseO(readfile("utf-8"))),
|
||||
)
|
||||
|
||||
fmt.Println(readFirst())
|
||||
|
||||
// Output:
|
||||
// Right[<nil>, option.Option[string]](Some[string](content of file1 (utf-8)))
|
||||
}
|
89
samples/mostly-adequate/support.go
Normal file
89
samples/mostly-adequate/support.go
Normal file
@@ -0,0 +1,89 @@
|
||||
// Copyright (c) 2023 IBM Corp.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package mostlyadequate
|
||||
|
||||
import (
|
||||
A "github.com/IBM/fp-go/array"
|
||||
F "github.com/IBM/fp-go/function"
|
||||
N "github.com/IBM/fp-go/number"
|
||||
)
|
||||
|
||||
type (
|
||||
Car struct {
|
||||
Name string
|
||||
Horsepower int
|
||||
DollarValue float32
|
||||
InStock bool
|
||||
}
|
||||
)
|
||||
|
||||
func (car Car) getInStock() bool {
|
||||
return car.InStock
|
||||
}
|
||||
|
||||
func (car Car) getDollarValue() float32 {
|
||||
return car.DollarValue
|
||||
}
|
||||
|
||||
func (car Car) getHorsepower() int {
|
||||
return car.Horsepower
|
||||
}
|
||||
|
||||
func (car Car) getName() string {
|
||||
return car.Name
|
||||
}
|
||||
|
||||
func average(val []float32) float32 {
|
||||
return F.Pipe2(
|
||||
val,
|
||||
A.Fold(N.MonoidSum[float32]()),
|
||||
N.Div(float32(len(val))),
|
||||
)
|
||||
}
|
||||
|
||||
var (
|
||||
Cars = A.From(Car{
|
||||
Name: "Ferrari FF",
|
||||
Horsepower: 660,
|
||||
DollarValue: 700000,
|
||||
InStock: true,
|
||||
}, Car{
|
||||
Name: "Spyker C12 Zagato",
|
||||
Horsepower: 650,
|
||||
DollarValue: 648000,
|
||||
InStock: false,
|
||||
}, Car{
|
||||
Name: "Jaguar XKR-S",
|
||||
Horsepower: 550,
|
||||
DollarValue: 132000,
|
||||
InStock: true,
|
||||
}, Car{
|
||||
Name: "Audi R8",
|
||||
Horsepower: 525,
|
||||
DollarValue: 114200,
|
||||
InStock: false,
|
||||
}, Car{
|
||||
Name: "Aston Martin One-77",
|
||||
Horsepower: 750,
|
||||
DollarValue: 1850000,
|
||||
InStock: true,
|
||||
}, Car{
|
||||
Name: "Pagani Huayra",
|
||||
Horsepower: 700,
|
||||
DollarValue: 1300000,
|
||||
InStock: false,
|
||||
})
|
||||
)
|
@@ -20,7 +20,7 @@ import (
|
||||
"strings"
|
||||
|
||||
F "github.com/IBM/fp-go/function"
|
||||
O "github.com/IBM/fp-go/ord"
|
||||
"github.com/IBM/fp-go/ord"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -31,7 +31,7 @@ var (
|
||||
ToLowerCase = strings.ToLower
|
||||
|
||||
// Ord implements the default ordering for strings
|
||||
Ord = O.FromStrictCompare[string]()
|
||||
Ord = ord.FromStrictCompare[string]()
|
||||
)
|
||||
|
||||
func Eq(left string, right string) bool {
|
||||
@@ -42,6 +42,10 @@ func ToBytes(s string) []byte {
|
||||
return []byte(s)
|
||||
}
|
||||
|
||||
func ToRunes(s string) []rune {
|
||||
return []rune(s)
|
||||
}
|
||||
|
||||
func IsEmpty(s string) bool {
|
||||
return len(s) == 0
|
||||
}
|
||||
|
Reference in New Issue
Block a user