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.