📚 g - Awesome Go Library for Functional

Go Gopher mascot for g

Functional programming framework for Go

🏷️ Functional
📂 Functional
0 stars
View on GitHub 🔗

Detailed Description of g

g - Functional programming framework for Go.

Go Reference Go Report Card Coverage Status Go Mentioned in Awesome Go Ask DeepWiki

go get github.com/enetx/g

Requires Go 1.24+


Quick Start

package main

import (
    "fmt"
    "github.com/enetx/g"
)

func main() {
    // Slice with functional operations
    g.SliceOf(1, 2, 3, 4, 5).
        Iter().
        Filter(func(x int) bool { return x%2 == 0 }).
        Map(func(x int) int { return x * x }).
        Collect().
        Println() // Slice[4, 16]

    // Safe map access with Option
    m := g.NewMap[string, int]()
    m.Insert("key", 42)

    value := m.Get("key").UnwrapOr(0)
    fmt.Println(value) // 42

    // Result for error handling
    result := g.String("123").TryInt()
    if result.IsOk() {
        fmt.Println(result.Unwrap()) // 123
    }
}

Navigation

CoreCollectionsSyncTypes
OptionSliceMutexString
ResultMapRwLockInt/Float
IteratorsSetPoolBytes
HeapFile/Dir
Deque

Option

Safe nullable values: Some(value) or None.

opt := g.Some(42)           // Some(42)
none := g.None[int]()       // None

opt.IsSome()                // true
opt.Unwrap()                // 42 (panics if None)
opt.UnwrapOr(0)             // 42 (returns default if None)
opt.UnwrapOrDefault()       // 42 (returns zero value if None)

// From map lookup
v, ok := myMap[key]
opt := g.OptionOf(v, ok)

// Chaining
g.Some(5).Then(func(x int) g.Option[int] {
    return g.Some(x * 2)
}) // Some(10)
All Option Methods
MethodDescription
IsSome()Returns true if contains value
IsNone()Returns true if empty
Some()Returns value (same as Unwrap)
Unwrap()Returns value, panics if None
UnwrapOr(default)Returns value or default
UnwrapOrDefault()Returns value or zero value
UnwrapOrElse(fn)Returns value or result of fn
Expect(msg)Returns value, panics with msg if None
Then(fn)Transforms value if Some
Option()Returns (value, bool)
Take()Takes value, leaves None
Filter(fn)Returns None if predicate fails

Result

Success (Ok) or failure (Err) with error.

ok := g.Ok(42)                              // Ok(42)
err := g.Err[int](errors.New("failed"))     // Err

ok.IsOk()                   // true
ok.Unwrap()                 // 42
err.UnwrapOr(0)             // 0

// From standard (value, error) pattern
result := g.ResultOf(strconv.Atoi("42"))    // Ok(42)
result := g.String("42").TryInt()           // Ok(42)

// Chaining
g.Ok(10).Then(func(x int) g.Result[int] {
    return g.Ok(x * 2)
}) // Ok(20)

// Convert to Option (discards error)
opt := result.Option()      // Some(42)
All Result Methods
MethodDescription
IsOk()Returns true if success
IsErr()Returns true if error
Ok()Returns value (same as Unwrap)
Err()Returns error
Unwrap()Returns value, panics if Err
UnwrapOr(default)Returns value or default
UnwrapOrDefault()Returns value or zero value
UnwrapOrElse(fn)Returns value or result of fn
Expect(msg)Returns value, panics with msg
Then(fn)Chains operation if Ok
ThenOf(fn)Chains (T, error) function
MapErr(fn)Transforms error if Err
Option()Converts to Option
Result()Returns (value, error)

Slice

Extended slice with 90+ methods.

s := g.SliceOf(1, 2, 3, 4, 5)
s := g.NewSlice[int](10)        // with capacity

s.Len()                         // Int(5)
s.Get(0)                        // Some(1)
s.Get(100)                      // None (safe!)
s.Last()                        // Some(5)
s.Contains(3)                   // true

s.Push(6, 7)                    // append in place
s.Pop()                         // Some(7)
s.Clone()                       // safe copy

