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 }