Skip to main content
Version: v2.2.82 (latest)
Reference · Collections

Record Equality

Comparing maps for equality. Use the Eq type class to define custom equality semantics for map values.


Core API

FunctionSignatureDescription
Eqfunc Eq[K comparable, V any](Eq[V]) Eq[map[K]V]Create map equality

Usage Examples

Basic Equality

basic.go
import (
  R "github.com/IBM/fp-go/v2/record"
  E "github.com/IBM/fp-go/v2/eq"
  N "github.com/IBM/fp-go/v2/number"
)

// Create record equality from value equality
recordEq := R.Eq(N.Eq)

m1 := map[string]int{"a": 1, "b": 2}
m2 := map[string]int{"a": 1, "b": 2}
m3 := map[string]int{"a": 1, "b": 3}
m4 := map[string]int{"a": 1}  // Different size

recordEq.Equals(m1, m2)  // true - same keys and values
recordEq.Equals(m1, m3)  // false - different values
recordEq.Equals(m1, m4)  // false - different keys

String Equality

string.go
import S "github.com/IBM/fp-go/v2/string"

stringRecordEq := R.Eq(S.Eq)

m1 := map[string]string{"name": "Alice", "role": "admin"}
m2 := map[string]string{"name": "Alice", "role": "admin"}
m3 := map[string]string{"name": "alice", "role": "admin"}

stringRecordEq.Equals(m1, m2)  // true
stringRecordEq.Equals(m1, m3)  // false - case sensitive

Custom Struct Equality

struct.go
type User struct {
  ID   int
  Name string
  Age  int
}

// Compare by ID only
userEq := E.FromEquals(func(a, b User) bool {
  return a.ID == b.ID
})

recordUserEq := R.Eq(userEq)

m1 := map[string]User{
  "alice": {ID: 1, Name: "Alice", Age: 30},
}
m2 := map[string]User{
  "alice": {ID: 1, Name: "Alice Updated", Age: 31},
}

recordUserEq.Equals(m1, m2)  // true - same ID

Case-Insensitive Equality

case_insensitive.go
// Case-insensitive string equality
caseInsensitiveEq := E.FromEquals(func(a, b string) bool {
  return strings.ToLower(a) == strings.ToLower(b)
})

recordEq := R.Eq(caseInsensitiveEq)

m1 := map[string]string{"name": "Alice"}
m2 := map[string]string{"name": "ALICE"}

recordEq.Equals(m1, m2)  // true

Nested Map Equality

nested.go
// Equality for nested maps
innerEq := R.Eq(N.Eq)
outerEq := R.Eq(innerEq)

m1 := map[string]map[string]int{
  "group1": {"a": 1, "b": 2},
  "group2": {"c": 3},
}
m2 := map[string]map[string]int{
  "group1": {"a": 1, "b": 2},
  "group2": {"c": 3},
}

outerEq.Equals(m1, m2)  // true

Array Value Equality

array.go
import A "github.com/IBM/fp-go/v2/array"

// Equality for maps with array values
arrayEq := A.Eq(N.Eq)
recordArrayEq := R.Eq(arrayEq)

m1 := map[string][]int{
  "nums1": {1, 2, 3},
  "nums2": {4, 5},
}
m2 := map[string][]int{
  "nums1": {1, 2, 3},
  "nums2": {4, 5},
}

recordArrayEq.Equals(m1, m2)  // true

Approximate Float Equality

float.go
// Approximate equality for floats
const epsilon = 0.0001

floatEq := E.FromEquals(func(a, b float64) bool {
  return math.Abs(a-b) < epsilon
})

recordFloatEq := R.Eq(floatEq)

m1 := map[string]float64{"pi": 3.14159}
m2 := map[string]float64{"pi": 3.14160}

recordFloatEq.Equals(m1, m2)  // true - within epsilon

Common Patterns

Configuration Comparison

config.go
type Config struct {
  Host    string
  Port    int
  Timeout int
}

configEq := E.FromEquals(func(a, b Config) bool {
  return a.Host == b.Host && a.Port == b.Port
  // Ignore Timeout in comparison
})

recordConfigEq := R.Eq(configEq)

configs1 := map[string]Config{
  "prod": {Host: "api.example.com", Port: 443, Timeout: 30},
}
configs2 := map[string]Config{
  "prod": {Host: "api.example.com", Port: 443, Timeout: 60},
}

recordConfigEq.Equals(configs1, configs2)  // true - timeout ignored

Testing Helper

testing.go
func AssertMapsEqual[K comparable, V any](
  t *testing.T,
  eq E.Eq[V],
  expected, actual map[K]V,
) {
  recordEq := R.Eq(eq)
  if !recordEq.Equals(expected, actual) {
      t.Errorf("Maps not equal:
Expected: %v
Actual: %v",
          expected, actual)
  }
}

// Usage in tests
func TestSomething(t *testing.T) {
  expected := map[string]int{"a": 1, "b": 2}
  actual := processData()
  
  AssertMapsEqual(t, N.Eq, expected, actual)
}

Semantic Equality

semantic.go
type Status string

const (
  Active   Status = "active"
  Inactive Status = "inactive"
  Enabled  Status = "enabled"
  Disabled Status = "disabled"
)

// Treat active/enabled and inactive/disabled as equal
statusEq := E.FromEquals(func(a, b Status) bool {
  normalize := func(s Status) string {
      if s == Active || s == Enabled {
          return "active"
      }
      return "inactive"
  }
  return normalize(a) == normalize(b)
})

recordStatusEq := R.Eq(statusEq)

m1 := map[string]Status{"service1": Active}
m2 := map[string]Status{"service1": Enabled}

recordStatusEq.Equals(m1, m2)  // true - semantically equal

Key Equality: Map keys must be comparable types in Go. The Eq instance only applies to values, not keys.

Custom Equality: Define custom Eq instances to implement domain-specific equality semantics, such as case-insensitive comparison or approximate numeric equality.