102

Go Reflection: Creating Objects from Types — Part II (Composite Types)

 6 years ago
source link: https://medium.com/kokster/go-reflection-creating-objects-from-types-part-ii-composite-types-69a0e8134f20
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Go Reflection: Creating Objects from Types — Part II (Composite Types)

This is part 2 of a 2 part blog series on Creating Objects from Types in Golang. This part discusses creation of composite types. The first post is available here.

1*65iXGLup5igJDZXA1oOFXw.png

Golang Gopher

In the previous blog post, I explained the concepts of type and kind in the go reflect package. In this post, I’ll be going deeper into the nomenclature used in Golang. This is because the terms type and kind carry more meaning for Composite types than Primitive types.

Types and Kinds

“type” is a term programmers use to describe metadata about data or functions in a program. The Go runtime and compiler has a different meaning for the word type.

This can be understood through an example. Consider this declaration in a Go program

var Version string

When referring to this declaration, programmers would say Version is a variable of type string.

Consider the following example, where Version is a composite type.

type VersionType stringvar Version VersionType

In this example, a new type called VersionType gets created in the first line. In the second line, Version is declared as a variable of type VersionType. The type of its type (VersionType) is string. The name for this is kind.

To summarize, the type of Version is VersionType. The kind of Version is string.

Type is the user defined metadata about data or function in a Go program. Kind is the compiler and runtime defined metadata about data or function in a Go program.

Kind is used by the runtime and compiler to allocate the memory layout for a variable or to allocate the stack layout for a function.

Creating Composite Objects from Primitive Kinds

Creation of Composite Objects whose kind is any of the following values is not different from creating Primitive Objects from Primitive Kinds.

        Bool
Int
Int8
Int16
Int32
Int64
Uint
Uint8
Uint16
Uint32
Uint64
Uintptr
Float32
Float64
Complex64
Complex128
String
UnsafePointer

Here’s an example of creating Version from its type (VersionType). Since Version has a primitive kind, it can be created by using the Zero value.

1  func CreatePrimitiveObjects(t reflect.Type) reflect.Value {
2 return reflect.Zero(t)
3 }
4
5 func extractVersionType(v reflect.Value) (VersionType, error) {
6 if v.Type().String() != "VersionType" {
7 return "", errors.New("invalid input")
8 }
9 return v.String(), nil
10 }

Note that the String() method of a Type variable returns the complete path and the name of the Type. i.e. If VersionType is defined in a different package called mypkg, then the String() method would return mypkg.VersionType.

Creating Composite Objects from Composite Kinds

Composite Kinds are ones that include other Kinds. Map , Struct , Array etc. are examples of Composite Kinds. Here is a the complete list of Composite Kinds.

        Array
Chan
Func
Interface
Map
Ptr
Slice
Struct

Composite objects can be created using the Zero value just like Primitive objects. However, they cannot be initialized with an empty value of a particular type without additional processing. The subsequent sections provide more detail about initializing Composite Kinds.

Creating Composite Array Objects from their type signature

Empty Array Objects can be created by using the Zero value. The Zero value of an Array is an empty Array object. Here’s an example of creating an Array object from its type signature

func CreateCompositeObjects(t reflect.Type) reflect.Value {
return reflect.Zero(t)
}

This function will create a reflect.Value struct holding an empty object of any Composite type.

The reflect package has a ArrayOf(Type, int) function, that can be used to create Arrays containing elements of type Type and specified length. Here’s an example showing its usage.

func CreateArray(t reflect.Type, length int) reflect.Value {
var arrayType reflect.Type
arrayType = reflect.ArrayOf(length, t)
return reflect.Zero(arrayType)
}

The type of the elements of the Array is decided by the incoming argument t. The length of the Array is decided by the incoming argument length. In order to extract the values into an array object, the best option using the reflect package is to process that array as an interface.

func extractArray(v reflect.Value) (interface{}, error) {
if v.Kind() != reflect.Array {
return nil, errors.New("invalid input")
}
var array interface{}
array = v.Interface()
return array, nil
}

Note that the value’s Slice() method can also be used to extract the value of the array. However, this methodology requires the conversion of the array type to an addressable array before its reflect.Value is computed. It’s better to avoid this subtlety to keep your code base simple and readable by using the Interface() method.

Creating Composite Channel Objects from their type signature

Empty channel Objects can be created using the Zero value. The Zero value of a channel is an empty channel object. Here’s an example of creating a channel object from its type signature

func CreateCompositeObjects(t reflect.Type) reflect.Value {
return reflect.Zero(t)
}

This function will create a reflect.Value struct holding an empty object of any Composite type.

The reflect package has two functions to create a chan. The ChanOf function can be used to create the type signature of the channel and then MakeChan(Type, int) function can be used to allocate memory for the channel. Here’s an example showing its usage.

