From a4c67cfa26405bf4f00c77f15208c4e97ae34615 Mon Sep 17 00:00:00 2001 From: Isabelle Erin COWAN-BERGMAN Date: Thu, 8 May 2025 09:21:44 +0200 Subject: [PATCH 1/2] feat: [#50] Add support for Go1.23+ iterators --- iter.go | 113 ++++++++++++++++++++++ iter_example_test.go | 145 ++++++++++++++++++++++++++++ iter_test.go | 220 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 478 insertions(+) create mode 100644 iter.go create mode 100644 iter_example_test.go create mode 100644 iter_test.go diff --git a/iter.go b/iter.go new file mode 100644 index 0000000..e7de690 --- /dev/null +++ b/iter.go @@ -0,0 +1,113 @@ +package immutable + +// Iterator is an interface for iterating over immutable collections. +type Iterator[T any] interface { + // First positions the iterator on the first index. + // If source is empty then no change is made. + First() + + // Done returns true if there are no more elements in the iterator. + Done() bool +} + +// IndexedIterator is an iterator that returns the index and value of the +// underlying collection. +type IndexedIterator[T any] interface { + Iterator[T] + + // Next returns the current index and its value & moves the iterator forward. + // Returns an index of -1 if the there are no more elements to return. + Next() (int, T) +} + +// KeyedIterator is an iterator that returns the key and value of the underlying +// collection. +type KeyedIterator[K, V any] interface { + Iterator[V] + + // Next returns the next key/value pair. + // Returns a nil key when no elements remain. + Next() (K, V, bool) +} + +// UnkeyedIterator is an iterator that returns the value of the underlying +// collection. +type UnkeyedIterator[T any] interface { + Iterator[T] + + // Next moves the iterator to the next value. + Next() (T, bool) +} + +// IndexedSeq takes an IndexedIterator for immutable collections (such as those +// returned by [List.Iterator]) and returns a function satisfying the [iter.Seq] +// type available starting in Go 1.23. +// This can be used to perform range-like operations on the [iter.Seq] and +// inter-operate with other libraries using the Go 1.23 iterable function API. +func IndexedSeq[T any](it IndexedIterator[T]) func(func(T) bool) { + seq2 := IndexedSeqWithIndex(it) + return func(yield func(T) bool) { + seq2(func(_ int, v T) bool { + return yield(v) + }) + } +} + +// IndexedSeqWithIndex takes an IndexedIterator for immutable collections (such +// as those returned by [List.Iterator]) and returns a function satisfying the +// [iter.Seq2] type available starting in Go 1.23. +// This can be used to perform range-like operations on the [iter.Seq2] and +// inter-operate with other libraries using the Go 1.23 iterable function API. +func IndexedSeqWithIndex[T any](it IndexedIterator[T]) func(func(int, T) bool) { + return func(yield func(int, T) bool) { + for !it.Done() { + index, v := it.Next() + assert(index >= 0, "index should never be negative after checking Done()") + + if !yield(index, v) { + return + } + } + } +} + +// KeyedSeq takes a KeyedIterator for immutable collections (such as those +// [Map.Iterator] and [OrderedMap.Iterator]) and returns a function satisfying +// the [iter.Seq2] type available starting in Go 1.23. +// This can be used to perform range-like operations on the [iter.Seq2] and +// inter-operate with other libraries using the Go 1.23 iterable function API. +func KeyedSeq[K, V any](it KeyedIterator[K, V]) func(func(K, V) bool) { + return func(yield func(K, V) bool) { + for !it.Done() { + k, v, ok := it.Next() + assert( + ok, + "keyed iterator should always return a key/value pair after checking "+ + "Done()", + ) + + if !yield(k, v) { + return + } + } + } +} + +// UnkeyedSeq takes an UnkeyedIterator for immutable collections (such as those +// returned by [List.Iterator]) and returns a function satisfying the [iter.Seq] +// type available starting in Go 1.23. +// This can be used to perform range-like operations on the [iter.Seq] and +// inter-operate with other libraries using the Go 1.23 iterable function API. +func UnkeyedSeq[T any](it UnkeyedIterator[T]) func(func(T) bool) { + return func(yield func(T) bool) { + for !it.Done() { + v, ok := it.Next() + assert( + ok, "unkeyed iterator should always return a value after checking Done()") + + if !yield(v) { + return + } + } + } +} diff --git a/iter_example_test.go b/iter_example_test.go new file mode 100644 index 0000000..a070061 --- /dev/null +++ b/iter_example_test.go @@ -0,0 +1,145 @@ +package immutable_test + +import ( + "fmt" + + "github.com/benbjohnson/immutable" +) + +// Demonstrates how to use the [immutable.IndexedSeq] function to iterate over +// an [immutable.List] of integers. +// +// In Go 1.23 with GOEXPERIMENT=rangefunc, or in Go 1.24 and later, this +// will be a range-like operation: +// +// for v := range seq { +// fmt.Println("Yielded:", v) +// } +func ExampleIndexedSeq() { + // Create a new list with some values + l := immutable.NewList(1, 2, 3, 4, 5) + + // Create an IndexedIterator for the list + it := l.Iterator() + + // Create a sequence using IndexedSeq + seq := immutable.IndexedSeq[int](it) + + // Define a yield function that increments the counter + yield := func(v int) bool { + fmt.Println("Yielded:", v) + return true + } + + seq(yield) + // Output: + // Yielded: 1 + // Yielded: 2 + // Yielded: 3 + // Yielded: 4 + // Yielded: 5 +} + +// Demonstrates how to use the [immutable.IndexedSeqWithIndex] function to +// iterate over an [immutable.List] of integers with their indices. +// +// In Go 1.23 with GOEXPERIMENT=rangefunc, or in Go 1.24 and later, this +// will be a range-like operation: +// +// for i, v := range seq { +// fmt.Printf("Yielded: %d at index %d\n", v, i) +// } +func ExampleIndexedSeqWithIndex() { + // Create a new list with some values + l := immutable.NewList(1, 2, 3, 4, 5) + + // Create an IndexedIterator for the list + it := l.Iterator() + + // Create a sequence using IndexedSeqWithIndex + seq := immutable.IndexedSeqWithIndex[int](it) + + // Define a yield function that increments the counter + yield := func(i int, v int) bool { + fmt.Printf("Yielded: %d at index %d\n", v, i) + return true + } + + seq(yield) + // Output: + // Yielded: 1 at index 0 + // Yielded: 2 at index 1 + // Yielded: 3 at index 2 + // Yielded: 4 at index 3 + // Yielded: 5 at index 4 +} + +// Demonstrates how to use the [immutable.KeyedSeq] function to iterate over an +// [immutable.Map] of integers to strings. +// +// In Go 1.23 with GOEXPERIMENT=rangefunc, or in Go 1.24 and later, this +// will be a range-like operation: +// +// for k, v := range seq { +// fmt.Printf("Yielded: %s at key %d\n", v, k) +// } +func ExampleKeyedSeq() { + // Create a new map builder with some values + mb := immutable.NewMapBuilder[int, string](nil) + mb.Set(1, "one") + mb.Set(2, "two") + mb.Set(3, "three") + + // Create a map from the builder + m := mb.Map() + + // Create a KeyedIterator for the map + it := m.Iterator() + + // Create a sequence using KeyedSeq + seq := immutable.KeyedSeq[int, string](it) + + // Define a yield function that increments the counter + yield := func(k int, v string) bool { + fmt.Printf("Yielded: %s at key %d\n", v, k) + return true + } + + seq(yield) + // Output: + // Yielded: one at key 1 + // Yielded: two at key 2 + // Yielded: three at key 3 +} + +// Demonstrates how to use the [immutable.UnkeyedSeq] function to iterate over an +// [immutable.Set] of integers. +// +// In Go 1.23 with GOEXPERIMENT=rangefunc, or in Go 1.24 and later, this +// will be a range-like operation: +// +// for v := range seq { +// fmt.Println("Yielded:", v) +// } +func ExampleUnkeyedSeq() { + // Create a new set with some values + s := immutable.NewSet(nil, 1, 2, 3) + + // Create an UnkeyedIterator for the set + it := s.Iterator() + + // Create a sequence using UnkeyedSeq + seq := immutable.UnkeyedSeq[int](it) + + // Define a yield function that increments the counter + yield := func(v int) bool { + fmt.Println("Yielded:", v) + return true + } + + seq(yield) + // Output: + // Yielded: 1 + // Yielded: 2 + // Yielded: 3 +} diff --git a/iter_test.go b/iter_test.go new file mode 100644 index 0000000..bd522c0 --- /dev/null +++ b/iter_test.go @@ -0,0 +1,220 @@ +package immutable + +import ( + "reflect" + "testing" +) + +func TestIndexedSeq(t *testing.T) { + // Create a new list with some values + l := NewList(1, 2, 3, 4, 5) + + // Create an IndexedIterator for the list + it := l.Iterator() + + // Create a sequence using IndexedSeq + seq := IndexedSeq[int](it) + + // Initialize a counter to track the number of yielded values + count := 0 + + // Define a yield function that increments the counter + yield := func(v int) bool { + count++ + return true + } + + // Call the sequence with the yield function + seq(yield) + + // Check if the count matches the expected number of elements in the list + if count != 5 { + t.Errorf("Expected 5 elements, got %d", count) + } +} + +func TestIndexedSeqWithIndex(t *testing.T) { + // Create a new list with some values + l := NewList(1, 2, 3, 4, 5) + + // Create an IndexedIterator for the list + it := l.Iterator() + + // Create a sequence using IndexedSeqWithIndex + seq := IndexedSeqWithIndex[int](it) + + // Initialize a counter to track the number of yielded values + count := 0 + + // Define a yield function that increments the counter + yield := func(i int, v int) bool { + count++ + return true + } + + // Call the sequence with the yield function + seq(yield) + + // Check if the count matches the expected number of elements in the list + if count != 5 { + t.Errorf("Expected 5 elements, got %d", count) + } +} + +func TestKeyedSeq(t *testing.T) { + // Create a new Map with some key-value pairs + mb := NewMapBuilder[string, int](nil) + mb.Set("a", 1) + mb.Set("b", 2) + mb.Set("c", 3) + m := mb.Map() + + // Create a KeyedIterator for the map + it := m.Iterator() + + // Create a sequence using KeyedSeq + seq := KeyedSeq[string, int](it) + + // Initialize a counter to track the number of yielded values + count := 0 + found := make(map[string]int, m.Len()) + + // Define a yield function that increments the counter + yield := func(k string, v int) bool { + count++ + // Store the key-value pair in the found map + found[k] = v + + return true + } + // Call the sequence with the yield function + seq(yield) + + // Check if the count matches the expected number of elements in the map + if count != 3 { + t.Errorf("Expected 3 calls of yeild, got %d", count) + } + + // Check if the keys and values are correct + expected := map[string]int{"a": 1, "b": 2, "c": 3} + if !reflect.DeepEqual(found, expected) { + t.Errorf("Expected %v, got %v", expected, found) + } +} + +func TestKeyedSeqSortedMap(t *testing.T) { + // Create a new SortedMap with some key-value pairs + mb := NewSortedMapBuilder[string, int](nil) + mb.Set("a", 1) + mb.Set("b", 2) + mb.Set("c", 3) + m := mb.Map() + + // Create a KeyedIterator for the map + it := m.Iterator() + + // Create a sequence using KeyedSeq + seq := KeyedSeq[string, int](it) + + // Initialize a counter to track the number of yielded values + count := 0 + found := make(map[string]int, m.Len()) + + // Define a yield function that increments the counter + yield := func(k string, v int) bool { + count++ + // Store the key-value pair in the found map + found[k] = v + + return true + } + // Call the sequence with the yield function + seq(yield) + + // Check if the count matches the expected number of elements in the map + if count != 3 { + t.Errorf("Expected 3 calls of yeild, got %d", count) + } + + // Check if the keys and values are correct + expected := map[string]int{"a": 1, "b": 2, "c": 3} + if !reflect.DeepEqual(found, expected) { + t.Errorf("Expected %v, got %v", expected, found) + } +} + +func TestUnkeyedSeq(t *testing.T) { + // Create a new set with some values + s := NewSet(nil, 1, 2, 3) + + // Create an UnkeyedIterator for the set + it := s.Iterator() + + // Create a sequence using UnkeyedSeq + seq := UnkeyedSeq[int](it) + + // Initialize a counter to track the number of yielded values + count := 0 + found := make(map[int]struct{}, s.Len()) + + // Define a yield function that increments the counter + yield := func(v int) bool { + count++ + // Store the value in the found map + found[v] = struct{}{} + + return true + } + + // Call the sequence with the yield function + seq(yield) + + // Check if the count matches the expected number of elements in the set + if count != 3 { + t.Errorf("Expected 3 calls of yeild, got %d", count) + } + + // Check if the keys and values are correct + expected := map[int]struct{}{1: {}, 2: {}, 3: {}} + if !reflect.DeepEqual(found, expected) { + t.Errorf("Expected %v, got %v", expected, found) + } +} + +func TestUnkeyedSeqSortedSet(t *testing.T) { + // Create a new sorted set with some values + s := NewSortedSet(nil, 1, 2, 3) + + // Create an UnkeyedIterator for the set + it := s.Iterator() + + // Create a sequence using UnkeyedSeq + seq := UnkeyedSeq[int](it) + + // Initialize a counter to track the number of yielded values + count := 0 + found := make(map[int]struct{}, s.Len()) + + // Define a yield function that increments the counter + yield := func(v int) bool { + count++ + // Store the value in the found map + found[v] = struct{}{} + + return true + } + + // Call the sequence with the yield function + seq(yield) + + // Check if the count matches the expected number of elements in the set + if count != 3 { + t.Errorf("Expected 3 calls of yeild, got %d", count) + } + + // Check if the keys and values are correct + expected := map[int]struct{}{1: {}, 2: {}, 3: {}} + if !reflect.DeepEqual(found, expected) { + t.Errorf("Expected %v, got %v", expected, found) + } +} From ba9e31d074ee764e3bd7b3ca0939e5f082857349 Mon Sep 17 00:00:00 2001 From: Isabelle Erin COWAN-BERGMAN Date: Fri, 9 May 2025 19:44:52 +0200 Subject: [PATCH 2/2] fix: refactor some things, add All functions to types --- immutable.go | 71 ++++++ immutable_seq_test.go | 249 +++++++++++++++++++++ immutable_test.go | 54 +++++ immutableiter/compat.go | 17 ++ immutableiter/iter.go | 119 ++++++++++ iter_test.go => immutableiter/iter_test.go | 170 ++++++++++---- iter.go | 113 ---------- iter_example_test.go | 63 ++---- sets.go | 29 +++ 9 files changed, 689 insertions(+), 196 deletions(-) create mode 100644 immutable_seq_test.go create mode 100644 immutableiter/compat.go create mode 100644 immutableiter/iter.go rename iter_test.go => immutableiter/iter_test.go (57%) delete mode 100644 iter.go diff --git a/immutable.go b/immutable.go index 1642de3..c93ecce 100644 --- a/immutable.go +++ b/immutable.go @@ -48,6 +48,7 @@ import ( "sort" "strings" + "github.com/benbjohnson/immutable/immutableiter" "golang.org/x/exp/constraints" ) @@ -229,6 +230,23 @@ func (l *List[T]) Iterator() *ListIterator[T] { return itr } +// All returns an [iter.Seq] function that can be used to perform range-like operations on the [iter.Seq] and inter-operate with other libraries using the Go 1.23 iterable function API. +// +// This is equivalent to calling the [List.Iterator] method and wrapping the iterator with [immutableiter.IndexedSeq]. +func (l *List[T]) All() immutableiter.Seq[T] { + return immutableiter.IndexedSeq[T](l.Iterator()) +} + +// AllWithIndex returns an [iter.Seq2] function that can be used to perform range-like operations on the [iter.Seq2] and inter-operate with other libraries using the Go 1.23 iterable function +// API. +// +// This is equivalent to calling the [List.Iterator] method and wrapping the iterator with [immutableiter.IndexedSeqWithIndex]. +// +// TODO(Izzette) chose either All or AllWithIndex? +func (l *List[T]) AllWithIndex() immutableiter.Seq2[int, T] { + return immutableiter.IndexedSeqWithIndex[T](l.Iterator()) +} + // ListBuilder represents an efficient builder for creating new Lists. type ListBuilder[T any] struct { list *List[T] // current state @@ -294,6 +312,25 @@ func (b *ListBuilder[T]) Iterator() *ListIterator[T] { return b.list.Iterator() } +// All returns an [iter.Seq] function that can be used to perform range-like operations on the [iter.Seq] and inter-operate with other libraries using the Go 1.23 iterable function API. +// +// This is equivalent to calling the [ListBuilder.Iterator] method and wrapping the iterator with [immutableiter.IndexedSeq]. +// +// TODO(Izzette): should the builders really have All or is this just noise? +func (l *ListBuilder[T]) All() immutableiter.Seq[T] { + return immutableiter.IndexedSeq[T](l.Iterator()) +} + +// AllWithIndex returns an [iter.Seq2] function that can be used to perform range-like operations on the [iter.Seq2] and inter-operate with other libraries using the Go 1.23 iterable function +// API. +// +// This is equivalent to calling the [ListBuilder.Iterator] method and wrapping the iterator with [immutableiter.IndexedSeqWithIndex]. +// +// TODO(Izzette): should the builders really have All or is this just noise? +func (l *ListBuilder[T]) AllWithIndex() immutableiter.Seq2[int, T] { + return immutableiter.IndexedSeqWithIndex[T](l.Iterator()) +} + // Constants for bit shifts used for levels in the List trie. const ( listNodeBits = 5 @@ -539,6 +576,8 @@ func (n *listLeafNode[T]) deleteAfter(index int, mutable bool) listNode[T] { } // ListIterator represents an ordered iterator over a list. +// +// ListIterator implements [immutableiter.IndexedIterator] for T. type ListIterator[T any] struct { list *List[T] // source list index int // current index position @@ -813,6 +852,13 @@ func (m *Map[K, V]) Iterator() *MapIterator[K, V] { return itr } +// All returns an [iter.Seq2] function that can be used to perform range-like operations on the [iter.Seq2] and inter-operate with other libraries using the Go 1.23 iterable function API. +// +// This is equivalent to calling the [Map.Iterator] method and wrapping the iterator with [immutableiter.KeyedSeq]. +func (m *Map[K, V]) All() immutableiter.Seq2[K, V] { + return immutableiter.KeyedSeq[K, V](m.Iterator()) +} + // MapBuilder represents an efficient builder for creating Maps. type MapBuilder[K, V any] struct { m *Map[K, V] // current state @@ -862,6 +908,13 @@ func (b *MapBuilder[K, V]) Iterator() *MapIterator[K, V] { return b.m.Iterator() } +// All returns an [iter.Seq2] function that can be used to perform range-like operations on the [iter.Seq2] and inter-operate with other libraries using the Go 1.23 iterable function API. +// +// This is equivalent to calling the [MapBuilder.Iterator] method and wrapping the iterator with [immutableiter.KeyedSeq]. +func (m *MapBuilder[K, V]) All() immutableiter.Seq2[K, V] { + return immutableiter.KeyedSeq[K, V](m.Iterator()) +} + // mapNode represents any node in the map tree. type mapNode[K, V any] interface { get(key K, shift uint, keyHash uint32, h Hasher[K]) (value V, ok bool) @@ -1435,6 +1488,8 @@ type mapEntry[K, V any] struct { // MapIterator represents an iterator over a map's key/value pairs. Although // map keys are not sorted, the iterator's order is deterministic. +// +// MapIterator implements [immutableiter.KeyedIterator] for K and V. type MapIterator[K, V any] struct { m *Map[K, V] // source map @@ -1704,6 +1759,13 @@ func (m *SortedMap[K, V]) Iterator() *SortedMapIterator[K, V] { return itr } +// All returns an [iter.Seq2] function that can be used to perform range-like operations on the [iter.Seq2] and inter-operate with other libraries using the Go 1.23 iterable function API. +// +// This is equivalent to calling the [SortedMap.Iterator] method and wrapping the iterator with [immutableiter.KeyedSeq]. +func (m *SortedMap[K, V]) All() immutableiter.Seq2[K, V] { + return immutableiter.KeyedSeq[K, V](m.Iterator()) +} + // SortedMapBuilder represents an efficient builder for creating sorted maps. type SortedMapBuilder[K, V any] struct { m *SortedMap[K, V] // current state @@ -1753,6 +1815,13 @@ func (b *SortedMapBuilder[K, V]) Iterator() *SortedMapIterator[K, V] { return b.m.Iterator() } +// All returns an [iter.Seq2] function that can be used to perform range-like operations on the [iter.Seq2] and inter-operate with other libraries using the Go 1.23 iterable function API. +// +// This is equivalent to calling the [SortedMapBuilder.Iterator] method and wrapping the iterator with [immutableiter.KeyedSeq]. +func (m *SortedMapBuilder[K, V]) All() immutableiter.Seq2[K, V] { + return immutableiter.KeyedSeq[K, V](m.Iterator()) +} + // sortedMapNode represents a branch or leaf node in the sorted map. type sortedMapNode[K, V any] interface { minKey() K @@ -2035,6 +2104,8 @@ func (n *sortedMapLeafNode[K, V]) delete(key K, c Comparer[K], mutable bool, res // SortedMapIterator represents an iterator over a sorted map. // Iteration can occur in natural or reverse order based on use of Next() or Prev(). +// +// SortedMapIterator implements the [immutableiter.KeyedIterator] interface for K and V. type SortedMapIterator[K, V any] struct { m *SortedMap[K, V] // source map diff --git a/immutable_seq_test.go b/immutable_seq_test.go new file mode 100644 index 0000000..7a34d7a --- /dev/null +++ b/immutable_seq_test.go @@ -0,0 +1,249 @@ +package immutable + +import ( + "reflect" + "sort" + "testing" + + "github.com/benbjohnson/immutable/immutableiter" +) + +// TODO(Izzette): add tests for the All function of the Map, SortedMap, Set, and SortedSet types and their corresponding builders. + +func TestList_All(t *testing.T) { + t.Run("Empty", func(t *testing.T) { + seqTestEmpty(t, func() immutableiter.Seq[int] { + return NewList[int]().All() + }) + }) + t.Run("One", func(t *testing.T) { + seqTestOne(t, func() immutableiter.Seq[int] { + return NewList[int](1).All() + }, 1) + }) + t.Run("OneBreak", func(t *testing.T) { + seqTestOneBreak(t, func() immutableiter.Seq[int] { + return NewList[int](1).All() + }, 1) + }) + t.Run("AllOrdered", func(t *testing.T) { + seqTestAllOrdered(t, func() immutableiter.Seq[int] { + return NewList[int](1, 2, 3).All() + }, []int{1, 2, 3}) + }) + t.Run("AllOrderedBreak", func(t *testing.T) { + seqTestAllOrderedBreak(t, func() immutableiter.Seq[int] { + return NewList[int](1, 2, 3, 4, 5).All() + }, []int{1, 2, 3}) + }) +} + +func TestListBuilder_All(t *testing.T) { + t.Run("Empty", func(t *testing.T) { + seqTestEmpty(t, func() immutableiter.Seq[int] { + return NewListBuilder[int]().All() + }) + }) + t.Run("One", func(t *testing.T) { + seqTestOne(t, func() immutableiter.Seq[int] { + lb := NewListBuilder[int]() + lb.Append(1) + return lb.All() + }, 1) + }) + t.Run("OneBreak", func(t *testing.T) { + seqTestOneBreak(t, func() immutableiter.Seq[int] { + lb := NewListBuilder[int]() + lb.Append(1) + return lb.All() + }, 1) + }) + t.Run("AllOrdered", func(t *testing.T) { + seqTestAllOrdered(t, func() immutableiter.Seq[int] { + lb := NewListBuilder[int]() + lb.Append(1) + lb.Append(2) + lb.Append(3) + return lb.All() + }, []int{1, 2, 3}) + }) + t.Run("AllOrderedBreak", func(t *testing.T) { + seqTestAllOrderedBreak(t, func() immutableiter.Seq[int] { + lb := NewListBuilder[int]() + for i := 1; i <= 5; i++ { + lb.Append(i) + } + return lb.All() + }, []int{1, 2, 3}) + }) +} + +// seqTestEmpty checks if the sequence returned by allFunc is empty. +func seqTestEmpty[T any](t *testing.T, allFunc func() immutableiter.Seq[T]) { + seq := allFunc() + + actual := make([]T, 0) + yield := func(v T) bool { + actual = append(actual, v) + return true + } + seq(yield) + + if len(actual) != 0 { + t.Errorf("Expected empty sequence, got %v", actual) + } +} + +// seqTestOne checks if the sequence returned by allFunc contains exactly one +// element and that it matches the expected value. +func seqTestOne[T any](t *testing.T, allFunc func() immutableiter.Seq[T], expected T) { + seq := allFunc() + + actual := make([]T, 0) + yield := func(v T) bool { + actual = append(actual, v) + return true + } + seq(yield) + + if len(actual) != 1 { + t.Errorf("Expected 1 element, got %d", len(actual)) + } + if !reflect.DeepEqual(actual, []T{expected}) { + t.Errorf("Expected %v, got %v", expected, actual[0]) + } +} + +// seqTestOneBreak checks if the sequence returned by allFunc contains exactly +// one element and that it matches the expected value, but stops yielding after +// the first element. +func seqTestOneBreak[T any](t *testing.T, allFunc func() immutableiter.Seq[T], expected T) { + seq := allFunc() + + actual := make([]T, 0) + yield := func(v T) bool { + actual = append(actual, v) + // Do not continue yielding, but this is the last element so it should + // not impact the result. + return false + } + seq(yield) + + if len(actual) != 1 { + t.Errorf("Expected 1 element, got %d", len(actual)) + } + if !reflect.DeepEqual(actual, []T{expected}) { + t.Errorf("Expected %v, got %v", expected, actual[0]) + } +} + +// seqTestAllOrdered checks if the sequence returned by allFunc contains all +// elements in the expected order. +// It compares the actual sequence with the expected sequence. +func seqTestAllOrdered[T any]( + t *testing.T, + allFunc func() immutableiter.Seq[T], + expected []T, +) { + seq := allFunc() + + actual := make([]T, 0, len(expected)) + yield := func(v T) bool { + actual = append(actual, v) + return true + } + seq(yield) + + if !reflect.DeepEqual(actual, expected) { + t.Errorf("Expected %v, got %v", expected, actual) + } +} + +// seqTestAllUnordered checks if the sequence returned by allFunc contains all +// elements in any order. +// It compares the actual sequence with the expected sequence after sorting +// both slices. +func seqTestAllUnordered[T immutableiter.Ordered]( + t *testing.T, + allFunc func() immutableiter.Seq[T], + expected []T, +) { + seq := allFunc() + + actual := make([]T, 0, len(expected)) + yield := func(v T) bool { + actual = append(actual, v) + return true + } + seq(yield) + + // Sort both slices for comparison + sort.Slice(actual, func(i, j int) bool { + return actual[i] < actual[j] + }) + sort.Slice(expected, func(i, j int) bool { + return expected[i] < expected[j] + }) + + if !reflect.DeepEqual(actual, expected) { + t.Errorf("Expected %v, got %v", expected, actual) + } +} + +// seqTestAllOrderedBreak checks if the sequence returned by allFunc contains +// all elements in the expected order, but stops yielding after the nth element +// where n is the length of the expected slice. +func seqTestAllOrderedBreak[T any]( + t *testing.T, + allFunc func() immutableiter.Seq[T], + expected []T, +) { + seq := allFunc() + + actual := make([]T, 0, len(expected)) + yield := func(v T) bool { + actual = append(actual, v) + return len(actual) < len(expected) + } + seq(yield) + + if !reflect.DeepEqual(actual, expected) { + t.Errorf("Expected %v, got %v", expected, actual) + } +} + +// seqTestAllSet checks if the sequence returned by allFunc contains all +// elements in any order, but ensures that no element is returned more than +// once. +// It stops yielding after the nth element where n is the sample parameter. +func seqTestAllSetBreak[T comparable]( + t *testing.T, + allFunc func() immutableiter.Seq[T], + sample int, + expected []T, +) { + seq := allFunc() + + actual := make([]T, 0, len(expected)) + yield := func(v T) bool { + actual = append(actual, v) + return len(actual) < sample + } + seq(yield) + + // Convert expected slice to a map for comparison + expectedMap := make(map[T]int, len(expected)) + for _, v := range expected { + expectedMap[v] = 0 + } + // Check if all yielded values are in the expected map no more than once + for _, v := range actual { + if count, ok := expectedMap[v]; !ok { + t.Errorf("Unexpected value %v in yielded values", v) + } else if count > 0 { + t.Errorf("Duplicate value %v in yielded values", v) + } else { + expectedMap[v]++ + } + } +} diff --git a/immutable_test.go b/immutable_test.go index 581d4d8..df0c529 100644 --- a/immutable_test.go +++ b/immutable_test.go @@ -236,6 +236,60 @@ func TestList(t *testing.T) { } }) + t.Run("AllSimple", func(t *testing.T) { + l := NewList(1, 2, 3, 4, 5) + seq := l.All() + + count := 0 + seq(func(v int) bool { + count++ + return true + }) + + if count != 5 { + t.Errorf("Expected 5 elements, got %d", count) + } + }) + + t.Run("ÄllWirhIndex", func(t *testing.T) { + l := NewList(1, 2, 3, 4, 5) + seq := l.AllWithIndex() + + count := 0 + seq(func(i int, v int) bool { + count++ + if i != count-1 { + t.Errorf("Expected index %d, got %d", count-1, i) + } + return true + }) + + if count != 5 { + t.Errorf("Expected 5 elements, got %d", count) + } + }) + + t.Run("AllBreaks", func(t *testing.T) { + lb := NewListBuilder[int]() + for i := 0; i < 100; i++ { + lb.Append(i) + } + l := lb.List() + + calls := 0 + l.All()(func(v int) bool { + calls++ + if v == 57 { + return false + } + return true + }) + + if calls != 58 { + t.Fatalf("expected 58 calls to the yield function. Got %d instead", calls) + } + }) + RunRandom(t, "Random", func(t *testing.T, rand *rand.Rand) { l := NewTList() for i := 0; i < 100000; i++ { diff --git a/immutableiter/compat.go b/immutableiter/compat.go new file mode 100644 index 0000000..893a748 --- /dev/null +++ b/immutableiter/compat.go @@ -0,0 +1,17 @@ +package immutableiter + +type ( + // Seq has the signature of the [iter.Seq] type available starting in Go 1.23. + Seq[T any] func(func(T) bool) + + // Seq2 has the signature of the [iter.Seq2] type available starting in Go 1.23. + Seq2[K, V any] func(func(K, V) bool) +) + +// Ordered has the signature of the [cmp.Ordered] type available starting in Go 1.21. +type Ordered interface { + ~int | ~int8 | ~int16 | ~int32 | ~int64 | + ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr | + ~float32 | ~float64 | + ~string +} diff --git a/immutableiter/iter.go b/immutableiter/iter.go new file mode 100644 index 0000000..8f3189b --- /dev/null +++ b/immutableiter/iter.go @@ -0,0 +1,119 @@ +// immutableiter is a package that provides interfaces for iterators used by [github.com/benbjohnson/immutable] collections. +// It allows converting iterators to functions that can be used with the Go 1.23 iterable function API. +// It also has types for retro-compatibility with [iter.Seq] and [iter.Seq2] from Go 1.23 and [cmp.Ordered] from Go 1.21. +package immutableiter + +// Iterator is an interface for iterating over immutable collections. +type Iterator[T any] interface { + // First positions the iterator on the first index. + // If source is empty then no change is made. + First() + + // Done returns true if there are no more elements in the iterator. + Done() bool +} + +// IndexedIterator is an iterator that returns the index and value of the underlying collection. +// +// TODO(Izzette): could this use a better name? +type IndexedIterator[T any] interface { + Iterator[T] + + // Next returns the current index and its value & moves the iterator forward. + // Returns an index of -1 if the there are no more elements to return. + Next() (int, T) +} + +// KeyedIterator is an iterator that returns the key and value of the underlying collection. +// +// TODO(Izzette): could this use a better name? +type KeyedIterator[K, V any] interface { + Iterator[V] + + // Next returns the next key/value pair. + // Returns a nil key when no elements remain. + Next() (K, V, bool) +} + +// UnkeyedIterator is an iterator that returns the value of the underlying collection. +// +// TODO(Izzette): could this use a better name? +type UnkeyedIterator[T any] interface { + Iterator[T] + + // Next moves the iterator to the next value. + Next() (T, bool) +} + +// IndexedSeq takes an IndexedIterator for immutable collections (such as those returned by [github.com/benbjohnson/immutable.List.Iterator]) and returns a function satisfying the [iter.Seq] +// type available starting in Go 1.23. +// This can be used to perform range-like operations on the [iter.Seq] and inter-operate with other libraries using the Go 1.23 iterable function API. +// +// TODO(Izzette): could this use a better name? +func IndexedSeq[T any](it IndexedIterator[T]) Seq[T] { + seq2 := IndexedSeqWithIndex(it) + return func(yield func(T) bool) { + seq2(func(_ int, v T) bool { + return yield(v) + }) + } +} + +// IndexedSeqWithIndex takes an IndexedIterator for immutable collections (such as those returned by [github.com/benbjohnson/immutable.List.Iterator]) and returns a function satisfying the +// [iter.Seq2] type available starting in Go 1.23. +// This can be used to perform range-like operations on the [iter.Seq2] and inter-operate with other libraries using the Go 1.23 iterable function API. +func IndexedSeqWithIndex[T any](it IndexedIterator[T]) Seq2[int, T] { + return func(yield func(int, T) bool) { + for !it.Done() { + index, v := it.Next() + assert(index >= 0, "index should never be negative after checking Done()") + + if !yield(index, v) { + return + } + } + } +} + +// KeyedSeq takes a KeyedIterator for immutable collections (such as those [github.com/benbjohnson/immutable.Map.Iterator] and [github.com/benbjohnson/immutable.OrderedMap.Iterator]) and returns +// a function satisfying the [iter.Seq2] type available starting in Go 1.23. +// This can be used to perform range-like operations on the [iter.Seq2] and inter-operate with other libraries using the Go 1.23 iterable function API. +// +// TODO(Izzette): could this use a better name? +func KeyedSeq[K, V any](it KeyedIterator[K, V]) Seq2[K, V] { + return func(yield func(K, V) bool) { + for !it.Done() { + k, v, ok := it.Next() + assert(ok, "keyed iterator should always return a key/value pair after checking Done()") + + if !yield(k, v) { + return + } + } + } +} + +// UnkeyedSeq takes an UnkeyedIterator for immutable collections (such as those returned by [github.com/benbjohnson/immutable.Set.Iterator]) and +// [github.com/benbjohnson/immutable.SortedSet.Iterator]) and returns a function satisfying the [iter.Seq] type available starting in Go 1.23. +// This can be used to perform range-like operations on the [iter.Seq] and inter-operate with other libraries using the Go 1.23 iterable function API. +// +// TODO(Izzette): could this use a better name? +func UnkeyedSeq[T any](it UnkeyedIterator[T]) Seq[T] { + return func(yield func(T) bool) { + for !it.Done() { + v, ok := it.Next() + assert(ok, "unkeyed iterator should always return a value after checking Done()") + + if !yield(v) { + return + } + } + } +} + +// assert is a helper function that panics with the given message if the condition is false. +func assert(condition bool, msg string) { + if !condition { + panic(msg) + } +} diff --git a/iter_test.go b/immutableiter/iter_test.go similarity index 57% rename from iter_test.go rename to immutableiter/iter_test.go index bd522c0..86e3d0a 100644 --- a/iter_test.go +++ b/immutableiter/iter_test.go @@ -1,16 +1,15 @@ -package immutable +package immutableiter import ( "reflect" "testing" ) -func TestIndexedSeq(t *testing.T) { - // Create a new list with some values - l := NewList(1, 2, 3, 4, 5) +// TODO(Izzette): test some non-happy cases where the assertations in iter.go will fail. - // Create an IndexedIterator for the list - it := l.Iterator() +func TestIndexedSeq(t *testing.T) { + // Create an IndexedIterator + it := &mockIndexedIterator[int]{values: []int{1, 2, 3, 4, 5}} // Create a sequence using IndexedSeq seq := IndexedSeq[int](it) @@ -34,11 +33,8 @@ func TestIndexedSeq(t *testing.T) { } func TestIndexedSeqWithIndex(t *testing.T) { - // Create a new list with some values - l := NewList(1, 2, 3, 4, 5) - - // Create an IndexedIterator for the list - it := l.Iterator() + // Create an IndexedIterator + it := &mockIndexedIterator[int]{values: []int{1, 2, 3, 4, 5}} // Create a sequence using IndexedSeqWithIndex seq := IndexedSeqWithIndex[int](it) @@ -62,22 +58,21 @@ func TestIndexedSeqWithIndex(t *testing.T) { } func TestKeyedSeq(t *testing.T) { - // Create a new Map with some key-value pairs - mb := NewMapBuilder[string, int](nil) - mb.Set("a", 1) - mb.Set("b", 2) - mb.Set("c", 3) - m := mb.Map() - - // Create a KeyedIterator for the map - it := m.Iterator() + // Create a KeyedIterator + it := &mockKeyedIterator[string, int]{ + Pairs: []kvPair[string, int]{ + {"a", 1}, + {"b", 2}, + {"c", 3}, + }, + } // Create a sequence using KeyedSeq seq := KeyedSeq[string, int](it) // Initialize a counter to track the number of yielded values count := 0 - found := make(map[string]int, m.Len()) + found := make(map[string]int, len(it.Pairs)) // Define a yield function that increments the counter yield := func(k string, v int) bool { @@ -103,22 +98,21 @@ func TestKeyedSeq(t *testing.T) { } func TestKeyedSeqSortedMap(t *testing.T) { - // Create a new SortedMap with some key-value pairs - mb := NewSortedMapBuilder[string, int](nil) - mb.Set("a", 1) - mb.Set("b", 2) - mb.Set("c", 3) - m := mb.Map() - - // Create a KeyedIterator for the map - it := m.Iterator() + // Create a KeyedIterator + it := &mockKeyedIterator[string, int]{ + Pairs: []kvPair[string, int]{ + {"a", 1}, + {"b", 2}, + {"c", 3}, + }, + } // Create a sequence using KeyedSeq seq := KeyedSeq[string, int](it) // Initialize a counter to track the number of yielded values count := 0 - found := make(map[string]int, m.Len()) + found := make(map[string]int, len(it.Pairs)) // Define a yield function that increments the counter yield := func(k string, v int) bool { @@ -144,18 +138,15 @@ func TestKeyedSeqSortedMap(t *testing.T) { } func TestUnkeyedSeq(t *testing.T) { - // Create a new set with some values - s := NewSet(nil, 1, 2, 3) - - // Create an UnkeyedIterator for the set - it := s.Iterator() + // Create an UnkeyedIterator + it := &mockUnkeyedIterator[int]{Values: []int{1, 2, 3}} // Create a sequence using UnkeyedSeq seq := UnkeyedSeq[int](it) // Initialize a counter to track the number of yielded values count := 0 - found := make(map[int]struct{}, s.Len()) + found := make(map[int]struct{}, len(it.Values)) // Define a yield function that increments the counter yield := func(v int) bool { @@ -182,18 +173,15 @@ func TestUnkeyedSeq(t *testing.T) { } func TestUnkeyedSeqSortedSet(t *testing.T) { - // Create a new sorted set with some values - s := NewSortedSet(nil, 1, 2, 3) - - // Create an UnkeyedIterator for the set - it := s.Iterator() + // Create an UnkeyedIterator + it := &mockUnkeyedIterator[int]{Values: []int{1, 2, 3}} // Create a sequence using UnkeyedSeq seq := UnkeyedSeq[int](it) // Initialize a counter to track the number of yielded values count := 0 - found := make(map[int]struct{}, s.Len()) + found := make(map[int]struct{}, len(it.Values)) // Define a yield function that increments the counter yield := func(v int) bool { @@ -218,3 +206,99 @@ func TestUnkeyedSeqSortedSet(t *testing.T) { t.Errorf("Expected %v, got %v", expected, found) } } + +// mockIndexedIterator is a mock implementation of [IndexedIterator] for testing purposes. +type mockIndexedIterator[T any] struct { + // The current index of the iterator + index int + + // The values to iterate over + values []T +} + +// First implements [IndexedIterator.First]. +func (m *mockIndexedIterator[T]) First() { + m.index = 0 +} + +// Done implements [IndexedIterator.Done]. +func (m *mockIndexedIterator[T]) Done() bool { + return m.index >= len(m.values) +} + +// Next implements [IndexedIterator.Next]. +func (m *mockIndexedIterator[T]) Next() (int, T) { + if m.Done() { + return -1, *new(T) + } + val := m.values[m.index] + m.index++ + return m.index - 1, val +} + +// kvPair is a key-value pair used in the mockKeyedIterator. +type kvPair[K, V any] struct { + // The Key of the pair + Key K + + // The value of the pair + Value V +} + +// mockKeyedIterator is a mock implementation of [KeyedIterator] for testing purposes. +type mockKeyedIterator[K, V any] struct { + // The current Index of the iterator + Index int + + // The key-value Pairs to iterate over + Pairs []kvPair[K, V] +} + +// First implements [KeyedIterator.First]. +func (m *mockKeyedIterator[K, V]) First() { + m.Index = 0 +} + +// Done implements [KeyedIterator.Done]. +func (m *mockKeyedIterator[K, V]) Done() bool { + return m.Index >= len(m.Pairs) +} + +// Next implements [KeyedIterator.Next]. +func (m *mockKeyedIterator[K, V]) Next() (K, V, bool) { + if m.Done() { + return *new(K), *new(V), false + } + pair := m.Pairs[m.Index] + m.Index++ + return pair.Key, pair.Value, true +} + +// mockUnkeyedIterator is a mock implementation of [UnkeyedIterator] for testing purposes. +type mockUnkeyedIterator[T any] struct { + // The current Index of the iterator + Index int + + // The Values to iterate over + Values []T +} + +// First implements [UnkeyedIterator.First]. +func (m *mockUnkeyedIterator[T]) First() { + m.Index = 0 +} + +// Done implements [UnkeyedIterator.Done]. +func (m *mockUnkeyedIterator[T]) Done() bool { + return m.Index >= len(m.Values) +} + +// Next implements [UnkeyedIterator.Next]. +func (m *mockUnkeyedIterator[T]) Next() (T, bool) { + if m.Done() { + return *new(T), false + } + val := m.Values[m.Index] + m.Index++ + return val, true +} diff --git a/iter.go b/iter.go deleted file mode 100644 index e7de690..0000000 --- a/iter.go +++ /dev/null @@ -1,113 +0,0 @@ -package immutable - -// Iterator is an interface for iterating over immutable collections. -type Iterator[T any] interface { - // First positions the iterator on the first index. - // If source is empty then no change is made. - First() - - // Done returns true if there are no more elements in the iterator. - Done() bool -} - -// IndexedIterator is an iterator that returns the index and value of the -// underlying collection. -type IndexedIterator[T any] interface { - Iterator[T] - - // Next returns the current index and its value & moves the iterator forward. - // Returns an index of -1 if the there are no more elements to return. - Next() (int, T) -} - -// KeyedIterator is an iterator that returns the key and value of the underlying -// collection. -type KeyedIterator[K, V any] interface { - Iterator[V] - - // Next returns the next key/value pair. - // Returns a nil key when no elements remain. - Next() (K, V, bool) -} - -// UnkeyedIterator is an iterator that returns the value of the underlying -// collection. -type UnkeyedIterator[T any] interface { - Iterator[T] - - // Next moves the iterator to the next value. - Next() (T, bool) -} - -// IndexedSeq takes an IndexedIterator for immutable collections (such as those -// returned by [List.Iterator]) and returns a function satisfying the [iter.Seq] -// type available starting in Go 1.23. -// This can be used to perform range-like operations on the [iter.Seq] and -// inter-operate with other libraries using the Go 1.23 iterable function API. -func IndexedSeq[T any](it IndexedIterator[T]) func(func(T) bool) { - seq2 := IndexedSeqWithIndex(it) - return func(yield func(T) bool) { - seq2(func(_ int, v T) bool { - return yield(v) - }) - } -} - -// IndexedSeqWithIndex takes an IndexedIterator for immutable collections (such -// as those returned by [List.Iterator]) and returns a function satisfying the -// [iter.Seq2] type available starting in Go 1.23. -// This can be used to perform range-like operations on the [iter.Seq2] and -// inter-operate with other libraries using the Go 1.23 iterable function API. -func IndexedSeqWithIndex[T any](it IndexedIterator[T]) func(func(int, T) bool) { - return func(yield func(int, T) bool) { - for !it.Done() { - index, v := it.Next() - assert(index >= 0, "index should never be negative after checking Done()") - - if !yield(index, v) { - return - } - } - } -} - -// KeyedSeq takes a KeyedIterator for immutable collections (such as those -// [Map.Iterator] and [OrderedMap.Iterator]) and returns a function satisfying -// the [iter.Seq2] type available starting in Go 1.23. -// This can be used to perform range-like operations on the [iter.Seq2] and -// inter-operate with other libraries using the Go 1.23 iterable function API. -func KeyedSeq[K, V any](it KeyedIterator[K, V]) func(func(K, V) bool) { - return func(yield func(K, V) bool) { - for !it.Done() { - k, v, ok := it.Next() - assert( - ok, - "keyed iterator should always return a key/value pair after checking "+ - "Done()", - ) - - if !yield(k, v) { - return - } - } - } -} - -// UnkeyedSeq takes an UnkeyedIterator for immutable collections (such as those -// returned by [List.Iterator]) and returns a function satisfying the [iter.Seq] -// type available starting in Go 1.23. -// This can be used to perform range-like operations on the [iter.Seq] and -// inter-operate with other libraries using the Go 1.23 iterable function API. -func UnkeyedSeq[T any](it UnkeyedIterator[T]) func(func(T) bool) { - return func(yield func(T) bool) { - for !it.Done() { - v, ok := it.Next() - assert( - ok, "unkeyed iterator should always return a value after checking Done()") - - if !yield(v) { - return - } - } - } -} diff --git a/iter_example_test.go b/iter_example_test.go index a070061..64765af 100644 --- a/iter_example_test.go +++ b/iter_example_test.go @@ -6,24 +6,22 @@ import ( "github.com/benbjohnson/immutable" ) -// Demonstrates how to use the [immutable.IndexedSeq] function to iterate over -// an [immutable.List] of integers. +// TODO(Izzette): add examples for the All functions of the SortedMap and SortedSet types. +// TODO(Izzette): add examples which prematurely break the iteration. + +// Demonstrates how to use the [immutable.List.All] function to iterate over an [immutable.List] of integers. // -// In Go 1.23 with GOEXPERIMENT=rangefunc, or in Go 1.24 and later, this -// will be a range-like operation: +// In Go 1.23 with GOEXPERIMENT=rangefunc, or in Go 1.24 and later, this will be a range-like operation: // // for v := range seq { // fmt.Println("Yielded:", v) // } -func ExampleIndexedSeq() { +func ExampleList_All() { // Create a new list with some values l := immutable.NewList(1, 2, 3, 4, 5) - // Create an IndexedIterator for the list - it := l.Iterator() - - // Create a sequence using IndexedSeq - seq := immutable.IndexedSeq[int](it) + // Get a sequence from the list + seq := l.All() // Define a yield function that increments the counter yield := func(v int) bool { @@ -40,24 +38,19 @@ func ExampleIndexedSeq() { // Yielded: 5 } -// Demonstrates how to use the [immutable.IndexedSeqWithIndex] function to -// iterate over an [immutable.List] of integers with their indices. +// Demonstrates how to use the [immutable.List.All] function to iterate over an [immutable.List] of integers with their indices. // -// In Go 1.23 with GOEXPERIMENT=rangefunc, or in Go 1.24 and later, this -// will be a range-like operation: +// In Go 1.23 with GOEXPERIMENT=rangefunc, or in Go 1.24 and later, this will be a range-like operation: // // for i, v := range seq { // fmt.Printf("Yielded: %d at index %d\n", v, i) // } -func ExampleIndexedSeqWithIndex() { +func ExampleList_AllWithIndex() { // Create a new list with some values l := immutable.NewList(1, 2, 3, 4, 5) - // Create an IndexedIterator for the list - it := l.Iterator() - - // Create a sequence using IndexedSeqWithIndex - seq := immutable.IndexedSeqWithIndex[int](it) + // Create a sequence from the list + seq := l.AllWithIndex() // Define a yield function that increments the counter yield := func(i int, v int) bool { @@ -74,16 +67,14 @@ func ExampleIndexedSeqWithIndex() { // Yielded: 5 at index 4 } -// Demonstrates how to use the [immutable.KeyedSeq] function to iterate over an -// [immutable.Map] of integers to strings. +// Demonstrates how to use the [immutable.Map.All] function to iterate over an [immutable.Map] of integers to strings. // -// In Go 1.23 with GOEXPERIMENT=rangefunc, or in Go 1.24 and later, this -// will be a range-like operation: +// In Go 1.23 with GOEXPERIMENT=rangefunc, or in Go 1.24 and later, this will be a range-like operation: // // for k, v := range seq { // fmt.Printf("Yielded: %s at key %d\n", v, k) // } -func ExampleKeyedSeq() { +func ExampleMap_All() { // Create a new map builder with some values mb := immutable.NewMapBuilder[int, string](nil) mb.Set(1, "one") @@ -93,11 +84,8 @@ func ExampleKeyedSeq() { // Create a map from the builder m := mb.Map() - // Create a KeyedIterator for the map - it := m.Iterator() - - // Create a sequence using KeyedSeq - seq := immutable.KeyedSeq[int, string](it) + // Create a sequence from the map + seq := m.All() // Define a yield function that increments the counter yield := func(k int, v string) bool { @@ -112,24 +100,19 @@ func ExampleKeyedSeq() { // Yielded: three at key 3 } -// Demonstrates how to use the [immutable.UnkeyedSeq] function to iterate over an -// [immutable.Set] of integers. +// Demonstrates how to use the [immutable.Set.All] function to iterate over an [immutable.Set] of integers. // -// In Go 1.23 with GOEXPERIMENT=rangefunc, or in Go 1.24 and later, this -// will be a range-like operation: +// In Go 1.23 with GOEXPERIMENT=rangefunc, or in Go 1.24 and later, this will be a range-like operation: // // for v := range seq { // fmt.Println("Yielded:", v) // } -func ExampleUnkeyedSeq() { +func ExampleSet_All() { // Create a new set with some values s := immutable.NewSet(nil, 1, 2, 3) - // Create an UnkeyedIterator for the set - it := s.Iterator() - - // Create a sequence using UnkeyedSeq - seq := immutable.UnkeyedSeq[int](it) + // Create a Sequence from the set + seq := s.All() // Define a yield function that increments the counter yield := func(v int) bool { diff --git a/sets.go b/sets.go index b41bd37..f7b9e87 100644 --- a/sets.go +++ b/sets.go @@ -1,5 +1,7 @@ package immutable +import "github.com/benbjohnson/immutable/immutableiter" + // Set represents a collection of unique values. The set uses a Hasher // to generate hashes and check for equality of key values. // @@ -62,8 +64,17 @@ func (s Set[T]) Iterator() *SetIterator[T] { return itr } +// All returns an [iter.Seq] function that can be used to perform range-like operations on the [iter.Seq] and inter-operate with other libraries using the Go 1.23 iterable function API. +// +// This is equivalent to calling the [Set.Iterator] method and wrapping the iterator with [immutableiter.UnkeyedSeq]. +func (s Set[T]) All() immutableiter.Seq[T] { + return immutableiter.UnkeyedSeq[T](s.Iterator()) +} + // SetIterator represents an iterator over a set. // Iteration can occur in natural or reverse order based on use of Next() or Prev(). +// +// SetIterator implements [immutableiter.UnkeyedIterator] for T. type SetIterator[T any] struct { mi *MapIterator[T, struct{}] } @@ -92,6 +103,9 @@ func NewSetBuilder[T any](hasher Hasher[T]) *SetBuilder[T] { return &SetBuilder[T]{s: NewSet(hasher)} } +// Set +// +// TODO(Izzette): challenge the naming of this function, as the Set type uses Add not Set for this functionality. func (s SetBuilder[T]) Set(val T) { s.s.m = s.s.m.set(val, struct{}{}, true) } @@ -108,6 +122,9 @@ func (s SetBuilder[T]) Len() int { return s.s.Len() } +// TODO(Izzette): is there any reason that the SetBuilder and SortedSetBuilder types do not return Iterators? +// If not, than should we add them to the types and add All() methods as well? + type SortedSet[T any] struct { m *SortedMap[T, struct{}] } @@ -167,8 +184,17 @@ func (s SortedSet[T]) Iterator() *SortedSetIterator[T] { return itr } +// All returns an [iter.Seq] function that can be used to perform range-like operations on the [iter.Seq] and inter-operate with other libraries using the Go 1.23 iterable function API. +// +// This is equivalent to calling the [SortedSet.Iterator] method and wrapping the iterator with [immutableiter.UnkeyedSeq]. +func (s SortedSet[T]) All() immutableiter.Seq[T] { + return immutableiter.UnkeyedSeq[T](s.Iterator()) +} + // SortedSetIterator represents an iterator over a sorted set. // Iteration can occur in natural or reverse order based on use of Next() or Prev(). +// +// SortedSetIterator implements [immutableiter.UnkeyedIterator] for T. type SortedSetIterator[T any] struct { mi *SortedMapIterator[T, struct{}] } @@ -217,6 +243,9 @@ func NewSortedSetBuilder[T any](comparer Comparer[T]) *SortedSetBuilder[T] { return &SortedSetBuilder[T]{s: &s} } +// Set +// +// TODO(Izzette): challenge the naming of this function, as the SortedSet type uses Add not Set for this functionality func (s SortedSetBuilder[T]) Set(val T) { s.s.m = s.s.m.set(val, struct{}{}, true) }