package merge2 import ( "fmt" "errors" ) var typeMismatch = errors.New("type mismatch") // Merges[X] is any type that can merge itself into some *X type Merges[X any] interface { // MergeInto should merge the receiver into the parameter *X. Callers are // expected to mutate the parameter in some way, returning any error // encountered when attempting to do so. 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 any](dest *X, sources ...Merges[X]) error { func Into[X any, Y Merges[X]](dest *X, sources ...Y) error { for _, src := range sources { if err := src.MergeInto(dest); err != nil { return err } } return nil } // Maps merges two maps. All keys that appear in source are merged into dest. // Keys that appear in src but not in dest merge into the zero value of T and // then store in dest. 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 } // Emerge is an empty merge: given some merge source, emerge merges the merge // source into the zero value of the merge destination type. // // Emerge[Y any](x Merges[Y]) (Y, error) func Emerge[X Merges[Y], Y any](x X) (Y, error) { var y Y return y, x.MergeInto(&y) } // Boxed is a type-erased container for merge semantics. // // Boxed exists in order to facilitate the merging of heterogeneous collections // of values. type Boxed struct { // the value inside of the box. Since the only way to put a value in a box // is through the Box function or by merging from another box, we know that // this value defines some merge function val interface{} // mkdest creates a value of the type to which the val field merges mkdest func() interface{} // homologous describes whether or not the contained value merges with its // own type. Any boxed type that defines merge semantics against its own // type can merge into an empty box. homologous bool // mergeInto is a function that defines how to merge the val field into // some destination mergeInto func(dest interface{}) error } func (b Boxed) String() string { return fmt.Sprint(b.val) } // IsEmpty is true for boxes that contain nothing func (b Boxed) IsEmpty() bool { return b.val == nil } // IsTerminal describes whether or not the box defines any merge semantics. A // box containing a value that does not define merge semantics is a box that // terminates a merge chain. func (b Boxed) IsTerminal() bool { return b.mergeInto == nil } // erasef1e erases type information from a function of input arity 1 that // returns an error func erasef1e[X any](f func(X) error) func(interface{}) error { return func(v interface{}) error { tv, ok := v.(X) if !ok { return fmt.Errorf("unexpected %T value, expected %T instead: %w", v, tv, typeMismatch) } return f(tv) } } func mergeFn[X Merges[Y], Y any](x X) func(interface{}) error { return nil // return func(v interface{}) error { // x.MergeInto // } } // nunu[X] creates a function for the type X that creates a new zero value // having the type X, then erases the type information by sticking it in an // empty interface. This is a constructor-constructor that creates a // type-erased constructor. Pretty gross! func nunu[X any]() func() interface {} { return func() interface{} { var zero X return zero } } // sametype determines whether or not some manually instantiated type // parameters are or are not the same type. func sametype[X, Y any]() bool { _, ok := interface{}(*new(X)).(Y) return ok } // Box boxes a value. Only values that define -some- merge semantic can go into // the box. // // Box[Y any](x Merges[Y]) Boxed func Box[X Merges[Y], Y any](x X) Boxed { return Boxed{ val: x, mkdest: nunu[Y](), homologous: sametype[X, Y](), mergeInto: erasef1e(x.MergeInto), } } // EndBox creates a terminal box in a merge chain. This boxed value contains a // value that does not define any merge semantics. It may only be used as a // merge destination, not a merge source. func EndBox[X any](x X) Boxed { return Boxed{ val: x, } } // MergeInto merges the Boxed value into some destination value. If the boxed // value does not merge into the value supplied, the failure occurs at runtime. // For merge semantics that are type-checked at compile time... don't box your // values, I dunno what to tell you. func (b Boxed) MergeInto(dest interface{}) error { if b.IsEmpty() { return nil } if db, ok := dest.(Boxed); ok { if db.IsEmpty() { v := b.mkdest() if b.homologous { db.homologous = true db.mergeInto = erasef1e(v.MergeInto) db.mkdest = b.mkdest } } } return b.mergeInto(dest) } // func Unbox[X Merges[Y], Y any](b Boxed) (X, error) { // p, ok := b.val.(*X) // if !ok { // var zero X // return zero, fmt.Errorf("type error") // } // return *p, nil // }