Skip to content

Commit

Permalink
Merge pull request #36 from IBM/cleue-mostly-adequate-more-samples
Browse files Browse the repository at this point in the history
Cleue mostly adequate more samples
  • Loading branch information
CarstenLeue authored Sep 8, 2023
2 parents 3ccafb5 + 1653560 commit cb15a3d
Show file tree
Hide file tree
Showing 9 changed files with 386 additions and 37 deletions.
34 changes: 14 additions & 20 deletions either/traverse.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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]))
}
6 changes: 3 additions & 3 deletions either/traverse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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))))
Expand Down
23 changes: 16 additions & 7 deletions option/sequence.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
}
7 changes: 7 additions & 0 deletions record/generic/record.go
Original file line number Diff line number Diff line change
Expand Up @@ -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] {
Expand Down
5 changes: 5 additions & 0 deletions record/record.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
37 changes: 37 additions & 0 deletions samples/mostly-adequate/chapter09_monadiconions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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("[email protected]")())
fmt.Println(joinMailingList("notanemail")())

// Output:
// Right[<nil>, string]([email protected])
// Left[*errors.errorString, string](email notanemail is invalid)
}
124 changes: 117 additions & 7 deletions samples/mostly-adequate/chapter10_applicativefunctor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
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 (
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
}

Expand All @@ -55,7 +104,7 @@ func Example_renderPage() {
idxToUrl,
H.MakeGetRequest,
H.ReadJson[PostItem](client),
R.Map(getTitle),
R.Map(PostItem.getTitle),
)

res := F.Pipe2(
Expand All @@ -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)
}
Loading

0 comments on commit cb15a3d

Please sign in to comment.