Skip to content

Commit b0ec7fa

Browse files
Foldable Maps and Lists (#995)
* Introduce Foldable lists and maps
1 parent 45bd6f6 commit b0ec7fa

File tree

7 files changed

+414
-1
lines changed

7 files changed

+414
-1
lines changed

common/types/list.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,15 @@ func (l *baseList) IsZeroValue() bool {
256256
return l.size == 0
257257
}
258258

259+
// Fold calls the FoldEntry method for each (index, value) pair in the list.
260+
func (l *baseList) Fold(f traits.Folder) {
261+
for i := 0; i < l.size; i++ {
262+
if !f.FoldEntry(i, l.get(i)) {
263+
break
264+
}
265+
}
266+
}
267+
259268
// Iterator implements the traits.Iterable interface method.
260269
func (l *baseList) Iterator() traits.Iterator {
261270
return newListIterator(l)
@@ -433,6 +442,15 @@ func (l *concatList) IsZeroValue() bool {
433442
return l.Size().(Int) == 0
434443
}
435444

445+
// Fold calls the FoldEntry method for each (index, value) pair in the list.
446+
func (l *concatList) Fold(f traits.Folder) {
447+
for i := Int(0); i < l.Size().(Int); i++ {
448+
if !f.FoldEntry(i, l.Get(i)) {
449+
break
450+
}
451+
}
452+
}
453+
436454
// Iterator implements the traits.Iterable interface method.
437455
func (l *concatList) Iterator() traits.Iterator {
438456
return newListIterator(l)

common/types/list_test.go

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -700,6 +700,119 @@ func TestValueListConvertToNative_Json(t *testing.T) {
700700
}
701701
}
702702

703+
func TestMutableList(t *testing.T) {
704+
l := NewMutableList(DefaultTypeAdapter)
705+
l.Add(NewRefValList(DefaultTypeAdapter, []ref.Val{String("hello")}))
706+
l.Add(NewRefValList(DefaultTypeAdapter, []ref.Val{String("world")}))
707+
il := l.ToImmutableList()
708+
if il.Size() != Int(2) {
709+
t.Errorf("il.Size() got %d, wanted size 2", il.Size())
710+
}
711+
l.Add(NewRefValList(DefaultTypeAdapter, []ref.Val{String("!")}))
712+
if il.Size() != Int(2) {
713+
t.Errorf("il.Size() got %d, wanted size 2", il.Size())
714+
}
715+
}
716+
717+
func TestListFold(t *testing.T) {
718+
719+
tests := []struct {
720+
l any
721+
folds int
722+
foldLimit int
723+
}{
724+
{
725+
l: []string{"hello", "world"},
726+
folds: 2,
727+
foldLimit: 2,
728+
},
729+
{
730+
l: []string{"hello", "world"},
731+
folds: 1,
732+
foldLimit: 1,
733+
},
734+
{
735+
l: []string{"hello"},
736+
folds: 1,
737+
foldLimit: 2,
738+
},
739+
{
740+
l: []ref.Val{},
741+
folds: 0,
742+
foldLimit: 20,
743+
},
744+
{
745+
l: []ref.Val{
746+
String("hello"),
747+
String("world"),
748+
String("goodbye"),
749+
String("cruel world"),
750+
},
751+
folds: 1,
752+
foldLimit: 1,
753+
},
754+
{
755+
l: []ref.Val{
756+
String("hello"),
757+
String("world"),
758+
String("goodbye"),
759+
String("cruel world"),
760+
},
761+
folds: 4,
762+
foldLimit: 10,
763+
},
764+
{
765+
l: DefaultTypeAdapter.NativeToValue([]ref.Val{
766+
String("hello"),
767+
String("world"),
768+
}).(traits.Lister).Add(DefaultTypeAdapter.NativeToValue([]ref.Val{
769+
String("goodbye"),
770+
String("cruel world"),
771+
})),
772+
folds: 4,
773+
foldLimit: 10,
774+
},
775+
{
776+
l: DefaultTypeAdapter.NativeToValue([]ref.Val{
777+
String("hello"),
778+
String("world"),
779+
}).(traits.Lister).Add(DefaultTypeAdapter.NativeToValue([]ref.Val{
780+
String("goodbye"),
781+
String("cruel world"),
782+
})),
783+
folds: 3,
784+
foldLimit: 3,
785+
},
786+
}
787+
reg := NewEmptyRegistry()
788+
for i, tst := range tests {
789+
tc := tst
790+
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
791+
f := &testListFolder{foldLimit: tc.foldLimit}
792+
l := reg.NativeToValue(tc.l).(traits.Foldable)
793+
l.Fold(f)
794+
if f.folds != tc.folds {
795+
t.Errorf("m.Fold(f) got %d, wanted %d folds", f.folds, tc.folds)
796+
}
797+
})
798+
}
799+
}
800+
801+
type testListFolder struct {
802+
foldLimit int
803+
folds int
804+
}
805+
806+
func (f *testListFolder) FoldEntry(k, v any) bool {
807+
if f.foldLimit != 0 {
808+
if f.folds >= f.foldLimit {
809+
return false
810+
}
811+
}
812+
f.folds++
813+
return true
814+
}
815+
703816
func getElem(t *testing.T, list traits.Indexer, index ref.Val) any {
704817
t.Helper()
705818
val := list.Get(index)

common/types/map.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,24 @@ func NewProtoMap(adapter Adapter, value *pb.Map) traits.Mapper {
9494
}
9595
}
9696

