mostlyadequate

package
v2.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 18, 2025 License: Apache-2.0 Imports: 4 Imported by: 0

README

Mostly Adequate: fp-go Companion Guide

This resource is meant to serve as a go "companion" resource to Professor Frisby's Mostly Adequate Guide.

It is a port of the mostly-adequate-fp-ts book.

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

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type IOOption

type IOOption[A any] = iooption.IOOption[A]

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

type IOResult[A any] = ioresult.IOResult[A]

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

type Option[A any] = option.Option[A]

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

type Result[A any] = result.Result[A]

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

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL