From fb91fd5dc893137603de3270747dc7a7fefd9381 Mon Sep 17 00:00:00 2001 From: "Dr. Carsten Leue" Date: Thu, 7 Sep 2023 17:21:39 +0200 Subject: [PATCH] fix: add more mostly-adequate examples and solutions Signed-off-by: Dr. Carsten Leue --- record/generic/record.go | 7 ++ record/record.go | 5 + .../chapter09_monadiconions_test.go | 37 ++++++ .../chapter10_applicativefunctor_test.go | 112 +++++++++++++++++- .../chapter11_transformagain_test.go | 89 ++++++++++++++ 5 files changed, 244 insertions(+), 6 deletions(-) create mode 100644 samples/mostly-adequate/chapter11_transformagain_test.go diff --git a/record/generic/record.go b/record/generic/record.go index 5c13b9b..a69eed3 100644 --- a/record/generic/record.go +++ b/record/generic/record.go @@ -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] { diff --git a/record/record.go b/record/record.go index 7bebd1f..6ef02bb 100644 --- a/record/record.go +++ b/record/record.go @@ -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) diff --git a/samples/mostly-adequate/chapter09_monadiconions_test.go b/samples/mostly-adequate/chapter09_monadiconions_test.go index 0e54b5b..5628e98 100644 --- a/samples/mostly-adequate/chapter09_monadiconions_test.go +++ b/samples/mostly-adequate/chapter09_monadiconions_test.go @@ -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[, string](sleepy@grandpa.net) + // Left[*errors.errorString, string](email notanemail is invalid) +} diff --git a/samples/mostly-adequate/chapter10_applicativefunctor_test.go b/samples/mostly-adequate/chapter10_applicativefunctor_test.go index b1cf1f7..32557d0 100644 --- a/samples/mostly-adequate/chapter10_applicativefunctor_test.go +++ b/samples/mostly-adequate/chapter10_applicativefunctor_test.go @@ -23,14 +23,53 @@ 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 { - UserId uint `json:"userId"` - Id uint `json:"id"` - Title string `json:"title"` - Body string `json:"body"` -} +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 ( + localStorage = LocalStorage{ + "player1": Player{ + Id: 1, + Name: "Albert", + }, + "player2": Player{ + Id: 2, + Name: "Theresa", + }, + } + + // 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 getTitle(item PostItem) string { return item.Title @@ -71,3 +110,64 @@ func Example_renderPage() { // Right[, string](
Destinations: [qui est esse], Events: [ea molestias quasi exercitationem repellat qui ipsa sit aut]
) } + +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) +} diff --git a/samples/mostly-adequate/chapter11_transformagain_test.go b/samples/mostly-adequate/chapter11_transformagain_test.go new file mode 100644 index 0000000..1ef7dfe --- /dev/null +++ b/samples/mostly-adequate/chapter11_transformagain_test.go @@ -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[, string](Albert) + // Right[, string](Gary) + // Right[, 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 +}