Documentation
¶
Overview ¶
Package monadz provides monadic types for Go 1.27 and later: Option, Result, Either, Validation, Patch, Lazy, Task, State, Reader, and Writer.
Monadz needs Go 1.27 or later with the jsonv2 experiment, which is on by default in Go 1.27. Do not set GOEXPERIMENT=nojsonv2.
Monadz uses Go 1.27 generic methods. A method can change the type of the value, so a pipeline reads left to right:
port := monadz.Lookup(env, "PORT"). // Option[string] OkOr(ErrNoPort). // Result[string] Then(strconv.Atoi). // Result[int] Ensure(validPort, ErrBadPort) // Result[int]
Inside Do, Try gives the value of a Result or an Option, or stops the block and makes Do return the error:
inv := monadz.Do(func() Invoice {
cust := repo.Customer(ctx, id).Try()
plan := monadz.Lookup(plans, cust.PlanID).OkOr(ErrNoPlan).Try()
return rate(cust, plan)
})
Design rules ¶
- Accept the shapes that Go code already has. Result.Then takes func(T) (U, error), and Option.Then takes func(T) (U, bool). Functions such as strconv.Atoi, url.Parse, and os.LookupEnv work directly.
- A bug must crash. Do recovers only its own early-exit signal. Use DoRecover when you want to turn other panics into errors, and you get a *PanicError with the stack trace.
- Be context-aware. A Task receives a context.Context, and the functions in package tasks cancel the work that is no longer necessary.
- Integrate with the standard library: iter.Seq, encoding/json (v1 and v2), database/sql, log/slog, and fmt.
Limits ¶
- Do not call Try in a deferred function. During a panic, the Try signal replaces the panic, so Do returns Err and the program does not crash.
- Do not call Try under code that recovers panics, such as a String method that fmt calls, or a text/template function. The failure is lost.
- Tag Option and Patch fields with omitzero, not omitempty. JSON v2 omitempty drops a Null Patch and Some("").
- Some(None) and Some of a nil pointer encode as null and decode as None. Use Patch to tell null from a missing field.
Packages ¶
Package monadz holds the types and their methods. The plural packages hold functions that work on many values, in the style of the slices and maps packages:
- options: Collect, Traverse, Values, FilterMap, First, Flatten, Zip, ...
- results: Collect, Traverse, Partition, FromSeq2, Fold, Map3, Zip, ...
- eithers: Partition, Lefts, Rights, Collect, ...
- validations: Map2 ... Map6, Collect, Traverse (error accumulation)
- tasks: All, AllLimit, AllSettled, Any, Race, Traverse (with a concurrency limit)
Index ¶
- Variables
- func ConstantBackoff(d time.Duration) func(int) time.Duration
- func ExponentialBackoff(base, limit time.Duration) func(int) time.Duration
- func Sleep(ctx context.Context, d time.Duration) error
- type Either
- func (e Either[L, R]) AndThen[R2 any](f func(R) Either[L, R2]) Either[L, R2]
- func (e Either[L, R]) BiMap[L2, R2 any](onLeft func(L) L2, onRight func(R) R2) Either[L2, R2]
- func (e Either[L, R]) Inspect(f func(R)) Either[L, R]
- func (e Either[L, R]) InspectLeft(f func(L)) Either[L, R]
- func (e Either[L, R]) IsLeft() bool
- func (e Either[L, R]) IsLeftAnd(pred func(L) bool) bool
- func (e Either[L, R]) IsRight() bool
- func (e Either[L, R]) IsRightAnd(pred func(R) bool) bool
- func (e Either[L, R]) Left() Option[L]
- func (e Either[L, R]) LeftOr(fallback L) L
- func (e Either[L, R]) LeftOrElse(f func(R) L) L
- func (e Either[L, R]) Map[R2 any](f func(R) R2) Either[L, R2]
- func (e Either[L, R]) MapLeft[L2 any](f func(L) L2) Either[L2, R]
- func (e Either[L, R]) MapOr[U any](fallback U, f func(R) U) U
- func (e Either[L, R]) Match[U any](onLeft func(L) U, onRight func(R) U) U
- func (e Either[L, R]) OrElse[L2 any](f func(L) Either[L2, R]) Either[L2, R]
- func (e Either[L, R]) Right() Option[R]
- func (e Either[L, R]) RightOr(fallback R) R
- func (e Either[L, R]) RightOrElse(f func(L) R) R
- func (e Either[L, R]) String() string
- func (e Either[L, R]) Swap() Either[R, L]
- func (e Either[L, R]) Unpack() (L, R, bool)
- type FieldError
- type Future
- type Lazy
- type Option
- func At[T any](s []T, i int) Option[T]
- func DoOption[T any](f func() T) (res Option[T])
- func FromPtr[T any](p *T) Option[T]
- func FromTuple[T any](v T, ok bool) Option[T]
- func FromZero[T comparable](v T) Option[T]
- func Lookup[K comparable, V any](m map[K]V, k K) Option[V]
- func None[T any]() Option[T]
- func Some[T any](v T) Option[T]
- func When[T any](cond bool, v T) Option[T]
- func WhenFunc[T any](cond bool, f func() T) Option[T]
- func (o Option[T]) All() iter.Seq[T]
- func (o Option[T]) And[U any](other Option[U]) Option[U]
- func (o Option[T]) AndThen[U any](f func(T) Option[U]) Option[U]
- func (o Option[T]) Expect(msg string) T
- func (o Option[T]) Filter(pred func(T) bool) Option[T]
- func (o Option[T]) Get() (T, bool)
- func (o *Option[T]) GetOrInsert(v T) *T
- func (o *Option[T]) GetOrInsertWith(f func() T) *T
- func (o *Option[T]) Insert(v T) *T
- func (o Option[T]) Inspect(f func(T)) Option[T]
- func (o Option[T]) IsNone() bool
- func (o Option[T]) IsNoneOr(pred func(T) bool) bool
- func (o Option[T]) IsSome() bool
- func (o Option[T]) IsSomeAnd(pred func(T) bool) bool
- func (o Option[T]) IsZero() bool
- func (o Option[T]) LogValue() slog.Value
- func (o Option[T]) Map[U any](f func(T) U) Option[U]
- func (o Option[T]) MapOr[U any](fallback U, f func(T) U) U
- func (o Option[T]) MapOrElse[U any](fallback func() U, f func(T) U) U
- func (o Option[T]) MarshalJSON() ([]byte, error)
- func (o Option[T]) MarshalJSONTo(enc *jsontext.Encoder) error
- func (o Option[T]) Match[U any](onSome func(T) U, onNone func() U) U
- func (o Option[T]) Must() T
- func (o Option[T]) OkOr(err error) Result[T]
- func (o Option[T]) OkOrElse(f func() error) Result[T]
- func (o Option[T]) Or(other Option[T]) Option[T]
- func (o Option[T]) OrElse(f func() Option[T]) Option[T]
- func (o Option[T]) Ptr() *T
- func (o *Option[T]) Replace(v T) Option[T]
- func (o *Option[T]) Scan(src any) error
- func (o Option[T]) String() string
- func (o *Option[T]) Take() Option[T]
- func (o *Option[T]) TakeIf(pred func(T) bool) Option[T]
- func (o Option[T]) Then[U any](f func(T) (U, bool)) Option[U]
- func (o Option[T]) Try() T
- func (o *Option[T]) UnmarshalJSON(b []byte) error
- func (o *Option[T]) UnmarshalJSONFrom(dec *jsontext.Decoder) error
- func (o Option[T]) Value() (driver.Value, error)
- func (o Option[T]) ValueOr(fallback T) T
- func (o Option[T]) ValueOrElse(f func() T) T
- func (o Option[T]) ValueOrZero() T
- func (o Option[T]) Xor(other Option[T]) Option[T]
- func (o Option[T]) ZipWith[U, V any](other Option[U], f func(T, U) V) Option[V]
- type Pair
- type PanicError
- type Patch
- func (p Patch[T]) Apply(dst *T)
- func (p Patch[T]) ApplyOption(dst *Option[T])
- func (p Patch[T]) IsMissing() bool
- func (p Patch[T]) IsNull() bool
- func (p Patch[T]) IsSet() bool
- func (p Patch[T]) IsZero() bool
- func (p Patch[T]) Map[U any](f func(T) U) Patch[U]
- func (p Patch[T]) MarshalJSON() ([]byte, error)
- func (p Patch[T]) MarshalJSONTo(enc *jsontext.Encoder) error
- func (p Patch[T]) Match[U any](onSet func(T) U, onNull func() U, onMissing func() U) U
- func (p Patch[T]) Option() Option[T]
- func (p Patch[T]) String() string
- func (p *Patch[T]) UnmarshalJSON(b []byte) error
- func (p *Patch[T]) UnmarshalJSONFrom(dec *jsontext.Decoder) error
- type Reader
- type Result
- func (r Result[T]) All() iter.Seq[T]
- func (r Result[T]) And[U any](other Result[U]) Result[U]
- func (r Result[T]) AndThen[U any](f func(T) Result[U]) Result[U]
- func (r Result[T]) Either() Either[error, T]
- func (r Result[T]) Ensure(pred func(T) bool, err error) Result[T]
- func (r Result[T]) Err() error
- func (r Result[T]) Expect(msg string) T
- func (r Result[T]) Get() (T, error)
- func (r Result[T]) Inspect(f func(T)) Result[T]
- func (r Result[T]) InspectErr(f func(error)) Result[T]
- func (r Result[T]) IsErr() bool
- func (r Result[T]) IsErrAnd(pred func(error) bool) bool
- func (r Result[T]) IsOk() bool
- func (r Result[T]) IsOkAnd(pred func(T) bool) bool
- func (r Result[T]) LogValue() slog.Value
- func (r Result[T]) Map[U any](f func(T) U) Result[U]
- func (r Result[T]) MapErr(f func(error) error) Result[T]
- func (r Result[T]) MapOr[U any](fallback U, f func(T) U) U
- func (r Result[T]) MapOrElse[U any](onErr func(error) U, onOk func(T) U) U
- func (r Result[T]) Match[U any](onOk func(T) U, onErr func(error) U) U
- func (r Result[T]) Must() T
- func (r Result[T]) MustErr() error
- func (r Result[T]) Ok() Option[T]
- func (r Result[T]) Or(other Result[T]) Result[T]
- func (r Result[T]) OrElse(f func(error) Result[T]) Result[T]
- func (r Result[T]) Recover(f func(error) (T, error)) Result[T]
- func (r Result[T]) String() string
- func (r Result[T]) Then[U any](f func(T) (U, error)) Result[U]
- func (r Result[T]) Try() T
- func (r Result[T]) Validation() Validation[T]
- func (r Result[T]) ValueOr(fallback T) T
- func (r Result[T]) ValueOrElse(f func(error) T) T
- func (r Result[T]) ValueOrZero() T
- func (r Result[T]) Wrap(msg string) Result[T]
- func (r Result[T]) Wrapf(format string, args ...any) Result[T]
- func (r Result[T]) ZipWith[U, V any](other Result[U], f func(T, U) V) Result[V]
- type RetryPolicy
- type State
- type Task
- func (t Task[T]) AndThen[U any](f func(T) Task[U]) Task[U]
- func (t Task[T]) Delay(d time.Duration) Task[T]
- func (t Task[T]) Ensure(pred func(T) bool, err error) Task[T]
- func (t Task[T]) Inspect(f func(T)) Task[T]
- func (t Task[T]) InspectErr(f func(error)) Task[T]
- func (t Task[T]) Map[U any](f func(T) U) Task[U]
- func (t Task[T]) MapErr(f func(error) error) Task[T]
- func (t Task[T]) Once() Task[T]
- func (t Task[T]) OrElse(f func(error) Task[T]) Task[T]
- func (t Task[T]) Recover(f func(error) (T, error)) Task[T]
- func (t Task[T]) Retry(p RetryPolicy) Task[T]
- func (t Task[T]) Run(ctx context.Context) Result[T]
- func (t Task[T]) Start(ctx context.Context) *Future[T]
- func (t Task[T]) Then[U any](f func(context.Context, T) (U, error)) Task[U]
- func (t Task[T]) Timeout(d time.Duration) Task[T]
- func (t Task[T]) Wrap(msg string) Task[T]
- func (t Task[T]) Wrapf(format string, args ...any) Task[T]
- func (t Task[T]) ZipWith[U, V any](other Task[U], f func(T, U) V) Task[V]
- type Triple
- type Unit
- type Validation
- func (v Validation[T]) AndThen[U any](f func(T) Validation[U]) Validation[U]
- func (v Validation[T]) Ensure(pred func(T) bool, err error) Validation[T]
- func (v Validation[T]) Err() error
- func (v Validation[T]) Errors() []error
- func (v Validation[T]) Expect(msg string) T
- func (v Validation[T]) Field(name string) Validation[T]
- func (v Validation[T]) Get() (T, error)
- func (v Validation[T]) Inspect(f func(T)) Validation[T]
- func (v Validation[T]) IsInvalid() bool
- func (v Validation[T]) IsValid() bool
- func (v Validation[T]) Map[U any](f func(T) U) Validation[U]
- func (v Validation[T]) MapErr(f func(error) error) Validation[T]
- func (v Validation[T]) Match[U any](onValid func(T) U, onInvalid func([]error) U) U
- func (v Validation[T]) Must() T
- func (v Validation[T]) Result() Result[T]
- func (v Validation[T]) String() string
- func (v Validation[T]) ValueOr(fallback T) T
- func (v Validation[T]) ValueOrElse(f func([]error) T) T
- func (v Validation[T]) ValueOrZero() T
- func (v Validation[T]) ZipWith[U, V any](other Validation[U], f func(T, U) V) Validation[V]
- type Writer
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ErrGoexit = errors.New("monadz: task goroutine called runtime.Goexit")
ErrGoexit is the error of a Task whose goroutine called runtime.Goexit, for example through testing.T.FailNow.
var ErrNilError = errors.New("monadz: failure constructed with a nil error")
ErrNilError is the error that Err, Invalid, and similar constructors store when you give them a nil error. A nil error cannot mean failure, so monadz replaces it with this error instead of returning a success by mistake.
var ErrNone = errors.New("monadz: value is None")
ErrNone is the error for a missing value: Option.Must on None, and Option.Try on None inside Do.
var ErrZeroTask = errors.New("monadz: Run on a zero Task; use NewTask")
ErrZeroTask is the error from Run on a zero Task.
Functions ¶
func ConstantBackoff ¶
ConstantBackoff waits d before every retry.
func ExponentialBackoff ¶
ExponentialBackoff waits base, 2*base, 4*base, ..., up to limit.
Types ¶
type Either ¶
type Either[L, R any] struct { // contains filtered or unexported fields }
Either holds a value of type L (Left) or a value of type R (Right).
Use Either when both outcomes are valid and carry their own data, for example Either[Paywall, Grant]. For "a value or an error", use Result.
Either is right-biased: Map and AndThen work on the Right value, as in Haskell and Scala. The zero value is Left(zero L).
func Left ¶
Left returns an Either that holds the Left value l. Go cannot infer R, so write both type arguments: monadz.Left[Paywall, Grant](p).
func Right ¶
Right returns an Either that holds the Right value r. Go cannot infer L, so write it: monadz.Right[Paywall](grant).
func (Either[L, R]) AndThen ¶
AndThen applies f to the Right value and returns its Either. This is the monad bind. (Haskell: >>=.)
func (Either[L, R]) BiMap ¶
BiMap applies onLeft or onRight to the value. (Haskell Bifunctor: bimap.)
func (Either[L, R]) Inspect ¶
Inspect calls f with the Right value, if any, and returns the Either unchanged.
func (Either[L, R]) InspectLeft ¶
InspectLeft calls f with the Left value, if any, and returns the Either unchanged.
func (Either[L, R]) IsLeftAnd ¶
IsLeftAnd reports whether the Either holds a Left value that satisfies pred.
func (Either[L, R]) IsRightAnd ¶
IsRightAnd reports whether the Either holds a Right value that satisfies pred.
func (Either[L, R]) Left ¶
Left returns the Left value as an Option. (Haskell: leftToMaybe, in the either package.)
func (Either[L, R]) LeftOr ¶
func (e Either[L, R]) LeftOr(fallback L) L
LeftOr returns the Left value, or fallback. (Haskell: fromLeft.)
func (Either[L, R]) LeftOrElse ¶
func (e Either[L, R]) LeftOrElse(f func(R) L) L
LeftOrElse returns the Left value, or f applied to the Right value.
func (Either[L, R]) MapOr ¶
MapOr applies f to the Right value, or returns fallback for a Left. (Rust: Option::map_or; Haskell: either (const fallback) f.)
func (Either[L, R]) Match ¶
Match calls onLeft or onRight and returns the result. (Haskell: either.)
Example ¶
Use Either when both outcomes are valid and carry data. Match folds both sides to one type.
package main
import (
"fmt"
"strconv"
"github.com/zkrebbekx/monadz"
)
func main() {
check := func(plan string) monadz.Either[string, int] {
if plan == "free" {
return monadz.Left[string, int]("upgrade to use SSO")
}
return monadz.Right[string](10)
}
for _, plan := range []string{"free", "pro"} {
status := check(plan).Match(
func(offer string) string { return "402: " + offer },
func(seats int) string { return "200: " + strconv.Itoa(seats) + " SSO seats" },
)
fmt.Println(status)
}
}
Output: 402: upgrade to use SSO 200: 10 SSO seats
func (Either[L, R]) OrElse ¶
OrElse applies f to the Left value and returns its Either. Use it to recover from a Left.
func (Either[L, R]) Right ¶
Right returns the Right value as an Option. (Haskell: rightToMaybe, in the either package.)
func (Either[L, R]) RightOr ¶
func (e Either[L, R]) RightOr(fallback R) R
RightOr returns the Right value, or fallback. (Haskell: fromRight.)
func (Either[L, R]) RightOrElse ¶
func (e Either[L, R]) RightOrElse(f func(L) R) R
RightOrElse returns the Right value, or f applied to the Left value.
type FieldError ¶
FieldError is an error about one input field.
func (*FieldError) Error ¶
func (e *FieldError) Error() string
Error returns "<field>: <err>", or "<field>: <nil>" for a nil Err.
type Future ¶
type Future[T any] struct { // contains filtered or unexported fields }
Future is a handle to a Task that runs in its own goroutine. Get one from Task.Start; the zero Future holds no Task.
func (*Future[T]) Await ¶
Await waits for the Task and returns its Result. If the Task panicked, Await panics in the caller's goroutine with a *PanicError that holds the original stack trace.
func (*Future[T]) AwaitContext ¶
AwaitContext is Await with a limit: it returns ctx.Err() if ctx ends first. It does not cancel the Task.
func (*Future[T]) Cancel ¶
func (f *Future[T]) Cancel()
Cancel cancels the context of the Task. The Task stops only if its work observes the context.
type Lazy ¶
type Lazy[T any] struct { // contains filtered or unexported fields }
Lazy holds a value that the first Get computes. Later calls to Get return the cached value. Get is safe for concurrent use; the function runs at most one time. If the function panics, every Get panics with the same value.
If the function calls runtime.Goexit, Get panics with a *runtime.PanicNilError, and every later Get panics too. This is the behaviour of sync.OnceValue.
Equivalents: Rust LazyLock, Haskell lazy evaluation.
func LazyOf ¶
LazyOf returns a Lazy for a function that can fail. The Lazy caches the error too, so a failed computation does not run again.
func (Lazy[T]) Get ¶
func (l Lazy[T]) Get() T
Get computes the value on the first call, and returns the cached value after that.
type Option ¶
type Option[T any] struct { // contains filtered or unexported fields }
Option holds a value of type T (Some) or no value (None).
The zero value is None. Equivalents: Rust Option<T>, Haskell Maybe a.
Example ¶
An Option holds a value or nothing. Map, Filter, and ValueOr work on it without a check for nil or a second bool.
package main
import (
"fmt"
"github.com/zkrebbekx/monadz"
)
func main() {
seats := map[string]int{"pro": 25, "free": 0}
pro := monadz.Lookup(seats, "pro").
Filter(func(n int) bool { return n > 0 }).
ValueOr(5)
free := monadz.Lookup(seats, "free").
Filter(func(n int) bool { return n > 0 }).
ValueOr(5)
missing := monadz.Lookup(seats, "team")
fmt.Println(pro, free, missing)
}
Output: 25 5 None
func At ¶
At returns s[i], or None if i is out of range. A negative i is out of range: At does not count from the end. (Rust: slice::get.)
func DoOption ¶
DoOption runs f and returns its value as Some. Inside f, Option.Try on None stops f and DoOption returns None.
Result.Try inside DoOption is a programming error, because None cannot carry the error. Its signal passes through as a panic. Convert the Result first with Result.Ok.
func FromTuple ¶
FromTuple converts a "comma ok" pair: FromTuple(m[k]) is not legal Go, so write v, ok := m[k]; FromTuple(v, ok). See also Lookup.
func FromZero ¶
func FromZero[T comparable](v T) Option[T]
FromZero returns None for the zero value of T, else Some(v). Use it for APIs where "" or 0 means "not set".
func Lookup ¶
func Lookup[K comparable, V any](m map[K]V, k K) Option[V]
Lookup returns the value of key k in map m, or None.
func WhenFunc ¶
WhenFunc returns Some(f()) if cond is true, else None. f runs only when cond is true. (Rust: bool::then.)
func (Option[T]) All ¶
All returns an iterator over the value: one element for Some, none for None. Use it in a range loop. (Rust: iter; Haskell: maybeToList.)
func (Option[T]) And ¶
And returns other if the Option is Some, else None. (Rust: and; Haskell: *>.)
func (Option[T]) AndThen ¶
AndThen applies f to the value and returns its Option. This is the monad bind. (Rust: and_then; Haskell: >>=.)
func (*Option[T]) GetOrInsert ¶
func (o *Option[T]) GetOrInsert(v T) *T
GetOrInsert stores v if the Option is None, and returns a pointer to the stored value. (Rust: get_or_insert.)
func (*Option[T]) GetOrInsertWith ¶
func (o *Option[T]) GetOrInsertWith(f func() T) *T
GetOrInsertWith stores f() if the Option is None, and returns a pointer to the stored value. f runs only for None. (Rust: get_or_insert_with.)
func (*Option[T]) Insert ¶
func (o *Option[T]) Insert(v T) *T
Insert puts Some(v) in place and returns a pointer to the stored value. (Rust: insert.)
func (Option[T]) Inspect ¶
Inspect calls f with the value, if any, and returns the Option unchanged.
func (Option[T]) IsNoneOr ¶
IsNoneOr reports whether the Option is None or holds a value that satisfies pred.
func (Option[T]) IsSomeAnd ¶
IsSomeAnd reports whether the Option holds a value that satisfies pred.
func (Option[T]) IsZero ¶
IsZero reports whether the Option is None. Some(0), Some(""), and Some(false) are NOT zero, so `json:",omitzero"` drops only None.
Use omitzero, not omitempty. Under encoding/json/v2, omitempty drops every value that encodes as null, "", [] or {}, including a Null Patch and Some("").
func (Option[T]) MapOrElse ¶
MapOrElse applies f to the value, or returns fallback(). (Rust: map_or_else.)
func (Option[T]) MarshalJSON ¶
MarshalJSON implements json.Marshaler: the value, or null for None.
encoding/json in Go 1.27 calls MarshalJSONTo first. This method is for direct callers.
func (Option[T]) MarshalJSONTo ¶
MarshalJSONTo implements the encoding/json/v2 MarshalerTo interface.
func (Option[T]) Match ¶
Match calls onSome with the value, or onNone, and returns the result. (Haskell: maybe.)
func (Option[T]) Must ¶
func (o Option[T]) Must() T
Must returns the value, or panics with ErrNone. (Rust: unwrap.)
func (Option[T]) OkOrElse ¶
OkOrElse converts Some(v) to Ok(v), and None to Err(f()). f runs only for None. (Rust: ok_or_else.)
func (Option[T]) Ptr ¶
func (o Option[T]) Ptr() *T
Ptr returns a pointer to a copy of the value, or nil.
func (*Option[T]) Replace ¶
Replace puts Some(v) in place and returns the old Option. (Rust: replace.)
func (Option[T]) Then ¶
Then applies a "comma ok" function to the value: a func(T) (U, bool), such as os.LookupEnv.
func (Option[T]) Try ¶
func (o Option[T]) Try() T
Try returns the value. On None, it stops the enclosing Do or DoOption. See Do.
func (*Option[T]) UnmarshalJSON ¶
UnmarshalJSON implements json.Unmarshaler: null gives None. To tell null from a missing field, use Patch.
Some(v) where v encodes as null (a None, or a nil pointer) decodes as None. Use Patch to keep the difference. Rust serde behaves the same.
encoding/json in Go 1.27 calls UnmarshalJSONFrom first. This method is for direct callers.
func (*Option[T]) UnmarshalJSONFrom ¶
UnmarshalJSONFrom implements the encoding/json/v2 UnmarshalerFrom interface: null gives None. As with UnmarshalJSON, Some(v) where v encodes as null decodes as None.
func (Option[T]) Value ¶
Value implements driver.Valuer: None gives NULL.
Value accepts only the value types of database/sql's DefaultParameterConverter, or a T that implements driver.Valuer. For any other T, Value returns an error.
func (Option[T]) ValueOr ¶
func (o Option[T]) ValueOr(fallback T) T
ValueOr returns the value, or fallback. (Rust: unwrap_or; Haskell: fromMaybe.)
func (Option[T]) ValueOrElse ¶
func (o Option[T]) ValueOrElse(f func() T) T
ValueOrElse returns the value, or the result of f. f runs only for None. (Rust: unwrap_or_else.)
func (Option[T]) ValueOrZero ¶
func (o Option[T]) ValueOrZero() T
ValueOrZero returns the value, or the zero value of T. (Rust: unwrap_or_default.)
type Pair ¶
type Pair[A, B any] struct { First A Second B }
Pair holds two values. The Zip functions in the plural packages return a Pair.
type PanicError ¶
type PanicError struct {
// Value is the argument of the panic call.
Value any
// Stack is the stack trace of the goroutine at the time of the panic.
Stack []byte
// contains filtered or unexported fields
}
PanicError holds a recovered panic. DoRecover returns it inside an Err. A Task that runs in another goroutine captures a panic in a PanicError, and Await raises it again in the waiting goroutine.
func (*PanicError) Error ¶
func (p *PanicError) Error() string
Error returns "panic: <value>". After the panic crossed goroutines, the text also holds the stack of the goroutine that panicked.
func (*PanicError) Unwrap ¶
func (p *PanicError) Unwrap() error
Unwrap returns the panic value if it is an error (for example a runtime.Error), so errors.Is and errors.As see it.
type Patch ¶
type Patch[T any] struct { // contains filtered or unexported fields }
Patch is a field of a JSON PATCH request. It has three states:
field not in the JSON -> Missing: do not change the stored value "field": null -> Null: clear the stored value "field": <value> -> Set: store the value
An Option cannot tell Missing from Null. Patch can. The zero value is Missing. Tag the field with `json:",omitzero"` so that a Missing field is not written.
Use omitzero, not omitempty. Under encoding/json/v2, omitempty drops every value that encodes as null, "", [] or {}, including a Null Patch and Some("").
Example ¶
A Patch field tells a missing field from null. Apply keeps the old value for a missing field and clears it for null.
package main
import (
"encoding/json"
"fmt"
"github.com/zkrebbekx/monadz"
)
func main() {
type update struct {
Name monadz.Patch[string] `json:"name,omitzero"`
Logo monadz.Patch[string] `json:"logo,omitzero"`
}
var req update
if err := json.Unmarshal([]byte(`{"logo": null}`), &req); err != nil {
panic(err)
}
name, logo := "Acme", "acme.png"
req.Name.Apply(&name)
req.Logo.Apply(&logo)
fmt.Printf("%v %v %q %q\n", req.Name, req.Logo, name, logo)
}
Output: Missing Null "Acme" ""
func PatchMissing ¶
PatchMissing returns a Patch that leaves the value unchanged.
func (Patch[T]) Apply ¶
func (p Patch[T]) Apply(dst *T)
Apply changes *dst: Set stores the value, Null stores the zero value, and Missing does nothing.
func (Patch[T]) ApplyOption ¶
ApplyOption changes *dst: Set stores Some, Null stores None, and Missing does nothing.
func (Patch[T]) Map ¶
Map applies f to the value of a Set Patch. A Null Patch stays Null, and a Missing Patch stays Missing.
func (Patch[T]) MarshalJSON ¶
MarshalJSON implements json.Marshaler. Missing and Null both write null; use omitzero to drop a Missing field.
encoding/json in Go 1.27 calls MarshalJSONTo first. This method is for direct callers.
func (Patch[T]) MarshalJSONTo ¶
MarshalJSONTo implements the encoding/json/v2 MarshalerTo interface. Missing and Null both write null.
func (Patch[T]) Match ¶
Match calls onSet with the value, or onNull, or onMissing, and returns the result.
func (*Patch[T]) UnmarshalJSON ¶
UnmarshalJSON implements json.Unmarshaler: null gives Null, and a value gives Set. As with Option, a value that decodes from null gives Null.
encoding/json in Go 1.27 calls UnmarshalJSONFrom first. This method is for direct callers.
type Reader ¶
type Reader[R, A any] struct { // contains filtered or unexported fields }
Reader is a computation that reads a shared environment R, such as a config or a set of dependencies. Nothing runs until Run. (Haskell: Reader r a.)
The zero Reader has no function. Run on a zero Reader panics; use NewReader.
func (Reader[R, A]) AndThen ¶
AndThen passes the value to f and runs the Reader that f returns with the same environment. (Haskell: >>=.)
type Result ¶
type Result[T any] struct { // contains filtered or unexported fields }
Result holds a value of type T (Ok) or an error (Err).
The zero value is Ok(zero T), which matches Go's (zero, nil) convention. Equivalents: Rust Result<T, E> with E = error, Haskell Either error a. For a typed failure value, use Either.
func Do ¶
Do runs f and returns its value as Ok. Inside f, call Try on a Result or an Option to get its value. If the Result is Err or the Option is None, Try stops f at once and Do returns the error. This is Go's version of Rust's "?" operator and Haskell's do notation:
monadz.Do(func() Invoice {
cust := repo.Customer(ctx, id).Try()
plan := monadz.Lookup(plans, cust.PlanID).OkOr(ErrNoPlan).Try()
return rate(cust, plan)
})
Do recovers ONLY the signal from Try. Any other panic (a nil dereference, an index out of range, an explicit panic) passes through, so a bug still crashes. Use DoRecover to turn every panic into an error.
An Option.Try on None returns ErrNone. Add context with OkOr first.
Call Try only in the goroutine that runs f. A Try in another goroutine is not inside Do, and its panic crashes the program.
Call Try only where no code between Try and Do recovers panics, for example a fmt String method or a text/template function. That code catches the Try signal, and Do does not see the failure.
Do not call Try in a deferred function. If a panic is in flight, the Try signal replaces it, and Do returns Err instead of crashing. In a defer, join the error into a named result with errors.Join.
Example ¶
Inside Do, Try returns the value of a Result or an Option. On a failure, Try stops the block and Do returns the error.
package main
import (
"errors"
"fmt"
"github.com/zkrebbekx/monadz"
)
func main() {
errNoPlan := errors.New("no plan")
plans := map[string]int{"pro": 25}
seats := func(plan string) monadz.Result[int] {
return monadz.Do(func() int {
n := monadz.Lookup(plans, plan).OkOr(errNoPlan).Try()
return n * 2
})
}
fmt.Println(seats("pro"))
fmt.Println(seats("team"))
}
Output: Ok(50) Err(no plan)
func DoRecover ¶
DoRecover is Do plus a recover for every other panic. A panic that is not a Try signal becomes an Err that holds a *PanicError with the stack trace.
Use DoRecover at a boundary where a crash costs more than a failed request: an HTTP handler, a queue consumer, or a plugin call.
Example ¶
Do lets a real panic (a bug) crash. DoRecover changes it into an Err that holds a *monadz.PanicError with the stack trace.
package main
import (
"errors"
"fmt"
"github.com/zkrebbekx/monadz"
)
func main() {
res := monadz.DoRecover(func() int {
var m map[string]int
m["x"] = 1 // a bug: assignment to a nil map
return 0
})
var pe *monadz.PanicError
fmt.Println(res.IsErr(), errors.As(res.Err(), &pe))
}
Output: true true
func Err ¶
Err returns a failed Result. A nil err becomes ErrNilError, so Err never returns a success by mistake.
func FromErr ¶
FromErr converts a function that returns only an error: monadz.FromErr(db.PingContext(ctx)).
func (Result[T]) AndThen ¶
AndThen applies f to the value and returns its Result. This is the monad bind. (Rust: and_then; Haskell: >>=.)
func (Result[T]) Ensure ¶
Ensure returns Err(err) if the value does not satisfy pred. (Scala: filterOrElse; Haskell: mfilter.)
func (Result[T]) Expect ¶
Expect returns the value, or panics with msg and the error. (Rust: expect.)
func (Result[T]) Get ¶
Get returns the Go pair (value, error). On a failure, the value is the zero value of T.
func (Result[T]) Inspect ¶
Inspect calls f with the value, if any, and returns the Result unchanged.
func (Result[T]) InspectErr ¶
InspectErr calls f with the error, if any, and returns the Result unchanged. Use it to log a failure in the middle of a chain.
func (Result[T]) IsErrAnd ¶
IsErrAnd reports whether the Result is a failure with an error that satisfies pred.
func (Result[T]) IsOkAnd ¶
IsOkAnd reports whether the Result is a success with a value that satisfies pred.
func (Result[T]) MapOrElse ¶
MapOrElse applies onOk to the value, or onErr to the error. (Rust: map_or_else.)
func (Result[T]) Match ¶
Match calls onOk with the value or onErr with the error, and returns the result. Unlike mo's result.Match, onErr receives the error. (Haskell: either.)
func (Result[T]) Must ¶
func (r Result[T]) Must() T
Must returns the value, or panics with the error. (Rust: unwrap.)
func (Result[T]) Recover ¶
Recover is OrElse with the Go shape func(error) (T, error). Return a value to recover, or return an error to stay on the failure track.
func (Result[T]) Then ¶
Then applies a Go-shaped step func(T) (U, error) to the value. strconv.Atoi, url.Parse, and your own functions work directly.
Example ¶
Then takes a Go-shaped function func(T) (U, error), such as strconv.Atoi. Wrap adds context to the error, and Match folds the Result to one value.
package main
import (
"fmt"
"strconv"
"github.com/zkrebbekx/monadz"
)
func main() {
parse := func(s string) monadz.Result[int] {
return monadz.Ok(s).
Then(strconv.Atoi).
Wrap("parse port")
}
for _, s := range []string{"8080", "http"} {
msg := parse(s).Match(
func(port int) string { return "port " + strconv.Itoa(port) },
func(err error) string { return "error: " + err.Error() },
)
fmt.Println(msg)
}
}
Output: port 8080 error: parse port: strconv.Atoi: parsing "http": invalid syntax
func (Result[T]) Try ¶
func (r Result[T]) Try() T
Try returns the value. On a failure, it stops the enclosing Do, and Do returns the error. See Do.
func (Result[T]) Validation ¶
func (r Result[T]) Validation() Validation[T]
Validation converts the Result to a Validation, so that it can collect errors together with other Validations.
func (Result[T]) ValueOr ¶
func (r Result[T]) ValueOr(fallback T) T
ValueOr returns the value, or fallback for a failure. (Rust: unwrap_or.)
func (Result[T]) ValueOrElse ¶
ValueOrElse returns the value, or f(err). (Rust: unwrap_or_else.)
func (Result[T]) ValueOrZero ¶
func (r Result[T]) ValueOrZero() T
ValueOrZero returns the value, or the zero value of T. (Rust: unwrap_or_default.)
func (Result[T]) Wrap ¶
Wrap adds context to the error: "msg: <err>". The original error stays reachable with errors.Is and errors.As. (Rust anyhow: context.)
type RetryPolicy ¶
type RetryPolicy struct {
// MaxAttempts is the total number of runs, including the first.
// A value below 1 means 1.
MaxAttempts int
// Backoff returns the wait before attempt n+1, after attempt n failed
// (n starts at 1). Nil means no wait.
Backoff func(attempt int) time.Duration
// RetryIf reports whether an error is worth a retry. Nil means every
// error. Retry always stops when its own ctx ends, so a nil RetryIf
// still retries a timeout from an inner Timeout.
RetryIf func(error) bool
}
RetryPolicy says when and how often Retry runs a Task again.
type State ¶
type State[S, A any] struct { // contains filtered or unexported fields }
State is a step that reads a state S and returns a value A and a new state. Nothing runs until Run. (Haskell: State s a.)
Build a large computation from small steps with AndThen, then run it against any start state. If the steps are pure, the run is a pure function, so you can replay it in a test.
The zero State has no step. Run on a zero State panics; use NewState.
func GetStateWith ¶
GetStateWith returns a step whose value is f(state). (Haskell: gets.)
func ModifyState ¶
ModifyState returns a step that applies f to the state. (Haskell: modify.)
func (State[S, A]) And ¶
And runs this step and then next, and keeps the value of next. (Haskell: >>.)
func (State[S, A]) AndThen ¶
AndThen runs this step, passes its value to f, and runs the step that f returns with the new state. This is the monad bind. (Haskell: >>=.)
Example ¶
AndThen chains State steps. The next step can use the value of the step before it.
package main
import (
"fmt"
"github.com/zkrebbekx/monadz"
)
func main() {
next := monadz.NewState(func(n int) (int, int) { return n, n + 1 })
pair := next.AndThen(func(a int) monadz.State[int, string] {
return next.Map(func(b int) string { return fmt.Sprint(a, "-", b) })
})
id, counter := pair.Run(41)
fmt.Println(id, counter)
}
Output: 41-42 43
func (State[S, A]) Eval ¶
func (m State[S, A]) Eval(s S) A
Eval runs the step and returns only the value. (Haskell: evalState.)
func (State[S, A]) Exec ¶
func (m State[S, A]) Exec(s S) S
Exec runs the step and returns only the final state. (Haskell: execState.)
type Task ¶
type Task[T any] struct { // contains filtered or unexported fields }
Task is a description of work that can fail and that takes a context. Nothing runs until you call Run or Start. You can run a Task many times; each run does the work again. That makes a Task the right input for retry, timeout, and concurrency combinators.
Equivalents: Haskell ReaderT Context (ExceptT error IO) a, Rust async fn, Scala ZIO.
Panics: Run does not recover panics, so a bug crashes in the caller's goroutine. Start, ZipWith, and the functions in package tasks run a Task in another goroutine. There, monadz captures a panic and raises it again in the goroutine that waits for the result. The message holds the original stack trace.
func NewTaskResult ¶
NewTaskResult returns a Task that calls f, which returns a Result.
func (Task[T]) InspectErr ¶
InspectErr calls f with the error after a failure.
func (Task[T]) Once ¶
Once returns a Task that runs the work one time and then returns the cached result. Concurrent runs wait for the first one. A caller that waits stops waiting when its own context ends.
A failure with a context error (context.Canceled or context.DeadlineExceeded, also from an inner Timeout) is not cached, so the next run tries again. A panic is cached: every later run panics with the same value, as sync.OnceValue does.
func (Task[T]) OrElse ¶
OrElse runs the Task that f returns after a failure. Use it for a fallback source.
func (Task[T]) Retry ¶
func (t Task[T]) Retry(p RetryPolicy) Task[T]
Retry runs the Task again after a failure, as the policy allows. The waits stop when ctx ends. The final error tells how many attempts ran.
Example ¶
A Task does nothing until Run, so Retry can run it again. A nil RetryIf retries every error.
package main
import (
"context"
"errors"
"fmt"
"github.com/zkrebbekx/monadz"
)
func main() {
attempts := 0
flaky := monadz.NewTask(func(ctx context.Context) (string, error) {
attempts++
if attempts < 3 {
return "", errors.New("unavailable")
}
return "charged", nil
})
res := flaky.Retry(monadz.RetryPolicy{MaxAttempts: 4}).Run(context.Background())
fmt.Println(res, attempts)
}
Output: Ok(charged) 3
func (Task[T]) Run ¶
Run runs the Task in the caller's goroutine. If ctx already ended, Run returns ctx.Err() and does not start the work.
func (Task[T]) Start ¶
Start runs the Task in a new goroutine and returns a Future for the result. Future.Cancel cancels the context that the Task receives.
func (Task[T]) Timeout ¶
Timeout fails the Task with context.DeadlineExceeded if it does not finish within d. The Task's context is canceled at the deadline.
Timeout returns at the deadline and does NOT wait for the work to stop, even if the work observes its context. Code in the work that runs after the deadline (cleanup, a late write) runs at the same time as the caller. This is the one exception to the structured-concurrency rule of package tasks. If the work panics after the deadline, the panic crashes the program, as an unrecovered goroutine panic does.
type Triple ¶
type Triple[A, B, C any] struct { First A Second B Third C }
Triple holds three values.
type Unit ¶
type Unit struct{}
Unit is the type with one value. Use it where a type parameter needs a type, but the value carries no data.
type Validation ¶
type Validation[T any] struct { // contains filtered or unexported fields }
Validation holds a value of type T (Valid) or one or more errors (Invalid).
Result stops at the first error. Validation collects ALL the errors, so a form can report every bad field in one response. Combine Validations with the Map2 ... Map6 functions in package validations, which run every check and join the errors. (Haskell: Data.Validation.)
AndThen is fail-fast, because the next check needs the value. Use the validations package for independent checks.
The zero value is Valid(zero T).
func Invalid ¶
func Invalid[T any](errs ...error) Validation[T]
Invalid returns a Validation that holds errs. Invalid drops nil errors. If no error is left, the Validation holds ErrNilError.
func ValidIf ¶
func ValidIf[T any](v T, ok bool, err error) Validation[T]
ValidIf returns Valid(v) if ok is true, else Invalid(err).
func (Validation[T]) AndThen ¶
func (v Validation[T]) AndThen[U any](f func(T) Validation[U]) Validation[U]
AndThen applies f to the value and returns its Validation. It is fail-fast: f does not run for an Invalid value.
func (Validation[T]) Ensure ¶
func (v Validation[T]) Ensure(pred func(T) bool, err error) Validation[T]
Ensure returns Invalid(err) if the Validation is Valid and its value does not satisfy pred. Ensure returns an Invalid Validation unchanged, and pred does not run.
func (Validation[T]) Err ¶
func (v Validation[T]) Err() error
Err returns nil for a Valid value. If the Validation holds exactly one error, Err returns that error itself. Else Err returns the errors joined with errors.Join.
func (Validation[T]) Errors ¶
func (v Validation[T]) Errors() []error
Errors returns a copy of the errors. It is empty for a Valid value.
func (Validation[T]) Expect ¶
func (v Validation[T]) Expect(msg string) T
Expect returns the value, or panics with msg and Err(). (Rust: expect.)
func (Validation[T]) Field ¶
func (v Validation[T]) Field(name string) Validation[T]
Field wraps every error in a *FieldError with the given field name, so an API can report which field failed.
func (Validation[T]) Get ¶
func (v Validation[T]) Get() (T, error)
Get returns the value and nil, or the zero value and Err().
func (Validation[T]) Inspect ¶
func (v Validation[T]) Inspect(f func(T)) Validation[T]
Inspect calls f with the value, if any, and returns the Validation unchanged.
func (Validation[T]) IsInvalid ¶
func (v Validation[T]) IsInvalid() bool
IsInvalid reports whether the Validation holds errors.
func (Validation[T]) IsValid ¶
func (v Validation[T]) IsValid() bool
IsValid reports whether the Validation holds a value.
func (Validation[T]) Map ¶
func (v Validation[T]) Map[U any](f func(T) U) Validation[U]
Map applies f to the value.
func (Validation[T]) MapErr ¶
func (v Validation[T]) MapErr(f func(error) error) Validation[T]
MapErr applies f to each error. If f returns nil, MapErr stores ErrNilError. f does not run for a Valid value.
func (Validation[T]) Match ¶
func (v Validation[T]) Match[U any](onValid func(T) U, onInvalid func([]error) U) U
Match calls onValid with the value or onInvalid with the errors, and returns the result. (Haskell: validation, in package Data.Validation.)
func (Validation[T]) Must ¶
func (v Validation[T]) Must() T
Must returns the value, or panics with Err(). (Rust: unwrap.)
func (Validation[T]) Result ¶
func (v Validation[T]) Result() Result[T]
Result converts the Validation to a Result with the joined errors.
func (Validation[T]) String ¶
func (v Validation[T]) String() string
String returns "Valid(<value>)" or "Invalid(<err>; <err>)".
func (Validation[T]) ValueOr ¶
func (v Validation[T]) ValueOr(fallback T) T
ValueOr returns the value, or fallback for an Invalid value. (Rust: unwrap_or.)
func (Validation[T]) ValueOrElse ¶
func (v Validation[T]) ValueOrElse(f func([]error) T) T
ValueOrElse returns the value, or f applied to the errors. f runs only for an Invalid value. (Rust: unwrap_or_else.)
func (Validation[T]) ValueOrZero ¶
func (v Validation[T]) ValueOrZero() T
ValueOrZero returns the value, or the zero value of T. (Rust: unwrap_or_default.)
func (Validation[T]) ZipWith ¶
func (v Validation[T]) ZipWith[U, V any](other Validation[U], f func(T, U) V) Validation[V]
ZipWith combines both values with f if both are Valid. Else ZipWith returns Invalid with the errors of both, in order. Unlike AndThen, it collects every error. (Haskell: liftA2 for Data.Validation.)
type Writer ¶
type Writer[W, A any] struct { // contains filtered or unexported fields }
Writer is a computation that returns a value A and appends to a log of W values, such as audit entries or metrics. (Haskell: Writer w a, with w = [W].)
The zero Writer has no value. Run on a zero Writer panics; use NewWriter.
func NewWriter ¶
NewWriter returns a Writer with the value a and the log entries w. NewWriter copies w, so a later change to the caller's slice does not change the log.
func (Writer[W, A]) AndThen ¶
AndThen passes the value to f, and joins the two logs in order. (Haskell: >>=.)
func (Writer[W, A]) Censor ¶
Censor applies f to the log. f receives a copy, so it can change the slice in place. (Haskell: censor.)
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package eithers has functions that work on many monadz.Either values.
|
Package eithers has functions that work on many monadz.Either values. |
|
Package options has functions that work on many monadz.Option values, or that cannot be methods because of Go's type rules.
|
Package options has functions that work on many monadz.Option values, or that cannot be methods because of Go's type rules. |
|
playground
|
|
|
scenarios
Package scenarios holds the stories that the monadz playground shows.
|
Package scenarios holds the stories that the monadz playground shows. |
|
wasm
command
Command wasm is the bridge between the monadz playground page and the scenarios package.
|
Command wasm is the bridge between the monadz playground page and the scenarios package. |
|
Package results has functions that work on many monadz.Result values, or that cannot be methods because of Go's type rules.
|
Package results has functions that work on many monadz.Result values, or that cannot be methods because of Go's type rules. |
|
Package tasks runs many monadz.Task values at the same time.
|
Package tasks runs many monadz.Task values at the same time. |
|
Package validations combines monadz.Validation values and collects every error, instead of stopping at the first one.
|
Package validations combines monadz.Validation values and collects every error, instead of stopping at the first one. |