1
0
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:
Dr. Carsten Leue
2023-09-07 17:21:39 +02:00
parent 3ccafb5302
commit 53f4e5ebd7
9 changed files with 386 additions and 37 deletions

View File

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

View File

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

View File

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

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

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

View File

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

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