Created
November 27, 2021 00:04
-
-
Save jordanorelli/491af5974f095e23f547f2704956746f to your computer and use it in GitHub Desktop.
an abomination
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"fmt" | |
"strings" | |
) | |
type A int | |
func (a *A) Merge(v *A) error { | |
*a += *v | |
return nil | |
} | |
func (a A) String() string { return fmt.Sprint(int(a)) } | |
func newA(v int) *A { | |
a := A(v) | |
return &a | |
} | |
type B int | |
func (b *B) Merge(v *B) error { | |
*b *= *v | |
return nil | |
} | |
func (b B) String() string { return fmt.Sprint(int(b)) } | |
func newB(v int) *B { | |
b := B(v) | |
return &b | |
} | |
// Bad is an example of something bad that I do -not- want to select for, it | |
// has a Merge method but the parameter is not the same as the receiver. | |
type Bad int | |
func (b *Bad) Merge(v *A) error { | |
*b /= Bad(*v) | |
return nil | |
} | |
func newBad(v int) *Bad { | |
b := Bad(v) | |
return &b | |
} | |
type merges[X any] interface { | |
Merge(X) error | |
} | |
// Merge takes two values of any type that can merge with itself. | |
func Merge[X merges[X]](x X, y X) error { | |
return x.Merge(y) | |
} | |
type Slice[X merges[X]] []X | |
func (s Slice[X]) All() X { | |
var x X | |
for _, v := range s { | |
x.Merge(v) | |
} | |
return x | |
} | |
type Pair[X merges[X], Y merges[Y]] struct { | |
Left X | |
Right Y | |
} | |
func cons[X merges[X], Y merges[Y]](x X, y Y) Pair[X, Y] { | |
return Pair[X, Y]{Left: x, Right: y} | |
} | |
// Table is a maping of keys to values where the keys are anything comparable | |
// and the values are any type that define their own semantics for merging one | |
// value into another value | |
type Table[K comparable, X merges[X]] map[K]X | |
// Merge lets you merge two tables together, performing a member-wise merge for | |
// keys that exist in both tables, and accepting the values from the provided | |
// table if no value existed in the receiver | |
func (t Table[K, X]) Merge(from Table[K, X]) error { | |
for k, m := range from { | |
existing, ok := t[k] | |
if !ok { | |
t[k] = m | |
continue | |
} | |
if err := existing.Merge(m); err != nil { | |
return fmt.Errorf("merge error at key %s: %w", k, err) | |
} | |
} | |
return nil | |
} | |
// ruin takes a function that has a parameter and returns a new function that | |
// removes the type from that parameter. This is really unbelievably gross. | |
func ruin[X any](f func(X) error) func (interface{}) error { | |
return func(v interface{}) error { | |
vv, ok := v.(X) | |
if !ok { | |
return fmt.Errorf("unexpected type: %T wanted: %T", v, vv) | |
} | |
return f(vv) | |
} | |
} | |
// erase takes any mergeable type and separates the value from the merge | |
// function. | |
func erase[X merges[X]] (x X) erased { | |
return erased{ | |
val: x, | |
mergeFn: ruin(x.Merge), // <-- where the magic happens! | |
} | |
} | |
// erased is a mergeable value that can merge with other erased values at | |
// runtime | |
type erased struct { | |
val interface{} | |
mergeFn func(interface{}) error | |
} | |
func (e erased) Merge(v erased) error { return e.mergeFn(v.val) } | |
// Namespace is a key-value mapping of strings to values that can merge against | |
// other values of their own type. | |
type Namespace struct { | |
// members is unexported. Members cannot be accessed directly. | |
members Table[string, erased] | |
} | |
func (n Namespace) String() string { | |
var s strings.Builder | |
s.WriteRune('{') | |
first := true | |
for k, m := range n.members { | |
if !first { | |
s.WriteString(", ") | |
} | |
first = false | |
fmt.Fprintf(&s, "%q: %v (%T)", k, m.val, m.val) | |
} | |
s.WriteRune('}') | |
return s.String() | |
} | |
func NewNamespace() Namespace { | |
return Namespace{members: make(map[string]erased)} | |
} | |
// Add is the only way to add values to a Namespace. It has to be implemented | |
// as a package-level function because methods can't accept type parameters. | |
func Add[X merges[X]] (n Namespace, key string, x X) { n.members[key] = erase(x) } | |
func (n Namespace) Merge(from Namespace) error { | |
// Merging a namespace just means merging its members table with the | |
// members table of another namespace. | |
return n.members.Merge(from.members) | |
} | |
func main() { | |
a1 := A(3) | |
a2 := A(5) | |
b1 := B(7) | |
b2 := B(3) | |
// Merge takes two values of any type that define how to merge values of | |
// that type and merges them. | |
fmt.Println(Merge(&a1, &a2)) | |
fmt.Println(a1) | |
fmt.Println(Merge(&b1, &b2)) | |
fmt.Println(b1) | |
// It's a compile-time error to try to merge two values of differing types | |
// fmt.Println(Merge(&a1, &b1)) | |
// It's also a compile-time error to try to merge two values of the same | |
// type if that type does not define how it can be merged | |
// fmt.Println(Merge("harry", "sally")) | |
// Bad doesn't actually satisfy the merge constraint because it doesn't | |
// merge with its own type, it merges with a type other than itself. This | |
// is a compile-time error: | |
// | |
// fmt.Println(Merge(newBad(10), newBad(13))) | |
// these all have the same type | |
alice := Table[string, *A]{ | |
"chocolate": newA(1), | |
"vanilla": newA(1), | |
"strawberry": newA(3), | |
"pistacchio": newA(5), | |
} | |
bob := Table[string, *A]{ | |
"chocolate": newA(2), | |
"vanilla": newA(3), | |
"banana": newA(1), | |
"hazlenut": newA(4), | |
} | |
fmt.Printf("alice: %v\n", alice) | |
fmt.Printf("bob: %v\n", bob) | |
fmt.Printf("merge error when merging alice and bob: %v\n", Merge(alice, bob)) | |
fmt.Printf("alice after merging with bob: %v\n", alice) | |
// while a Table requires that every value be of a single type, a Namespace | |
// allows you to store a value of any type so long as the type of that | |
// value defines how it will merge with other values of its own type | |
n := NewNamespace() | |
// the keys "chocolate" and "vanilla" associate to different types | |
Add(n, "chocolate", newB(1)) | |
Add(n, "vanilla", newA(1)) | |
Add(n, "strawberry", newA(3)) | |
Add(n, "pistacchio", newA(5)) | |
fmt.Printf("n: %v\n", n) | |
// These two Namespaces have a few pairs where the key name and the type | |
// are the same, so the values can be merged. | |
n2 := NewNamespace() | |
// since "chocolate" and "vanilla" have the same type in both n and n2, the | |
// namespaces n and n2 can still do a member-wise merge | |
Add(n2, "chocolate", newB(2)) | |
Add(n2, "vanilla", newA(3)) | |
Add(n2, "banana", newA(1)) | |
Add(n2, "hazlenut", newA(4)) | |
fmt.Printf("n2: %v\n", n2) | |
fmt.Println(Merge(n, n2)) | |
fmt.Printf("n merged with n2: %v\n", n) | |
n3 := NewNamespace() | |
Add(n3, "chocolate", newB(2)) | |
// vanilla has a different type in namespace n3 than it does in the | |
// namespaces n and n2, which will cause n3 fail to merge with the other | |
// two namespaces at runtime. | |
Add(n3, "vanilla", newB(3)) | |
Add(n3, "banana", newA(1)) | |
Add(n3, "hazlenut", newA(4)) | |
fmt.Printf("n3: %v\n", n2) | |
// here we get a runtime error, you can't merge these namespaces because | |
// there's a key that exists in both but has a mis-matched value type | |
fmt.Println(Merge(n, n3)) | |
// These are compile time errors: | |
// Add(n3, "lobster", newBad(5)) | |
// Add(n3, "mac-and-cheese", "zombo.com") | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment