mirror of
https://github.com/IBM/fp-go.git
synced 2025-08-30 19:52:00 +02:00
Compare commits
19 Commits
cleue-samp
...
v1.0.24
Author | SHA1 | Date | |
---|---|---|---|
|
cb15a3d9fc | ||
|
16535605f5 | ||
|
53f4e5ebd7 | ||
|
fb91fd5dc8 | ||
|
3ccafb5302 | ||
|
52b71ef4f3 | ||
|
5d77d5bb3d | ||
|
8ba8f852fa | ||
|
29d9882d2a | ||
|
f80ca31e14 | ||
|
8692078972 | ||
|
12a4f6801c | ||
|
8650a8a600 | ||
|
fb3b1f115c | ||
|
ce66cf2295 | ||
|
80e579dd0b | ||
|
ddafd1ee57 | ||
|
45e05f25ff | ||
|
1346b9378a |
@@ -73,7 +73,7 @@ This library aims to provide a set of data types and functions that make it easy
|
||||
|
||||
The library itself also comprises many small functions, but it's admittedly harder to maintain than code that uses it. However this asymmetry is intended because it offloads complexity from users into a central component.
|
||||
|
||||
## Comparation to Idiomatic Go
|
||||
## Comparison to Idiomatic Go
|
||||
|
||||
In this section we discuss how the functional APIs differ from idiomatic go function signatures and how to convert back and forth.
|
||||
|
||||
@@ -181,7 +181,7 @@ The `Map` operation for `ReaderIOEither` is defined as:
|
||||
func Map[R, E, A, B any](f func(A) B) func(fa ReaderIOEither[R, E, A]) ReaderIOEither[R, E, B]
|
||||
```
|
||||
|
||||
and in fact the equivalent operations for all other mondas follow the same pattern, we could try to introduce a new type for `ReaderIOEither` (without a parameter) as a HKT, e.g. like so (made-up syntax, does not work in go):
|
||||
and in fact the equivalent operations for all other monads follow the same pattern, we could try to introduce a new type for `ReaderIOEither` (without a parameter) as a HKT, e.g. like so (made-up syntax, does not work in go):
|
||||
|
||||
```go
|
||||
func Map[HKT, R, E, A, B any](f func(A) B) func(HKT[R, E, A]) HKT[R, E, B]
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -27,13 +27,13 @@ HKTRB = HKT<Either[B]>
|
||||
HKTA = HKT<A>
|
||||
HKTB = HKT<B>
|
||||
*/
|
||||
func traverse[E, A, B, HKTA, HKTB, HKTRB any](
|
||||
_of func(Either[E, B]) HKTRB,
|
||||
_map func(HKTB, func(B) Either[E, B]) HKTRB,
|
||||
func traverse[E, A, B, HKTB, HKTRB any](
|
||||
mof func(Either[E, B]) HKTRB,
|
||||
mmap func(func(B) Either[E, B]) func(HKTB) HKTRB,
|
||||
) func(Either[E, A], func(A) HKTB) HKTRB {
|
||||
|
||||
left := F.Flow2(Left[B, E], _of)
|
||||
right := F.Bind2nd(_map, Right[E, B])
|
||||
left := F.Flow2(Left[B, E], mof)
|
||||
right := mmap(Right[E, B])
|
||||
|
||||
return func(ta Either[E, A], f func(A) HKTB) HKTRB {
|
||||
return MonadFold(ta,
|
||||
@@ -43,27 +43,21 @@ func traverse[E, A, B, HKTA, HKTB, HKTRB any](
|
||||
}
|
||||
}
|
||||
|
||||
func Traverse[E, A, B, HKTA, HKTB, HKTRB any](
|
||||
_of func(Either[E, B]) HKTRB,
|
||||
_map func(HKTB, func(B) Either[E, B]) HKTRB,
|
||||
// Traverse converts an [Either] of some higher kinded type into the higher kinded type of an [Either]
|
||||
func Traverse[A, E, B, HKTB, HKTRB any](
|
||||
mof func(Either[E, B]) HKTRB,
|
||||
mmap func(func(B) Either[E, B]) func(HKTB) HKTRB,
|
||||
) func(func(A) HKTB) func(Either[E, A]) HKTRB {
|
||||
delegate := traverse[E, A, B, HKTA](_of, _map)
|
||||
delegate := traverse[E, A, B](mof, mmap)
|
||||
return func(f func(A) HKTB) func(Either[E, A]) HKTRB {
|
||||
return F.Bind2nd(delegate, f)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
We need to pass the members of the applicative explicitly, because golang does neither support higher kinded types nor template methods on structs or interfaces
|
||||
|
||||
HKTRA = HKT<Either[A]>
|
||||
HKTA = HKT<A>
|
||||
HKTB = HKT<B>
|
||||
*/
|
||||
// Sequence converts an [Either] of some higher kinded type into the higher kinded type of an [Either]
|
||||
func Sequence[E, A, HKTA, HKTRA any](
|
||||
_of func(Either[E, A]) HKTRA,
|
||||
_map func(HKTA, func(A) Either[E, A]) HKTRA,
|
||||
mof func(Either[E, A]) HKTRA,
|
||||
mmap func(func(A) Either[E, A]) func(HKTA) HKTRA,
|
||||
) func(Either[E, HKTA]) HKTRA {
|
||||
return Fold(F.Flow2(Left[A, E], _of), F.Bind2nd(_map, Right[E, A]))
|
||||
return Fold(F.Flow2(Left[A, E], mof), mmap(Right[E, A]))
|
||||
}
|
||||
|
@@ -30,9 +30,9 @@ func TestTraverse(t *testing.T) {
|
||||
}
|
||||
return O.None[int]()
|
||||
}
|
||||
trav := Traverse[string, int, int, O.Option[Either[string, int]]](
|
||||
trav := Traverse[int](
|
||||
O.Of[Either[string, int]],
|
||||
O.MonadMap[int, Either[string, int]],
|
||||
O.Map[int, Either[string, int]],
|
||||
)(f)
|
||||
|
||||
assert.Equal(t, O.Of(Left[int]("a")), F.Pipe1(Left[int]("a"), trav))
|
||||
@@ -44,7 +44,7 @@ func TestSequence(t *testing.T) {
|
||||
|
||||
seq := Sequence(
|
||||
O.Of[Either[string, int]],
|
||||
O.MonadMap[int, Either[string, int]],
|
||||
O.Map[int, Either[string, int]],
|
||||
)
|
||||
|
||||
assert.Equal(t, O.Of(Right[string](1)), seq(Right[string](O.Of(1))))
|
||||
|
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"
|
||||
)
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
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 := Cache(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"
|
||||
)
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
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
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
F "github.com/IBM/fp-go/function"
|
||||
C "github.com/IBM/fp-go/internal/chain"
|
||||
L "github.com/IBM/fp-go/internal/lazy"
|
||||
)
|
||||
|
||||
// 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
|
||||
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
|
||||
}
|
||||
return L.Memoize[GA, A](ma)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 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] {
|
||||
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
|
||||
})
|
||||
}
|
||||
|
||||
func MagmaDiv[A Number]() M.Magma[A] {
|
||||
return M.MakeMagma(func(first A, second A) A {
|
||||
return first / second
|
||||
})
|
||||
}
|
||||
|
@@ -19,6 +19,7 @@ import (
|
||||
M "github.com/IBM/fp-go/monoid"
|
||||
)
|
||||
|
||||
// MonoidSum is the [Monoid] that adds elements with a zero empty element
|
||||
func MonoidSum[A Number]() M.Monoid[A] {
|
||||
s := SemigroupSum[A]()
|
||||
return M.MakeMonoid(
|
||||
@@ -26,3 +27,12 @@ func MonoidSum[A Number]() M.Monoid[A] {
|
||||
0,
|
||||
)
|
||||
}
|
||||
|
||||
// MonoidProduct is the [Monoid] that multiplies elements with a one empty element
|
||||
func MonoidProduct[A Number]() M.Monoid[A] {
|
||||
s := SemigroupProduct[A]()
|
||||
return M.MakeMonoid(
|
||||
s.Concat,
|
||||
1,
|
||||
)
|
||||
}
|
||||
|
@@ -24,3 +24,9 @@ func SemigroupSum[A Number]() S.Semigroup[A] {
|
||||
return first + second
|
||||
})
|
||||
}
|
||||
|
||||
func SemigroupProduct[A Number]() S.Semigroup[A] {
|
||||
return S.MakeSemigroup(func(first A, second A) A {
|
||||
return first * second
|
||||
})
|
||||
}
|
||||
|
@@ -24,16 +24,30 @@ type Number interface {
|
||||
}
|
||||
|
||||
// Add is a curried function used to add two numbers
|
||||
func Add[T Number](left T) func(T) T {
|
||||
return func(right T) T {
|
||||
func Add[T Number](right T) func(T) T {
|
||||
return func(left T) T {
|
||||
return left + right
|
||||
}
|
||||
}
|
||||
|
||||
// Mul is a curried function used to add two numbers
|
||||
func Mul[T Number](coeff T) func(T) T {
|
||||
return func(value T) T {
|
||||
return coeff * value
|
||||
// Sub is a curried function used to subtract two numbers
|
||||
func Sub[T Number](right T) func(T) T {
|
||||
return func(left T) T {
|
||||
return left - right
|
||||
}
|
||||
}
|
||||
|
||||
// Mul is a curried function used to multiply two numbers
|
||||
func Mul[T Number](right T) func(T) T {
|
||||
return func(left T) T {
|
||||
return left * right
|
||||
}
|
||||
}
|
||||
|
||||
// Div is a curried function used to divide two numbers
|
||||
func Div[T Number](right T) func(T) T {
|
||||
return func(left T) T {
|
||||
return left / right
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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))
|
||||
}
|
||||
}
|
||||
|
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])
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 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 {
|
||||
@@ -142,9 +140,7 @@ func Lt[A any](O Ord[A]) func(A) func(A) bool {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 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 {
|
||||
@@ -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 {
|
||||
return func(second A) func(A) bool {
|
||||
@@ -164,9 +160,7 @@ func Gt[A any](O Ord[A]) func(A) func(A) bool {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test 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 {
|
||||
|
@@ -186,6 +186,13 @@ func MapRefWithIndex[M ~map[K]V, N ~map[K]R, K comparable, V, R any](f func(K, *
|
||||
return F.Bind2nd(MonadMapRefWithIndex[M, N, K, V, R], f)
|
||||
}
|
||||
|
||||
func MonadLookup[M ~map[K]V, K comparable, V any](m M, k K) O.Option[V] {
|
||||
if val, ok := m[k]; ok {
|
||||
return O.Some(val)
|
||||
}
|
||||
return O.None[V]()
|
||||
}
|
||||
|
||||
func Lookup[M ~map[K]V, K comparable, V any](k K) func(M) O.Option[V] {
|
||||
n := O.None[V]()
|
||||
return func(m M) O.Option[V] {
|
||||
|
@@ -102,6 +102,11 @@ func Lookup[V any, K comparable](k K) func(map[K]V) O.Option[V] {
|
||||
return G.Lookup[map[K]V](k)
|
||||
}
|
||||
|
||||
// MonadLookup returns the entry for a key in a map if it exists
|
||||
func MonadLookup[V any, K comparable](m map[K]V, k K) O.Option[V] {
|
||||
return G.MonadLookup[map[K]V](m, k)
|
||||
}
|
||||
|
||||
// Has tests if a key is contained in a map
|
||||
func Has[K comparable, V any](k K, r map[K]V) bool {
|
||||
return G.Has(k, r)
|
||||
|
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"
|
||||
|
||||
F "github.com/IBM/fp-go/function"
|
||||
O "github.com/IBM/fp-go/ord"
|
||||
"github.com/IBM/fp-go/ord"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -31,7 +31,7 @@ var (
|
||||
ToLowerCase = strings.ToLower
|
||||
|
||||
// Ord implements the default ordering for strings
|
||||
Ord = O.FromStrictCompare[string]()
|
||||
Ord = ord.FromStrictCompare[string]()
|
||||
)
|
||||
|
||||
func Eq(left string, right string) bool {
|
||||
@@ -42,6 +42,10 @@ func ToBytes(s string) []byte {
|
||||
return []byte(s)
|
||||
}
|
||||
|
||||
func ToRunes(s string) []rune {
|
||||
return []rune(s)
|
||||
}
|
||||
|
||||
func IsEmpty(s string) bool {
|
||||
return len(s) == 0
|
||||
}
|
||||
|
Reference in New Issue
Block a user