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

Compare commits

...

20 Commits

Author SHA1 Message Date
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
Dr. Carsten Leue
fb3b1f115c fix: add examples for [Frisby's Mostly Adequate Guide] 2023-09-01 17:31:47 +02:00
Carsten Leue
ce66cf2295 Merge pull request #31 from IBM/cleue-add-cache
fix: implement simple cache for pure functions
2023-08-31 11:07:46 +02:00
Dr. Carsten Leue
80e579dd0b fix: implement simple cache for pure functions
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2023-08-31 10:27:32 +02:00
45 changed files with 1892 additions and 87 deletions

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

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

30
function/cache.go Normal file
View 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
View 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
View 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
View 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
}
}

View File

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

View File

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

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

@@ -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,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 {

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,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.

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

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

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

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

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

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

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

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,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

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
}