Generated banner for Golang Samber Mo
Generated logo for Golang Samber Mo

Golang Samber Mo

Samuel BertheCommunityCreated by samberDistributed by Samuel Berthe

  • Skill
  • Tool integration
  • Software development
  • MIT
  • No issues found

From the author

Monadic types for Golang using samber/mo — Option, Result, Either, Future, IO, Task, and State types for type-safe nullable values, error handling, and functional composition with pipeline sub-packages. Apply when using or adopting samber/mo, when the codebase imports `github.com/samber/mo`, or when considering functional programming patterns as a safety design for Golang. Not for nil-safety and zero-value design without this library (→ See `samber/cc-skills-golang@golang-safety` skill), nor for native error wrapping with fmt.Errorf, errors.Is and errors.As (→ See `samber/cc-skills-golang@golang-error-handling` skill).
— the description by samber

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

The samber/mo library introduces functional programming abstractions to Go, enabling developers to replace traditional nil checks and error handling with type-safe monadic structures. By utilizing types like Option, Result, and Either, developers can create more robust, composable pipelines that make impossible states unrepresentable. The library is designed for Go 1.18+ and provides a suite of tools for handling side effects, asynchronous computations, and stateful logic, bridging the gap between imperative Go and functional safety patterns.

What it does

  • Eliminates nil pointer risks using the Option type.
  • Replaces standard (T, error) patterns with the composable Result type.
  • Implements Either for handling multiple valid alternative outcomes.
  • Provides Future, IO, and Task types for managing side effects and asynchronous operations.
  • Enables functional pipeline composition using sub-package functions.
  • Supports Do notation to bridge imperative code with monadic error propagation.

When to use it

  • When you want to enforce type-level safety for nullable values or error states.
  • When building complex data processing pipelines that require clean, composable transformations.
  • When you need to represent multiple valid outcomes or alternative strategies clearly.
  • When adopting functional programming patterns to improve code maintainability and robustness in Go.
  • When you need to handle asynchronous computations or side effects in a structured, lazy manner.

Advantages

  • Reduces runtime panics by replacing nil checks with explicit Option types.
  • Improves code readability by chaining operations instead of nesting if/else blocks.
  • Provides consistent, type-safe error handling across the application.
  • Facilitates cleaner API boundaries by converting native Go errors into Result types.
  • Offers a standard way to handle complex state and side effects without side-effect-heavy imperative code.

Usage examples

“How do I safely handle a nullable string in my struct using samber/mo?”
You can use mo.Option[string] in your struct fields, which allows you to distinguish between an absent value (None) and an empty string, and provides methods like OrElse for safe extraction.
“How can I chain multiple operations that might fail in Go?”
You can use mo.Result[T] and its associated pipeline functions like result.Pipe2 to chain transformations; if any step returns an error, the pipeline short-circuits automatically.

Install

  1. Download the skill as a ZIP:

    https://autogineer.com/agent-skills/samber/golang-samber-mo/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip golang-samber-mo.zip -d .agents/skills

Documentation by samber

Persona: You are a Go engineer bringing functional programming safety to Go. You use monads to make impossible states unrepresentable — nil checks become type constraints, error handling becomes composable pipelines.

Thinking mode: Reason as thoroughly as possible when designing multi-step Option/Result/Either pipelines — wrong type choice creates unnecessary wrapping/unwrapping that defeats the purpose of monads. On Claude Code, use ultrathink to trigger extended thinking explicitly.

samber/mo — Monads and Functional Abstractions for Go

Go 1.18+ library providing type-safe monadic types with zero dependencies. Inspired by Scala, Rust, and fp-ts.

Official Resources:

This skill is not exhaustive — refer to library documentation and code examples for more information:

  • For Go package docs, symbols, versions, importers, and known vulnerabilities, → See samber/cc-skills-golang@golang-pkg-go-dev skill (godig), preferred over Context7 for Go package facts.

  • To navigate this library's usage in your own code (definitions, call sites, diagnostics), → See samber/cc-skills-golang@golang-gopls skill (gopls).

  • Context7 remains a fallback for docs not indexed on pkg.go.dev.

go get github.com/samber/mo

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

Core Types at a Glance