97+
// NewMutableMap constructs a mutable map from an adapter and a set of map values.
98+
func NewMutableMap(adapter Adapter, mutableValues map[ref.Val]ref.Val) traits.MutableMapper {
99+
mutableCopy := make(map[ref.Val]ref.Val, len(mutableValues))
100+
for k, v := range mutableValues {
101+
mutableCopy[k] = v
102+
}
103+
m := &mutableMap{
104+
baseMap: &baseMap{
105+
Adapter: adapter,
106+
mapAccessor: newRefValMapAccessor(mutableCopy),
107+
value: mutableCopy,
108+
size: len(mutableCopy),
109+
},
110+
mutableValues: mutableCopy,
111+
}
112+
return m
113+
}
114+
97115
// mapAccessor is a private interface for finding values within a map and iterating over the keys.
98116
// This interface implements portions of the API surface area required by the traits.Mapper
99117
// interface.
@@ -105,6 +123,9 @@ type mapAccessor interface {
105123

106124
// Iterator returns an Iterator over the map key set.
107125
Iterator() traits.Iterator
126+
127+
// Fold calls the FoldEntry method for each (key, value) pair in the map.
128+
Fold(traits.Folder)
108129
}
109130

110131
// baseMap is a reflection based map implementation designed to handle a variety of map-like types.
@@ -307,6 +328,28 @@ func (m *baseMap) Value() any {
307328
return m.value
308329
}
309330