// Sorting
s.SortBy(cmp.Cmp)               // ascending
s.Reverse()                     // in place
s.Shuffle()                     // random order
All Slice Methods
CategoryMethods
AccessGet, Last, First, Random, RandomSample
ModifySet, Push, Pop, Insert, Remove, Clear
SearchContains, ContainsBy, Index, IndexBy
TransformClone, Reverse, Shuffle, SortBy, SubSlice
ConvertIter, Heap, Std
InfoLen, Cap, IsEmpty

Map

Extended map with Entry API.

m := g.NewMap[string, int]()

m.Insert("a", 1)                // insert value
m.Get("a")                      // Some(1)
m.Get("x")                      // None
m.Contains("a")                 // true
m.Remove("a")                   // remove

m.Keys()                        // Slice of keys
m.Values()                      // Slice of values

Entry API

Efficient update without multiple lookups:

// Insert if absent
m.Entry("counter").OrInsert(0)

// Update if present, insert if absent
m.Entry("counter").AndModify(func(v *int) { *v++ }).OrInsert(1)

// Lazy initialization
m.Entry("key").OrInsertWith(func() int {
    return expensiveComputation()
})

Word frequency example:

words := g.SliceOf("apple", "banana", "apple", "cherry", "banana", "apple")
freq := g.NewMap[string, int]()

for _, word := range words {
    freq.Entry(word).AndModify(func(v *int) { *v++ }).OrInsert(1)
}
// {"apple": 3, "banana": 2, "cherry": 1}
Entry Pattern Matching
switch e := m.Entry("key").(type) {
case g.OccupiedEntry[string, int]:
    fmt.Println("Exists:", e.Get())
    e.Insert(newValue)      // replace
    e.Remove()              // delete
case g.VacantEntry[string, int]:
    e.Insert(defaultValue)  // insert
}

Set

Collection of unique elements.

s := g.SetOf(1, 2, 3)
s.Insert(4)                     // add
s.Remove(1)                     // remove
s.Contains(2)                   // true

Set Operations

a := g.SetOf(1, 2, 3, 4)
b := g.SetOf(3, 4, 5, 6)

a.Union(b).Collect()            // {1, 2, 3, 4, 5, 6}
a.Intersection(b).Collect()     // {3, 4}
a.Difference(b).Collect()       // {1, 2}
a.SymmetricDifference(b).Collect() // {1, 2, 5, 6}

a.Subset(b)                     // false
a.Superset(b)                   // false

Heap

Priority queue (binary heap).

import "github.com/enetx/g/cmp"

// Min-heap (smallest first)
h := g.NewHeap(cmp.Cmp[int])
h.Push(5, 3, 8, 1, 9)

h.Peek()                        // Some(1)
h.Pop()                         // Some(1)
h.Pop()                         // Some(3)

// Max-heap (largest first)
maxH := g.NewHeap(func(a, b int) cmp.Ordering {
    return cmp.Cmp(b, a)
})

// From slice
heap := g.SliceOf(5, 3, 8, 1).Heap(cmp.Cmp)

Deque

Double-ended queue (ring buffer). O(1) at both ends.

dq := g.NewDeque[int]()
dq := g.DequeOf(1, 2, 3)

dq.PushFront(0)                 // add to front
dq.PushBack(4)                  // add to back

dq.Front()                      // Some(0)
dq.Back()                       // Some(4)

dq.PopFront()                   // Some(0)
dq.PopBack()                    // Some(4)

dq.Get(1)                       // element at index
dq.Len()                        // length
dq.IsEmpty()                    // true/false

Iterators

Functional operations on sequences.

g.SliceOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).
    Iter().
    Filter(func(x int) bool { return x%2 == 0 }).
    Map(func(x int) int { return x * x }).
    Take(3).
    Collect()
// [4, 16, 36]

Common Patterns

// Chain iterators
g.SliceOf(1, 2).Iter().Chain(g.SliceOf(3, 4).Iter()).Collect()
// [1, 2, 3, 4]

// Sum with Fold
g.SliceOf(1, 2, 3, 4, 5).Iter().Fold(0, func(acc, x int) int { return acc + x })
// 15