TypePurposeThink of it as...
Option[T]Value that may be absentRust's Option, Java's Optional
Result[T]Operation that may failRust's Result<T, E>, replaces (T, error)
Either[L, R]Value of one of two typesScala's Either, TypeScript discriminated union
EitherX[L, R]Value of one of X typesScala's Either, TypeScript discriminated union
Future[T]Async value not yet availableJavaScript Promise
IO[T]Lazy synchronous side effectHaskell's IO
Task[T]Lazy async computationfp-ts Task
State[S, A]Stateful computationHaskell'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(&u.ID, &u.Email, &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

  1. 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

  2. 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

  3. Use Result[T] for errors, Either[L, R] for alternatives — Result is specialized for success/failure; Either is for two valid types

  4. Option for nullable fields, not zero values — Option[string] distinguishes "absent" from "empty string"; use plain string when empty string is a valid value

  5. Chain, don't nest — result.Map(...).FlatMap(...).OrElse(default) reads left-to-right; avoid nested if/else patterns when monadic chaining is cleaner

  6. 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

Files

references/advanced-types.md

Advanced Types Reference

These types are less commonly used than Option/Result/Either but provide powerful abstractions for specific scenarios.

Table of Contents

  • Type Hierarchy

  • Future[T] — Asynchronous Values

    • Constructor

    • Chaining

    • Collecting Results

    • Cancellation

  • IO[T] — Synchronous Side Effects

    • Variants by Parameter Count

  • IOEither[T] — Synchronous Side Effects with Errors

    • Variants by Parameter Count

  • Task[T] — Asynchronous Computations

    • From IO

    • Variants by Parameter Count

  • TaskEither[T] — Async Computations with Errors

    • Methods

  • State[S, A] — Stateful Computations

    • Constructor

    • ReturnState — wrap a value without modifying state

    • State Manipulation

    • Chaining State Computations

  • When to Use Advanced Types

Type Hierarchy

Synchronous                    Asynchronous
-----------                    ------------
IO[T]     (no error)    →     Task[T]     (no error)    → Future[T]
IOEither[T] (with error) →     TaskEither[T] (with error) → Future[T]
  • IO wraps a synchronous side-effecting computation

  • Task wraps an asynchronous side-effecting computation (returns a Future)

  • Future represents a value that will be available later

  • Either variants add error handling capability

Future[T] — Asynchronous Values

Represents a value that may not yet be available. Similar to JavaScript's Promise.

Constructor
future := mo.NewFuture(func(resolve func(int), reject func(error)) {
    // runs asynchronously
    result, err := expensiveComputation()
    if err != nil {
        reject(err)
    } else {
        resolve(result)
    }
})
Chaining
future.
    Then(func(v int) (int, error) {
        return v * 2, nil  // transform on success
    }).
    Catch(func(err error) (int, error) {
        return 0, err  // handle error
    }).
    Finally(func(v int, err error) (int, error) {
        // always runs, regardless of success/failure
        log.Println("Done")
        return v, err
    })
Collecting Results
value, err := future.Collect()    // blocks until resolved
result := future.Result()          // blocks, returns Result[T]
either := future.Either()          // blocks, returns Either[error, T]
Cancellation
future.Cancel()  // terminates the future chain

IO[T] — Synchronous Side Effects

Wraps a function that performs side effects. The computation is lazy — it only runs when Run() is called. IO never fails.

Variants by Parameter Count
// No parameters
io := mo.NewIO(func() string { return "hello" })
result := io.Run()  // "hello"

// 1 parameter
io1 := mo.NewIO1(func(name string) string { return "hello " + name })
result := io1.Run("Alice")  // "hello Alice"

// 2-5 parameters (IO2, IO3, IO4, IO5)
io2 := mo.NewIO2(func(a, b int) int { return a + b })
result := io2.Run(1, 2)  // 3

IOEither[T] — Synchronous Side Effects with Errors

Like IO but the computation can fail. The callback must return Either[error, R], not (R, error).

io := mo.NewIOEither(func() mo.Either[error, string] {
    data, err := os.ReadFile("config.yaml")
    if err != nil {
        return mo.Left[error, string](err)
    }
    return mo.Right[error, string](string(data))
})

either := io.Run()  // Either[error, string]
Variants by Parameter Count
// 1 parameter
io1 := mo.NewIOEither1(func(path string) mo.Either[error, string] {
    data, err := os.ReadFile(path)
    if err != nil {
        return mo.Left[error, string](err)
    }
    return mo.Right[error, string](string(data))
})
either := io1.Run("config.yaml")  // Either[error, string]

