Skip to content
This repository was archived by the owner on Aug 4, 2025. It is now read-only.

appthrust/mu

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

mu - Functional Programming Primitives for Go

Go Reference Go Report Card License

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.

Name Origin and Pronunciation

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.

Why mu?

  • 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

Quick Start

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]()
}

Core Types

Option[T] - Safe Optional Values

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] - Failable Operations

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] - Disjoint Union

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

Go Generics Design

Due to Go's type system limitations, this library uses a specific pattern to provide both ergonomic and powerful APIs:

Methods vs Functions

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

Integration with Go Ecosystem

Error Handling Bridge

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

Converting Go Patterns to mu Types

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.

Advanced Patterns

Chaining Failable Operations

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

Option Chaining

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

Best Practices

When to Use Each Type

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

API Design Guidelines

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"))
}

Documentation

For detailed API documentation, see the Go Reference.

Key Packages

Contributing

We welcome contributions! Please see our Contributing Guidelines for details.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Inspiration

This library draws inspiration from functional programming languages and libraries:

  • Rust's Option and Result 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.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published