From e435841c7261aed9bed677e14287e3e8c280a479 Mon Sep 17 00:00:00 2001 From: Jordan Orelli Date: Tue, 30 Nov 2021 11:25:42 -0600 Subject: [PATCH] nah --- merge2/additive.go | 1 + merge2/merge.go | 107 ++++++++++++++++++++++++++++ merge2/merge_test.go | 163 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 271 insertions(+) create mode 100644 merge2/additive.go create mode 100644 merge2/merge.go create mode 100644 merge2/merge_test.go diff --git a/merge2/additive.go b/merge2/additive.go new file mode 100644 index 0000000..8cdc3f6 --- /dev/null +++ b/merge2/additive.go @@ -0,0 +1 @@ +package merge2 diff --git a/merge2/merge.go b/merge2/merge.go new file mode 100644 index 0000000..dbd6e15 --- /dev/null +++ b/merge2/merge.go @@ -0,0 +1,107 @@ +package merge2 + +import ( + "fmt" +) + +type Merges[X any] interface { + MergeInto(*X) error +} + +// func Together[X Merges[Y], Y any](vals ...X) (Y, error) { +// var dest Y +// for _, v := range vals { +// if err := v.MergeInto(&dest); err != nil { +// return dest, fmt.Errorf("error merging values: %w", err) +// } +// } +// return dest, nil +// } + +// Into merges any number of values into some commonly-targetable value. +func Into[X Merges[Y], Y any](dest *Y, sources ...X) error { + for _, src := range sources { + if err := src.MergeInto(dest); err != nil { + return err + } + } + return nil +} + +func Maps[K comparable, V Merges[T], T any](dest map[K]T, src map[K]V) error { + for k, v := range src { + dv := dest[k] + if err := v.MergeInto(&dv); err != nil { + return fmt.Errorf("map merge error at key %v: %w", k, err) + } + dest[k] = dv + } + return nil +} + +// hmmmmm is it possible to make a map type where the keys are anythign +// comparable and the values are any mixed set of values that merge into a +// single type? +// +// I sorta want this: +// Map[K comparable, V Merges[T], T any] + +type Map[K comparable, V Merges[V]] map[K]V + +func (m Map[K, V]) MergeInto(dest map[K]V) error { + for k, v := range m { + dv := dest[k] + if err := v.MergeInto(&dv); err != nil { + return fmt.Errorf("merge failed on key %v: %w", k, err) + } + dest[k] = dv + } + return nil +} + +type Boxed struct { + p interface{} + merge func(interface{}) error +} + +func (b Boxed) String() string { + return fmt.Sprint(b.p) +} + +func (b Boxed) IsEmpty() bool { + return b.p == nil +} + +func Box[X Merges[X]](x X) Boxed { + return Boxed{ + p: &x, + merge: func(dest interface{}) error { + return fmt.Errorf("not yet") + // dp, ok := dest.p.(*X) + // if !ok { + // return fmt.Errorf("boxed val tried to merge into %T but can only merge into %T", dest, dp) + // } + // if dp == nil { + // return fmt.Errorf("that shit is empty fuck you") + // } + // fmt.Printf("merging %v into %v\n", x, dp) + // return nil + }, + } +} + +func (b Boxed) MergeInto(dest interface{}) error { + if b.IsEmpty() { + return nil + } + return b.merge(dest) +} + +func Unbox[X Merges[Y], Y any](b Boxed) (X, error) { + p, ok := b.p.(*X) + if !ok { + var zero X + return zero, fmt.Errorf("type error") + } + return *p, nil +} diff --git a/merge2/merge_test.go b/merge2/merge_test.go new file mode 100644 index 0000000..1377cf3 --- /dev/null +++ b/merge2/merge_test.go @@ -0,0 +1,163 @@ +package merge2 + +import ( + "testing" + "constraints" +) + +type additive int + +func (a additive) MergeInto(dest *additive) error { + *dest += a + return nil +} + +type multiplicative int + +func (m multiplicative) MergeInto(dest *multiplicative) error { + *dest *= m + return nil +} + +func newGCounter[ID comparable]() gcounter[ID] { + return gcounter[ID]{ + slots: make(map[ID]int), + } +} + +type gcounter[ID comparable] struct { + slots map[ID]int +} + +func (g gcounter[ID]) incr(id ID) { + g.slots[id]++ +} + +func (g gcounter[ID]) add(id ID, n int) { + if n < 0 { + panic("no") + } + g.slots[id] += n +} + +func max[N constraints.Ordered](a, b N) N { + if a >= b { + return a + } + return b +} + +func (g gcounter[ID]) MergeInto(dest *gcounter[ID]) error { + if dest.slots == nil { + dest.slots = make(map[ID]int) + } + for id, count := range g.slots { + dest.slots[id] = max(count, dest.slots[id]) + } + return nil +} + +// func TestTogether(t *testing.T) { +// a, b, c := additive(3), additive(10), additive(17) +// total, err := Together[Merges[*additive], additive](a, b, c) +// if err != nil { +// t.Fatalf("total error: %v", err) +// } +// if total != 30 { +// t.Fatalf("%d != 30", total) +// } +// } + +func TestInto(t *testing.T) { + a, b, c := additive(3), additive(10), additive(17) + if err := Into(&a, b, c); err != nil { + t.Fatalf("error: %v", err) + } + if a != 30 { + t.Fatalf("%d != 30", a) + } +} + +func TestMaps(t *testing.T) { + alice := map[string]additive{ + "vanilla": 3, + "chocolate": 5, + "strawberry": 2, + } + + bob := map[string]additive{ + "vanilla": 2, + "chocolate": 3, + "pistacchio": 5, + } + + totals := make(map[string]additive) + if err := Maps(totals, alice); err != nil { + t.Fatalf("map error: %v", err) + } + Maps(totals, bob) + t.Log(totals) +} + +func TestMap(t *testing.T) { + alice := Map[string, additive]{ + "vanilla": 3, + "chocolate": 5, + "strawberry": 2, + } + t.Log(alice) +} + +func TestBox(t *testing.T) { + t.Run("empty", func (t *testing.T) { + var a Boxed + var b Boxed + if err := a.MergeInto(&b); err != nil { + t.Fatalf("empty boxes failed to merge: %v", err) + } + }) + + // an empty box can merge into anything and it should be a no-op + t.Run("empty source", func (t *testing.T) { + var a Boxed + b := additive(3) + if err := a.MergeInto(&b); err != nil { + t.Fatalf("empty boxes failed to merge: %v", err) + } + }) + + t.Run("homologous", func(t *testing.T) { + a, b := Box(additive(3)), Box(additive(8)) + if err := a.MergeInto(b); err != nil { + t.Fatalf("homologous boxes failed to merge: %v", err) + } + }) + + // a, b := Box(additive(3)), Box(additive(5)) + // if err := b.MergeInto(&a); err != nil { + // t.Errorf("merge boxed failed: %v", err) + // } + + // total, err := Unbox[additive](a) + // if err != nil { + // t.Errorf("unbox failed: %v", err) + // } + // if total != 8 { + // t.Errorf("%d != 8", total) + // } + + // alice := Map[string, Boxed]{ + // "vanilla": Box(additive(3)), + // "chocolate": Box(multiplicative(5)), + // "strawberry": Box(multiplicative(2)), + // } + + // bob := Map[string, Boxed]{ + // "vanilla": Box(additive(3)), + // "chocolate": Box(additive(5)), + // "pistacchio": Box(additive(2)), + // } + + // Maps(alice, bob) + // t.Log(alice) +}