mu is a production-ready Go library that brings functional programming primitives to Go with a focus on safety, expressiveness, and seamless integration with existing Go codebases. It provides three core types: Option
, Result
, and Either
.
mu is pronounced "mew" (/mjuː/) and comes from the Greek letter μ (mu). The name represents both "micro" (small, composable building blocks) and the mathematical notation used for monads in functional programming theory. We chose it for its simplicity—just two letters that are easy to type and remember, while connecting to the mathematical foundations of the abstractions this library provides.
- Type Safety: Eliminate nil pointer dereferences and unhandled errors at compile time
- Expressive Code: Write more declarative, chainable operations for complex logic
- Go-Native: Designed specifically for Go's type system and idioms
- Zero Dependencies: No external dependencies beyond Go's standard library
- Production Ready: Thoroughly tested with comprehensive documentation
go get github.com/appthrust/mu
package main
import (
"fmt"
"github.com/appthrust/mu"
)
func main() {
// Option: Handle optional values safely
user := findUser("john")
greeting := user.Map(func(name string) string {
return "Hello, " + name + "!"
}).OrElse("Hello, stranger!")
fmt.Println(greeting)
// Result: Chain failable operations
result := mu.Ok(42).
Map(func(x int) int { return x * 2 }).
Map(func(x int) int { return x + 10 })
if value, ok := result.Unwrap(); ok {
fmt.Println("Result:", value) // 94
}
}
func findUser(id string) mu.Option[string] {
users := map[string]string{"john": "John Doe"}
if name, exists := users[id]; exists {
return mu.Some(name)
}
return mu.None[string]()
}
Option[T]
represents a value that may or may not be present, providing a type-safe alternative to pointers and nil checks.
Use Cases:
- Optional function parameters
- Database fields that may be NULL
- Configuration values with defaults
- Safe array/slice access
// Constructor functions
some := mu.Some(42) // Option containing 42
none := mu.None[int]() // Empty Option
// Safe value extraction
value := some.OrElse(0) // Returns 42, or 0 if None
value = some.OrZero() // Returns 42, or zero value if None
// Chaining operations
result := some.
Filter(func(x int) bool { return x > 0 }).
Map(func(x int) int { return x * 2 })
Result[T]
represents operations that can succeed with a value or fail with an error. The error type is fixed to Go's built-in error
interface for maximum ecosystem compatibility.
Use Cases:
- File I/O operations
- HTTP requests
- Database queries
- Any operation that can fail
// Constructor functions
success := mu.Ok(42) // Successful result
failure := mu.Err[int](errors.New("failed")) // Failed result
// Chain operations (short-circuits on error)
result := success.
Map(func(x int) int { return x * 2 }).
Map(func(x int) int { return x + 10 })
// Convert back to Go's standard pattern
value, err := result.Unpack()
if err != nil {
// handle error
}
Either[L, R]
represents a value that can be one of two types. This implementation is right-biased, meaning operations like Map
work on the Right
value by default.
Use Cases:
- Representing different types of responses
- State machines with distinct states
- Validation results with different error types
// Constructor functions
left := mu.Left[string, int]("error") // Left value
right := mu.Right[string, int](42) // Right value
// Right-biased operations (work on Right value)
result := right.Map(func(x int) int { return x * 2 })
// Explicit left/right operations
leftResult := right.MapLeft(func(s string) string { return "ERROR: " + s })
Due to Go's type system limitations, this library uses a specific pattern to provide both ergonomic and powerful APIs:
Methods (same type transformations):
// T → T transformations use methods for fluent chaining
option.Map(func(x int) int { return x * 2 }).
Filter(func(x int) bool { return x > 0 })
Functions (type-changing transformations):
// T → U transformations use functions in separate packages
import "github.com/appthrust/mu/muo"
stringOpt := muo.MapTo(intOpt, func(x int) string {
return strconv.Itoa(x)
})
This design provides:
- Fluent method chaining for same-type operations
- Powerful type transformations through utility functions
- Clear separation of concerns
Convert between mu.Result
and Go's standard (value, error)
pattern:
// Go function that returns (T, error)
func fetchData() (string, error) { /* ... */ }
// Wrap in Result
result := mu.Try(fetchData)
// Convert back to Go pattern
value, err := result.Unpack()
Convert common Go patterns into Option
and Result
types:
import (
"github.com/appthrust/mu"
"github.com/appthrust/mu/muo"
"github.com/appthrust/mu/mur"
)
// Convert (value, bool) pattern to Option
value, ok := someMap["key"]
option := muo.From(value, ok)
// Convert slice access to Option
numbers := []int{1, 2, 3}
first := muo.FromSlice(numbers) // Some(1)
empty := muo.FromSlice([]int{}) // None
// Convert map lookup to Option
users := map[string]string{"john": "admin"}
role := muo.FromMap(users, "john") // Some("admin")
missing := muo.FromMap(users, "bob") // None
// Convert (value, error) pattern to Result
data, err := fetchData()
result := mur.From(data, err)
These functions bridge the gap between traditional Go patterns and monadic types, making it easier to adopt mu
in existing codebases.
Replace nested error checking with declarative chains:
// Before: nested error checking
func processOrder(orderID int) (string, error) {
order, err := fetchOrder(orderID)
if err != nil {
return "", fmt.Errorf("fetch failed: %w", err)
}
payment, err := processPayment(order)
if err != nil {
return "", fmt.Errorf("payment failed: %w", err)
}
return "success", nil
}
// After: declarative chain
func processOrder(orderID int) (string, error) {
return mu.Ok(orderID).
FlatMap(fetchOrderResult).
FlatMap(processPaymentResult).
Map(func(_ Payment) string { return "success" }).
Unpack()
}
Handle complex optional value operations:
// Extract nested optional values
result := user.GetProfile().
FlatMap(func(p Profile) mu.Option[string] { return p.GetEmail() }).
Map(func(email string) string { return strings.ToLower(email) }).
OrElse("no-email@example.com")
Use Option[T]
when:
- A value might legitimately be absent
- You want to avoid nil pointer errors
- Working with optional configuration or user input
Use Result[T]
when:
- Operations can fail with meaningful errors
- You want to chain multiple failable operations
- Converting from existing
(T, error)
patterns
Use Either[L, R]
when:
- You have exactly two distinct value types
- Neither type represents an "error" state
- Implementing state machines or parsers
For library authors:
// Public APIs should use standard Go patterns
func ProcessData(input string) (Output, error) {
// Use mu internally for complex logic
result := mu.Ok(input).
FlatMap(validate).
FlatMap(transform)
// Convert back to standard Go at API boundary
return result.Unpack()
}
For application code:
// Use mu types throughout your application logic
func processUserData(userData mu.Option[UserInput]) mu.Result[ProcessedData] {
return muo.MapTo(userData, processInput).
ToResult(errors.New("no user data"))
}
For detailed API documentation, see the Go Reference.
- github.com/appthrust/mu - Core types and methods
- github.com/appthrust/mu/muo - Option utility functions
- github.com/appthrust/mu/mur - Result utility functions
- github.com/appthrust/mu/mue - Either utility functions
We welcome contributions! Please see our Contributing Guidelines for details.
This project is licensed under the MIT License - see the LICENSE file for details.
This library draws inspiration from functional programming languages and libraries:
- Rust's
Option
andResult
types - Scala's functional collections
- Swift's optionals
- Existing Go libraries like samber/mo
The design prioritizes Go's philosophy of simplicity and explicitness while bringing the safety and expressiveness of functional programming to Go developers.