331+
// mutableMap holds onto a set of mutable values which are used for intermediate computations.
332+
type mutableMap struct {
333+
*baseMap
334+
mutableValues map[ref.Val]ref.Val
335+
}
336+
337+
// Insert implements the traits.MutableMapper interface method, returning true if the key insertion
338+
// succeeds.
339+
func (m *mutableMap) Insert(k, v ref.Val) bool {
340+
if _, found := m.mutableValues[k]; found {
341+
return false
342+
}
343+
m.mutableValues[k] = v
344+
return true
345+
}
346+
347+
// ToImmutableMap implements the traits.MutableMapper interface method, converting a mutable map
348+
// an immutable map implementation.
349+
func (m *mutableMap) ToImmutableMap() traits.Mapper {
350+
return NewRefValMap(m.Adapter, m.mutableValues)
351+
}
352+
310353
func newJSONStructAccessor(adapter Adapter, st map[string]*structpb.Value) mapAccessor {
311354
return &jsonStructAccessor{
312355
Adapter: adapter,
@@ -350,6 +393,15 @@ func (a *jsonStructAccessor) Iterator() traits.Iterator {
350393
}
351394
}
352395

396+
// Fold calls the FoldEntry method for each (key, value) pair in the map.
397+
func (a *jsonStructAccessor) Fold(f traits.Folder) {
398+
for k, v := range a.st {
399+
if !f.FoldEntry(k, v) {
400+
break
401+
}
402+
}
403+
}
404+
353405
func newReflectMapAccessor(adapter Adapter, value reflect.Value) mapAccessor {
354406
keyType := value.Type().Key()
355407
return &reflectMapAccessor{
@@ -424,6 +476,16 @@ func (m *reflectMapAccessor) Iterator() traits.Iterator {
424476
}
425477
}
426478

479+
// Fold calls the FoldEntry method for each (key, value) pair in the map.
480+
func (m *reflectMapAccessor) Fold(f traits.Folder) {
481+
mapRange := m.refValue.MapRange()
482+
for mapRange.Next() {
483+
if !f.FoldEntry(mapRange.Key().Interface(), mapRange.Value().Interface()) {
484+
break
485+
}
486+
}
487+
}
488+
427489
func newRefValMapAccessor(mapVal map[ref.Val]ref.Val) mapAccessor {
428490
return &refValMapAccessor{mapVal: mapVal}
429491
}
@@ -477,6 +539,15 @@ func (a *refValMapAccessor) Iterator() traits.Iterator {
477539
}
478540
}
479541

542+
// Fold calls the FoldEntry method for each (key, value) pair in the map.
543+
func (a *refValMapAccessor) Fold(f traits.Folder) {
544+
for k, v := range a.mapVal {
545+
if !f.FoldEntry(k, v) {
546+
break
547+
}
548+
}
549+
}
550+
480551
func newStringMapAccessor(strMap map[string]string) mapAccessor {
481552
return &stringMapAccessor{mapVal: strMap}
482553
}
@@ -515,6 +586,15 @@ func (a *stringMapAccessor) Iterator() traits.Iterator {
515586
}
516587
}
517588

589+
// Fold calls the FoldEntry method for each (key, value) pair in the map.
590+
func (a *stringMapAccessor) Fold(f traits.Folder) {
591+
for k, v := range a.mapVal {
592+
if !f.FoldEntry(k, v) {
593+
break
594+
}
595+
}
596+
}
597+
518598
func newStringIfaceMapAccessor(adapter Adapter, mapVal map[string]any) mapAccessor {
519599
return &stringIfaceMapAccessor{
520600
Adapter: adapter,
@@ -557,6 +637,15 @@ func (a *stringIfaceMapAccessor) Iterator() traits.Iterator {
557637
}
558638
}
559639

640+
// Fold calls the FoldEntry method for each (key, value) pair in the map.
641+
func (a *stringIfaceMapAccessor) Fold(f traits.Folder) {
642+
for k, v := range a.mapVal {
643+
if !f.FoldEntry(k, v) {
644+
break
645+
}
646+
}
647+
}
648+
560649
// protoMap is a specialized, separate implementation of the traits.Mapper interfaces tailored to
561650
// accessing protoreflect.Map values.
562651
type protoMap struct {
@@ -769,6 +858,13 @@ func (m *protoMap) Iterator() traits.Iterator {
769858
}
770859
}
771860

861+
// Fold calls the FoldEntry method for each (key, value) pair in the map.
862+
func (m *protoMap) Fold(f traits.Folder) {
863+
m.value.Range(func(k protoreflect.MapKey, v protoreflect.Value) bool {
864+
return f.FoldEntry(k.Interface(), v.Interface())
865+
})
866+
}
867+
772868
// Size returns the number of entries in the protoreflect.Map.
773869
func (m *protoMap) Size() ref.Val {
774870
return Int(m.value.Len())

0 commit comments

Comments
 (0)