Multiplex Bitmaps

Bitmaps and Reflection: A Programmer’s Quest for Cleaner Template Functions

In creating my own Hugo clone, I encountered a common programming challenge: writing similar code repeatedly with slight variations.

The Problem: Template Functions Are Weird

Go template engine functions need exceptional flexibility. They must:

  • Accept varying parameter counts
  • Handle different parameter types
  • Support optional parameters
  • Maintain type safety

Consider Hugo’s substr function. It can be called as substr("hello", 1, 3) or simply substr("hello"). This typically requires multiple functions or manual type checking in Go.

Hugo’s Implementation: The Common Way

Hugo handles this with variadic parameters and type assertions:

func (ns *Namespace) Substr(a any, nums ...any) (string, error) {
    s, err := cast.ToStringE(a)
    if err != nil {
        return "", err
    }

    start := 0
    length := len(s)

    if len(nums) > 0 {
        start, err = cast.ToIntE(nums[0])
        if err != nil {
            return "", err
        }
    }

    // Additional code...
    
    return s[start : start+length], nil
}

This works but requires repetitive type checking throughout their codebase — like trying to solve a Rubik’s cube while someone keeps turning the lights on and off.

The Solution: Bitmaps to the Rescue

What if we could centralize this reflection logic? My approach encodes parameter types into bitmaps and matches them against method signatures.

Bitmap Generation: Turning Types Into Numbers

First, we convert argument types to numbers for efficient comparison:

func GenerateBitmapFromArgs(args []any) uint64 {
    bitmaps := make([]uint16, len(args))
    for i, value := range args {
        // Reflection logic to determine type
        val := reflect.ValueOf(value)
        for ; val.Kind() == reflect.Ptr || val.Kind() == reflect.Interface; val = val.Elem() {
            // Unwrap type
        }
        
        // Get bit flag for this type
        tvpe := reflect.TypeOf(val.Interface())
        kind, _ := normalizeKind(tvpe.Kind())
        if bitflag, ok := bitmapMap[uint(kind)]; ok {
            bitmaps[i] = bitflag
        }
    }

    // Pack into uint64
    var output uint64
    output |= uint64(len(args)) << 0
    for i, v := range bitmaps {
        output |= uint64(v) << ((paramBitsize * i) + nibble)
    }

    return output
}

Function Matching: Finding the Right Match

The system identifies the function that best matches provided arguments through systematic comparison of type patterns encoded in the bitmap. This process efficiently evaluates compatibility between argument types and available function signatures.

func FindBestBitMatch(bitmap uint64, bitmasks []uint64) (uint64, error) {
    var bestMaskRank int = math.MaxInt
    var bestBitMatch uint64 = math.MaxUint64
    
    for index, bitmask := range bitmasks {
        if bitmap == bitmask {
            return uint64(index), nil
        }

        // Check parameter counts and type compatibility
    }

    if bestBitMatch == math.MaxUint64 {
        return bestBitMatch, errors.New("no matching function found")
    }

    return bestBitMatch, nil
}

The Function Callback Pattern

The system uses callbacks to handle function returns:

type StringsMux struct {
    Substr                  func(string) (string, error)
    SubstrWithStart         func(string, int) (string, error)
    SubstrWithStartAndCount func(string, int, int) (string, error)
    
    Skip []int

When calling Parse, it matches your arguments with the appropriate function:

mux := &StringsMux{}
result := function.Parse([]any{"Hello, World!", 7}, mux)
output, err := result.Call() // "World!"

Hugo vs. Bitmap Approach: Comparison

|  Aspect                     |  Hugo's Approach            |  Bitmap Approach             |
|:---------------------------:|:---------------------------:|:----------------------------:|
|  Type checking              |  Manual, distributed        |  Automated, centralized      |
|  Optional parameters        |  Variadic arguments         |  Multiple method signatures  |
|  Implementation complexity  |  Simple but repetitive      |  Complex but reusable        |
|  Error handling             |  Inline                     |  Structured, consistent      |
|  Performance                |  Direct (faster)            |  Overhead (slightly slower)  |
|  Code reuse                 |  Minimal                    |  High                        |
|  Maintainability            |  Changes needed everywhere  |  Centralized changes         |

When to Choose Each Approach

Use Hugo’s approach when:

  • Simplicity is preferred
  • Performance is critical
  • Implementation speed matters

Use the bitmap approach when:

  • Reducing code duplication is important
  • Consistency is valued
  • Many template functions exist
  • Long-term maintenance is prioritized

Conclusion

Both approaches rely on Go’s reflection system. Hugo’s method is proven and straightforward, while the bitmap system trades initial complexity for long-term code cleanliness.

The full code is available under the MIT No Attribution license.


This post was written by a programmer who optimized template functions and lived to tell the tale.