func CreateChan(t reflect.Type, buffer int) reflect.Value {
chanType := reflect.ChanOf(reflect.BothDir, t)
return reflect.MakeChan(chanType, buffer)
}

The type of the elements of the channel is decided by the incoming argument t. The length of the channel is decided by the incoming argument buffer. In order to extract the values into a channel object, the best option using the reflect package is to process that channel as an interface. The direction of the channel being created can be controlled by passing the direction as the first parameter to the call to ChanOf. The possible values for ChanDir are below.

        SendDir
RecvDir
BothDir

BothDir denotes that the channel can be written to and read from. SendDir denotes that the channel can only be written to. RecvDir denotes that the channel can only receive data.

func extractChan(v reflect.Value) (interface{}, error) {
if v.Kind() != reflect.Chan {
return nil, errors.New("invalid input")
}
var ch interface{}
ch = v.Interface()
return ch, nil
}

Creating Composite Function Objects from their type signature

Function Objects cannot be created by using the Zero value.

The reflect package has two functions to create a function. The FuncOf function can be used to create the type signature of the function and then MakeFunc(Type, func(args []Value) (results []Value)) Value function can be used to allocate memory for the function. Here’s an example showing its usage.

func CreateFunc(
fType reflect.Type,
f func(args []reflect.Value)
(results []reflect.Value)
) (reflect.Value, error)
{
if fType.Kind() != reflect.Func {
return reflect.Value{}, errors.New("invalid input")
}

var ins, outs *[]reflect.Type

ins = new([]reflect.Type)
outs = new([]reflect.Type)

for i:=0; i<fType.NumIn(); i++ {
*ins = append(*ins, fType.In(i))
}

for i:=0; i<fType.NumOut(); i++ {
*outs = append(*outs, fType.Out(i))
}
var variadic bool
variadic = fType.IsVariadic() return AllocateStackFrame(*ins, *outs, variadic, f), nil
}func AllocateStackFrame(
ins []reflect.Type,
outs []reflect.Type,
variadic bool,
f func(args []reflect.Value)
(results []reflect.Value)
) reflect.Value
{
var funcType reflect.Type
funcType = reflect.FuncOf(ins, outs, variadic)
return reflect.MakeFunc(funcType, f)
}

CreateFunc takes 2 arguments. The first is the type of the function that you’d like to create, and the second is an actual function that implements the first argument’s type but the inputs and return values are converted to reflect.Value objects. I’ve created this function to be a convenience method for users so that they do not have to re-invent how functions are created on the fly.

In order to use this to create functions, the type signature of the function needs to be defined. Here’s an example of a type signature

type fn func(int) (int)

The Type struct for the function signature above can be constructed using the method below.

1 var funcVar fn
2 var funcType reflect.Type
3 funcType = reflect.TypeOf(funcVar)

funcType can be used as the first argument passed into CreateFunc. A function that satisfies the type signature of funcType is required to be passed as the second argument. The example below illustrates how:

func doubler(input int) (int) {
return input * 2
}

This function doubler multiplies the input by 2. It takes in 1 argument of type int and returns 1 argument of type int. This function satisfies the fn type, but does not satisfy the generic func type:

f func(args []reflect.Value) (results []reflect.Value)

To satisfy the generic func type, an equivalent version of the doubler should be created that satisfies the generic func type. This is shown below.

func doublerReflect(args []reflect.Value) (result []reflect.Value) {
if len(args) != 1 {
panic(fmt.Sprintf("expected 1 arg, found %d", len(args)))
}
if args[0].Kind() != reflect.Int {
panic(fmt.Sprintf("expected 1 arg of kind int, found 1 args of kind", args[0].Kind()))
}

var intVal int64
intVal = args[0].Int()

var doubleIntVal int
doubleIntVal = doubler(int(intVal))

var returnValue reflect.Value
returnValue = reflect.ValueOf(doubleIntVal)

return []reflect.Value{returnValue}
}

doublerReflect is functionally equivalent to doubler and it satisfies the generic func type signature. i.e. It takes 1 argument, which is a slice of reflect.Value objects, and returns 1 value, which is a slice of reflect.Value objects. The input represents the input to the function, and return value represents the return value of the new function being made.

The function call to CreateFunc can be made with funcType and doublerReflect as the arguments. The below example shows a function call being made from the newly created function.

func main() {
var funcVar fn
var funcType reflect.Type funcType = reflect.TypeOf(funcVar)
v, err := CreateFunc(funcType, doublerReflect)
if err != nil {
fmt.Printf("Error creating function %v\n", err)
}

input := 42

ins := []reflect.Value([]reflect.Value{reflect.ValueOf(input)})

outs := v.Call(ins)
for i := range outs {
fmt.Printf("%+v\n", outs[i].Interface())
}
}
// Output: 84

Creating Composite Map Objects from their type signature