// IOEither2 through IOEither5 follow the same pattern

Task[T] — Asynchronous Computations

Lazy async computation — Run() calls the wrapped function, which returns a *Future[T]. Never fails.

task := mo.NewTask(func() *mo.Future[int] {
    return mo.NewFuture(func(resolve func(int), reject func(error)) {
        time.Sleep(time.Second)
        resolve(42)
    })
})

future := task.Run()           // executes the function, returns *Future[int]
value, err := future.Collect() // blocks until done

Note: NewTask accepts func() *Future[R] — it wraps a Future-producing function for lazy execution.

From IO
io := mo.NewIO(func() int { return 42 })
task := mo.NewTaskFromIO(io)  // wrap IO as async Task
Variants by Parameter Count
task1 := mo.NewTask1(func(n int) *mo.Future[int] {
    return mo.NewFuture(func(resolve func(int), reject func(error)) {
        resolve(n * 2)
    })
})
future := task1.Run(21)  // *Future[int] resolving to 42

TaskEither[T] — Async Computations with Errors

Like Task but the computation can fail. Combines Task semantics with error handling.

te := mo.NewTaskEither(func() *mo.Future[string] {
    return mo.NewFuture(func(resolve func(string), reject func(error)) {
        resp, err := http.Get("https://api.example.com/data")
        if err != nil {
            reject(err)
            return
        }
        defer resp.Body.Close()
        body, err := io.ReadAll(resp.Body)
        if err != nil {
            reject(err)
            return
        }
        resolve(string(body))
    })
})

Note: Like NewTask, NewTaskEither accepts func() *Future[R]. The difference is in the methods available on the returned type — TaskEither provides Match, OrElse, ToEither, and ToTask.

Methods
te.OrElse("fallback")                    // blocks, returns value or fallback
te.ToEither()                            // blocks, returns Either[error, T]
te.ToTask("fallback")                    // converts to Task (uses fallback on error)
te.Match(
    func(err error) mo.Either[error, string] { ... },  // on error
    func(v string) mo.Either[error, string] { ... },    // on success
)

State[S, A] — Stateful Computations

Represents a computation that threads state through a series of operations. The state type S flows through the computation while producing result values of type A.

Constructor
// State computation: takes state, returns (result, newState)
counter := mo.NewState(func(count int) (string, int) {
    return fmt.Sprintf("count=%d", count), count + 1
})

result, newState := counter.Run(0)  // ("count=0", 1)
ReturnState — wrap a value without modifying state
state := mo.ReturnState[int, string]("hello")
result, s := state.Run(42)  // ("hello", 42) — state unchanged
State Manipulation
// Get — return current state as result
getter := mo.NewState(func(s int) (int, int) { return s, s })

// Put — replace state
putter := state.Put(100)
_, s := putter.Run(0)  // (_, 100)

// Modify — transform state
modified := state.Modify(func(s int) int { return s * 2 })
_, s := modified.Run(5)  // (_, 10)
Chaining State Computations

State is useful for accumulating results while threading context:

// Parse tokens while tracking position
type ParseState struct {
    Input    string
    Position int
}

parseChar := mo.NewState(func(s ParseState) (byte, ParseState) {
    ch := s.Input[s.Position]
    return ch, ParseState{Input: s.Input, Position: s.Position + 1}
})

When to Use Advanced Types

TypeUse when...
FutureYou need async computation with chaining (Then/Catch/Finally)
IOYou want to defer and compose synchronous side effects
IOEitherDeferred side effects that can fail
TaskDeferred async computation (lazy Future)
TaskEitherDeferred async computation that can fail
StateThreading state through a series of pure computations

Most Go projects only need Option, Result, and Either. The advanced types are valuable when building functional pipelines or when you want explicit control over when side effects execute.

Requirements and compatibility

Written for: Claude Code, OpenAI Codex

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Needs: Declares allowed tools, Claude Code frontmatter

The author says: Designed for Claude Code, Codex or similar harness, and for projects using Golang.

Security

No issues found by our static scan· No issues found

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Created by
samber
Publisher
Samuel Berthe (Community)
Distributed by
Samuel Berthe
Source
samber/cc-skills-golang · skills/golang-samber-mo · commit bb1d43d
Licence
MIT License (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by samber or Samuel Berthe.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download golang-samber-mo.zip