golang-samber-mo

Monadic types for Golang using samber/mo — Option, Result, Either, Future, IO, Task, and State types for type-safe nullable values, error handling, and…

INSTALLATION
npx skills add https://github.com/samber/cc-skills-golang --skill golang-samber-mo
Run in your project or agent environment. Adjust flags if your CLI version differs.

SKILL.md

$27

For an introduction to functional programming concepts and why monads are valuable in Go, see Monads Guide.

Core Types at a Glance

Type

Purpose

Think of it as...

Option[T]

Value that may be absent

Rust's Option, Java's Optional

Result[T]

Operation that may fail

Rust's Result<T, E>, replaces (T, error)

Either[L, R]

Value of one of two types

Scala's Either, TypeScript discriminated union

EitherX[L, R]

Value of one of X types

Scala's Either, TypeScript discriminated union

Future[T]

Async value not yet available

JavaScript Promise

IO[T]

Lazy synchronous side effect

Haskell's IO

Task[T]

Lazy async computation

fp-ts Task

State[S, A]

Stateful computation

Haskell's State monad

Option[T] — Nullable Values Without nil

Represents a value that is either present (Some) or absent (None). Eliminates nil pointer risks at the type level.

import "github.com/samber/mo"

name := mo.Some("Alice")          // Option[string] with value

empty := mo.None[string]()        // Option[string] without value

fromPtr := mo.PointerToOption(ptr) // nil pointer -> None

// Safe extraction

name.OrElse("Anonymous")  // "Alice"

empty.OrElse("Anonymous")  // "Anonymous"

// Transform if present, skip if absent

upper := name.Map(func(s string) (string, bool) {

    return strings.ToUpper(s), true

})

Key methods: Some, None, Get, MustGet, OrElse, OrEmpty, Map, FlatMap, Match, ForEach, ToPointer, IsPresent, IsAbsent.

Option implements json.Marshaler/Unmarshaler, sql.Scanner, driver.Valuer — use it directly in JSON structs and database models.

For full API reference, see Option Reference.

Result[T] — Error Handling as Values

Represents success (Ok) or failure (Err). Equivalent to Either[error, T] but specialized for Go's error pattern.

// Wrap Go's (value, error) pattern

result := mo.TupleToResult(os.ReadFile("config.yaml"))

// Same-type transform — errors short-circuit automatically

upper := mo.Ok("hello").Map(func(s string) (string, error) {

    return strings.ToUpper(s), nil

})

// Ok("HELLO")

// Extract with fallback

val := upper.OrElse("default")

Go limitation: Direct methods (.Map, .FlatMap) cannot change the type parameter — Result[T].Map returns Result[T], not Result[U]. Go methods cannot introduce new type parameters. For type-changing transforms (e.g. Result[[]byte] to Result[Config]), use sub-package functions or mo.Do:

import "github.com/samber/mo/result"

// Type-changing pipeline: []byte -> Config -> ValidConfig

parsed := result.Pipe2(

    mo.TupleToResult(os.ReadFile("config.yaml")),

    result.Map(func(data []byte) Config { return parseConfig(data) }),

    result.FlatMap(func(cfg Config) mo.Result[ValidConfig] { return validate(cfg) }),

)

Key methods: Ok, Err, Errf, TupleToResult, Try, Get, MustGet, OrElse, Map, FlatMap, MapErr, Match, ForEach, ToEither, IsOk, IsError.

For full API reference, see Result Reference.

Either[L, R] — Discriminated Union of Two Types

Represents a value that is one of two possible types. Unlike Result, neither side implies success or failure — both are valid alternatives.

// API that returns either cached data or fresh data

func fetchUser(id string) mo.Either[CachedUser, FreshUser] {

    if cached, ok := cache.Get(id); ok {

        return mo.Left[CachedUser, FreshUser](cached)

    }

    return mo.Right[CachedUser, FreshUser](db.Fetch(id))

}

// Pattern match

result := fetchUser("user-123")

result.Match(

    func(cached CachedUser) mo.Either[CachedUser, FreshUser] { /* use cached */ },

    func(fresh FreshUser) mo.Either[CachedUser, FreshUser] { /* use fresh */ },

)

When to use Either vs Result: Use Result[T] when one path is an error. Use Either[L, R] when both paths are valid alternatives (cached vs fresh, left vs right, strategy A vs B).

Either3[T1, T2, T3], Either4, and Either5 extend this to 3-5 type variants.

For full API reference, see Either Reference.

Do Notation — Imperative Style with Monadic Safety

mo.Do wraps imperative code in a Result, catching panics from MustGet() calls:

result := mo.Do(func() int {

    // MustGet panics on None/Err — Do catches it as Result error

    a := mo.Some(21).MustGet()

    b := mo.Ok(2).MustGet()

    return a * b  // 42

})

// result is Ok(42)

result := mo.Do(func() int {

    val := mo.None[int]().MustGet()  // panics

    return val

})

// result is Err("no such element")

Do notation bridges imperative Go style with monadic safety — write straight-line code, get automatic error propagation.

Pipeline Sub-Packages vs Direct Chaining

samber/mo provides two ways to compose operations:

Direct methods (.Map, .FlatMap) — work when the output type equals the input type:

opt := mo.Some(42)

doubled := opt.Map(func(v int) (int, bool) {

    return v * 2, true

})  // Option[int]

Sub-package functions (option.Map, result.Map) — required when the output type differs from input:

import "github.com/samber/mo/option"

// int -> string type change: use sub-package Map

strOpt := option.Map(func(v int) string {

    return fmt.Sprintf("value: %d", v)

})(mo.Some(42))  // Option[string]

Pipe functions (option.Pipe3, result.Pipe3) — chain multiple type-changing transformations readably:

import "github.com/samber/mo/option"

result := option.Pipe3(

    mo.Some(42),

    option.Map(func(v int) string { return strconv.Itoa(v) }),

    option.Map(func(s string) []byte { return []byte(s) }),

    option.FlatMap(func(b []byte) mo.Option[string] {

        if len(b) > 0 { return mo.Some(string(b)) }

        return mo.None[string]()

    }),

)

Rule of thumb: Use direct methods for same-type transforms. Use sub-package functions + pipes when types change across steps.

For detailed pipeline API reference, see Pipelines Reference.

Common Patterns

JSON API responses with Option

type UserResponse struct {

    Name     string            `json:"name"`

    Nickname mo.Option[string] `json:"nickname"`  // omits null gracefully

    Bio      mo.Option[string] `json:"bio"`

}

Database nullable columns

type User struct {

    ID       int

    Email    string

    Phone    mo.Option[string]  // implements sql.Scanner + driver.Valuer

}

err := row.Scan(&#x26;u.ID, &#x26;u.Email, &#x26;u.Phone)

Wrapping existing Go APIs

// Convert map lookup to Option

func MapGet[K comparable, V any](m map[K]V, key K) mo.Option[V] {

    return mo.TupleToOption(m[key])  // m[key] returns (V, bool)

}

Uniform extraction with Fold

mo.Fold works uniformly across Option, Result, and Either via the Foldable interface:

str := mo.Fold[error, int, string](

    mo.Ok(42),  // works with Option, Result, or Either

    func(v int) string { return fmt.Sprintf("got %d", v) },

    func(err error) string { return "failed" },

)

// "got 42"

Best Practices

  • **Prefer OrElse over MustGet** — MustGet panics on absent/error values; use it only inside mo.Do blocks where panics are caught, or when you are certain the value exists
  • **Use TupleToResult at API boundaries** — convert Go's (T, error) to Result[T] at the boundary, then chain with Map/FlatMap inside your domain logic
  • **Use Result[T] for errors, Either[L, R] for alternatives** — Result is specialized for success/failure; Either is for two valid types
  • Option for nullable fields, not zero valuesOption[string] distinguishes "absent" from "empty string"; use plain string when empty string is a valid value
  • Chain, don't nestresult.Map(...).FlatMap(...).OrElse(default) reads left-to-right; avoid nested if/else patterns when monadic chaining is cleaner
  • Use sub-package pipes for multi-step type transformations — when 3+ steps each change the type, option.Pipe3(...) is more readable than nested function calls

For advanced types (Future, IO, Task, State), see Advanced Types Reference.

If you encounter a bug or unexpected behavior in samber/mo, open an issue at https://github.com/samber/mo/issues.

Cross-References

  • -> See samber/cc-skills-golang@golang-samber-lo skill for functional collection transforms (Map, Filter, Reduce on slices) that compose with mo types
  • -> See samber/cc-skills-golang@golang-error-handling skill for idiomatic Go error handling patterns
  • -> See samber/cc-skills-golang@golang-safety skill for nil-safety and defensive Go coding
  • -> See samber/cc-skills-golang@golang-database skill for database access patterns
  • -> See samber/cc-skills-golang@golang-design-patterns skill for functional options and other Go patterns
BrowserAct

Let your agent run on any real-world website

Bypass CAPTCHA & anti-bot for free. Start local, scale to cloud.

Explore BrowserAct Skills →

Stop writing automation&scrapers

Install the CLI. Run your first Skill in 30 seconds. Scale when you're ready.

Start free
free · no credit card