mirror of
https://github.com/IBM/fp-go.git
synced 2025-11-23 22:14:53 +02:00
237 lines
7.1 KiB
Go
237 lines
7.1 KiB
Go
// Copyright (c) 2025 IBM Corp.
|
|
// All rights reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
// Package option implements the Option monad using idiomatic Go tuple signatures.
|
|
//
|
|
// Unlike the standard option package which uses wrapper structs, this package represents
|
|
// Options as tuples (value, bool) where the boolean indicates presence (true) or absence (false).
|
|
// This approach is more idiomatic in Go and has better performance characteristics.
|
|
//
|
|
// # Type Signatures
|
|
//
|
|
// The core types used in this package are:
|
|
//
|
|
// Operator[A, B any] = func(A, bool) (B, bool) // Transforms an Option[A] to Option[B]
|
|
// Kleisli[A, B any] = func(A) (B, bool) // Monadic function from A to Option[B]
|
|
//
|
|
// # Basic Usage
|
|
//
|
|
// Create an Option with Some or None:
|
|
//
|
|
// some := Some(42) // (42, true)
|
|
// none := None[int]() // (0, false)
|
|
// opt := Of(42) // Alternative to Some: (42, true)
|
|
//
|
|
// Check if an Option contains a value:
|
|
//
|
|
// value, ok := Some(42)
|
|
// if ok {
|
|
// // value == 42
|
|
// }
|
|
//
|
|
// if IsSome(Some(42)) {
|
|
// // Option contains a value
|
|
// }
|
|
// if IsNone(None[int]()) {
|
|
// // Option is empty
|
|
// }
|
|
//
|
|
// Extract values:
|
|
//
|
|
// value, ok := Some(42) // Direct tuple unpacking: value == 42, ok == true
|
|
// value := GetOrElse(func() int { return 0 })(Some(42)) // Returns 42
|
|
// value := GetOrElse(func() int { return 0 })(None[int]()) // Returns 0
|
|
//
|
|
// # Transformations
|
|
//
|
|
// Map transforms the contained value:
|
|
//
|
|
// double := Map(func(x int) int { return x * 2 })
|
|
// result := double(Some(21)) // (42, true)
|
|
// result := double(None[int]()) // (0, false)
|
|
//
|
|
// Chain sequences operations that may fail:
|
|
//
|
|
// validate := Chain(func(x int) (int, bool) {
|
|
// if x > 0 { return x * 2, true }
|
|
// return 0, false
|
|
// })
|
|
// result := validate(Some(5)) // (10, true)
|
|
// result := validate(Some(-1)) // (0, false)
|
|
//
|
|
// Filter keeps values that satisfy a predicate:
|
|
//
|
|
// isPositive := Filter(func(x int) bool { return x > 0 })
|
|
// result := isPositive(Some(5)) // (5, true)
|
|
// result := isPositive(Some(-1)) // (0, false)
|
|
//
|
|
// # Working with Collections
|
|
//
|
|
// Transform arrays using TraverseArray:
|
|
//
|
|
// doublePositive := func(x int) (int, bool) {
|
|
// if x > 0 { return x * 2, true }
|
|
// return 0, false
|
|
// }
|
|
// result := TraverseArray(doublePositive)([]int{1, 2, 3}) // ([2, 4, 6], true)
|
|
// result := TraverseArray(doublePositive)([]int{1, -2, 3}) // ([], false)
|
|
//
|
|
// Transform with indexes:
|
|
//
|
|
// f := func(i int, x int) (int, bool) {
|
|
// if x > i { return x, true }
|
|
// return 0, false
|
|
// }
|
|
// result := TraverseArrayWithIndex(f)([]int{1, 2, 3}) // ([1, 2, 3], true)
|
|
//
|
|
// Transform records (maps):
|
|
//
|
|
// double := func(x int) (int, bool) { return x * 2, true }
|
|
// result := TraverseRecord(double)(map[string]int{"a": 1, "b": 2})
|
|
// // (map[string]int{"a": 2, "b": 4}, true)
|
|
//
|
|
// # Algebraic Operations
|
|
//
|
|
// Option supports various algebraic structures:
|
|
//
|
|
// - Functor: Map operations for transforming values
|
|
// - Applicative: Ap operations for applying wrapped functions
|
|
// - Monad: Chain operations for sequencing computations
|
|
// - Alternative: Alt operations for providing fallbacks
|
|
//
|
|
// Applicative example:
|
|
//
|
|
// fab := Some(func(x int) int { return x * 2 })
|
|
// fa := Some(21)
|
|
// result := Ap[int](fa)(fab) // (42, true)
|
|
//
|
|
// Alternative example:
|
|
//
|
|
// withDefault := Alt(func() (int, bool) { return 100, true })
|
|
// result := withDefault(Some(42)) // (42, true)
|
|
// result := withDefault(None[int]()) // (100, true)
|
|
//
|
|
// # Conversion Functions
|
|
//
|
|
// Convert predicates to Options:
|
|
//
|
|
// isPositive := FromPredicate(func(n int) bool { return n > 0 })
|
|
// result := isPositive(5) // (5, true)
|
|
// result := isPositive(-1) // (0, false)
|
|
//
|
|
// Convert nullable pointers to Options:
|
|
//
|
|
// var ptr *int = nil
|
|
// result := FromNillable(ptr) // (nil, false)
|
|
// val := 42
|
|
// result := FromNillable(&val) // (&val, true)
|
|
//
|
|
// Convert zero/non-zero values to Options:
|
|
//
|
|
// result := FromZero[int]()(0) // (0, true)
|
|
// result := FromZero[int]()(5) // (0, false)
|
|
// result := FromNonZero[int]()(5) // (5, true)
|
|
// result := FromNonZero[int]()(0) // (0, false)
|
|
//
|
|
// Use equality-based conversion:
|
|
//
|
|
// import "github.com/IBM/fp-go/v2/eq"
|
|
// equals42 := FromEq(eq.FromStrictEquals[int]())(42)
|
|
// result := equals42(42) // (42, true)
|
|
// result := equals42(10) // (0, false)
|
|
//
|
|
// # Do-Notation Style
|
|
//
|
|
// Build complex computations using do-notation:
|
|
//
|
|
// type Result struct {
|
|
// x int
|
|
// y int
|
|
// sum int
|
|
// }
|
|
//
|
|
// result := F.Pipe3(
|
|
// Do(Result{}),
|
|
// Bind(func(x int) func(Result) Result {
|
|
// return func(r Result) Result { r.x = x; return r }
|
|
// }, func(r Result) (int, bool) { return Some(10) }),
|
|
// Bind(func(y int) func(Result) Result {
|
|
// return func(r Result) Result { r.y = y; return r }
|
|
// }, func(r Result) (int, bool) { return Some(20) }),
|
|
// Let(func(sum int) func(Result) Result {
|
|
// return func(r Result) Result { r.sum = sum; return r }
|
|
// }, func(r Result) int { return r.x + r.y }),
|
|
// ) // (Result{x: 10, y: 20, sum: 30}, true)
|
|
//
|
|
// # Lens-Based Operations
|
|
//
|
|
// Use lenses for cleaner field updates:
|
|
//
|
|
// type Person struct {
|
|
// Name string
|
|
// Age int
|
|
// }
|
|
//
|
|
// ageLens := lens.MakeLens(
|
|
// func(p Person) int { return p.Age },
|
|
// func(p Person, age int) Person { p.Age = age; return p },
|
|
// )
|
|
//
|
|
// // Update using a lens
|
|
// incrementAge := BindL(ageLens, func(age int) (int, bool) {
|
|
// if age < 120 { return age + 1, true }
|
|
// return 0, false
|
|
// })
|
|
// result := incrementAge(Some(Person{Name: "Alice", Age: 30}))
|
|
// // (Person{Name: "Alice", Age: 31}, true)
|
|
//
|
|
// // Set using a lens
|
|
// setAge := LetToL(ageLens, 25)
|
|
// result := setAge(Some(Person{Name: "Bob", Age: 30}))
|
|
// // (Person{Name: "Bob", Age: 25}, true)
|
|
//
|
|
// # Folding and Reducing
|
|
//
|
|
// Fold provides a way to handle both Some and None cases:
|
|
//
|
|
// handler := Fold(
|
|
// func() string { return "no value" },
|
|
// func(x int) string { return fmt.Sprintf("value: %d", x) },
|
|
// )
|
|
// result := handler(Some(42)) // "value: 42"
|
|
// result := handler(None[int]()) // "no value"
|
|
//
|
|
// Reduce folds an Option into a single value:
|
|
//
|
|
// sum := Reduce(func(acc, val int) int { return acc + val }, 0)
|
|
// result := sum(Some(5)) // 5
|
|
// result := sum(None[int]()) // 0
|
|
//
|
|
// # Debugging
|
|
//
|
|
// Convert Options to strings for debugging:
|
|
//
|
|
// str := ToString(Some(42)) // "Some[int](42)"
|
|
// str := ToString(None[int]()) // "None[int]"
|
|
//
|
|
// # Subpackages
|
|
//
|
|
// - option/number: Number conversion utilities for working with Options
|
|
package option
|
|
|
|
//go:generate go run .. option --count 10 --filename gen.go
|
|
|
|
// Made with Bob
|