github.com/grailbio/bigslice@v0.0.0-20230519005545-30c4c12152ad/slicefunc/func.go (about) 1 // Copyright 2019 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 slicefunc provides types and code to call user-defined functions with 6 // Bigslice. 7 package slicefunc 8 9 import ( 10 "context" 11 "reflect" 12 13 "github.com/grailbio/bigslice/slicetype" 14 ) 15 16 // Nil is a nil Func. 17 var Nil Func 18 19 var typeOfContext = reflect.TypeOf((*context.Context)(nil)).Elem() 20 21 // Func represents a user-defined function within Bigslice. Currently it's a 22 // simple shim that's used to determine whether a context should be supplied to 23 // the callee. 24 // 25 // TODO(marius): Evolve this abstraction over time to avoid the use of 26 // reflection. For example, we can generate (via a template) code for 27 // invocations on common types. Having this abstraction in place makes this 28 // possible to do without changing any of the callers. 29 // 30 // TODO(marius): Another possibility is to exploit the fact that we have already 31 // typechecked the data pipeline within Bigslice, and thus can avoid the 32 // per-call typechecking overhead that is incurred by reflection. For example, 33 // we could allow Funcs to mint a new re-usable call frame that we can write 34 // values directly into. This might get us most of the former but with more 35 // generality and arguably less complexity. We could even pre-generate 36 // call frames for each row in a frame, since that is re-used also. 37 // 38 // TODO(marius): consider using this package to allow user-defined funcs to 39 // return an error in the last argument. 40 type Func struct { 41 // In and Out represent the slicetype of the function's input and output, 42 // respectively. 43 In, Out slicetype.Type 44 // IsVariadic is whether the function's final parameter is variadic. If it 45 // is, In.Out(In.NumOut()-1) returns the parameter's implicit actual slice 46 // type. For example, if this represents func(x int, y ...string): 47 // 48 // fn.In.NumOut() == 2 49 // fn.In.Out(0) is the reflect.Type for "int" 50 // fn.In.Out(1) is the reflect.Type for "[]string" 51 // fn.IsVariadic == true 52 IsVariadic bool 53 fn reflect.Value 54 contextFunc bool 55 } 56 57 type funcSliceType struct { 58 reflect.Type 59 } 60 61 func (funcSliceType) Prefix() int { return 1 } 62 63 // Of creates a Func from the provided function, along with a bool indicating 64 // whether fn is a valid function. If it is not, the returned Func is invalid. 65 func Of(fn interface{}) (Func, bool) { 66 t := reflect.TypeOf(fn) 67 if t == nil { 68 return Nil, false 69 } 70 if t.Kind() != reflect.Func { 71 return Nil, false 72 } 73 in := make([]reflect.Type, t.NumIn()) 74 for i := range in { 75 in[i] = t.In(i) 76 } 77 context := len(in) > 0 && in[0] == typeOfContext 78 if context { 79 in = in[1:] 80 } 81 return Func{ 82 In: slicetype.New(in...), 83 Out: funcSliceType{t}, 84 IsVariadic: t.IsVariadic(), 85 fn: reflect.ValueOf(fn), 86 contextFunc: context, 87 }, true 88 } 89 90 // Call invokes the function with the provided arguments, and returns the 91 // reflected return values. 92 // 93 // TODO(marius): using reflect.Value here is not ideal for performance, 94 // but there may be more holistic approaches (see above) since we're 95 // (almost) always invoking functions from frames. 96 func (f Func) Call(ctx context.Context, args []reflect.Value) []reflect.Value { 97 if f.contextFunc { 98 return f.fn.Call(append([]reflect.Value{reflect.ValueOf(ctx)}, args...)) 99 } 100 return f.fn.Call(args) 101 } 102 103 // IsNil returns whether the Func f is nil. 104 func (f Func) IsNil() bool { 105 return f.fn == reflect.Value{} 106 }