// Enumerate
g.SliceOf("a", "b", "c").Iter().Enumerate().ForEach(func(i g.Int, v string) {
    fmt.Printf("%d: %s\n", i, v)
})

// Using f package predicates
import "github.com/enetx/g/f"

g.SliceOf(1, 2, 3, 4, 5).Iter().Filter(f.Ne(3)).Collect()       // [1, 2, 4, 5]
g.SliceOf("", "a", "").Iter().Exclude(f.IsZero).Collect()       // ["a"]
All Iterator Methods
TransformSliceCombineAggregateSearchOther
MapTakeChainCollectFindForEach
FilterSkipZipFoldAnyInspect
ExcludeStepByEnumerateReduceAllRange
FilterMapFirstIntersperseCountMaxByScan
FlatMapLastCycleCounterMinByCombinations
FlattenNthPartitionPermutations
DedupChunksGroupByContext
UniqueWindowsChan
SortByParallel

Mutex

Typed mutex — data bound to lock.

counter := g.NewMutex(0)

// Lock and modify
guard := counter.Lock()
guard.Set(guard.Get() + 1)
guard.Unlock()

// With defer (recommended)
func increment(c *g.Mutex[int]) {
    guard := c.Lock()
    defer guard.Unlock()
    guard.Set(guard.Get() + 1)
}

// Direct pointer access
guard := counter.Lock()
defer guard.Unlock()
*guard.Deref() += 1

// Non-blocking
if opt := counter.TryLock(); opt.IsSome() {
    guard := opt.Unwrap()
    defer guard.Unlock()
    // got the lock
}

Why typed mutex?

// Traditional — easy to forget locking
type Old struct {
    mu   sync.Mutex
    data map[string]int  // what protects this?
}

// Typed — impossible to access without lock
type New struct {
    data *g.Mutex[g.Map[string, int]]
}

func (s *New) Get(key string) g.Option[int] {
    guard := s.data.Lock()
    defer guard.Unlock()
    return guard.Deref().Get(key)
}

RwLock

Multiple readers OR single writer.

config := g.NewRwLock(Config{Port: 8080})

// Read (concurrent)
func getPort(c *g.RwLock[Config]) int {
    guard := c.Read()
    defer guard.Unlock()
    return guard.Get().Port
}

// Write (exclusive)
func setPort(c *g.RwLock[Config], port int) {
    guard := c.Write()
    defer guard.Unlock()
    guard.Deref().Port = port
}

// Non-blocking
if opt := config.TryRead(); opt.IsSome() { ... }
if opt := config.TryWrite(); opt.IsSome() { ... }
Use CaseChoose
Read-heavy (config, cache)RwLock
Write-heavy or balancedMutex
Simple countersMutex

Pool

Goroutine pool for parallel tasks.

import "github.com/enetx/g/pool"

p := pool.New[int]().Limit(4)

for i := range 10 {
    p.Go(func() g.Result[int] {
        return g.Ok(i * 2)
    })
}

for result := range p.Wait() {
    if result.IsOk() {
        fmt.Println(result.Unwrap())
    }
}
With Context and Cancel on Error
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

p := pool.New[int]().
    Context(ctx).
    CancelOnError().
    Limit(4)

for i := range 100 {
    p.Go(func() g.Result[int] {
        if i == 50 {
            return g.Err[int](errors.New("error"))
        }
        return g.Ok(i)
    })
}

for result := range p.Wait() {
    // stops after first error
}

String

Extended string with 80+ methods.

s := g.String("Hello, World!")

s.Len()                         // 13
s.Upper()                       // "HELLO, WORLD!"
s.Lower()                       // "hello, world!"
s.Contains("World")             // true
s.Split(", ").Collect()         // ["Hello", "World!"]
s.Trim()                        // remove whitespace

// Conversions
s.Std()                         // Go string
s.Bytes()                       // Bytes type
g.String("42").TryInt()         // Result[Int]

// Encoding
s.Hash().MD5()                  // hash
s.Encode().Base64()             // encode
s.Compress().Gzip()             // compress

Int / Float

Numeric types with utility methods.

