mirror of
https://github.com/IBM/fp-go.git
synced 2025-08-28 19:49:07 +02:00
Compare commits
23 Commits
v1.0.20
...
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 | ||
|
fb3b1f115c | ||
|
ce66cf2295 | ||
|
80e579dd0b |
@@ -187,6 +187,14 @@ func IsEmpty[AS ~[]A, A any](as AS) bool {
|
|||||||
return array.IsEmpty(as)
|
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 {
|
func Match[AS ~[]A, A, B any](onEmpty func() B, onNonEmpty func(AS) B) func(AS) B {
|
||||||
return func(as AS) B {
|
return func(as AS) B {
|
||||||
if IsEmpty(as) {
|
if IsEmpty(as) {
|
||||||
|
@@ -21,6 +21,13 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
H "net/http"
|
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 {
|
type PostItem struct {
|
||||||
@@ -30,6 +37,47 @@ type PostItem struct {
|
|||||||
Body string `json:"body"`
|
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) {
|
func TestSendSingleRequest(t *testing.T) {
|
||||||
|
|
||||||
client := MakeClient(H.DefaultClient)
|
client := MakeClient(H.DefaultClient)
|
||||||
@@ -40,7 +88,70 @@ func TestSendSingleRequest(t *testing.T) {
|
|||||||
|
|
||||||
resp1 := readItem(req1)
|
resp1 := readItem(req1)
|
||||||
|
|
||||||
resE := resp1(context.Background())()
|
resE := resp1(context.TODO())()
|
||||||
|
|
||||||
fmt.Println(resE)
|
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] {
|
func SequenceArray[E, A any](ma []Either[E, A]) Either[E, []A] {
|
||||||
return SequenceArrayG[[]A](ma)
|
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
|
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)))
|
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)
|
from := FromOption[E, B](onNone)
|
||||||
return func(f func(A) O.Option[B]) func(Either[E, A]) Either[E, B] {
|
return func(f func(A) O.Option[B]) func(Either[E, A]) Either[E, B] {
|
||||||
return Chain(F.Flow2(f, from))
|
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])
|
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 func(value any) Either[E, A] {
|
||||||
return F.Pipe2(
|
return F.Pipe2(
|
||||||
value,
|
value,
|
||||||
|
@@ -100,7 +100,7 @@ func TestChainFirst(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestChainOptionK(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 {
|
if n > 0 {
|
||||||
return O.Some(n)
|
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] {
|
func SequenceRecord[K comparable, E, A any](ma map[K]Either[E, A]) Either[E, map[K]A] {
|
||||||
return SequenceRecordG[map[K]A](ma)
|
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>
|
HKTA = HKT<A>
|
||||||
HKTB = HKT<B>
|
HKTB = HKT<B>
|
||||||
*/
|
*/
|
||||||
func traverse[E, A, B, HKTA, HKTB, HKTRB any](
|
func traverse[E, A, B, HKTB, HKTRB any](
|
||||||
_of func(Either[E, B]) HKTRB,
|
mof func(Either[E, B]) HKTRB,
|
||||||
_map func(HKTB, func(B) Either[E, B]) HKTRB,
|
mmap func(func(B) Either[E, B]) func(HKTB) HKTRB,
|
||||||
) func(Either[E, A], func(A) HKTB) HKTRB {
|
) func(Either[E, A], func(A) HKTB) HKTRB {
|
||||||
|
|
||||||
left := F.Flow2(Left[B, E], _of)
|
left := F.Flow2(Left[B, E], mof)
|
||||||
right := F.Bind2nd(_map, Right[E, B])
|
right := mmap(Right[E, B])
|
||||||
|
|
||||||
return func(ta Either[E, A], f func(A) HKTB) HKTRB {
|
return func(ta Either[E, A], f func(A) HKTB) HKTRB {
|
||||||
return MonadFold(ta,
|
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](
|
// Traverse converts an [Either] of some higher kinded type into the higher kinded type of an [Either]
|
||||||
_of func(Either[E, B]) HKTRB,
|
func Traverse[A, E, B, HKTB, HKTRB any](
|
||||||
_map func(HKTB, func(B) Either[E, B]) HKTRB,
|
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 {
|
) 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 func(f func(A) HKTB) func(Either[E, A]) HKTRB {
|
||||||
return F.Bind2nd(delegate, f)
|
return F.Bind2nd(delegate, f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Sequence converts an [Either] of some higher kinded type into the higher kinded type of an [Either]
|
||||||
*
|
|
||||||
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>
|
|
||||||
*/
|
|
||||||
func Sequence[E, A, HKTA, HKTRA any](
|
func Sequence[E, A, HKTA, HKTRA any](
|
||||||
_of func(Either[E, A]) HKTRA,
|
mof func(Either[E, A]) HKTRA,
|
||||||
_map func(HKTA, func(A) Either[E, A]) HKTRA,
|
mmap func(func(A) Either[E, A]) func(HKTA) HKTRA,
|
||||||
) func(Either[E, 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]()
|
return O.None[int]()
|
||||||
}
|
}
|
||||||
trav := Traverse[string, int, int, O.Option[Either[string, int]]](
|
trav := Traverse[int](
|
||||||
O.Of[Either[string, int]],
|
O.Of[Either[string, int]],
|
||||||
O.MonadMap[int, Either[string, int]],
|
O.Map[int, Either[string, int]],
|
||||||
)(f)
|
)(f)
|
||||||
|
|
||||||
assert.Equal(t, O.Of(Left[int]("a")), F.Pipe1(Left[int]("a"), trav))
|
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(
|
seq := Sequence(
|
||||||
O.Of[Either[string, int]],
|
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))))
|
assert.Equal(t, O.Of(Right[string](1)), seq(Right[string](O.Of(1))))
|
||||||
|
30
function/cache.go
Normal file
30
function/cache.go
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
// 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 function
|
||||||
|
|
||||||
|
import (
|
||||||
|
G "github.com/IBM/fp-go/function/generic"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Memoize converts a unary function into a unary function that caches the value depending on the parameter
|
||||||
|
func Memoize[K comparable, T any](f func(K) T) func(K) T {
|
||||||
|
return G.Memoize(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContramapMemoize converts a unary function into a unary function that caches the value depending on the parameter
|
||||||
|
func ContramapMemoize[A any, K comparable, T any](kf func(A) K) func(func(A) T) func(A) T {
|
||||||
|
return G.ContramapMemoize[func(A) T](kf)
|
||||||
|
}
|
50
function/cache_test.go
Normal file
50
function/cache_test.go
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
// 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 function
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCache(t *testing.T) {
|
||||||
|
var count int
|
||||||
|
|
||||||
|
withSideEffect := func(n int) int {
|
||||||
|
count++
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
cached := Memoize(withSideEffect)
|
||||||
|
|
||||||
|
assert.Equal(t, 0, count)
|
||||||
|
|
||||||
|
assert.Equal(t, 10, cached(10))
|
||||||
|
assert.Equal(t, 1, count)
|
||||||
|
|
||||||
|
assert.Equal(t, 10, cached(10))
|
||||||
|
assert.Equal(t, 1, count)
|
||||||
|
|
||||||
|
assert.Equal(t, 20, cached(20))
|
||||||
|
assert.Equal(t, 2, count)
|
||||||
|
|
||||||
|
assert.Equal(t, 20, cached(20))
|
||||||
|
assert.Equal(t, 2, count)
|
||||||
|
|
||||||
|
assert.Equal(t, 10, cached(10))
|
||||||
|
assert.Equal(t, 2, count)
|
||||||
|
}
|
65
function/generic/cache.go
Normal file
65
function/generic/cache.go
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
// 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 generic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
L "github.com/IBM/fp-go/internal/lazy"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Memoize converts a unary function into a unary function that caches the value depending on the parameter
|
||||||
|
func Memoize[F ~func(K) T, K comparable, T any](f F) F {
|
||||||
|
return ContramapMemoize[F](func(k K) K { return k })(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContramapMemoize converts a unary function into a unary function that caches the value depending on the parameter
|
||||||
|
func ContramapMemoize[F ~func(A) T, KF func(A) K, A any, K comparable, T any](kf KF) func(F) F {
|
||||||
|
return CacheCallback[F](kf, getOrCreate[K, T]())
|
||||||
|
}
|
||||||
|
|
||||||
|
// getOrCreate is a naive implementation of a cache, without bounds
|
||||||
|
func getOrCreate[K comparable, T any]() func(K, func() func() T) func() T {
|
||||||
|
cache := make(map[K]func() T)
|
||||||
|
var l sync.Mutex
|
||||||
|
|
||||||
|
return func(k K, cb func() func() T) func() T {
|
||||||
|
// only lock to access a lazy accessor to the value
|
||||||
|
l.Lock()
|
||||||
|
existing, ok := cache[k]
|
||||||
|
if !ok {
|
||||||
|
existing = cb()
|
||||||
|
cache[k] = existing
|
||||||
|
}
|
||||||
|
l.Unlock()
|
||||||
|
// compute the value outside of the lock
|
||||||
|
return existing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CacheCallback converts a unary function into a unary function that caches the value depending on the parameter
|
||||||
|
func CacheCallback[F ~func(A) T, KF func(A) K, C ~func(K, func() func() T) func() T, A any, K comparable, T any](kf KF, getOrCreate C) func(F) F {
|
||||||
|
return func(f F) F {
|
||||||
|
return func(a A) T {
|
||||||
|
// cache entry
|
||||||
|
return getOrCreate(kf(a), func() func() T {
|
||||||
|
return L.Memoize[func() T](func() T {
|
||||||
|
return f(a)
|
||||||
|
})
|
||||||
|
})()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
34
internal/lazy/memoize.go
Normal file
34
internal/lazy/memoize.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
// 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 lazy
|
||||||
|
|
||||||
|
import "sync"
|
||||||
|
|
||||||
|
// Memoize computes the value of the provided IO monad lazily but exactly once
|
||||||
|
func Memoize[GA ~func() A, A any](ma GA) GA {
|
||||||
|
// synchronization primitives
|
||||||
|
var once sync.Once
|
||||||
|
var result A
|
||||||
|
// callback
|
||||||
|
gen := func() {
|
||||||
|
result = ma()
|
||||||
|
}
|
||||||
|
// returns our memoized wrapper
|
||||||
|
return func() A {
|
||||||
|
once.Do(gen)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
@@ -16,11 +16,11 @@
|
|||||||
package generic
|
package generic
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
F "github.com/IBM/fp-go/function"
|
F "github.com/IBM/fp-go/function"
|
||||||
C "github.com/IBM/fp-go/internal/chain"
|
C "github.com/IBM/fp-go/internal/chain"
|
||||||
|
L "github.com/IBM/fp-go/internal/lazy"
|
||||||
)
|
)
|
||||||
|
|
||||||
// type IO[A any] = func() A
|
// type IO[A any] = func() A
|
||||||
@@ -119,18 +119,7 @@ func Flatten[GA ~func() A, GAA ~func() GA, A any](mma GAA) GA {
|
|||||||
|
|
||||||
// Memoize computes the value of the provided IO monad lazily but exactly once
|
// Memoize computes the value of the provided IO monad lazily but exactly once
|
||||||
func Memoize[GA ~func() A, A any](ma GA) GA {
|
func Memoize[GA ~func() A, A any](ma GA) GA {
|
||||||
// synchronization primitives
|
return L.Memoize[GA, A](ma)
|
||||||
var once sync.Once
|
|
||||||
var result A
|
|
||||||
// callback
|
|
||||||
gen := func() {
|
|
||||||
result = ma()
|
|
||||||
}
|
|
||||||
// returns our memoized wrapper
|
|
||||||
return func() A {
|
|
||||||
once.Do(gen)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delay creates an operation that passes in the value after some delay
|
// Delay creates an operation that passes in the value after some delay
|
||||||
|
@@ -83,7 +83,7 @@ func Flatten[A any](mma Lazy[Lazy[A]]) Lazy[A] {
|
|||||||
return G.Flatten(mma)
|
return G.Flatten(mma)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Memoize computes the value of the provided IO monad lazily but exactly once
|
// Memoize computes the value of the provided [Lazy] monad lazily but exactly once
|
||||||
func Memoize[A any](ma Lazy[A]) Lazy[A] {
|
func Memoize[A any](ma Lazy[A]) Lazy[A] {
|
||||||
return G.Memoize(ma)
|
return G.Memoize(ma)
|
||||||
}
|
}
|
||||||
|
23
number/integer/string.go
Normal file
23
number/integer/string.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
// 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 integer
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ToString converts an integer to a string
|
||||||
|
ToString = strconv.Itoa
|
||||||
|
)
|
@@ -24,3 +24,9 @@ func MagmaSub[A Number]() M.Magma[A] {
|
|||||||
return first - second
|
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"
|
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] {
|
func MonoidSum[A Number]() M.Monoid[A] {
|
||||||
s := SemigroupSum[A]()
|
s := SemigroupSum[A]()
|
||||||
return M.MakeMonoid(
|
return M.MakeMonoid(
|
||||||
@@ -26,3 +27,12 @@ func MonoidSum[A Number]() M.Monoid[A] {
|
|||||||
0,
|
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
|
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
|
// Add is a curried function used to add two numbers
|
||||||
func Add[T Number](left T) func(T) T {
|
func Add[T Number](right T) func(T) T {
|
||||||
return func(right T) T {
|
return func(left T) T {
|
||||||
return left + right
|
return left + right
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mul is a curried function used to add two numbers
|
// Sub is a curried function used to subtract two numbers
|
||||||
func Mul[T Number](coeff T) func(T) T {
|
func Sub[T Number](right T) func(T) T {
|
||||||
return func(value T) T {
|
return func(left T) T {
|
||||||
return coeff * value
|
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
|
// 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 func(sa Iso[S, A]) A {
|
||||||
return sa.Get(s)
|
return sa.Get(s)
|
||||||
}
|
}
|
||||||
@@ -81,8 +81,8 @@ func Wrap[S, A any](a A) func(Iso[S, A]) S {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// From wraps the value
|
// From wraps the value
|
||||||
func To[S, A any](s S) func(Iso[S, A]) A {
|
func To[A, S any](s S) func(Iso[S, A]) A {
|
||||||
return Unwrap[S, A](s)
|
return Unwrap[A, S](s)
|
||||||
}
|
}
|
||||||
|
|
||||||
// To unwraps the value
|
// 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
|
// 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 `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`
|
// 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)
|
defa := F.Constant(defaultA)
|
||||||
return func(ab Lens[A, B]) func(Lens[S, O.Option[A]]) Lens[S, O.Option[B]] {
|
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]))
|
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 `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 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'
|
// 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)
|
defa := F.Constant(defaultA)
|
||||||
noops := F.Constant(F.Identity[S])
|
noops := F.Constant(F.Identity[S])
|
||||||
noneb := O.None[B]()
|
noneb := O.None[B]()
|
||||||
|
@@ -209,7 +209,7 @@ func TestComposeOption(t *testing.T) {
|
|||||||
// compose lenses
|
// compose lenses
|
||||||
lens := F.Pipe1(
|
lens := F.Pipe1(
|
||||||
inner,
|
inner,
|
||||||
ComposeOption[Outer, *Inner, int](defaultInner)(value),
|
ComposeOption[Outer, int](defaultInner)(value),
|
||||||
)
|
)
|
||||||
outer1 := Outer{inner: &Inner{Value: 1, Foo: "a"}}
|
outer1 := Outer{inner: &Inner{Value: 1, Foo: "a"}}
|
||||||
// the checks
|
// the checks
|
||||||
@@ -235,7 +235,7 @@ func TestComposeOptions(t *testing.T) {
|
|||||||
// compose lenses
|
// compose lenses
|
||||||
lens := F.Pipe1(
|
lens := F.Pipe1(
|
||||||
inner,
|
inner,
|
||||||
ComposeOptions[OuterOpt, *InnerOpt, *int](defaultInner)(value),
|
ComposeOptions[OuterOpt, *int](defaultInner)(value),
|
||||||
)
|
)
|
||||||
// additional settings
|
// additional settings
|
||||||
defaultValue2 := 2
|
defaultValue2 := 2
|
||||||
|
@@ -24,7 +24,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// AtRecord returns a lens that focusses on a value in a record
|
// 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)
|
addKey := F.Bind1of2(RR.UpsertAt[M, K, V])(key)
|
||||||
delKey := F.Bind1of1(RR.DeleteAt[M, K, V])(key)
|
delKey := F.Bind1of1(RR.DeleteAt[M, K, V])(key)
|
||||||
fold := O.Fold(
|
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`
|
// 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))
|
return L.Compose[S](AtRecord[M](key))
|
||||||
}
|
}
|
||||||
|
@@ -22,11 +22,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// AtRecord returns a lens that focusses on a value in a record
|
// 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)
|
return G.AtRecord[map[K]V](key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AtKey returns a `Lens` focused on a required key of a `ReadonlyRecord`
|
// 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)
|
return G.AtKey[map[K]V, S](key)
|
||||||
}
|
}
|
||||||
|
@@ -31,7 +31,7 @@ type (
|
|||||||
func TestAtKey(t *testing.T) {
|
func TestAtKey(t *testing.T) {
|
||||||
sa := F.Pipe1(
|
sa := F.Pipe1(
|
||||||
L.Id[S](),
|
L.Id[S](),
|
||||||
AtKey[S, string, int]("a"),
|
AtKey[S, int]("a"),
|
||||||
)
|
)
|
||||||
|
|
||||||
assert.Equal(t, O.Some(1), sa.Get(S{"a": 1}))
|
assert.Equal(t, O.Some(1), sa.Get(S{"a": 1}))
|
||||||
|
@@ -198,7 +198,7 @@ func TestOuterLensLaws(t *testing.T) {
|
|||||||
eqValue := EQT.Eq[int]()
|
eqValue := EQT.Eq[int]()
|
||||||
eqOptValue := O.Eq(eqValue)
|
eqOptValue := O.Eq(eqValue)
|
||||||
// lens to access a value from outer
|
// 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
|
// try to access the value, this should get an option
|
||||||
assert.True(t, eqOptValue.Equals(valueFromOuter.Get(&emptyOuter), O.None[int]()))
|
assert.True(t, eqOptValue.Equals(valueFromOuter.Get(&emptyOuter), O.None[int]()))
|
||||||
// update the object
|
// update the object
|
||||||
@@ -234,7 +234,7 @@ func TestOuterOptLensLaws(t *testing.T) {
|
|||||||
valueFromOuter := F.Pipe3(
|
valueFromOuter := F.Pipe3(
|
||||||
valueOptLens,
|
valueOptLens,
|
||||||
LI.Compose[*InnerOpt](intIso),
|
LI.Compose[*InnerOpt](intIso),
|
||||||
L.ComposeOptions[*OuterOpt, *InnerOpt, int](&defaultInnerOpt),
|
L.ComposeOptions[*OuterOpt, int](&defaultInnerOpt),
|
||||||
I.Ap[L.Lens[*OuterOpt, O.Option[int]]](outerOptLens),
|
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] {
|
func SequenceArray[A any](ma []Option[A]) Option[[]A] {
|
||||||
return SequenceArrayG[[]A](ma)
|
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] {
|
func SequenceRecord[K comparable, A any](ma map[K]Option[A]) Option[map[K]A] {
|
||||||
return SequenceRecordG[map[K]A](ma)
|
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"),
|
"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"
|
F "github.com/IBM/fp-go/function"
|
||||||
)
|
)
|
||||||
|
|
||||||
// HKTA = HKT<A>
|
// Sequence converts an [Option] of some higher kinded type into the higher kinded type of an [Option]
|
||||||
// HKTOA = HKT<Option<A>>
|
|
||||||
//
|
|
||||||
// Sequence converts an option of some higher kinded type into the higher kinded type of an option
|
|
||||||
func Sequence[A, HKTA, HKTOA any](
|
func Sequence[A, HKTA, HKTOA any](
|
||||||
_of func(Option[A]) HKTOA,
|
mof func(Option[A]) HKTOA,
|
||||||
_map func(HKTA, func(A) Option[A]) HKTOA,
|
mmap func(func(A) Option[A]) func(HKTA) HKTOA,
|
||||||
) func(Option[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"
|
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,
|
func AssertLaws[A, B, C any](t *testing.T,
|
||||||
eqa EQ.Eq[A],
|
eqa EQ.Eq[A],
|
||||||
eqb EQ.Eq[B],
|
eqb EQ.Eq[B],
|
||||||
|
14
ord/ord.go
14
ord/ord.go
@@ -131,9 +131,7 @@ func FromStrictCompare[A C.Ordered]() Ord[A] {
|
|||||||
return MakeOrd(strictCompare[A], strictEq[A])
|
return MakeOrd(strictCompare[A], strictEq[A])
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Lt tests whether one value is strictly less than another
|
||||||
* Test 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(second A) func(A) bool {
|
||||||
return func(first A) bool {
|
return func(first A) bool {
|
||||||
@@ -142,9 +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
|
||||||
* Test whether one value is less or equal than_ another
|
|
||||||
*/
|
|
||||||
func Leq[A any](O Ord[A]) func(A) func(A) bool {
|
func Leq[A any](O Ord[A]) func(A) func(A) bool {
|
||||||
return func(second A) func(A) bool {
|
return func(second A) func(A) bool {
|
||||||
return func(first A) bool {
|
return func(first A) bool {
|
||||||
@@ -154,7 +150,7 @@ 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 Gt[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(second A) func(A) bool {
|
||||||
@@ -164,9 +160,7 @@ func Gt[A any](O Ord[A]) func(A) func(A) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// Geq tests whether one value is greater or equal than another
|
||||||
* Test whether one value is greater or equal than_ another
|
|
||||||
*/
|
|
||||||
func Geq[A any](O Ord[A]) func(A) func(A) bool {
|
func Geq[A any](O Ord[A]) func(A) func(A) bool {
|
||||||
return func(second A) func(A) bool {
|
return func(second A) func(A) bool {
|
||||||
return func(first 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)
|
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)
|
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)
|
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)
|
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)
|
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)
|
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]]()
|
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)
|
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)
|
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)
|
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)
|
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
|
// Local changes the value of the local context during the execution of the action `ma` (similar to `Contravariant`'s
|
||||||
// `contramap`).
|
// `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)
|
return G.Local[ReaderEither[R1, E, A], ReaderEither[R2, E, A]](f)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read applies a context to a reader to obtain its value
|
// 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)
|
return G.Read[ReaderEither[E, E1, A]](e)
|
||||||
}
|
}
|
||||||
|
@@ -42,7 +42,7 @@ func TestMap(t *testing.T) {
|
|||||||
func TestAp(t *testing.T) {
|
func TestAp(t *testing.T) {
|
||||||
g := F.Pipe1(
|
g := F.Pipe1(
|
||||||
Of[MyContext, error](utils.Double),
|
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))
|
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)
|
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)
|
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)
|
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)
|
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)
|
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)
|
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)
|
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)
|
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)
|
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)
|
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]]()
|
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)
|
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)
|
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)
|
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)
|
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) {
|
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 + "!")
|
return RIO.Of[context.Context](s + "!")
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ func TestChainReaderK(t *testing.T) {
|
|||||||
|
|
||||||
g := F.Pipe1(
|
g := F.Pipe1(
|
||||||
Of[context.Context, error](1),
|
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))
|
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)
|
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] {
|
func Lookup[M ~map[K]V, K comparable, V any](k K) func(M) O.Option[V] {
|
||||||
n := O.None[V]()
|
n := O.None[V]()
|
||||||
return func(m M) O.Option[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)
|
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
|
// Has tests if a key is contained in a map
|
||||||
func Has[K comparable, V any](k K, r map[K]V) bool {
|
func Has[K comparable, V any](k K, r map[K]V) bool {
|
||||||
return G.Has(k, r)
|
return G.Has(k, r)
|
||||||
|
6
samples/mostly-adequate/README.md
Normal file
6
samples/mostly-adequate/README.md
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# Mostly Adequate: fp-go Companion Guide
|
||||||
|
|
||||||
|
This resource is meant to serve as a go "companion" resource to Professor [Frisby's Mostly Adequate Guide](https://github.com/MostlyAdequate/mostly-adequate-guide).
|
||||||
|
|
||||||
|
It is a port of the [mostly-adequate-fp-ts](https://github.com/ChuckJonas/mostly-adequate-fp-ts/) book.
|
||||||
|
|
47
samples/mostly-adequate/chapter01_test.go
Normal file
47
samples/mostly-adequate/chapter01_test.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
// 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"
|
||||||
|
|
||||||
|
type Flock struct {
|
||||||
|
Seagulls int
|
||||||
|
}
|
||||||
|
|
||||||
|
func MakeFlock(n int) Flock {
|
||||||
|
return Flock{Seagulls: n}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Flock) Conjoin(other *Flock) *Flock {
|
||||||
|
f.Seagulls += other.Seagulls
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Flock) Breed(other *Flock) *Flock {
|
||||||
|
f.Seagulls = f.Seagulls * other.Seagulls
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func Example_flock() {
|
||||||
|
|
||||||
|
flockA := MakeFlock(4)
|
||||||
|
flockB := MakeFlock(2)
|
||||||
|
flockC := MakeFlock(0)
|
||||||
|
|
||||||
|
fmt.Println(flockA.Conjoin(&flockC).Breed(&flockB).Conjoin(flockA.Breed(&flockB)).Seagulls)
|
||||||
|
|
||||||
|
// Output: 32
|
||||||
|
}
|
@@ -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
|
||||||
|
}
|
83
samples/mostly-adequate/chapter04_currying_test.go
Normal file
83
samples/mostly-adequate/chapter04_currying_test.go
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
// 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"
|
||||||
|
"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"
|
||||||
|
S "github.com/IBM/fp-go/string"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
Match = F.Curry2((*regexp.Regexp).FindStringSubmatch)
|
||||||
|
Matches = F.Curry2((*regexp.Regexp).MatchString)
|
||||||
|
Split = F.Curry2(F.Bind3of3((*regexp.Regexp).Split)(-1))
|
||||||
|
|
||||||
|
Add = N.Add[int]
|
||||||
|
ToString = I.ToString
|
||||||
|
ToLower = strings.ToLower
|
||||||
|
ToUpper = strings.ToUpper
|
||||||
|
Concat = F.Curry2(S.Monoid.Concat)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Replace cannot be generated via [F.Curry3] because the order of parameters does not match our desired curried order
|
||||||
|
func Replace(search *regexp.Regexp) func(replace string) func(s string) string {
|
||||||
|
return func(replace string) func(s string) string {
|
||||||
|
return func(s string) string {
|
||||||
|
return search.ReplaceAllString(s, replace)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Example_solution04A() {
|
||||||
|
// words :: String -> [String]
|
||||||
|
words := Split(regexp.MustCompile(` `))
|
||||||
|
|
||||||
|
fmt.Println(words("Jingle bells Batman smells"))
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// [Jingle bells Batman smells]
|
||||||
|
}
|
||||||
|
|
||||||
|
func Example_solution04B() {
|
||||||
|
// filterQs :: [String] -> [String]
|
||||||
|
filterQs := A.Filter(Matches(regexp.MustCompile(`q`)))
|
||||||
|
|
||||||
|
fmt.Println(filterQs(A.From("quick", "camels", "quarry", "over", "quails")))
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// [quick quarry quails]
|
||||||
|
}
|
||||||
|
|
||||||
|
func Example_solution04C() {
|
||||||
|
|
||||||
|
keepHighest := N.Max[int]
|
||||||
|
|
||||||
|
// max :: [Number] -> Number
|
||||||
|
max := A.Reduce(keepHighest, math.MinInt)
|
||||||
|
|
||||||
|
fmt.Println(max(A.From(323, 523, 554, 123, 5234)))
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// 5234
|
||||||
|
}
|
110
samples/mostly-adequate/chapter05_composing_test.go
Normal file
110
samples/mostly-adequate/chapter05_composing_test.go
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
// 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"
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
Exclaim = S.Format[string]("%s!")
|
||||||
|
Shout = F.Flow2(ToUpper, Exclaim)
|
||||||
|
Dasherize = F.Flow4(
|
||||||
|
Replace(regexp.MustCompile(`\s{2,}`))(" "),
|
||||||
|
Split(regexp.MustCompile(` `)),
|
||||||
|
A.Map(ToLower),
|
||||||
|
A.Intercalate(S.Monoid)("-"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
func Example_shout() {
|
||||||
|
fmt.Println(Shout("send in the clowns"))
|
||||||
|
|
||||||
|
// Output: SEND IN THE CLOWNS!
|
||||||
|
}
|
||||||
|
|
||||||
|
func Example_dasherize() {
|
||||||
|
fmt.Println(Dasherize("The world is a vampire"))
|
||||||
|
|
||||||
|
// Output: the-world-is-a-vampire
|
||||||
|
}
|
||||||
|
|
||||||
|
func Example_pipe() {
|
||||||
|
output := F.Pipe2(
|
||||||
|
"send in the clowns",
|
||||||
|
ToUpper,
|
||||||
|
Exclaim,
|
||||||
|
)
|
||||||
|
|
||||||
|
fmt.Println(output)
|
||||||
|
|
||||||
|
// Output: SEND IN THE CLOWNS!
|
||||||
|
}
|
||||||
|
|
||||||
|
func Example_solution05A() {
|
||||||
|
IsLastInStock := F.Flow2(
|
||||||
|
A.Last[Car],
|
||||||
|
O.Map(Car.getInStock),
|
||||||
|
)
|
||||||
|
|
||||||
|
fmt.Println(IsLastInStock(Cars[0:3]))
|
||||||
|
fmt.Println(IsLastInStock(Cars[3:]))
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// Some[bool](true)
|
||||||
|
// Some[bool](false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Example_solution05B() {
|
||||||
|
// averageDollarValue :: [Car] -> Int
|
||||||
|
averageDollarValue := F.Flow2(
|
||||||
|
A.Map(Car.getDollarValue),
|
||||||
|
average,
|
||||||
|
)
|
||||||
|
|
||||||
|
fmt.Println(averageDollarValue(Cars))
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// 790700
|
||||||
|
}
|
||||||
|
|
||||||
|
func Example_solution05C() {
|
||||||
|
// order by horsepower
|
||||||
|
ordByHorsepower := ord.Contramap(Car.getHorsepower)(I.Ord)
|
||||||
|
|
||||||
|
// fastestCar :: [Car] -> Option[String]
|
||||||
|
fastestCar := F.Flow3(
|
||||||
|
A.Sort(ordByHorsepower),
|
||||||
|
A.Last[Car],
|
||||||
|
O.Map(F.Flow2(
|
||||||
|
Car.getName,
|
||||||
|
S.Format[string]("%s is the fastest"),
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
|
||||||
|
fmt.Println(fastestCar(Cars))
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// Some[string](Aston Martin One-77 is the fastest)
|
||||||
|
}
|
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
|
||||||
|
|
||||||
|
}
|
276
samples/mostly-adequate/chapter08_tupperware_test.go
Normal file
276
samples/mostly-adequate/chapter08_tupperware_test.go
Normal file
@@ -0,0 +1,276 @@
|
|||||||
|
// 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"
|
||||||
|
"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"
|
||||||
|
S "github.com/IBM/fp-go/string"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Account struct {
|
||||||
|
Balance float32
|
||||||
|
}
|
||||||
|
|
||||||
|
func MakeAccount(b float32) Account {
|
||||||
|
return Account{Balance: b}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBalance(a Account) float32 {
|
||||||
|
return a.Balance
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
Chapter08User struct {
|
||||||
|
Id int
|
||||||
|
Name string
|
||||||
|
Active bool
|
||||||
|
Saved bool
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
albert08 = Chapter08User{
|
||||||
|
Id: 1,
|
||||||
|
Active: true,
|
||||||
|
Name: "Albert",
|
||||||
|
}
|
||||||
|
|
||||||
|
gary08 = Chapter08User{
|
||||||
|
Id: 2,
|
||||||
|
Active: false,
|
||||||
|
Name: "Gary",
|
||||||
|
}
|
||||||
|
|
||||||
|
theresa08 = Chapter08User{
|
||||||
|
Id: 3,
|
||||||
|
Active: true,
|
||||||
|
Name: "Theresa",
|
||||||
|
}
|
||||||
|
|
||||||
|
yi08 = Chapter08User{Id: 4, Name: "Yi", Active: true}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (u Chapter08User) getName() string {
|
||||||
|
return u.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u Chapter08User) isActive() bool {
|
||||||
|
return u.Active
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ordFloat32 = ord.FromStrictCompare[float32]()
|
||||||
|
UpdateLedger = F.Identity[Account]
|
||||||
|
RemainingBalance = F.Flow2(
|
||||||
|
getBalance,
|
||||||
|
S.Format[float32]("Your balance is $%0.2f"),
|
||||||
|
)
|
||||||
|
FinishTransaction = F.Flow2(
|
||||||
|
UpdateLedger,
|
||||||
|
RemainingBalance,
|
||||||
|
)
|
||||||
|
getTwenty = F.Flow2(
|
||||||
|
Withdraw(20),
|
||||||
|
O.Fold(F.Constant("You're broke!"), FinishTransaction),
|
||||||
|
)
|
||||||
|
|
||||||
|
// showWelcome :: User -> String
|
||||||
|
showWelcome = F.Flow2(
|
||||||
|
Chapter08User.getName,
|
||||||
|
S.Format[string]("Welcome %s"),
|
||||||
|
)
|
||||||
|
|
||||||
|
// checkActive :: User -> Either error User
|
||||||
|
checkActive = E.FromPredicate(Chapter08User.isActive, F.Constant1[Chapter08User](fmt.Errorf("Your account is not active")))
|
||||||
|
|
||||||
|
// validateUser :: (User -> Either String ()) -> User -> Either String User
|
||||||
|
validateUser = F.Curry2(func(validate func(Chapter08User) 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] {
|
||||||
|
|
||||||
|
return F.Flow3(
|
||||||
|
getBalance,
|
||||||
|
O.FromPredicate(ord.Geq(ordFloat32)(amount)),
|
||||||
|
O.Map(F.Flow2(
|
||||||
|
N.Add(-amount),
|
||||||
|
MakeAccount,
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
BirthDate string
|
||||||
|
}
|
||||||
|
|
||||||
|
func getBirthDate(u User) string {
|
||||||
|
return u.BirthDate
|
||||||
|
}
|
||||||
|
|
||||||
|
func MakeUser(d string) User {
|
||||||
|
return User{BirthDate: d}
|
||||||
|
}
|
||||||
|
|
||||||
|
var parseDate = F.Bind1of2(E.Eitherize2(time.Parse))(time.DateOnly)
|
||||||
|
|
||||||
|
func GetAge(now time.Time) func(User) E.Either[error, float64] {
|
||||||
|
return F.Flow3(
|
||||||
|
getBirthDate,
|
||||||
|
parseDate,
|
||||||
|
E.Map[error](F.Flow3(
|
||||||
|
now.Sub,
|
||||||
|
time.Duration.Hours,
|
||||||
|
N.Mul(1/24.0),
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Example_widthdraw() {
|
||||||
|
fmt.Println(getTwenty(MakeAccount(200)))
|
||||||
|
fmt.Println(getTwenty(MakeAccount(10)))
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// Your balance is $180.00
|
||||||
|
// You're broke!
|
||||||
|
}
|
||||||
|
|
||||||
|
func Example_getAge() {
|
||||||
|
now, err := time.Parse(time.DateOnly, "2023-09-01")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(GetAge(now)(MakeUser("2005-12-12")))
|
||||||
|
fmt.Println(GetAge(now)(MakeUser("July 4, 2001")))
|
||||||
|
|
||||||
|
fortune := F.Flow3(
|
||||||
|
N.Add(365.0),
|
||||||
|
S.Format[float64]("%0.0f"),
|
||||||
|
Concat("If you survive, you will be "),
|
||||||
|
)
|
||||||
|
|
||||||
|
zoltar := F.Flow3(
|
||||||
|
GetAge(now),
|
||||||
|
E.Map[error](fortune),
|
||||||
|
E.GetOrElse(errors.ToString),
|
||||||
|
)
|
||||||
|
|
||||||
|
fmt.Println(zoltar(MakeUser("2005-12-12")))
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// Right[<nil>, float64](6472)
|
||||||
|
// 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)
|
||||||
|
}
|
186
samples/mostly-adequate/chapter09_monadiconions_test.go
Normal file
186
samples/mostly-adequate/chapter09_monadiconions_test.go
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
// 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"
|
||||||
|
"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 (
|
||||||
|
Street struct {
|
||||||
|
Name string
|
||||||
|
Number int
|
||||||
|
}
|
||||||
|
|
||||||
|
Address struct {
|
||||||
|
Street Street
|
||||||
|
Postcode string
|
||||||
|
}
|
||||||
|
|
||||||
|
AddressBook struct {
|
||||||
|
Addresses []Address
|
||||||
|
}
|
||||||
|
|
||||||
|
Chapter09User struct {
|
||||||
|
Id int
|
||||||
|
Name string
|
||||||
|
Address Address
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
albert09 = Chapter09User{
|
||||||
|
Id: 1,
|
||||||
|
Name: "Albert",
|
||||||
|
Address: Address{
|
||||||
|
Street: Street{
|
||||||
|
Number: 22,
|
||||||
|
Name: "Walnut St",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
gary09 = Chapter09User{
|
||||||
|
Id: 2,
|
||||||
|
Name: "Gary",
|
||||||
|
Address: Address{
|
||||||
|
Street: Street{
|
||||||
|
Number: 14,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
theresa09 = Chapter09User{
|
||||||
|
Id: 3,
|
||||||
|
Name: "Theresa",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (ab AddressBook) getAddresses() []Address {
|
||||||
|
return ab.Addresses
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Address) getStreet() Street {
|
||||||
|
return s.Street
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Street) getName() string {
|
||||||
|
return s.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u Chapter09User) getAddress() Address {
|
||||||
|
return u.Address
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
FirstAddressStreet = F.Flow3(
|
||||||
|
AddressBook.getAddresses,
|
||||||
|
A.Head[Address],
|
||||||
|
O.Map(Address.getStreet),
|
||||||
|
)
|
||||||
|
|
||||||
|
// getFile :: IO String
|
||||||
|
getFile = io.Of("/home/mostly-adequate/ch09.md")
|
||||||
|
|
||||||
|
// pureLog :: String -> IO ()
|
||||||
|
pureLog = io.Logf[string]("%s")
|
||||||
|
|
||||||
|
// addToMailingList :: Email -> IOEither([Email])
|
||||||
|
addToMailingList = F.Flow2(
|
||||||
|
A.Of[string],
|
||||||
|
IOE.Of[error, []string],
|
||||||
|
)
|
||||||
|
|
||||||
|
// validateEmail :: Email -> Either error Email
|
||||||
|
validateEmail = E.FromPredicate(Matches(regexp.MustCompile(`\S+@\S+\.\S+`)), errors.OnSome[string]("email %s is invalid"))
|
||||||
|
|
||||||
|
// emailBlast :: [Email] -> IO ()
|
||||||
|
emailBlast = F.Flow2(
|
||||||
|
A.Intercalate(S.Monoid)(","),
|
||||||
|
IOE.Of[error, string],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
func Example_street() {
|
||||||
|
s := FirstAddressStreet(AddressBook{
|
||||||
|
Addresses: A.From(Address{Street: Street{Name: "Mulburry", Number: 8402}, Postcode: "WC2N"}),
|
||||||
|
})
|
||||||
|
fmt.Println(s)
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// Some[mostlyadequate.Street]({Mulburry 8402})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Example_solution09A() {
|
||||||
|
// // getStreetName :: User -> Maybe String
|
||||||
|
getStreetName := F.Flow4(
|
||||||
|
Chapter09User.getAddress,
|
||||||
|
Address.getStreet,
|
||||||
|
Street.getName,
|
||||||
|
O.FromPredicate(S.IsNonEmpty),
|
||||||
|
)
|
||||||
|
|
||||||
|
fmt.Println(getStreetName(albert09))
|
||||||
|
fmt.Println(getStreetName(gary09))
|
||||||
|
fmt.Println(getStreetName(theresa09))
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// Some[string](Walnut St)
|
||||||
|
// None[string]
|
||||||
|
// None[string]
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func Example_solution09B() {
|
||||||
|
logFilename := F.Flow2(
|
||||||
|
io.Map(path.Base),
|
||||||
|
io.ChainFirst(pureLog),
|
||||||
|
)
|
||||||
|
|
||||||
|
fmt.Println(logFilename(getFile)())
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// ch09.md
|
||||||
|
}
|
||||||
|
|
||||||
|
func Example_solution09C() {
|
||||||
|
|
||||||
|
// // joinMailingList :: Email -> Either String (IO ())
|
||||||
|
joinMailingList := F.Flow4(
|
||||||
|
validateEmail,
|
||||||
|
IOE.FromEither[error, string],
|
||||||
|
IOE.Chain(addToMailingList),
|
||||||
|
IOE.Chain(emailBlast),
|
||||||
|
)
|
||||||
|
|
||||||
|
fmt.Println(joinMailingList("sleepy@grandpa.net")())
|
||||||
|
fmt.Println(joinMailingList("notanemail")())
|
||||||
|
|
||||||
|
// Output:
|
||||||
|
// Right[<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)))
|
||||||
|
}
|
19
samples/mostly-adequate/doc.go
Normal file
19
samples/mostly-adequate/doc.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
// 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 is meant to serve as a go "companion" resource to Professor [Frisby's Mostly Adequate Guide].
|
||||||
|
//
|
||||||
|
// [Frisby's Mostly Adequate Guide]: https://github.com/MostlyAdequate/mostly-adequate-guide
|
||||||
|
package mostlyadequate
|
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"
|
"strings"
|
||||||
|
|
||||||
F "github.com/IBM/fp-go/function"
|
F "github.com/IBM/fp-go/function"
|
||||||
O "github.com/IBM/fp-go/ord"
|
"github.com/IBM/fp-go/ord"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -31,7 +31,7 @@ var (
|
|||||||
ToLowerCase = strings.ToLower
|
ToLowerCase = strings.ToLower
|
||||||
|
|
||||||
// Ord implements the default ordering for strings
|
// Ord implements the default ordering for strings
|
||||||
Ord = O.FromStrictCompare[string]()
|
Ord = ord.FromStrictCompare[string]()
|
||||||
)
|
)
|
||||||
|
|
||||||
func Eq(left string, right string) bool {
|
func Eq(left string, right string) bool {
|
||||||
@@ -42,6 +42,10 @@ func ToBytes(s string) []byte {
|
|||||||
return []byte(s)
|
return []byte(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ToRunes(s string) []rune {
|
||||||
|
return []rune(s)
|
||||||
|
}
|
||||||
|
|
||||||
func IsEmpty(s string) bool {
|
func IsEmpty(s string) bool {
|
||||||
return len(s) == 0
|
return len(s) == 0
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user