summaryrefslogtreecommitdiffstats
path: root/generic
diff options
context:
space:
mode:
Diffstat (limited to 'generic')
-rw-r--r--generic/generic.go106
-rw-r--r--generic/generic_test.go149
2 files changed, 255 insertions, 0 deletions
diff --git a/generic/generic.go b/generic/generic.go
new file mode 100644
index 0000000..8ac3c51
--- /dev/null
+++ b/generic/generic.go
@@ -0,0 +1,106 @@
+// Package generic provides common generic utility functions and types.
+//
+// It includes zero value helpers, pointer helpers, iteration helpers for map-like sequences,
+// and a simple generic Set implementation.
+package generic
+
+import (
+ "iter"
+ "maps"
+)
+
+// Package generic provides common generic utility functions and types.
+//
+// It includes zero value helpers, pointer helpers, iteration helpers for map-like sequences,
+// and a simple generic Set implementation.
+func Zero[T any]() T {
+ var v T
+ return v
+}
+
+// IsZero reports whether the given value is the zero value of its type.
+//
+// T must be comparable (support == operator).
+//
+// Example:
+//
+// generic.IsZero(0) // true
+// generic.IsZero("a") // false
+func IsZero[T comparable](v T) bool {
+ return v == Zero[T]()
+}
+
+// Ptr returns a pointer to the given value.
+//
+// This is a concise helper to get the address of a value inline.
+//
+// Example:
+//
+// p := generic.Ptr(42) // *int with value 42
+func Ptr[T any](v T) *T {
+ return &v
+}
+
+// Keys returns an iterator (Seq) over the keys of the input map-like sequence.
+//
+// Utilizes iter.Seq2[K,V] as input and yields keys K.
+//
+// Usage example (assuming iter.Seq is a func type yielding values):
+//
+// forKeys := generic.Keys(yourMapSeq)
+// forKeys(func(k K) bool {
+// fmt.Println(k)
+// return true
+// })
+func Keys[K comparable, V any](m iter.Seq2[K, V]) iter.Seq[K] {
+ return func(yield func(K) bool) {
+ for k := range m {
+ if !yield(k) {
+ return
+ }
+ }
+ }
+}
+
+// Value returns an iterator over the values of the input map-like sequence.
+//
+// Usage is analogous to Keys() but yields values of type V.
+func Value[K comparable, V any](m iter.Seq2[K, V]) iter.Seq[V] {
+ return func(yield func(V) bool) {
+ for _, v := range m {
+ if !yield(v) {
+ return
+ }
+ }
+ }
+}
+
+// Set represents a generic set of comparable items.
+//
+// It is a thin wrapper around a map[T]struct{} for set semantics.
+type Set[T comparable] map[T]struct{}
+
+func NewSet[T comparable]() Set[T] {
+ return make(Set[T])
+}
+
+// All returns an iterator (Seq) over all elements in the Set.
+func (s Set[T]) All() iter.Seq[T] {
+ return maps.Keys(s)
+}
+
+// Add inserts the value v into the Set.
+func (s Set[T]) Add(v T) {
+ s[v] = struct{}{}
+}
+
+// Del removes the value v from the Set.
+func (s Set[T]) Del(v T) {
+ delete(s, v)
+}
+
+// Has reports whether the value v is present in the Set.
+func (s Set[T]) Has(v T) bool {
+ _, ok := s[v]
+ return ok
+}
diff --git a/generic/generic_test.go b/generic/generic_test.go
new file mode 100644
index 0000000..953f2ac
--- /dev/null
+++ b/generic/generic_test.go
@@ -0,0 +1,149 @@
+package generic
+
+import (
+ "maps"
+ "slices"
+ "testing"
+)
+
+type X struct{}
+
+func TestZero(t *testing.T) {
+ if Zero[int]() != 0 {
+ t.Errorf("Zero[int]() != 0")
+ }
+ if Zero[string]() != "" {
+ t.Errorf(`Zero[string]() != ""`)
+ }
+ if Zero[bool]() != false {
+ t.Errorf("Zero[bool]() != false")
+ }
+ if Zero[*X]() != nil {
+ t.Errorf("Zero[*X]() != nil")
+ }
+}
+
+func TestIsZero(t *testing.T) {
+ tests := []struct {
+ name string
+ val any
+ want bool
+ }{
+ {
+ name: "int zero",
+ val: 0,
+ want: true,
+ },
+ {
+ "int non-zero",
+ 1,
+ false,
+ },
+ {
+ "string zero",
+ "",
+ true,
+ },
+ {
+ "string non-zero",
+ "x",
+ false,
+ },
+ {
+ "nil pointer",
+ (*X)(nil),
+ true,
+ },
+ {
+ "non-nil pointer",
+ Ptr(X{}),
+ false,
+ },
+ }
+
+ for _, tt := range tests {
+ switch v := tt.val.(type) {
+ case int:
+ got := IsZero(v)
+ if got != tt.want {
+ t.Errorf("%s: got %v, want %v", tt.name, got, tt.want)
+ }
+ case string:
+ got := IsZero(v)
+ if got != tt.want {
+ t.Errorf("%s: got %v, want %v", tt.name, got, tt.want)
+ }
+ case *X:
+ got := IsZero(v)
+ if got != tt.want {
+ t.Errorf("%s: got %v, want %v", tt.name, got, tt.want)
+ }
+ }
+ }
+}
+
+func TestPtr(t *testing.T) {
+ v := 42
+ p := Ptr(v)
+ if p == nil || *p != v {
+ t.Errorf("Ptr(42) = %v, want pointer to 42", p)
+ }
+}
+
+func TestKeys(t *testing.T) {
+ m := map[string]int{"a": 1, "b": 2, "c": 3}
+ seq := Keys(maps.All(m))
+ keys := slices.Collect(seq)
+ want := slices.Collect(maps.Keys(m))
+
+ slices.Sort(keys)
+ slices.Sort(want)
+ if !slices.Equal(keys, want) {
+ t.Errorf("Keys() = %v, want %v", keys, want)
+ }
+}
+
+func TestValue(t *testing.T) {
+ m := map[string]int{"a": 1, "b": 2, "c": 3}
+ seq := Value(maps.All(m))
+ values := slices.Collect(seq)
+ want := slices.Collect(maps.Values(m))
+
+ // unordered compare
+ slices.Sort(values)
+ slices.Sort(want)
+ if !slices.Equal(values, want) {
+ t.Errorf("Values() = %v, want %v", values, want)
+ }
+}
+
+func TestSet_Add_Has_Del(t *testing.T) {
+ s := NewSet[int]()
+ if s.Has(10) {
+ t.Errorf("Set should not contain 10 initially")
+ }
+ s.Add(10)
+ if !s.Has(10) {
+ t.Errorf("Set should contain 10 after Add")
+ }
+ s.Del(10)
+ if s.Has(10) {
+ t.Errorf("Set should not contain 10 after Del")
+ }
+}
+
+func TestSet_All(t *testing.T) {
+ s := NewSet[string]()
+ s.Add("x")
+ s.Add("y")
+ s.Add("z")
+
+ got := slices.Collect(s.All())
+ want := []string{"x", "y", "z"}
+
+ slices.Sort(got)
+ slices.Sort(want)
+ if !slices.Equal(got, want) {
+ t.Errorf("Set.All() = %v, want %v", got, want)
+ }
+}