i := g.Int(42)

i.Add(8)                        // 50
i.Mul(2)                        // 84
i.Abs()                         // absolute value
i.Min(10, 20)                   // minimum
i.Max(10, 20)                   // maximum

i.IsZero()                      // false
i.IsPositive()                  // true
i.IsNegative()                  // false

i.Binary()                      // binary string
i.Hex()                         // hex string

// Float
f := g.Float(3.14159)
f.Round()                       // Int(3)
f.RoundDecimal(2)               // Float(3.14)
f.Sqrt()                        // square root

// Compare
i.Cmp(g.Int(50))                // cmp.Less

Bytes

Extended byte slice.

b := g.Bytes([]byte("Hello"))

b.Len()
b.Upper()
b.Lower()
b.Contains([]byte("ell"))

b.String()                      // to String
b.Std()                         // to []byte

File / Directory

File

// Read/Write
content := g.NewFile("config.json").Read()      // Result[String]
g.NewFile("output.txt").Write("Hello")
g.NewFile("log.txt").Append("line\n")

// Open a new file with the specified name "text.txt" and process it line by line.
g.NewFile("text.txt").
	Lines().                            // Reads the file line by line.
	Skip(2).                            // Skips the first 2 lines in the iterator.
	Exclude(f.IsZero).                  // Excludes lines that are empty or contain only whitespaces.
	Dedup().                            // Removes consecutive duplicate lines.
	Map(g.String.Upper).                // Converts each line to uppercase.
	Range(func(s g.Result[g.String]) bool { // Iterates over the lines while a condition is true.
		if s.IsErr() { // Handles any errors encountered while reading lines.
			fmt.Println("Error:", s.Err())
			return false // Stops the iteration if an error occurs.
		}

		if s.Ok().Contains("COULD") { // Checks if the line contains the substring "COULD".
			return false // Stops the iteration if the condition is met.
		}

		fmt.Println(s.Ok()) // Prints the line.
		return true         // Continues the iteration.
	})

// Properties
f := g.NewFile("test.txt")
f.Exist()                       // check existence
f.Stat()                        // file info
f.Copy("backup.txt")            // copy
f.Rename("new.txt")             // rename
f.Remove()                      // delete

File Guard (Exclusive Lock)

f := g.NewFile("data.txt").Guard()  // holds exclusive lock
f.Write("exclusive data")
f.Close()                           // release lock

Directory

g.NewDir("mydir").Create()
g.NewDir("path/to/deep").CreateAll()

// Read contents
for file := range g.NewDir(".").Read() {
    if file.IsOk() {
        file.Ok().Name().Println()
    }
}

// Recursively walk through the directory tree starting from the current directory
g.NewDir(".").Walk().
	// Exclude directories and symlinked directories
	Exclude(func(f *g.File) bool { return f.IsDir() && f.Dir().Ok().IsLink() }).
    // Exclude file symlinks
	Exclude((*File).IsLink).
	// Process each walk result
	ForEach(func(v g.Result[*g.File]) {
		if v.IsOk() {
			// Print the path of the file if no error occurred
			v.Ok().Path().Ok().Println()
		}
	})

// Iterate over and print the names of files in the current directory with a *.go extension
g.NewDir("*.go").Glob().ForEach(func(f g.Result[*g.File]) { f.Ok().Name().Println() })

// Copy the contents of the current directory to a new directory named "copy".
g.NewDir(".").Copy("copy").Unwrap()

Other Maps

MapOrd — Ordered Map

Maintains insertion order.

m := g.NewMapOrd[string, int]()
m.Insert("c", 3)
m.Insert("a", 1)
m.Insert("b", 2)

for k, v := range m.Iter() {
    fmt.Println(k, v)  // c, a, b order
}

m.SortByKey(cmp.Cmp)            // sort by key
m.SortByValue(cmp.Cmp)          // sort by value

MapSafe — Concurrent Map

Thread-safe for concurrent access.

m := g.NewMapSafe[string, int]()

// Safe from multiple goroutines
go func() { m.Insert("a", 1) }()
go func() { m.Get("a") }()

License

MIT License