Using Golang Reflect to Sum Any Variable Type

Jan 25 2021 · 4 min read

I needed a function to dynamically sum things in Golang, but couldn't find any implementations. Let's create one ourselves!

Golang is a strongly-typed language. Compared to Python that can accept variable types dynamically, we have to specify the type for a variable. Fortunately for us, there is a type called interface{}, an abstract type in Golang.

Pseudocode

Before jumping into the code, let’s specify what we want our code to do. We want the code to:

  • Pass the number if the input is a simple integer;
  • Sum the contents of a map with integer elements;
  • Sum the contents of arrays and slices with integer elements; and
  • Recursively sum a map inside a map, slice inside a slice, etc, if any integer elements are found within it.

From the requirements above, we can make the pseudocode:

function Sum(input) returns (int64, error):
     result := int64(0)
     check the input type:
          if type is Integer:
               set result = input value
          if type is Map:
               for every element in input:
                    set result = result + Sum(element)
          if type is Slice or Array:
               for every element in input:
                    set result = result + Sum(element)
          else:
               return 0, error invalid
     return result, nil error

Note: For this tutorial, I’m not handling Float, String, etc types, expand and explore it yourself!

Now that we have a clear idea how the function should work, we can translate it into code!

The Code

Here’s the function that we will go through today, I will first write it out and explain it after:

func Sum(in interface{}) (int64, error) {
     res := int64(0)
     v := reflect.ValueOf(in)
     switch v.Kind() {
     case reflect.Int, reflect.Int8, reflect.Int32, reflect.Int64:
         res = v.Int()
     case reflect.Slice, reflect.Array:
         for i := 0; i < v.Len(); i++ {
             sliceRes, err := Sum(v.Index(i).Interface())
             if err != nil {
                 return 0, err
             }
             res = res + sliceRes
         }
     case reflect.Map:
         for _, k := range v.MapKeys() {
             mapRes, err := Sum(v.MapIndex(k).Interface())
             if err != nil {
                 return 0, err
             }
             res = res + mapRes
         }
     default:
          return 0, fmt.Errorf("input passed was invalid.") 
     }
     return res, nil
}

Explanation

First things first, we declare a new function called Sum that receives an interface as input and gives int64 and error as outputs. After declaring the function, we also declare res := int64(0) to hold our result value.

func Sum(in interface{}) (int64, error) {
     res := int64(0)

This is where we will start going to new territory.

v := reflect.ValueOf(in)

We will be using the reflect package to help our function. From the documentation, Reflect is described as:

Package reflect implements run-time reflection, 
allowing a program to manipulate objects with arbitrary types.

In this code, we are using Reflect to receive the type of the input interface and give us the type being contained inside the interface.

Now that we have the reflect value contained in v, we get into the core of our function:

switch v.Kind() {
     case reflect.Int, reflect.Int8, reflect.Int32, reflect.Int64:
         res = v.Int()
     case reflect.Slice, reflect.Array:
          for i := 0; i < v.Len(); i++ {
               sliceRes, err := Sum(v.Index(i).Interface())
               if err != nil {
                    return 0, err
               }
               res = res + sliceRes
          }
     case reflect.Map:
          for _, k := range v.MapKeys() {
               mapRes, err := Sum(v.MapIndex(k).Interface())
               if err != nil {
                    return 0, err
               }
               res = res + mapRes
          }
     default:
          return 0, fmt.Errorf("input passed was invalid.") 
}

We created a switch statement, using v.Kind() as the comparator. v.Kind() can be compared using reflect.Slice, reflect.Map, and so on. Knowing this, we create 3 different cases: one for various integer types, one for slices and array, and one for maps. If the input value is not any of those types, we return an error.

Just in our pseudocode, when we get an integer as the input, we just return it as the result. In case of slices, arrays, and maps, we call Sum() to every elements inside the type. We split map, slices, and arrays to 2 separate case because how reflect handles the element iteration. Every recursive Sum() will return a value and an error. If an error is found, we just simply return the error. Else, we continue and add value into result.

After going through that function, we can finally return the value with no error:

     return res, nil
}

Followed by a closing curly bracket, indicating that our function is complete.

And That’s It!

Congratulations on finishing this tutorial! Let’s summarize what we’ve learned:

  • Golang is a strongly-typed language, meaning that every variable must have a type.
  • Golang types can also be assigned as an interface{}, receiving abstract types.
  • With the reflect package, we can use interface{} to dynamically process variables.

Note: Do not use this in production level. This post is just to showcase the capabilities of Golang’s reflect package.

If you want to see a working example, you can try it out here: https://play.golang.org/p/99UD-hfP44h

Tags

Share