You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

211 lines
5.6 KiB
Go

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
// }