Appearance
Type System
The expression language implements a robust type system that handles various data types and provides automatic type conversion where appropriate. This document provides a comprehensive overview of the type system implementation.
1. Supported Types
1.1 Primitive Types
String
- Represented as double or single quoted text:
"example"
or'example'
- Used for text comparisons and string operations
- Common operations:
==
,!=
,starts
,ends
,includes
,match
Number
- Supports both integers and floating-point numbers
- All numbers are internally converted to
float64
for consistent comparison - Examples:
42
,3.14
,-1
,1.0
- Automatic type conversion between integer and float types
- Used in numeric comparisons:
>
,<
,>=
,<=
Boolean
- Represents true/false values
- Result type of all comparison operations
- Used in logical operations (
and
,or
,not
) - Result type of boolean functions
1.2 Complex Types
Arrays
- Accessed using square bracket notation:
array[0]
- Zero-based indexing
- Supports nested access:
users[0].name
- Returns
nil
for out-of-bounds access without error
Objects (Maps)
- Accessed using dot notation:
user.name
- Supports nested access:
user.address.city
- Returns
nil
for missing fields without error - Keys are always strings
2. Type Conversion
2.1 Numeric Conversion
- Automatic conversion between integer and float types
- All numeric values are converted to
float64
for comparison - Conversion happens in these cases:
- Field access (integers are converted to float64)
- Numeric comparisons
- Function arguments expecting numbers
2.2 Type Coercion Rules
- No implicit conversion between strings and numbers
- Boolean conversion follows "truthy" rules:
- Empty string = false
- Zero = false
- nil = false
- Empty arrays/objects = false
- All other values = true switch v := val.(type) { case bool: return v case string: return len(v) > 0 case float64: return v != 0 case int: return v != 0 case []interface{}: return len(v) > 0 case map[string]interface{}: return len(v) > 0 default: return false
go
// Truthy evaluation rules
func isTruthy(val interface{}) bool {
if val == nil {
return false
}
switch v := val.(type) {
case bool:
return v
case string:
return v != ""
case float64:
return v != 0
case int:
return v != 0
case []interface{}:
return len(v) > 0
case map[string]interface{}:
return len(v) > 0
default:
return false
}
}
3. Type Comparison
3.1 Equality Comparison
- Strict equality comparison using
==
and!=
- Type-aware comparison that considers:
- Primitive type equality
- Deep equality for complex types
- nil value handling
- Numeric type conversion
go
// Example equality comparison
func isEqual(a, b interface{}) bool {
// Handle nil cases
if a == nil || b == nil {
return a == b
}
// Type-specific comparisons
switch va := a.(type) {
case float64:
if vb, ok := b.(float64); ok {
return va == vb
}
if vb, ok := b.(int); ok {
return va == float64(vb)
}
case string:
if vb, ok := b.(string); ok {
return va == vb
}
case bool:
if vb, ok := b.(bool); ok {
return va == vb
}
}
return reflect.DeepEqual(a, b)
}
3.2 Numeric Comparison
- Supports
>
,<
,>=
,<=
operators - Automatic type conversion to float64
- Error handling for non-numeric comparisons
go
// Example numeric comparison
func compareNumeric(a, b interface{}, op string) (bool, error) {
var aVal, bVal float64
// Convert first value to float64
switch va := a.(type) {
case float64:
aVal = va
case int:
aVal = float64(va)
default:
return false, fmt.Errorf("cannot compare non-numeric type: %T", a)
}
// Convert second value to float64
switch vb := b.(type) {
case float64:
bVal = vb
case int:
bVal = float64(vb)
default:
return false, fmt.Errorf("cannot compare non-numeric type: %T", b)
}
// Perform comparison based on operator
switch op {
case string(GT):
return aVal > bVal, nil
case string(LT):
return aVal < bVal, nil
case string(GTE):
return aVal >= bVal, nil
case string(LTE):
return aVal <= bVal, nil
default:
return false, fmt.Errorf("unknown comparison operator: %s", op)
}
}
4. Error Handling
4.1 Type Errors
- Invalid type comparisons (e.g., comparing string with number)
- Non-numeric values in numeric comparisons
- Invalid array index types
- Missing field access on nil values
4.2 Graceful Handling
- Missing field access returns nil without error
- Out-of-bounds array access returns nil without error
- Invalid type access returns nil without error
5. Best Practices
Always use appropriate comparison operators for the data type:
- Use
==
,!=
for all types - Use
>
,<
,>=
,<=
only for numbers - Use string operations (
starts
,ends
,includes
,match
) for strings
- Use
Be aware of automatic numeric conversion:
// These are equivalent due to automatic conversion value == 42 value == 42.0
Use explicit type checking in functions when needed:
// Example function implementation if val, ok := value.(float64); ok { // Handle float64 case } else if val, ok := value.(int); ok { // Handle int case }
Handle nil values appropriately:
// Check for nil before operations if value != nil { // Perform operations }
Use proper error handling for type-related operations:
if result, err := compareNumeric(val1, val2, ">"); err != nil { // Handle type error } else { // Use result }