github.com/grailbio/bigslice@v0.0.0-20230519005545-30c4c12152ad/frame/ops.go (about)

     1  // Copyright 2018 GRAIL, Inc. All rights reserved.
     2  // Use of this source code is governed by the Apache 2.0
     3  // license that can be found in the LICENSE file.
     4  
     5  package frame
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"reflect"
    11  	"runtime"
    12  	"sync"
    13  
    14  	"github.com/spaolacci/murmur3"
    15  )
    16  
    17  //go:generate go run genops.go
    18  
    19  var (
    20  	mu        sync.Mutex
    21  	makeOps   = map[reflect.Type]reflect.Value{}
    22  	locations = map[reflect.Type]string{}
    23  	typeOfOps = reflect.TypeOf((*Ops)(nil)).Elem()
    24  )
    25  
    26  // Ops represents a set of operations on a single frame instance. Ops
    27  // are instantiated from implementations registered with RegisterOps
    28  // and are managed by the frame instance.
    29  //
    30  // Note that not all types may support all operations.
    31  type Ops struct {
    32  	// Less compares two indices of a slice.
    33  	Less func(i, j int) bool
    34  	// HashWithSeed computes a 32-bit hash, given a seed, of an index
    35  	// of a slice.
    36  	HashWithSeed func(i int, seed uint32) uint32
    37  
    38  	// Encode encodes a slice of the underlying vector. Encode is
    39  	// optional, and overrides the default encoding (gob) used when
    40  	// serializing and deserializing frames. Encode and decode must
    41  	// always be specified together.
    42  	Encode func(enc Encoder, i, j int) error
    43  
    44  	// Decode decodes a slice encoded by Encode(i, j).
    45  	Decode func(dec Decoder, i, j int) error
    46  
    47  	// Swap swaps two elements in a slice. It is implemented generically
    48  	// and cannot be overridden by a user implementation.
    49  	swap func(i, j int)
    50  }
    51  
    52  // RegisterOps registers an ops implementation. The provided argument
    53  // make should be a function of the form
    54  //
    55  //	func(slice []t) Ops
    56  //
    57  // returning operations for a t-typed slice. RegisterOps panics if
    58  // the argument does not have the required shape or if operations
    59  // have already been registered for type t.
    60  func RegisterOps(make interface{}) {
    61  	typ := reflect.TypeOf(make)
    62  	check := func(ok bool) {
    63  		if !ok {
    64  			panic("frame.RegisterOps: bad type " + typ.String() + "; expected func([]t) frame.Ops")
    65  		}
    66  	}
    67  	check(typ.Kind() == reflect.Func)
    68  	check(typ.NumIn() == 1 && typ.In(0).Kind() == reflect.Slice)
    69  	check(typ.NumOut() == 1 && typ.Out(0) == typeOfOps)
    70  	elem := typ.In(0).Elem()
    71  	mu.Lock()
    72  	defer mu.Unlock()
    73  	if _, ok := makeOps[elem]; ok {
    74  		location, ok := locations[elem]
    75  		if !ok {
    76  			location = "<unknown>"
    77  		}
    78  		panic("frame.RegisterOps: ops already registered for type " + elem.String() + " at " + location)
    79  	}
    80  	makeOps[elem] = reflect.ValueOf(make)
    81  	if _, file, line, ok := runtime.Caller(1); ok {
    82  		locations[elem] = fmt.Sprintf("%s:%d", file, line)
    83  	}
    84  }
    85  
    86  func makeSliceOps(typ reflect.Type, slice reflect.Value) Ops {
    87  	make, ok := makeOps[typ]
    88  	if !ok {
    89  		return Ops{}
    90  	}
    91  	ops := make.Call([]reflect.Value{slice})[0].Interface().(Ops)
    92  	if (ops.Encode != nil) != (ops.Decode != nil) {
    93  		panic("encode and decode not defined together")
    94  	}
    95  	return ops
    96  }
    97  
    98  // CanCompare returns whether values of the provided type are comparable.
    99  func CanCompare(typ reflect.Type) bool {
   100  	return makeSliceOps(typ, reflect.MakeSlice(reflect.SliceOf(typ), 0, 0)).Less != nil
   101  }
   102  
   103  // CanHash returns whether values of the provided type can be hashed.
   104  func CanHash(typ reflect.Type) bool {
   105  	return makeSliceOps(typ, reflect.MakeSlice(reflect.SliceOf(typ), 0, 0)).HashWithSeed != nil
   106  }
   107  
   108  func init() {
   109  	RegisterOps(func(slice [][]byte) Ops {
   110  		return Ops{
   111  			Less:         func(i, j int) bool { return bytes.Compare(slice[i], slice[j]) < 0 },
   112  			HashWithSeed: func(i int, seed uint32) uint32 { return murmur3.Sum32WithSeed(slice[i], seed) },
   113  		}
   114  	})
   115  	RegisterOps(func(slice []bool) Ops {
   116  		return Ops{
   117  			Less: func(i, j int) bool { return !slice[i] && slice[j] },
   118  			HashWithSeed: func(i int, seed uint32) uint32 {
   119  				if slice[i] {
   120  					return seed + 1
   121  				}
   122  				return seed
   123  			},
   124  		}
   125  	})
   126  	RegisterOps(func(slice []struct{}) Ops {
   127  		return Ops{
   128  			Less: func(i, j int) bool { return false },
   129  			HashWithSeed: func(i int, seed uint32) uint32 {
   130  				return seed
   131  			},
   132  		}
   133  	})
   134  }