Help us shape the future of Go! Take the 2026 Go Developer Survey

mostlyadequate

package
v2.7.0 Latest Latest
Warning

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

Go to latest
Published: Oct 19, 2026 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)

Example_application demonstrates building a complete application using functional composition to fetch and process data from the Flickr API.

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
}

// Example_application demonstrates building a complete application using
// functional composition to fetch and process data from the Flickr API.
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)

Example_dasherize demonstrates composing string transformations to convert text to lowercase and replace spaces with dashes.

fmt.Println(Dasherize("The world is a vampire"))
Output:
the-world-is-a-vampire
Example (Flock)

Example_flock demonstrates object-oriented programming with mutable state and method chaining, showing the imperative approach before introducing functional alternatives.

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
}

// Example_flock demonstrates object-oriented programming with mutable state
// and method chaining, showing the imperative approach before introducing
// functional alternatives.
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 (Get_age)

Example_get_age demonstrates using Result (Either) monad for error handling when parsing dates and calculating ages.

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)

Example_greeting demonstrates first-class functions in Go, showing how functions can be assigned to variables and passed around like any other value.

package main

import "fmt"

func Hi(name string) string {
	return fmt.Sprintf("Hi %s", name)
}

func Greeting(name string) string {
	return Hi(name)
}

// Example_greeting demonstrates first-class functions in Go, showing how
// functions can be assigned to variables and passed around like any other value.
func main() {
	// functions are first class objects
	greet := Hi

	fmt.Println(Greeting("times"))
	fmt.Println(greet("times"))

}
Output:
Hi times
Hi times
Example (Pipe)

Example_pipe demonstrates using Pipe to compose functions in a left-to-right readable order.

output := F.Pipe2(
	"send in the clowns",
	ToUpper,
	Exclaim,
)

fmt.Println(output)
Output:
SEND IN THE CLOWNS!
Example (Render_page)

Example_render_page demonstrates using applicative functors to fetch multiple resources in parallel and combine their results.

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

Example_shout demonstrates simple function composition to transform a string to uppercase and add exclamation.

fmt.Println(Shout("send in the clowns"))
Output:
SEND IN THE CLOWNS!
Example (Solution_04a)

Example_solution_04a demonstrates currying by creating a words function that splits strings using a pre-configured regex pattern.

// words :: String -> [String]
words := Split(regexp.MustCompile(` `))

fmt.Println(words("Jingle bells Batman smells"))
Output:
[Jingle bells Batman smells]
Example (Solution_04b)

Example_solution_04b demonstrates using curried functions to create a specialized filter for strings matching a pattern.

// 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 (Solution_04c)

Example_solution_04c demonstrates using curried max function to find the highest value in a collection.

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 (Solution_05a)

Example_solution_05a demonstrates composing functions to check if the last car in a list is in stock using Option monad.

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 (Solution_05b)

Example_solution_05b demonstrates calculating the average dollar value of cars using functional composition.

// averageDollarValue :: [Car] -> Int
averageDollarValue := F.Flow2(
	A.Map(Car.getDollarValue),
	average,
)

fmt.Println(averageDollarValue(Cars))
Output:
790700
Example (Solution_05c)

Example_solution_05c demonstrates sorting and selecting the fastest cars using functional composition with ordering.

// 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 (Solution_08a)

Example_solution_08a demonstrates using the Identity functor's Map function to increment a value.

incrF := I.Map(N.Add(1))

fmt.Println(incrF(I.Of(2)))
Output:
3
Example (Solution_08b)

Example_solution_08b demonstrates extracting the first character from a user's name using Option monad.

// initial :: User -> Option rune
initial := F.Flow3(
	Chapter08User.getName,
	S.ToRunes,
	A.Head[rune],
)

fmt.Println(initial(albert08))
Output:
Some[int32](65)
Example (Solution_08c)

Example_solution_08c demonstrates using Result monad to check user account status and display a welcome message.

// 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 (Solution_08d)

Example_solution_08d demonstrates validating user data with Result monad, checking both name length and email format.

// // 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 (Solution_09a)

Example_solution_09a demonstrates extracting street names from user addresses using monadic composition with Option.

// // 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 (Solution_09b)

Example_solution_09b demonstrates using IO monad to perform side effects like logging while processing file paths.

logFilename := F.Flow2(
	io.Map(path.Base),
	io.ChainFirst(pureLog),
)

fmt.Println(logFilename(getFile)())
Output:
ch09.md
Example (Solution_09c)

Example_solution_09c demonstrates combining validation with IO effects to join a mailing list with proper error handling.

// // 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 (Solution_10a)

Example_solution_10a demonstrates using applicative functors with Option to safely add two optional values.

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 (Solution_10b)

Example_solution_10b demonstrates an alternative implementation of safe addition using SequenceTuple and monoid operations.

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 (Solution_10c)

Example_solution_10c demonstrates using applicative functors with IO to compose game initialization with player data.

// 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 (Solution_11a)

Example_solution_11a demonstrates transforming Result (Either) to Option, discarding error information to get an optional value.

// 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 (Solution_11b)

Example_solution_11b demonstrates finding a user by ID and extracting their name using IOResult monad transformations.

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 (Solution_11c)

Example_solution_11c demonstrates splitting a string into individual characters using regex-based splitting.

// 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 (Solution_12a)

Example_solution_12a demonstrates traversing a map of routes to fetch JSON data, transforming Map Route Route into Task Error (Map Route JSON).

// 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 (Solution_12b)

Example_solution_12b demonstrates starting a game by traversing a list of players and collecting results as Either values.

// 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 (Solution_12c)

Example_solution_12c demonstrates traversing optional values to save preferences, handling None cases gracefully.

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[common.Option[string]](Some[string](content of file1 (utf-8)))
Example (Street)

Example_street demonstrates safely accessing nested data structures using Option monad to handle missing values.

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 (Withdraw)

Example_withdraw demonstrates using Option monad to handle account balance validation with safe withdrawal operations.

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