Empty Map Objects can be created by using the Zero value. The Zero value of a Map is an empty Map object. Here’s an example of creating a Map object from its type signature

func CreateCompositeObjects(t reflect.Type) reflect.Value {
return reflect.Zero(t)
}

This function will create a reflect.Value struct holding an empty object of any Composite type.

The reflect package has a MapOf(Type, Type) function, that can be used to create Maps containing keys of the first argument’s type and elements of the second argument’s type. Here’s an example showing its usage.

func CreateMap(key , elem reflect.Type) reflect.Value {
var mapType reflect.Type
mapType = reflect.MapOf(key, elem)
return reflect.MakeMap(mapType)
}

In order to extract the values into an map object, the best possible option using the reflect package is to process that map as an interface.

func extractMap(v reflect.Value) (interface{}, error) {
if v.Kind() != reflect.Map {
return nil, errors.New("invalid input")
}
var mapVal interface{}
mapVal = v.Interface()
return mapVal, nil
}

Note that maps can also be allocated by using MakeMapWithSize. The steps to create maps using this method are exactly the same as above, but the MakeMap call can be substituted with MakeMapWithSize and a size parameter.

Creating Composite Ptr Objects from their type signature

Empty Ptr Objects can be created by using the Zero value. The Zero value of a Ptr is an nil pointer object. Here’s an example of creating a Ptr object from its type signature

func CreateCompositeObjects(t reflect.Type) reflect.Value {
return reflect.Zero(t)
}

This function will create a reflect.Value struct holding an empty object of any Composite type.

The reflect package has a PtrTo(Type) function, that can be used to create Ptrs pointing to elements of type Type. Here’s an example showing its usage.

func CreatePtr(t reflect.Type) reflect.Value {
var ptrType reflect.Type
ptrType = reflect.PtrTo(t)
return reflect.Zero(ptrType)
}

Note that the above functionality can also be achieved by using reflect.New(t). The technique illustrated above is functionally equivalent to using reflect.New(t).

The type of the element pointed to by the Ptr is decided by the incoming argument t. In order to extract the values into an object, the best option using the reflect package is to first use the Indirect() call or the Elem() method to determine the value, and then process the value as an interface.

func extractElement(v reflect.Value) (interface{}, error) {
if v.Kind() != reflect.Ptr {
return nil, errors.New("invalid input")
}
var elem reflect.Value
elem = v.Elem()

var elem interface{}
elem = v.Interface()
return elem, nil
}

Creating Composite Slice Objects from their type signature

Empty Slice Objects can be created by using the Zero value. The Zero value of a Slice is an empty Slice object. Here’s an example of creating an Slice object from its type signature

func CreateCompositeObjects(t reflect.Type) reflect.Value {
return reflect.Zero(t)
}

This function will create a reflect.Value struct holding an empty object of any Composite type.

The reflect package has a SliceOf(Type) function that can be used to create Slice containing elements of type Type. Here’s an example showing its usage.

func CreateSlice(t reflect.Type) reflect.Value {
var sliceType reflect.Type
sliceType = reflect.SliceOf(length, t)
return reflect.Zero(sliceType)
}

The type of the elements of the Slice is decided by the incoming argument t. In order to extract the values into a slice object, the best option using the reflect package is to process that slice as an interface.

func extractSlice(v reflect.Value) (interface{}, error) {
if v.Kind() != reflect.Slice {
return nil, errors.New("invalid input")
}
var slice interface{}
slice = v.Interface()
return slice, nil
}

Creating Composite Struct Objects from their type signature

Empty Struct Objects can be created by using the Zero value. The Zero value of Struct is an empty Struct object. Here’s an example of creating an Struct object from its type signature

func CreateCompositeObjects(t reflect.Type) reflect.Value {
return reflect.Zero(t)
}

This function will create a reflect.Value struct holding an empty object of any Composite type.

The reflect package has a StructOf([]reflect.StructFields) function that can be used to create Struct containing fields of types defined in theStructField object. Here’s an example showing its usage.

func CreateStruct(fields []reflect.StructField) reflect.Value {
var structType reflect.Type
structType = reflect.StructOf(fields)
return reflect.Zero(structType)
}

In order to extract the values into an array object, the best option using the reflect package is to process that struct as an interface.

func extractStruct(v reflect.Value) (interface{}, error) {
if v.Kind() != reflect.Struct {
return nil, errors.New("invalid input")
}
var st interface{}
st = v.Interface()
return st, nil
}

Conclusion

This is a complete tutorial on creating any Go type on the fly during runtime using the reflect package. I’ve provided convenience methods for creating Func type since it is more complex than the other types and can easily pollute your code base if not carefully designed.

Stay tuned for my next blog posts on converting any type to any other type in Golang! Following that, I’ll be explaining how to write a JIT (Just-in-Time) in Golang, and then about code generation using reflect in Go.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK