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.
68 lines
1.6 KiB
Go
68 lines
1.6 KiB
Go
package merge
|
|
|
|
import (
|
|
"fmt"
|
|
"errors"
|
|
)
|
|
|
|
// Boxed is a boxed value that is able to merge with other values. Boxing a
|
|
// value pushes its type-checking from compile time to run time. This can be
|
|
// useful in the event that you wish to construct mergeable values with
|
|
// heterogeneous members, but should likely be avoided otherwise.
|
|
type Boxed struct {
|
|
val interface{}
|
|
ident func() interface{}
|
|
merge func(interface{}) error
|
|
}
|
|
|
|
func (b Boxed) Merge(from Boxed) error {
|
|
if err := b.merge(from.val); err != nil {
|
|
return fmt.Errorf("boxed merge failed: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (b Boxed) MergeIdentity() Boxed {
|
|
return Boxed{
|
|
val: b.ident(),
|
|
ident: b.ident,
|
|
merge: b.merge,
|
|
}
|
|
}
|
|
|
|
var typeMismatch = errors.New("mismatched types")
|
|
|
|
// strip takes a typed function and erases the type information from its
|
|
// parameter
|
|
func strip[X any](f func(X) error) func(interface{}) error {
|
|
return func(v interface{}) error {
|
|
vv, ok := v.(X)
|
|
if !ok {
|
|
return fmt.Errorf("unexpected %T value, expected %T instead: %w", v, vv, typeMismatch)
|
|
}
|
|
return f(vv)
|
|
}
|
|
}
|
|
|
|
// Box takes a mergeable value and creates a new mergeable value of type Boxed.
|
|
// Any two Boxed values can attempt to merge at runtime.
|
|
func Box[X Merges[X]](x X) Boxed {
|
|
return Boxed{
|
|
val: x,
|
|
ident: func() interface{} {
|
|
return x.MergeIdentity()
|
|
},
|
|
merge: strip(x.Merge),
|
|
}
|
|
}
|
|
|
|
// Unbox removes a value from its box.
|
|
func Unbox[X Merges[X]](b Boxed) (X, error) {
|
|
if v, ok := b.val.(X); ok {
|
|
return v, nil
|
|
} else {
|
|
var zero X
|
|
return zero, fmt.Errorf("box contains %T, not %T: %w", b.val, zero, typeMismatch)
|
|
}
|
|
}
|