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  }