mirror of
https://github.com/IBM/fp-go.git
synced 2025-07-17 01:32:23 +02:00
fix: add more mostly-adequate examples and solutions
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
This commit is contained in:
@ -27,13 +27,13 @@ HKTRB = HKT<Either[B]>
|
||||
HKTA = HKT<A>
|
||||
HKTB = HKT<B>
|
||||
*/
|
||||
func traverse[E, A, B, HKTA, HKTB, HKTRB any](
|
||||
_of func(Either[E, B]) HKTRB,
|
||||
_map func(HKTB, func(B) Either[E, B]) HKTRB,
|
||||
func traverse[E, A, B, HKTB, HKTRB any](
|
||||
mof func(Either[E, B]) HKTRB,
|
||||
mmap func(func(B) Either[E, B]) func(HKTB) HKTRB,
|
||||
) func(Either[E, A], func(A) HKTB) HKTRB {
|
||||
|
||||
left := F.Flow2(Left[B, E], _of)
|
||||
right := F.Bind2nd(_map, Right[E, B])
|
||||
left := F.Flow2(Left[B, E], mof)
|
||||
right := mmap(Right[E, B])
|
||||
|
||||
return func(ta Either[E, A], f func(A) HKTB) HKTRB {
|
||||
return MonadFold(ta,
|
||||
@ -43,27 +43,21 @@ func traverse[E, A, B, HKTA, HKTB, HKTRB any](
|
||||
}
|
||||
}
|
||||
|
||||
func Traverse[E, A, B, HKTA, HKTB, HKTRB any](
|
||||
_of func(Either[E, B]) HKTRB,
|
||||
_map func(HKTB, func(B) Either[E, B]) HKTRB,
|
||||
// Traverse converts an [Either] of some higher kinded type into the higher kinded type of an [Either]
|
||||
func Traverse[A, E, B, HKTB, HKTRB any](
|
||||
mof func(Either[E, B]) HKTRB,
|
||||
mmap func(func(B) Either[E, B]) func(HKTB) HKTRB,
|
||||
) func(func(A) HKTB) func(Either[E, A]) HKTRB {
|
||||
delegate := traverse[E, A, B, HKTA](_of, _map)
|
||||
delegate := traverse[E, A, B](mof, mmap)
|
||||
return func(f func(A) HKTB) func(Either[E, A]) HKTRB {
|
||||
return F.Bind2nd(delegate, f)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
We need to pass the members of the applicative explicitly, because golang does neither support higher kinded types nor template methods on structs or interfaces
|
||||
|
||||
HKTRA = HKT<Either[A]>
|
||||
HKTA = HKT<A>
|
||||
HKTB = HKT<B>
|
||||
*/
|
||||
// Sequence converts an [Either] of some higher kinded type into the higher kinded type of an [Either]
|
||||
func Sequence[E, A, HKTA, HKTRA any](
|
||||
_of func(Either[E, A]) HKTRA,
|
||||
_map func(HKTA, func(A) Either[E, A]) HKTRA,
|
||||
mof func(Either[E, A]) HKTRA,
|
||||
mmap func(func(A) Either[E, A]) func(HKTA) HKTRA,
|
||||
) func(Either[E, HKTA]) HKTRA {
|
||||
return Fold(F.Flow2(Left[A, E], _of), F.Bind2nd(_map, Right[E, A]))
|
||||
return Fold(F.Flow2(Left[A, E], mof), mmap(Right[E, A]))
|
||||
}
|
||||
|
@ -30,9 +30,9 @@ func TestTraverse(t *testing.T) {
|
||||
}
|
||||
return O.None[int]()
|
||||
}
|
||||
trav := Traverse[string, int, int, O.Option[Either[string, int]]](
|
||||
trav := Traverse[int](
|
||||
O.Of[Either[string, int]],
|
||||
O.MonadMap[int, Either[string, int]],
|
||||
O.Map[int, Either[string, int]],
|
||||
)(f)
|
||||
|
||||
assert.Equal(t, O.Of(Left[int]("a")), F.Pipe1(Left[int]("a"), trav))
|
||||
@ -44,7 +44,7 @@ func TestSequence(t *testing.T) {
|
||||
|
||||
seq := Sequence(
|
||||
O.Of[Either[string, int]],
|
||||
O.MonadMap[int, Either[string, int]],
|
||||
O.Map[int, Either[string, int]],
|
||||
)
|
||||
|
||||
assert.Equal(t, O.Of(Right[string](1)), seq(Right[string](O.Of(1))))
|
||||
|
@ -19,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))
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -18,10 +18,14 @@ 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"
|
||||
)
|
||||
@ -104,6 +108,21 @@ var (
|
||||
|
||||
// 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() {
|
||||
@ -147,3 +166,21 @@ func Example_solution09B() {
|
||||
// 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)
|
||||
}
|
||||
|
@ -23,16 +23,65 @@ import (
|
||||
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 {
|
||||
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 getTitle(item PostItem) string {
|
||||
func (player Player) getId() int {
|
||||
return player.Id
|
||||
}
|
||||
|
||||
func (item PostItem) getTitle() string {
|
||||
return item.Title
|
||||
}
|
||||
|
||||
@ -55,7 +104,7 @@ func Example_renderPage() {
|
||||
idxToUrl,
|
||||
H.MakeGetRequest,
|
||||
H.ReadJson[PostItem](client),
|
||||
R.Map(getTitle),
|
||||
R.Map(PostItem.getTitle),
|
||||
)
|
||||
|
||||
res := F.Pipe2(
|
||||
@ -71,3 +120,64 @@ func Example_renderPage() {
|
||||
// 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)))
|
||||
}
|
Reference in New Issue
Block a user