Documentation
¶
Overview ¶
Package mostlyadequate contains examples from the "Mostly Adequate Guide to Functional Programming" adapted to Go using fp-go. These examples demonstrate functional programming concepts in a practical way.
Example (Application) ¶
package main
import (
"context"
"fmt"
"net/http"
"regexp"
A "github.com/IBM/fp-go/v2/array"
E "github.com/IBM/fp-go/v2/either"
F "github.com/IBM/fp-go/v2/function"
J "github.com/IBM/fp-go/v2/json"
S "github.com/IBM/fp-go/v2/string"
R "github.com/IBM/fp-go/v2/context/readerioresult"
H "github.com/IBM/fp-go/v2/context/readerioresult/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 main() {
// 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
Example (Dasherize) ¶
fmt.Println(Dasherize("The world is a vampire"))
Output: the-world-is-a-vampire
Example (Flock) ¶
package main
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 *= other.Seagulls
return f
}
func main() {
flockA := MakeFlock(4)
flockB := MakeFlock(2)
flockC := MakeFlock(0)
fmt.Println(flockA.Conjoin(&flockC).Breed(&flockB).Conjoin(flockA.Breed(&flockB)).Seagulls)
}
Output: 32
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),
R.Map(fortune),
R.GetOrElse(errors.ToString),
)
fmt.Println(zoltar(MakeUser("2005-12-12")))
Output: Right[float64](6472) Left[*time.ParseError](parsing time "July 4, 2001" as "2006-01-02": cannot parse "July 4, 2001" as "2006") If you survive, you will be 6837
Example (Greeting) ¶
package main
import "fmt"
func Hi(name string) string {
return fmt.Sprintf("Hi %s", name)
}
func Greeting(name string) string {
return Hi(name)
}
func main() {
// functions are first class objects
greet := Hi
fmt.Println(Greeting("times"))
fmt.Println(greet("times"))
}
Output: Hi times Hi times
Example (Pipe) ¶
output := F.Pipe2( "send in the clowns", ToUpper, Exclaim, ) fmt.Println(output)
Output: SEND IN THE CLOWNS!
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[string](<div>Destinations: [qui est esse], Events: [ea molestias quasi exercitationem repellat qui ipsa sit aut]</div>)
Example (Shout) ¶
fmt.Println(Shout("send in the clowns"))
Output: SEND IN THE CLOWNS!
Example (Solution04A) ¶
// words :: String -> [String]
words := Split(regexp.MustCompile(` `))
fmt.Println(words("Jingle bells Batman smells"))
Output: [Jingle bells Batman smells]
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]
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
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)
Example (Solution05B) ¶
// averageDollarValue :: [Car] -> Int averageDollarValue := F.Flow2( A.Map(Car.getDollarValue), average, ) fmt.Println(averageDollarValue(Cars))
Output: 790700
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)
Example (Solution08A) ¶
incrF := I.Map(N.Add(1)) fmt.Println(incrF(I.Of(2)))
Output: 3
Example (Solution08B) ¶
// initial :: User -> Option rune initial := F.Flow3( Chapter08User.getName, S.ToRunes, A.Head[rune], ) fmt.Println(initial(albert08))
Output: Some[int32](65)
Example (Solution08C) ¶
// eitherWelcome :: User -> Either String String eitherWelcome := F.Flow2( checkActive, R.Map(showWelcome), ) fmt.Println(eitherWelcome(gary08)) fmt.Println(eitherWelcome(theresa08))
Output: Left[*errors.errorString](your account is not active) Right[string](Welcome Theresa)
Example (Solution08D) ¶
// // validateName :: User -> Either String ()
validateName := F.Flow3(
Chapter08User.getName,
R.FromPredicate(F.Flow2(
S.Size,
ord.Gt(ord.FromStrictCompare[int]())(3),
), errors.OnSome[string]("Your name %s is larger than 3 characters")),
R.Map(F.ToAny[string]),
)
saveAndWelcome := F.Flow2(
save,
ioresult.Map(showWelcome),
)
register := F.Flow3(
validateUser(validateName),
ioresult.FromEither[Chapter08User],
ioresult.Chain(saveAndWelcome),
)
fmt.Println(validateName(gary08))
fmt.Println(validateName(yi08))
fmt.Println(register(albert08)())
fmt.Println(register(yi08)())
Output: Right[string](Gary) Left[*errors.errorString](Your name Yi is larger than 3 characters) Right[string](Welcome Albert) Left[*errors.errorString](Your name Yi is larger than 3 characters)
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]
Example (Solution09B) ¶
logFilename := F.Flow2( io.Map(path.Base), io.ChainFirst(pureLog), ) fmt.Println(logFilename(getFile)())
Output: ch09.md
Example (Solution09C) ¶
// // joinMailingList :: Email -> Either String (IO ())
joinMailingList := F.Flow4(
validateEmail,
ioresult.FromEither[string],
ioresult.Chain(addToMailingList),
ioresult.Chain(emailBlast),
)
fmt.Println(joinMailingList("sleepy@grandpa.net")())
fmt.Println(joinMailingList("notanemail")())
Output: Right[string](sleepy@grandpa.net) Left[*errors.errorString](email notanemail is invalid)
Example (Solution10A) ¶
safeAdd := F.Curry2(func(a, b Option[int]) 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]
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]
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)
Example (Solution11A) ¶
// eitherToMaybe :: Either b a -> Maybe a
eitherToMaybe := R.ToOption[string]
fmt.Println(eitherToMaybe(R.Of("one eyed willy")))
fmt.Println(eitherToMaybe(R.Left[string](fmt.Errorf("some error"))))
Output: Some[string](one eyed willy) None[string]
Example (Solution11B) ¶
findByNameID := F.Flow2( findUserByID, ioresult.Map(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](user 4 not found)
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
Example (Solution12A) ¶
// getJsons :: Map Route Route -> Task Error (Map Route JSON) getJsons := ioresult.TraverseRecord[string](httpGet) fmt.Println(getJsons(routes)())
Output: Right[map[string]string](map[/:json for / /about:json for /about])
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[string](Game started) Left[*errors.errorString](player 4 must have a name)
Example (Solution12C) ¶
traverseO := O.Traverse[string](
ioresult.Of[Option[string]],
ioresult.Map[string, Option[string]],
)
// readFirst :: String -> Task Error (Maybe String)
readFirst := F.Pipe2(
readdir,
ioresult.Map(A.Head[string]),
ioresult.Chain(traverseO(readfile("utf-8"))),
)
fmt.Println(readFirst())
Output: Right[option.Option[string]](Some[string](content of file1 (utf-8)))
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})
Example (Widthdraw) ¶
fmt.Println(getTwenty(MakeAccount(200))) fmt.Println(getTwenty(MakeAccount(10)))
Output: Your balance is $180.00 You're broke!
Index ¶
Examples ¶
- Package (Application)
- Package (Dasherize)
- Package (Flock)
- Package (GetAge)
- Package (Greeting)
- Package (Pipe)
- Package (RenderPage)
- Package (Shout)
- Package (Solution04A)
- Package (Solution04B)
- Package (Solution04C)
- Package (Solution05A)
- Package (Solution05B)
- Package (Solution05C)
- Package (Solution08A)
- Package (Solution08B)
- Package (Solution08C)
- Package (Solution08D)
- Package (Solution09A)
- Package (Solution09B)
- Package (Solution09C)
- Package (Solution10A)
- Package (Solution10B)
- Package (Solution10C)
- Package (Solution11A)
- Package (Solution11B)
- Package (Solution11C)
- Package (Solution12A)
- Package (Solution12B)
- Package (Solution12C)
- Package (Street)
- Package (Widthdraw)
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type IOOption ¶
IOOption represents a lazy computation that may not produce a value. It combines IO (lazy evaluation) with Option (optional values). Use this when you have side effects that might not return a value.
Example:
func readConfig() IOOption[Config] {
return func() option.Option[Config] {
// Read from file system (side effect)
if fileExists {
return option.Some(config)
}
return option.None[Config]()
}
}
type IOResult ¶
IOResult represents a lazy computation that may fail with an error. It combines IO (lazy evaluation) with Result (error handling). Use this for side effects that can fail, like file I/O or HTTP requests.
Example:
func readFile(path string) IOResult[[]byte] {
return func() result.Result[[]byte] {
data, err := os.ReadFile(path)
if err != nil {
return result.Error[[]byte](err)
}
return result.Ok(data)
}
}
type Option ¶
Option represents an optional value - either Some(value) or None. Use this instead of pointers or sentinel values to represent absence of a value.
Example:
func findUser(id int) Option[User] {
if user, found := users[id]; found {
return option.Some(user)
}
return option.None[User]()
}
type Result ¶
Result represents a computation that may fail with an error. It's an alias for result.Result[A] which is equivalent to either.Either[error, A]. Use this when you need error handling with a specific success type.
Example:
func divide(a, b int) Result[int] {
if b == 0 {
return result.Error[int](errors.New("division by zero"))
}
return result.Ok(a / b)
}