1
0
mirror of https://github.com/IBM/fp-go.git synced 2025-08-28 19:49:07 +02:00

Compare commits

..

20 Commits

Author SHA1 Message Date
Dr. Carsten Leue
ff1b6faf84 fix: order of parameters in optics
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2023-09-10 21:49:44 +02:00
Carsten Leue
38120764e7 Merge pull request #40 from IBM/cleue-add-missing-lenses
fix: change Cache to Memoize and fix order of parameters to ToType
2023-09-10 21:37:38 +02:00
Dr. Carsten Leue
a83c2aec49 fix: change Cache to Memoize and fix order of parameters to ToType
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2023-09-10 21:29:01 +02:00
Carsten Leue
03debd37c8 Merge pull request #39 from IBM/cleue-implement-compact
fix: add compact methods
2023-09-08 22:57:18 +02:00
Dr. Carsten Leue
4f04344cda fix: add compact methods
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2023-09-08 22:54:58 +02:00
Carsten Leue
cf1c886f48 Merge pull request #37 from IBM/cleue-fix-order-of-generics-for-ChainOptionK
fix: order of generic parameters
2023-09-08 18:18:36 +02:00
Dr. Carsten Leue
3e0eb99b88 fix: order of generic parameters
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2023-09-08 18:16:06 +02:00
Carsten Leue
cb15a3d9fc Merge pull request #36 from IBM/cleue-mostly-adequate-more-samples
Cleue mostly adequate more samples
2023-09-08 15:31:01 +02:00
Dr. Carsten Leue
16535605f5 fix: add better sequence and traverse for option and either
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2023-09-08 15:26:56 +02:00
Dr. Carsten Leue
53f4e5ebd7 fix: add more mostly-adequate examples and solutions
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2023-09-08 15:25:44 +02:00
Dr. Carsten Leue
fb91fd5dc8 fix: add more mostly-adequate examples and solutions
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2023-09-07 17:21:39 +02:00
Carsten Leue
3ccafb5302 Merge pull request #35 from IBM/cleue-mostly-adequate-capter10
Add more examples for mostly-adequate
2023-09-06 15:15:00 +02:00
Dr. Carsten Leue
52b71ef4f3 fix: add more examples
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2023-09-05 22:51:46 +02:00
Dr. Carsten Leue
5d77d5bb3d fix: add sample app for rendering cat images
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2023-09-04 22:55:14 +02:00
Dr. Carsten Leue
8ba8f852fa Merge branch 'main' into cleue-mostly-adequate-capter10 2023-09-04 22:35:13 +02:00
Carsten Leue
29d9882d2a Merge pull request #34 from IBM/cleue-request-builder-example
doc: document how to use a request builder for http
2023-09-04 11:15:28 +02:00
Dr. Carsten Leue
f80ca31e14 doc: document how to use a request builder for http
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2023-09-04 11:13:58 +02:00
Dr. Carsten Leue
8692078972 doc: add examples and solutions from mostly adequate
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2023-09-03 22:21:23 +02:00
Dr. Carsten Leue
12a4f6801c fix: add ap sample
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2023-09-02 14:30:01 +02:00
Carsten Leue
8650a8a600 Merge pull request #33 from IBM/cleue-mostly-adequate-fp-go
fix: add examples for [Frisby's Mostly Adequate Guide]
2023-09-01 17:50:59 +02:00
46 changed files with 1356 additions and 103 deletions

View File

@@ -187,6 +187,14 @@ func IsEmpty[AS ~[]A, A any](as AS) bool {
return array.IsEmpty(as)
}
func IsNil[GA ~[]A, A any](as GA) bool {
return array.IsNil(as)
}
func IsNonNil[GA ~[]A, A any](as GA) bool {
return array.IsNonNil(as)
}
func Match[AS ~[]A, A, B any](onEmpty func() B, onNonEmpty func(AS) B) func(AS) B {
return func(as AS) B {
if IsEmpty(as) {

View File

@@ -21,6 +21,13 @@ import (
"testing"
H "net/http"
R "github.com/IBM/fp-go/context/readerioeither"
E "github.com/IBM/fp-go/either"
"github.com/IBM/fp-go/errors"
F "github.com/IBM/fp-go/function"
IOE "github.com/IBM/fp-go/ioeither"
"github.com/stretchr/testify/assert"
)
type PostItem struct {
@@ -30,6 +37,47 @@ type PostItem struct {
Body string `json:"body"`
}
func getTitle(item PostItem) string {
return item.Title
}
type simpleRequestBuilder struct {
method string
url string
headers H.Header
}
func requestBuilder() simpleRequestBuilder {
return simpleRequestBuilder{method: "GET"}
}
func (b simpleRequestBuilder) WithURL(url string) simpleRequestBuilder {
b.url = url
return b
}
func (b simpleRequestBuilder) WithHeader(key, value string) simpleRequestBuilder {
if b.headers == nil {
b.headers = make(H.Header)
} else {
b.headers = b.headers.Clone()
}
b.headers.Set(key, value)
return b
}
func (b simpleRequestBuilder) Build() R.ReaderIOEither[*H.Request] {
return func(ctx context.Context) IOE.IOEither[error, *H.Request] {
return IOE.TryCatchError(func() (*H.Request, error) {
req, err := H.NewRequestWithContext(ctx, b.method, b.url, nil)
if err == nil {
req.Header = b.headers
}
return req, err
})
}
}
func TestSendSingleRequest(t *testing.T) {
client := MakeClient(H.DefaultClient)
@@ -40,7 +88,70 @@ func TestSendSingleRequest(t *testing.T) {
resp1 := readItem(req1)
resE := resp1(context.Background())()
resE := resp1(context.TODO())()
fmt.Println(resE)
}
// setHeaderUnsafe updates a header value in a request object by mutating the request object
func setHeaderUnsafe(key, value string) func(*H.Request) *H.Request {
return func(req *H.Request) *H.Request {
req.Header.Set(key, value)
return req
}
}
func TestSendSingleRequestWithHeaderUnsafe(t *testing.T) {
client := MakeClient(H.DefaultClient)
// this is not safe from a puristic perspective, because the map call mutates the request object
req1 := F.Pipe2(
"https://jsonplaceholder.typicode.com/posts/1",
MakeGetRequest,
R.Map(setHeaderUnsafe("Content-Type", "text/html")),
)
readItem := ReadJson[PostItem](client)
resp1 := F.Pipe2(
req1,
readItem,
R.Map(getTitle),
)
res := F.Pipe1(
resp1(context.TODO())(),
E.GetOrElse(errors.ToString),
)
assert.Equal(t, "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", res)
}
func TestSendSingleRequestWithHeaderSafe(t *testing.T) {
client := MakeClient(H.DefaultClient)
// the request builder assembles config values to construct
// the final http request. Each `With` step creates a copy of the settings
// so the flow is pure
request := requestBuilder().
WithURL("https://jsonplaceholder.typicode.com/posts/1").
WithHeader("Content-Type", "text/html").
Build()
readItem := ReadJson[PostItem](client)
response := F.Pipe2(
request,
readItem,
R.Map(getTitle),
)
res := F.Pipe1(
response(context.TODO())(),
E.GetOrElse(errors.ToString),
)
assert.Equal(t, "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", res)
}

View File

@@ -44,3 +44,15 @@ func SequenceArrayG[GA ~[]A, GOA ~[]Either[E, A], E, A any](ma GOA) Either[E, GA
func SequenceArray[E, A any](ma []Either[E, A]) Either[E, []A] {
return SequenceArrayG[[]A](ma)
}
// CompactArrayG discards the none values and keeps the some values
func CompactArrayG[A1 ~[]Either[E, A], A2 ~[]A, E, A any](fa A1) A2 {
return RA.Reduce(fa, func(out A2, value Either[E, A]) A2 {
return MonadFold(value, F.Constant1[E](out), F.Bind1st(RA.Append[A2, A], out))
}, make(A2, len(fa)))
}
// CompactArray discards the none values and keeps the some values
func CompactArray[E, A any](fa []Either[E, A]) []A {
return CompactArrayG[[]Either[E, A], []A](fa)
}

View File

@@ -91,11 +91,11 @@ func MonadChainTo[E, A, B any](ma Either[E, A], mb Either[E, B]) Either[E, B] {
return mb
}
func MonadChainOptionK[E, A, B any](onNone func() E, ma Either[E, A], f func(A) O.Option[B]) Either[E, B] {
func MonadChainOptionK[A, B, E any](onNone func() E, ma Either[E, A], f func(A) O.Option[B]) Either[E, B] {
return MonadChain(ma, F.Flow2(f, FromOption[E, B](onNone)))
}
func ChainOptionK[E, A, B any](onNone func() E) func(func(A) O.Option[B]) func(Either[E, A]) Either[E, B] {
func ChainOptionK[A, B, E any](onNone func() E) func(func(A) O.Option[B]) func(Either[E, A]) Either[E, B] {
from := FromOption[E, B](onNone)
return func(f func(A) O.Option[B]) func(Either[E, A]) Either[E, B] {
return Chain(F.Flow2(f, from))
@@ -209,7 +209,7 @@ func OrElse[E, A any](onLeft func(e E) Either[E, A]) func(Either[E, A]) Either[E
return Fold(onLeft, Of[E, A])
}
func ToType[E, A any](onError func(any) E) func(any) Either[E, A] {
func ToType[A, E any](onError func(any) E) func(any) Either[E, A] {
return func(value any) Either[E, A] {
return F.Pipe2(
value,

View File

@@ -100,7 +100,7 @@ func TestChainFirst(t *testing.T) {
}
func TestChainOptionK(t *testing.T) {
f := ChainOptionK[string, int, int](F.Constant("a"))(func(n int) O.Option[int] {
f := ChainOptionK[int, int](F.Constant("a"))(func(n int) O.Option[int] {
if n > 0 {
return O.Some(n)
}

View File

@@ -43,3 +43,24 @@ func SequenceRecordG[GA ~map[K]A, GOA ~map[K]Either[E, A], K comparable, E, A an
func SequenceRecord[K comparable, E, A any](ma map[K]Either[E, A]) Either[E, map[K]A] {
return SequenceRecordG[map[K]A](ma)
}
func upsertAtReadWrite[M ~map[K]V, K comparable, V any](r M, k K, v V) M {
r[k] = v
return r
}
// CompactRecordG discards the noe values and keeps the some values
func CompactRecordG[M1 ~map[K]Either[E, A], M2 ~map[K]A, K comparable, E, A any](m M1) M2 {
out := make(M2)
onLeft := F.Constant1[E](out)
return RR.ReduceWithIndex(m, func(key K, _ M2, value Either[E, A]) M2 {
return MonadFold(value, onLeft, func(v A) M2 {
return upsertAtReadWrite(out, key, v)
})
}, out)
}
// CompactRecord discards all none values and keeps the somes
func CompactRecord[K comparable, E, A any](m map[K]Either[E, A]) map[K]A {
return CompactRecordG[map[K]Either[E, A], map[K]A](m)
}

37
either/record_test.go Normal file
View 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)
}

View File

@@ -27,13 +27,13 @@ HKTRB = HKT<Either[B]>
HKTA = HKT<A>
HKTB = HKT<B>
*/
func traverse[E, A, B, HKTA, HKTB, HKTRB any](
_of func(Either[E, B]) HKTRB,
_map func(HKTB, func(B) Either[E, B]) HKTRB,
func traverse[E, A, B, HKTB, HKTRB any](
mof func(Either[E, B]) HKTRB,
mmap func(func(B) Either[E, B]) func(HKTB) HKTRB,
) func(Either[E, A], func(A) HKTB) HKTRB {
left := F.Flow2(Left[B, E], _of)
right := F.Bind2nd(_map, Right[E, B])
left := F.Flow2(Left[B, E], mof)
right := mmap(Right[E, B])
return func(ta Either[E, A], f func(A) HKTB) HKTRB {
return MonadFold(ta,
@@ -43,27 +43,21 @@ func traverse[E, A, B, HKTA, HKTB, HKTRB any](
}
}
func Traverse[E, A, B, HKTA, HKTB, HKTRB any](
_of func(Either[E, B]) HKTRB,
_map func(HKTB, func(B) Either[E, B]) HKTRB,
// Traverse converts an [Either] of some higher kinded type into the higher kinded type of an [Either]
func Traverse[A, E, B, HKTB, HKTRB any](
mof func(Either[E, B]) HKTRB,
mmap func(func(B) Either[E, B]) func(HKTB) HKTRB,
) func(func(A) HKTB) func(Either[E, A]) HKTRB {
delegate := traverse[E, A, B, HKTA](_of, _map)
delegate := traverse[E, A, B](mof, mmap)
return func(f func(A) HKTB) func(Either[E, A]) HKTRB {
return F.Bind2nd(delegate, f)
}
}
/*
*
We need to pass the members of the applicative explicitly, because golang does neither support higher kinded types nor template methods on structs or interfaces
HKTRA = HKT<Either[A]>
HKTA = HKT<A>
HKTB = HKT<B>
*/
// Sequence converts an [Either] of some higher kinded type into the higher kinded type of an [Either]
func Sequence[E, A, HKTA, HKTRA any](
_of func(Either[E, A]) HKTRA,
_map func(HKTA, func(A) Either[E, A]) HKTRA,
mof func(Either[E, A]) HKTRA,
mmap func(func(A) Either[E, A]) func(HKTA) HKTRA,
) func(Either[E, HKTA]) HKTRA {
return Fold(F.Flow2(Left[A, E], _of), F.Bind2nd(_map, Right[E, A]))
return Fold(F.Flow2(Left[A, E], mof), mmap(Right[E, A]))
}

View File

@@ -30,9 +30,9 @@ func TestTraverse(t *testing.T) {
}
return O.None[int]()
}
trav := Traverse[string, int, int, O.Option[Either[string, int]]](
trav := Traverse[int](
O.Of[Either[string, int]],
O.MonadMap[int, Either[string, int]],
O.Map[int, Either[string, int]],
)(f)
assert.Equal(t, O.Of(Left[int]("a")), F.Pipe1(Left[int]("a"), trav))
@@ -44,7 +44,7 @@ func TestSequence(t *testing.T) {
seq := Sequence(
O.Of[Either[string, int]],
O.MonadMap[int, Either[string, int]],
O.Map[int, Either[string, int]],
)
assert.Equal(t, O.Of(Right[string](1)), seq(Right[string](O.Of(1))))

View File

@@ -19,12 +19,12 @@ import (
G "github.com/IBM/fp-go/function/generic"
)
// Cache converts a unary function into a unary function that caches the value depending on the parameter
func Cache[K comparable, T any](f func(K) T) func(K) T {
return G.Cache(f)
// Memoize converts a unary function into a unary function that caches the value depending on the parameter
func Memoize[K comparable, T any](f func(K) T) func(K) T {
return G.Memoize(f)
}
// ContramapCache converts a unary function into a unary function that caches the value depending on the parameter
func ContramapCache[A any, K comparable, T any](kf func(A) K) func(func(A) T) func(A) T {
return G.ContramapCache[func(A) T](kf)
// ContramapMemoize converts a unary function into a unary function that caches the value depending on the parameter
func ContramapMemoize[A any, K comparable, T any](kf func(A) K) func(func(A) T) func(A) T {
return G.ContramapMemoize[func(A) T](kf)
}

View File

@@ -29,7 +29,7 @@ func TestCache(t *testing.T) {
return n
}
cached := Cache(withSideEffect)
cached := Memoize(withSideEffect)
assert.Equal(t, 0, count)

View File

@@ -21,13 +21,13 @@ import (
L "github.com/IBM/fp-go/internal/lazy"
)
// Cache converts a unary function into a unary function that caches the value depending on the parameter
func Cache[F ~func(K) T, K comparable, T any](f F) F {
return ContramapCache[F](func(k K) K { return k })(f)
// Memoize converts a unary function into a unary function that caches the value depending on the parameter
func Memoize[F ~func(K) T, K comparable, T any](f F) F {
return ContramapMemoize[F](func(k K) K { return k })(f)
}
// ContramapCache converts a unary function into a unary function that caches the value depending on the parameter
func ContramapCache[F ~func(A) T, KF func(A) K, A any, K comparable, T any](kf KF) func(F) F {
// ContramapMemoize converts a unary function into a unary function that caches the value depending on the parameter
func ContramapMemoize[F ~func(A) T, KF func(A) K, A any, K comparable, T any](kf KF) func(F) F {
return CacheCallback[F](kf, getOrCreate[K, T]())
}

View File

@@ -24,3 +24,9 @@ func MagmaSub[A Number]() M.Magma[A] {
return first - second
})
}
func MagmaDiv[A Number]() M.Magma[A] {
return M.MakeMagma(func(first A, second A) A {
return first / second
})
}

View File

@@ -19,6 +19,7 @@ import (
M "github.com/IBM/fp-go/monoid"
)
// MonoidSum is the [Monoid] that adds elements with a zero empty element
func MonoidSum[A Number]() M.Monoid[A] {
s := SemigroupSum[A]()
return M.MakeMonoid(
@@ -26,3 +27,12 @@ func MonoidSum[A Number]() M.Monoid[A] {
0,
)
}
// MonoidProduct is the [Monoid] that multiplies elements with a one empty element
func MonoidProduct[A Number]() M.Monoid[A] {
s := SemigroupProduct[A]()
return M.MakeMonoid(
s.Concat,
1,
)
}

View File

@@ -24,3 +24,9 @@ func SemigroupSum[A Number]() S.Semigroup[A] {
return first + second
})
}
func SemigroupProduct[A Number]() S.Semigroup[A] {
return S.MakeSemigroup(func(first A, second A) A {
return first * second
})
}

View File

@@ -24,16 +24,30 @@ type Number interface {
}
// Add is a curried function used to add two numbers
func Add[T Number](left T) func(T) T {
return func(right T) T {
func Add[T Number](right T) func(T) T {
return func(left T) T {
return left + right
}
}
// Mul is a curried function used to add two numbers
func Mul[T Number](coeff T) func(T) T {
return func(value T) T {
return coeff * value
// Sub is a curried function used to subtract two numbers
func Sub[T Number](right T) func(T) T {
return func(left T) T {
return left - right
}
}
// Mul is a curried function used to multiply two numbers
func Mul[T Number](right T) func(T) T {
return func(left T) T {
return left * right
}
}
// Div is a curried function used to divide two numbers
func Div[T Number](right T) func(T) T {
return func(left T) T {
return left / right
}
}

View File

@@ -67,7 +67,7 @@ func Modify[S, A any](f func(A) A) func(Iso[S, A]) func(S) S {
}
// Wrap wraps the value
func Unwrap[S, A any](s S) func(Iso[S, A]) A {
func Unwrap[A, S any](s S) func(Iso[S, A]) A {
return func(sa Iso[S, A]) A {
return sa.Get(s)
}
@@ -81,8 +81,8 @@ func Wrap[S, A any](a A) func(Iso[S, A]) S {
}
// From wraps the value
func To[S, A any](s S) func(Iso[S, A]) A {
return Unwrap[S, A](s)
func To[A, S any](s S) func(Iso[S, A]) A {
return Unwrap[A, S](s)
}
// To unwraps the value

View File

@@ -121,7 +121,7 @@ func Compose[S, A, B any](ab Lens[A, B]) func(Lens[S, A]) Lens[S, B] {
// the getter returns an `Option[B]` because the container `A` could already be an option
// if the setter is invoked with `Some[B]` then the value of `B` will be set, potentially on a default value of `A` if `A` did not exist
// if the setter is invoked with `None[B]` then the container `A` is reset to `None[A]` because this is the only way to remove `B`
func ComposeOption[S, A, B any](defaultA A) func(ab Lens[A, B]) func(Lens[S, O.Option[A]]) Lens[S, O.Option[B]] {
func ComposeOption[S, B, A any](defaultA A) func(ab Lens[A, B]) func(Lens[S, O.Option[A]]) Lens[S, O.Option[B]] {
defa := F.Constant(defaultA)
return func(ab Lens[A, B]) func(Lens[S, O.Option[A]]) Lens[S, O.Option[B]] {
foldab := O.Fold(O.None[B], F.Flow2(ab.Get, O.Some[B]))
@@ -172,7 +172,7 @@ func ComposeOption[S, A, B any](defaultA A) func(ab Lens[A, B]) func(Lens[S, O.O
// if the setter is called with `Some[B]` and `A` does not exist, the default of 'A' is updated with `B`
// if the setter is called with `None[B]` and `A` does not exist this is the identity operation on 'S'
// if the setter is called with `None[B]` and `A` does exist, 'B' is removed from 'A'
func ComposeOptions[S, A, B any](defaultA A) func(ab Lens[A, O.Option[B]]) func(Lens[S, O.Option[A]]) Lens[S, O.Option[B]] {
func ComposeOptions[S, B, A any](defaultA A) func(ab Lens[A, O.Option[B]]) func(Lens[S, O.Option[A]]) Lens[S, O.Option[B]] {
defa := F.Constant(defaultA)
noops := F.Constant(F.Identity[S])
noneb := O.None[B]()

View File

@@ -209,7 +209,7 @@ func TestComposeOption(t *testing.T) {
// compose lenses
lens := F.Pipe1(
inner,
ComposeOption[Outer, *Inner, int](defaultInner)(value),
ComposeOption[Outer, int](defaultInner)(value),
)
outer1 := Outer{inner: &Inner{Value: 1, Foo: "a"}}
// the checks
@@ -235,7 +235,7 @@ func TestComposeOptions(t *testing.T) {
// compose lenses
lens := F.Pipe1(
inner,
ComposeOptions[OuterOpt, *InnerOpt, *int](defaultInner)(value),
ComposeOptions[OuterOpt, *int](defaultInner)(value),
)
// additional settings
defaultValue2 := 2

View File

@@ -24,7 +24,7 @@ import (
)
// AtRecord returns a lens that focusses on a value in a record
func AtRecord[M ~map[K]V, K comparable, V any](key K) L.Lens[M, O.Option[V]] {
func AtRecord[M ~map[K]V, V any, K comparable](key K) L.Lens[M, O.Option[V]] {
addKey := F.Bind1of2(RR.UpsertAt[M, K, V])(key)
delKey := F.Bind1of1(RR.DeleteAt[M, K, V])(key)
fold := O.Fold(
@@ -44,6 +44,6 @@ func AtRecord[M ~map[K]V, K comparable, V any](key K) L.Lens[M, O.Option[V]] {
}
// AtKey returns a `Lens` focused on a required key of a `ReadonlyRecord`
func AtKey[M ~map[K]V, S any, K comparable, V any](key K) func(sa L.Lens[S, M]) L.Lens[S, O.Option[V]] {
func AtKey[M ~map[K]V, S any, V any, K comparable](key K) func(sa L.Lens[S, M]) L.Lens[S, O.Option[V]] {
return L.Compose[S](AtRecord[M](key))
}

View File

@@ -22,11 +22,11 @@ import (
)
// AtRecord returns a lens that focusses on a value in a record
func AtRecord[K comparable, V any](key K) L.Lens[map[K]V, O.Option[V]] {
func AtRecord[V any, K comparable](key K) L.Lens[map[K]V, O.Option[V]] {
return G.AtRecord[map[K]V](key)
}
// AtKey returns a `Lens` focused on a required key of a `ReadonlyRecord`
func AtKey[S any, K comparable, V any](key K) func(sa L.Lens[S, map[K]V]) L.Lens[S, O.Option[V]] {
func AtKey[S any, V any, K comparable](key K) func(sa L.Lens[S, map[K]V]) L.Lens[S, O.Option[V]] {
return G.AtKey[map[K]V, S](key)
}

View File

@@ -31,7 +31,7 @@ type (
func TestAtKey(t *testing.T) {
sa := F.Pipe1(
L.Id[S](),
AtKey[S, string, int]("a"),
AtKey[S, int]("a"),
)
assert.Equal(t, O.Some(1), sa.Get(S{"a": 1}))

View File

@@ -198,7 +198,7 @@ func TestOuterLensLaws(t *testing.T) {
eqValue := EQT.Eq[int]()
eqOptValue := O.Eq(eqValue)
// lens to access a value from outer
valueFromOuter := L.ComposeOption[*Outer, *Inner, int](&defaultInner)(valueLens)(outerLens)
valueFromOuter := L.ComposeOption[*Outer, int](&defaultInner)(valueLens)(outerLens)
// try to access the value, this should get an option
assert.True(t, eqOptValue.Equals(valueFromOuter.Get(&emptyOuter), O.None[int]()))
// update the object
@@ -234,7 +234,7 @@ func TestOuterOptLensLaws(t *testing.T) {
valueFromOuter := F.Pipe3(
valueOptLens,
LI.Compose[*InnerOpt](intIso),
L.ComposeOptions[*OuterOpt, *InnerOpt, int](&defaultInnerOpt),
L.ComposeOptions[*OuterOpt, int](&defaultInnerOpt),
I.Ap[L.Lens[*OuterOpt, O.Option[int]]](outerOptLens),
)

View File

@@ -44,3 +44,15 @@ func SequenceArrayG[GA ~[]A, GOA ~[]Option[A], A any](ma GOA) Option[GA] {
func SequenceArray[A any](ma []Option[A]) Option[[]A] {
return SequenceArrayG[[]A](ma)
}
// CompactArrayG discards the none values and keeps the some values
func CompactArrayG[A1 ~[]Option[A], A2 ~[]A, A any](fa A1) A2 {
return RA.Reduce(fa, func(out A2, value Option[A]) A2 {
return MonadFold(value, F.Constant(out), F.Bind1st(RA.Append[A2, A], out))
}, make(A2, len(fa)))
}
// CompactArray discards the none values and keeps the some values
func CompactArray[A any](fa []Option[A]) []A {
return CompactArrayG[[]Option[A], []A](fa)
}

View File

@@ -44,3 +44,21 @@ func SequenceRecordG[GA ~map[K]A, GOA ~map[K]Option[A], K comparable, A any](ma
func SequenceRecord[K comparable, A any](ma map[K]Option[A]) Option[map[K]A] {
return SequenceRecordG[map[K]A](ma)
}
func upsertAtReadWrite[M ~map[K]V, K comparable, V any](r M, k K, v V) M {
r[k] = v
return r
}
// CompactRecordG discards the noe values and keeps the some values
func CompactRecordG[M1 ~map[K]Option[A], M2 ~map[K]A, K comparable, A any](m M1) M2 {
bnd := F.Bind12of3(upsertAtReadWrite[M2])
return RR.ReduceWithIndex(m, func(key K, m M2, value Option[A]) M2 {
return MonadFold(value, F.Constant(m), bnd(m, key))
}, make(M2))
}
// CompactRecord discards the noe values and keeps the some values
func CompactRecord[K comparable, A any](m map[K]Option[A]) map[K]A {
return CompactRecordG[map[K]Option[A], map[K]A](m)
}

View File

@@ -30,3 +30,18 @@ func TestSequenceRecord(t *testing.T) {
"b": Of("B"),
}))
}
func TestCompactRecord(t *testing.T) {
// make the map
m := make(map[string]Option[int])
m["foo"] = None[int]()
m["bar"] = Some(1)
// compact it
m1 := CompactRecord(m)
// check expected
exp := map[string]int{
"bar": 1,
}
assert.Equal(t, exp, m1)
}

View File

@@ -19,13 +19,22 @@ import (
F "github.com/IBM/fp-go/function"
)
// HKTA = HKT<A>
// HKTOA = HKT<Option<A>>
//
// Sequence converts an option of some higher kinded type into the higher kinded type of an option
// Sequence converts an [Option] of some higher kinded type into the higher kinded type of an [Option]
func Sequence[A, HKTA, HKTOA any](
_of func(Option[A]) HKTOA,
_map func(HKTA, func(A) Option[A]) HKTOA,
mof func(Option[A]) HKTOA,
mmap func(func(A) Option[A]) func(HKTA) HKTOA,
) func(Option[HKTA]) HKTOA {
return Fold(F.Nullary2(None[A], _of), F.Bind2nd(_map, Some[A]))
return Fold(F.Nullary2(None[A], mof), mmap(Some[A]))
}
// Traverse converts an [Option] of some higher kinded type into the higher kinded type of an [Option]
func Traverse[A, B, HKTB, HKTOB any](
mof func(Option[B]) HKTOB,
mmap func(func(B) Option[B]) func(HKTB) HKTOB,
) func(func(A) HKTB) func(Option[A]) HKTOB {
onNone := F.Nullary2(None[B], mof)
onSome := mmap(Some[B])
return func(f func(A) HKTB) func(Option[A]) HKTOB {
return Fold(onNone, F.Flow2(f, onSome))
}
}

View File

@@ -23,7 +23,7 @@ import (
O "github.com/IBM/fp-go/option"
)
// AssertLaws asserts the apply monad laws for the `Either` monad
// AssertLaws asserts the apply monad laws for the [Option] monad
func AssertLaws[A, B, C any](t *testing.T,
eqa EQ.Eq[A],
eqb EQ.Eq[B],

View File

@@ -131,7 +131,7 @@ func FromStrictCompare[A C.Ordered]() Ord[A] {
return MakeOrd(strictCompare[A], strictEq[A])
}
// Lt tests whether one value is _strictly less than_ another
// Lt tests whether one value is strictly less than another
func Lt[A any](O Ord[A]) func(A) func(A) bool {
return func(second A) func(A) bool {
return func(first A) bool {
@@ -140,7 +140,7 @@ func Lt[A any](O Ord[A]) func(A) func(A) bool {
}
}
// Leq Tests whether one value is less or equal than_ another
// Leq Tests whether one value is less or equal than another
func Leq[A any](O Ord[A]) func(A) func(A) bool {
return func(second A) func(A) bool {
return func(first A) bool {
@@ -150,9 +150,9 @@ func Leq[A any](O Ord[A]) func(A) func(A) bool {
}
/**
* Test whether one value is _strictly greater than_ another
* Test whether one value is strictly greater than another
*/
func cc[A any](O Ord[A]) func(A) func(A) bool {
func Gt[A any](O Ord[A]) func(A) func(A) bool {
return func(second A) func(A) bool {
return func(first A) bool {
return O.Compare(first, second) > 0
@@ -160,7 +160,7 @@ func cc[A any](O Ord[A]) func(A) func(A) bool {
}
}
// Geq tests whether one value is greater or equal than_ another
// Geq tests whether one value is greater or equal than another
func Geq[A any](O Ord[A]) func(A) func(A) bool {
return func(second A) func(A) bool {
return func(first A) bool {

View File

@@ -32,7 +32,7 @@ func FromEither[E, L, A any](e ET.Either[L, A]) ReaderEither[E, L, A] {
return G.FromEither[ReaderEither[E, L, A]](e)
}
func RightReader[E, L, A any](r R.Reader[E, A]) ReaderEither[E, L, A] {
func RightReader[L, E, A any](r R.Reader[E, A]) ReaderEither[E, L, A] {
return G.RightReader[R.Reader[E, A], ReaderEither[E, L, A]](r)
}
@@ -76,7 +76,7 @@ func MonadAp[E, L, A, B any](fab ReaderEither[E, L, func(A) B], fa ReaderEither[
return G.MonadAp[ReaderEither[E, L, A], ReaderEither[E, L, B], ReaderEither[E, L, func(A) B]](fab, fa)
}
func Ap[E, L, A, B any](fa ReaderEither[E, L, A]) func(ReaderEither[E, L, func(A) B]) ReaderEither[E, L, B] {
func Ap[B, E, L, A any](fa ReaderEither[E, L, A]) func(ReaderEither[E, L, func(A) B]) ReaderEither[E, L, B] {
return G.Ap[ReaderEither[E, L, A], ReaderEither[E, L, B], ReaderEither[E, L, func(A) B]](fa)
}
@@ -96,7 +96,7 @@ func OrElse[E, L1, A, L2 any](onLeft func(L1) ReaderEither[E, L2, A]) func(Reade
return G.OrElse[ReaderEither[E, L1, A]](onLeft)
}
func OrLeft[L1, E, L2, A any](onLeft func(L1) R.Reader[E, L2]) func(ReaderEither[E, L1, A]) ReaderEither[E, L2, A] {
func OrLeft[A, L1, E, L2 any](onLeft func(L1) R.Reader[E, L2]) func(ReaderEither[E, L1, A]) ReaderEither[E, L2, A] {
return G.OrLeft[ReaderEither[E, L1, A], ReaderEither[E, L2, A]](onLeft)
}
@@ -104,19 +104,19 @@ func Ask[E, L any]() ReaderEither[E, L, E] {
return G.Ask[ReaderEither[E, L, E]]()
}
func Asks[E, L, A any](r R.Reader[E, A]) ReaderEither[E, L, A] {
func Asks[L, E, A any](r R.Reader[E, A]) ReaderEither[E, L, A] {
return G.Asks[R.Reader[E, A], ReaderEither[E, L, A]](r)
}
func MonadChainEitherK[E, L, A, B any](ma ReaderEither[E, L, A], f func(A) ET.Either[L, B]) ReaderEither[E, L, B] {
func MonadChainEitherK[A, B, L, E any](ma ReaderEither[E, L, A], f func(A) ET.Either[L, B]) ReaderEither[E, L, B] {
return G.MonadChainEitherK[ReaderEither[E, L, A], ReaderEither[E, L, B]](ma, f)
}
func ChainEitherK[E, L, A, B any](f func(A) ET.Either[L, B]) func(ma ReaderEither[E, L, A]) ReaderEither[E, L, B] {
func ChainEitherK[A, B, L, E any](f func(A) ET.Either[L, B]) func(ma ReaderEither[E, L, A]) ReaderEither[E, L, B] {
return G.ChainEitherK[ReaderEither[E, L, A], ReaderEither[E, L, B]](f)
}
func ChainOptionK[E, L, A, B any](onNone func() L) func(func(A) O.Option[B]) func(ReaderEither[E, L, A]) ReaderEither[E, L, B] {
func ChainOptionK[E, A, B, L any](onNone func() L) func(func(A) O.Option[B]) func(ReaderEither[E, L, A]) ReaderEither[E, L, B] {
return G.ChainOptionK[ReaderEither[E, L, A], ReaderEither[E, L, B]](onNone)
}
@@ -135,11 +135,11 @@ func BiMap[E, E1, E2, A, B any](f func(E1) E2, g func(A) B) func(ReaderEither[E,
// Local changes the value of the local context during the execution of the action `ma` (similar to `Contravariant`'s
// `contramap`).
func Local[R2, R1, E, A any](f func(R2) R1) func(ReaderEither[R1, E, A]) ReaderEither[R2, E, A] {
func Local[E, A, R2, R1 any](f func(R2) R1) func(ReaderEither[R1, E, A]) ReaderEither[R2, E, A] {
return G.Local[ReaderEither[R1, E, A], ReaderEither[R2, E, A]](f)
}
// Read applies a context to a reader to obtain its value
func Read[E, E1, A any](e E) func(ReaderEither[E, E1, A]) ET.Either[E1, A] {
func Read[E1, A, E any](e E) func(ReaderEither[E, E1, A]) ET.Either[E1, A] {
return G.Read[ReaderEither[E, E1, A]](e)
}

View File

@@ -42,7 +42,7 @@ func TestMap(t *testing.T) {
func TestAp(t *testing.T) {
g := F.Pipe1(
Of[MyContext, error](utils.Double),
Ap[MyContext, error, int, int](Of[MyContext, error](1)),
Ap[int](Of[MyContext, error](1)),
)
assert.Equal(t, ET.Of[error](2), g(defaultContext))

View File

@@ -93,7 +93,7 @@ func MonadChainReaderK[R, E, A, B any](ma ReaderIOEither[R, E, A], f func(A) RD.
return G.MonadChainReaderK[ReaderIOEither[R, E, A], ReaderIOEither[R, E, B]](ma, f)
}
func ChainReaderK[R, E, A, B any](f func(A) RD.Reader[R, B]) func(ReaderIOEither[R, E, A]) ReaderIOEither[R, E, B] {
func ChainReaderK[E, R, A, B any](f func(A) RD.Reader[R, B]) func(ReaderIOEither[R, E, A]) ReaderIOEither[R, E, B] {
return G.ChainReaderK[ReaderIOEither[R, E, A], ReaderIOEither[R, E, B]](f)
}
@@ -121,7 +121,7 @@ func ChainFirstIOK[R, E, A, B any](f func(A) io.IO[B]) func(ma ReaderIOEither[R,
return G.ChainFirstIOK[ReaderIOEither[R, E, A]](f)
}
func ChainOptionK[R, E, A, B any](onNone func() E) func(func(A) O.Option[B]) func(ReaderIOEither[R, E, A]) ReaderIOEither[R, E, B] {
func ChainOptionK[R, A, B, E any](onNone func() E) func(func(A) O.Option[B]) func(ReaderIOEither[R, E, A]) ReaderIOEither[R, E, B] {
return G.ChainOptionK[ReaderIOEither[R, E, A], ReaderIOEither[R, E, B]](onNone)
}
@@ -149,7 +149,7 @@ func Left[R, A, E any](e E) ReaderIOEither[R, E, A] {
return G.Left[ReaderIOEither[R, E, A]](e)
}
func ThrowError[R, E, A any](e E) ReaderIOEither[R, E, A] {
func ThrowError[R, A, E any](e E) ReaderIOEither[R, E, A] {
return G.ThrowError[ReaderIOEither[R, E, A]](e)
}
@@ -166,7 +166,7 @@ func FromEither[R, E, A any](t ET.Either[E, A]) ReaderIOEither[R, E, A] {
return G.FromEither[ReaderIOEither[R, E, A]](t)
}
func RightReader[R, E, A any](ma RD.Reader[R, A]) ReaderIOEither[R, E, A] {
func RightReader[E, R, A any](ma RD.Reader[R, A]) ReaderIOEither[R, E, A] {
return G.RightReader[RD.Reader[R, A], ReaderIOEither[R, E, A]](ma)
}
@@ -174,7 +174,7 @@ func LeftReader[A, R, E any](ma RD.Reader[R, E]) ReaderIOEither[R, E, A] {
return G.LeftReader[RD.Reader[R, E], ReaderIOEither[R, E, A]](ma)
}
func FromReader[R, E, A any](ma RD.Reader[R, A]) ReaderIOEither[R, E, A] {
func FromReader[E, R, A any](ma RD.Reader[R, A]) ReaderIOEither[R, E, A] {
return G.FromReader[RD.Reader[R, A], ReaderIOEither[R, E, A]](ma)
}
@@ -202,11 +202,11 @@ func Ask[R, E any]() ReaderIOEither[R, E, R] {
return G.Ask[ReaderIOEither[R, E, R]]()
}
func Asks[R, E, A any](r RD.Reader[R, A]) ReaderIOEither[R, E, A] {
func Asks[E, R, A any](r RD.Reader[R, A]) ReaderIOEither[R, E, A] {
return G.Asks[RD.Reader[R, A], ReaderIOEither[R, E, A]](r)
}
func FromOption[R, E, A any](onNone func() E) func(O.Option[A]) ReaderIOEither[R, E, A] {
func FromOption[R, A, E any](onNone func() E) func(O.Option[A]) ReaderIOEither[R, E, A] {
return G.FromOption[ReaderIOEither[R, E, A]](onNone)
}
@@ -226,7 +226,7 @@ func OrElse[R, E1, A, E2 any](onLeft func(E1) ReaderIOEither[R, E2, A]) func(Rea
return G.OrElse[ReaderIOEither[R, E1, A]](onLeft)
}
func OrLeft[E1, R, E2, A any](onLeft func(E1) RIO.ReaderIO[R, E2]) func(ReaderIOEither[R, E1, A]) ReaderIOEither[R, E2, A] {
func OrLeft[A, E1, R, E2 any](onLeft func(E1) RIO.ReaderIO[R, E2]) func(ReaderIOEither[R, E1, A]) ReaderIOEither[R, E2, A] {
return G.OrLeft[ReaderIOEither[R, E1, A], RIO.ReaderIO[R, E2], ReaderIOEither[R, E2, A]](onLeft)
}

View File

@@ -39,7 +39,7 @@ func TestMap(t *testing.T) {
}
func TestOrLeft(t *testing.T) {
f := OrLeft[string, context.Context, string, int](func(s string) RIO.ReaderIO[context.Context, string] {
f := OrLeft[int](func(s string) RIO.ReaderIO[context.Context, string] {
return RIO.Of[context.Context](s + "!")
})
@@ -70,7 +70,7 @@ func TestChainReaderK(t *testing.T) {
g := F.Pipe1(
Of[context.Context, error](1),
ChainReaderK[context.Context, error](func(v int) R.Reader[context.Context, string] {
ChainReaderK[error](func(v int) R.Reader[context.Context, string] {
return R.Of[context.Context](fmt.Sprintf("%d", v))
}),
)

View File

@@ -186,6 +186,13 @@ func MapRefWithIndex[M ~map[K]V, N ~map[K]R, K comparable, V, R any](f func(K, *
return F.Bind2nd(MonadMapRefWithIndex[M, N, K, V, R], f)
}
func MonadLookup[M ~map[K]V, K comparable, V any](m M, k K) O.Option[V] {
if val, ok := m[k]; ok {
return O.Some(val)
}
return O.None[V]()
}
func Lookup[M ~map[K]V, K comparable, V any](k K) func(M) O.Option[V] {
n := O.None[V]()
return func(m M) O.Option[V] {

View File

@@ -102,6 +102,11 @@ func Lookup[V any, K comparable](k K) func(map[K]V) O.Option[V] {
return G.Lookup[map[K]V](k)
}
// MonadLookup returns the entry for a key in a map if it exists
func MonadLookup[V any, K comparable](m map[K]V, k K) O.Option[V] {
return G.MonadLookup[map[K]V](m, k)
}
// Has tests if a key is contained in a map
func Has[K comparable, V any](k K, r map[K]V) bool {
return G.Has(k, r)

View File

@@ -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
}

View File

@@ -16,9 +16,12 @@
package mostlyadequate
import (
"fmt"
"math"
"regexp"
"strings"
A "github.com/IBM/fp-go/array"
F "github.com/IBM/fp-go/function"
N "github.com/IBM/fp-go/number"
I "github.com/IBM/fp-go/number/integer"
@@ -26,8 +29,9 @@ import (
)
var (
Match = F.Curry2((*regexp.Regexp).FindStringSubmatch)
Split = F.Curry2(F.Bind3of3((*regexp.Regexp).Split)(-1))
Match = F.Curry2((*regexp.Regexp).FindStringSubmatch)
Matches = F.Curry2((*regexp.Regexp).MatchString)
Split = F.Curry2(F.Bind3of3((*regexp.Regexp).Split)(-1))
Add = N.Add[int]
ToString = I.ToString
@@ -44,3 +48,36 @@ func Replace(search *regexp.Regexp) func(replace string) func(s string) string {
}
}
}
func Example_solution04A() {
// words :: String -> [String]
words := Split(regexp.MustCompile(` `))
fmt.Println(words("Jingle bells Batman smells"))
// Output:
// [Jingle bells Batman smells]
}
func Example_solution04B() {
// filterQs :: [String] -> [String]
filterQs := A.Filter(Matches(regexp.MustCompile(`q`)))
fmt.Println(filterQs(A.From("quick", "camels", "quarry", "over", "quails")))
// Output:
// [quick quarry quails]
}
func Example_solution04C() {
keepHighest := N.Max[int]
// max :: [Number] -> Number
max := A.Reduce(keepHighest, math.MinInt)
fmt.Println(max(A.From(323, 523, 554, 123, 5234)))
// Output:
// 5234
}

View File

@@ -21,6 +21,9 @@ import (
A "github.com/IBM/fp-go/array"
F "github.com/IBM/fp-go/function"
I "github.com/IBM/fp-go/number/integer"
O "github.com/IBM/fp-go/option"
"github.com/IBM/fp-go/ord"
S "github.com/IBM/fp-go/string"
)
@@ -58,3 +61,50 @@ func Example_pipe() {
// Output: SEND IN THE CLOWNS!
}
func Example_solution05A() {
IsLastInStock := F.Flow2(
A.Last[Car],
O.Map(Car.getInStock),
)
fmt.Println(IsLastInStock(Cars[0:3]))
fmt.Println(IsLastInStock(Cars[3:]))
// Output:
// Some[bool](true)
// Some[bool](false)
}
func Example_solution05B() {
// averageDollarValue :: [Car] -> Int
averageDollarValue := F.Flow2(
A.Map(Car.getDollarValue),
average,
)
fmt.Println(averageDollarValue(Cars))
// Output:
// 790700
}
func Example_solution05C() {
// order by horsepower
ordByHorsepower := ord.Contramap(Car.getHorsepower)(I.Ord)
// fastestCar :: [Car] -> Option[String]
fastestCar := F.Flow3(
A.Sort(ordByHorsepower),
A.Last[Car],
O.Map(F.Flow2(
Car.getName,
S.Format[string]("%s is the fastest"),
)),
)
fmt.Println(fastestCar(Cars))
// Output:
// Some[string](Aston Martin One-77 is the fastest)
}

View 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
}

View File

@@ -19,9 +19,12 @@ import (
"fmt"
"time"
A "github.com/IBM/fp-go/array"
E "github.com/IBM/fp-go/either"
"github.com/IBM/fp-go/errors"
F "github.com/IBM/fp-go/function"
I "github.com/IBM/fp-go/identity"
IOE "github.com/IBM/fp-go/ioeither"
N "github.com/IBM/fp-go/number"
O "github.com/IBM/fp-go/option"
"github.com/IBM/fp-go/ord"
@@ -40,6 +43,45 @@ func getBalance(a Account) float32 {
return a.Balance
}
type (
Chapter08User struct {
Id int
Name string
Active bool
Saved bool
}
)
var (
albert08 = Chapter08User{
Id: 1,
Active: true,
Name: "Albert",
}
gary08 = Chapter08User{
Id: 2,
Active: false,
Name: "Gary",
}
theresa08 = Chapter08User{
Id: 3,
Active: true,
Name: "Theresa",
}
yi08 = Chapter08User{Id: 4, Name: "Yi", Active: true}
)
func (u Chapter08User) getName() string {
return u.Name
}
func (u Chapter08User) isActive() bool {
return u.Active
}
var (
ordFloat32 = ord.FromStrictCompare[float32]()
UpdateLedger = F.Identity[Account]
@@ -55,6 +97,33 @@ var (
Withdraw(20),
O.Fold(F.Constant("You're broke!"), FinishTransaction),
)
// showWelcome :: User -> String
showWelcome = F.Flow2(
Chapter08User.getName,
S.Format[string]("Welcome %s"),
)
// checkActive :: User -> Either error User
checkActive = E.FromPredicate(Chapter08User.isActive, F.Constant1[Chapter08User](fmt.Errorf("Your account is not active")))
// validateUser :: (User -> Either String ()) -> User -> Either String User
validateUser = F.Curry2(func(validate func(Chapter08User) E.Either[error, any], user Chapter08User) E.Either[error, Chapter08User] {
return F.Pipe2(
user,
validate,
E.MapTo[error, any](user),
)
})
// save :: User -> IOEither error User
save = func(user Chapter08User) IOE.IOEither[error, Chapter08User] {
return IOE.FromIO[error](func() Chapter08User {
var u = user
u.Saved = true
return u
})
}
)
func Withdraw(amount float32) func(account Account) O.Option[Account] {
@@ -131,3 +200,77 @@ func Example_getAge() {
// Left[*time.ParseError, float64](parsing time "July 4, 2001" as "2006-01-02": cannot parse "July 4, 2001" as "2006")
// If you survive, you will be 6837
}
func Example_solution08A() {
incrF := I.Map(N.Add(1))
fmt.Println(incrF(I.Of(2)))
// Output: 3
}
func Example_solution08B() {
// initial :: User -> Option rune
initial := F.Flow3(
Chapter08User.getName,
S.ToRunes,
A.Head[rune],
)
fmt.Println(initial(albert08))
// Output:
// Some[int32](65)
}
func Example_solution08C() {
// eitherWelcome :: User -> Either String String
eitherWelcome := F.Flow2(
checkActive,
E.Map[error](showWelcome),
)
fmt.Println(eitherWelcome(gary08))
fmt.Println(eitherWelcome(theresa08))
// Output:
// Left[*errors.errorString, string](Your account is not active)
// Right[<nil>, string](Welcome Theresa)
}
func Example_solution08D() {
// // validateName :: User -> Either String ()
validateName := F.Flow3(
Chapter08User.getName,
E.FromPredicate(F.Flow2(
S.Size,
ord.Gt(ord.FromStrictCompare[int]())(3),
), errors.OnSome[string]("Your name %s is larger than 3 characters")),
E.Map[error](F.ToAny[string]),
)
saveAndWelcome := F.Flow2(
save,
IOE.Map[error](showWelcome),
)
register := F.Flow3(
validateUser(validateName),
IOE.FromEither[error, Chapter08User],
IOE.Chain(saveAndWelcome),
)
fmt.Println(validateName(gary08))
fmt.Println(validateName(yi08))
fmt.Println(register(albert08)())
fmt.Println(register(yi08)())
// Output:
// Right[<nil>, string](Gary)
// Left[*errors.errorString, <nil>](Your name Yi is larger than 3 characters)
// Right[<nil>, string](Welcome Albert)
// Left[*errors.errorString, string](Your name Yi is larger than 3 characters)
}

View File

@@ -17,10 +17,17 @@ package mostlyadequate
import (
"fmt"
"path"
"regexp"
A "github.com/IBM/fp-go/array"
E "github.com/IBM/fp-go/either"
"github.com/IBM/fp-go/errors"
F "github.com/IBM/fp-go/function"
"github.com/IBM/fp-go/io"
IOE "github.com/IBM/fp-go/ioeither"
O "github.com/IBM/fp-go/option"
S "github.com/IBM/fp-go/string"
)
type (
@@ -37,20 +44,85 @@ type (
AddressBook struct {
Addresses []Address
}
Chapter09User struct {
Id int
Name string
Address Address
}
)
func getAddresses(ab AddressBook) []Address {
var (
albert09 = Chapter09User{
Id: 1,
Name: "Albert",
Address: Address{
Street: Street{
Number: 22,
Name: "Walnut St",
},
},
}
gary09 = Chapter09User{
Id: 2,
Name: "Gary",
Address: Address{
Street: Street{
Number: 14,
},
},
}
theresa09 = Chapter09User{
Id: 3,
Name: "Theresa",
}
)
func (ab AddressBook) getAddresses() []Address {
return ab.Addresses
}
func getStreet(s Address) Street {
func (s Address) getStreet() Street {
return s.Street
}
var FirstAddressStreet = F.Flow3(
getAddresses,
A.Head[Address],
O.Map(getStreet),
func (s Street) getName() string {
return s.Name
}
func (u Chapter09User) getAddress() Address {
return u.Address
}
var (
FirstAddressStreet = F.Flow3(
AddressBook.getAddresses,
A.Head[Address],
O.Map(Address.getStreet),
)
// getFile :: IO String
getFile = io.Of("/home/mostly-adequate/ch09.md")
// pureLog :: String -> IO ()
pureLog = io.Logf[string]("%s")
// addToMailingList :: Email -> IOEither([Email])
addToMailingList = F.Flow2(
A.Of[string],
IOE.Of[error, []string],
)
// validateEmail :: Email -> Either error Email
validateEmail = E.FromPredicate(Matches(regexp.MustCompile(`\S+@\S+\.\S+`)), errors.OnSome[string]("email %s is invalid"))
// emailBlast :: [Email] -> IO ()
emailBlast = F.Flow2(
A.Intercalate(S.Monoid)(","),
IOE.Of[error, string],
)
)
func Example_street() {
@@ -62,3 +134,53 @@ func Example_street() {
// Output:
// Some[mostlyadequate.Street]({Mulburry 8402})
}
func Example_solution09A() {
// // getStreetName :: User -> Maybe String
getStreetName := F.Flow4(
Chapter09User.getAddress,
Address.getStreet,
Street.getName,
O.FromPredicate(S.IsNonEmpty),
)
fmt.Println(getStreetName(albert09))
fmt.Println(getStreetName(gary09))
fmt.Println(getStreetName(theresa09))
// Output:
// Some[string](Walnut St)
// None[string]
// None[string]
}
func Example_solution09B() {
logFilename := F.Flow2(
io.Map(path.Base),
io.ChainFirst(pureLog),
)
fmt.Println(logFilename(getFile)())
// Output:
// ch09.md
}
func Example_solution09C() {
// // joinMailingList :: Email -> Either String (IO ())
joinMailingList := F.Flow4(
validateEmail,
IOE.FromEither[error, string],
IOE.Chain(addToMailingList),
IOE.Chain(emailBlast),
)
fmt.Println(joinMailingList("sleepy@grandpa.net")())
fmt.Println(joinMailingList("notanemail")())
// Output:
// Right[<nil>, string](sleepy@grandpa.net)
// Left[*errors.errorString, string](email notanemail is invalid)
}

View 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)
}

View 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
}

View 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)))
}

View 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,
})
)

View File

@@ -20,7 +20,7 @@ import (
"strings"
F "github.com/IBM/fp-go/function"
O "github.com/IBM/fp-go/ord"
"github.com/IBM/fp-go/ord"
)
var (
@@ -31,7 +31,7 @@ var (
ToLowerCase = strings.ToLower
// Ord implements the default ordering for strings
Ord = O.FromStrictCompare[string]()
Ord = ord.FromStrictCompare[string]()
)
func Eq(left string, right string) bool {
@@ -42,6 +42,10 @@ func ToBytes(s string) []byte {
return []byte(s)
}
func ToRunes(s string) []rune {
return []rune(s)
}
func IsEmpty(s string) bool {
return len(s) == 0
}