Skip to content

Maps

Maps are key-value data structures that provide efficient lookups and storage. Harneet's maps follow Go-like syntax and behavior with strong type safety, reference semantics, and modern conveniences like dot-access and built-in methods.

Overview

Maps in Harneet are: - Reference types - passed by reference, not by value - Type-safe - support both typed and untyped maps - Flexible keys - support strings, numbers, booleans, floats, arrays, and structs as keys - Zero value - None (must be initialized before use) - Nested - support unlimited nesting depth - Method-rich - built-in .keys(), .values(), .has()/.contains(), .delete(), .deleteAll(), .putAll(), .entries() methods - Dot-accessible - convenient obj.key syntax alongside obj["key"]

Quick Start

Quick Start
package main
import fmt

function main() {
    // Create and access maps
    var user = {"name": "Alice", "age": 30}
    fmt.Println(user.name)  // Dot-access: "Alice"
    fmt.Println(user["age"]) // Bracket access: 30

    // Map methods
    var keys = user.keys()      // ["name", "age"]
    var hasEmail = user.has("email")  // false

    // Add and delete
    user["email"] = "alice@example.com"
    user.delete("age")

    // Advanced: struct keys
    type Point struct { x int; y int }
    var locations = {
        Point{x: 0, y: 0}: "origin",
        Point{x: 10, y: 20}: "home"
    }
}

Map Literals

Create maps using map literal syntax with curly braces:

Map Literals
1
2
3
4
5
6
7
8
9
// Basic map literal
package main
var user = {"name": "Alice", "age": 25, "active": true}

// Empty map
var empty = {}

// Mixed key types
var mixed = {1: "one", "two": 2, true: "boolean"}

Map Type Declarations

Declare typed maps with specific key and value types:

Map Type Declarations
// String keys, integer values
package main
var scores map[string]int

// Integer keys, string values  
var grades map[int]string

// String keys, boolean values
var features map[string]bool

// Boolean keys, string values
var status map[bool]string

Map Assignment

Assign map literals to typed map variables:

Map Assignment
1
2
3
4
5
6
package main
var scores map[string]int
scores = {"math": 95, "science": 87, "english": 92}

var features map[string]bool
features = {"dark_mode": true, "notifications": false}

Map Access

Access map values using square bracket notation:

Map Access
1
2
3
4
5
6
7
8
9
package main
var user = {"name": "Alice", "age": 25}

// Access existing keys
var name = user["name"]     // Returns "Alice"
var age = user["age"]       // Returns 25

// Access non-existent keys
var missing = user["email"] // Returns None

Zero Values

Maps have a zero value of None and must be initialized before use:

Zero Values
1
2
3
4
5
6
7
8
9
package main
import fmt
var m map[string]int
if m == None {
    fmt.Println("Map is None - must initialize")
}

// Initialize before use
m = {"key": 42}

Nested Maps

Maps support unlimited nesting depth:

Nested Maps
// Two-level nesting
package main
var userProfiles map[string]map[string]int
var alice = {"age": 25, "score": 95}
var bob = {"age": 30, "score": 87}
userProfiles = {"alice": alice, "bob": bob}

// Access nested values
var aliceAge = userProfiles["alice"]["age"]

// Three-level nesting
var gameData map[string]map[string]map[string]int
var level1 = {"enemies": 5, "coins": 100}
var world1 = {"level1": level1}
gameData = {"world1": world1}

var enemies = gameData["world1"]["level1"]["enemies"]

Reference Semantics

Maps are reference types - assignment creates new references to the same data:

Reference Semantics
1
2
3
4
5
6
7
8
package main
import fmt
var original = {"count": 1, "value": 42}
var reference = original

// Both variables point to the same map
fmt.Printf("Original: %s\n", original)   // {count: 1, value: 42}
fmt.Printf("Reference: %s\n", reference) // {count: 1, value: 42}

Type Safety

Harneet provides compile-time type checking for maps:

Type Safety
1
2
3
4
5
6
7
8
// Type-safe assignments
package main
var scores map[string]int
scores = {"math": 95, "science": 87}  // ✅ Valid

// Type compatibility
var untyped = {"key": "value"}
var typed map[string]string = untyped  // ✅ Valid

Key Types

Maps support various hashable key types:

String Keys

String Keys
1
2
3
package main
var stringMap = {"name": "Alice", "city": "NYC"}
var typed map[string]string = stringMap

Integer Keys

Integer Keys
1
2
3
package main
var intMap = {1: "one", 2: "two", 3: "three"}
var typed map[int]string = intMap

Boolean Keys

Boolean Keys
1
2
3
package main
var boolMap = {true: "enabled", false: "disabled"}
var typed map[bool]string = boolMap

Mixed Keys (Untyped Maps)

Mixed Keys
1
2
3
4
5
6
package main
var mixed = {
    1: "integer key",
    "text": "string key", 
    true: "boolean key"
}

Complex Examples

Configuration Management

Configuration Management
1
2
3
4
5
6
7
8
package main
var config map[string]map[string]string
var database = {"host": "localhost", "port": "5432", "name": "mydb"}
var server = {"host": "0.0.0.0", "port": "8080", "env": "dev"}
config = {"database": database, "server": server}

var dbHost = config["database"]["host"]
var serverPort = config["server"]["port"]

Game Data Structure

Game Data Structure
1
2
3
4
5
6
7
8
package main
var gameData map[string]map[string]map[string]int
var level1 = {"enemies": 5, "coins": 100, "powerups": 3}
var level2 = {"enemies": 8, "coins": 150, "powerups": 2}
var world1 = {"level1": level1, "level2": level2}
gameData = {"world1": world1}

var coins = gameData["world1"]["level1"]["coins"]

User Management System

User Management System
package main
var users map[string]map[string]int
var alice = {"age": 25, "score": 95, "level": 3}
var bob = {"age": 30, "score": 87, "level": 2}
var charlie = {"age": 28, "score": 92, "level": 4}
users = {"alice": alice, "bob": bob, "charlie": charlie}

// Access user data
var aliceScore = users["alice"]["score"]
var bobLevel = users["bob"]["level"]

Best Practices

1. Use Typed Maps for Better Safety

Use Typed Maps
1
2
3
4
5
6
// Preferred: Explicit types
package main
var scores map[string]int = {"math": 95, "science": 87}

// Acceptable: Type inference
var scores = {"math": 95, "science": 87}

2. Check for None Before Use

Check for None
1
2
3
4
5
package main
var m map[string]int
if m == None {
    m = {"default": 0}
}

3. Handle Missing Keys

Handle Missing Keys
1
2
3
4
5
6
7
package main
import fmt
var user = {"name": "Alice"}
var email = user["email"]
if email == None {
    fmt.Println("Email not provided")
}

4. Use Descriptive Key Names

Descriptive Key Names
1
2
3
4
5
6
// Good: Descriptive keys
package main
var userProfile = {"firstName": "Alice", "lastName": "Smith", "isActive": true}

// Avoid: Cryptic keys  
var profile = {"fn": "Alice", "ln": "Smith", "act": true}

Performance Characteristics

  • Access Time: O(1) average case for key lookup
  • Memory: Reference types - efficient memory usage
  • Assignment: O(1) - only copies reference, not data
  • Nesting: No performance penalty for nested access

Comparison with Other Languages

Feature Harneet Go JavaScript Python
Syntax {"key": value} map[string]int{} {"key": value} {"key": value}
Type Safety ✅ Compile-time ✅ Compile-time ❌ Runtime ❌ Runtime
Reference Types ✅ Yes ✅ Yes ✅ Yes ✅ Yes
Zero Value None nil undefined None
Nested Maps ✅ Unlimited ✅ Unlimited ✅ Unlimited ✅ Unlimited
Dot-Access obj.key ❌ No obj.key ❌ No
Methods .keys() .values() .has() .delete() ❌ Use functions ✅ Built-in ✅ Built-in
Float Keys ✅ Yes ❌ No ✅ Yes ✅ Yes
Array Keys ✅ Yes ❌ No ❌ No ❌ No (tuples only)
Struct Keys ✅ Yes ✅ Yes ❌ No ❌ No

Map Methods

Maps support several built-in methods for common operations:

keys()

Returns an array containing all keys in the map:

keys()
1
2
3
package main
var user = {"name": "Alice", "age": 30, "city": "NYC"}
var keys = user.keys()  // ["name", "age", "city"]

values()

Returns an array containing all values in the map:

values()
1
2
3
package main
var scores = {"math": 95, "science": 87, "english": 92}
var values = scores.values()  // [95, 87, 92]

has(key) / contains(key)

Checks if a key exists in the map (returns boolean):

has() and contains()
1
2
3
4
package main
var config = {"debug": true, "port": 8080}
var hasDebug = config.has("debug")      // true
var hasHost = config.contains("host")    // false

delete(key)

Removes a key-value pair from the map:

delete()
1
2
3
4
package main
var data = {"a": 1, "b": 2, "c": 3}
data.delete("b")  // Removes key "b"
// data is now {"a": 1, "c": 3}

deleteAll(keys)

Removes multiple keys at once. Accepts an array of keys or another map:

deleteAll()
1
2
3
4
5
6
7
8
9
package main
var data = {"a": 1, "b": 2, "c": 3, "d": 4}

// Delete array of keys
data.deleteAll(["a", "c"])  // data is now {"b": 2, "d": 4}

// Delete all keys from another map
var toRemove = {"b": 0, "d": 0}
data.deleteAll(toRemove)  // data is now {}

putAll(other)

Merges all key-value pairs from another map into this map:

putAll()
package main
var base = {"a": 1, "b": 2}
var additional = {"c": 3, "d": 4}
base.putAll(additional)
// base is now {"a": 1, "b": 2, "c": 3, "d": 4}

// Overwrites existing keys
var updates = {"a": 100}
base.putAll(updates)
// base is now {"a": 100, "b": 2, "c": 3, "d": 4}

entries()

Returns an array of tuples, where each tuple contains a (key, value) pair:

entries()
package main
var scores = {"Alice": 95, "Bob": 87, "Carol": 92}
var entries = scores.entries()
// Returns [(Alice, 95), (Bob, 87), (Carol, 92)]

// Iterate over entries
for entry in entries {
    var name, score = entry
    fmt.Printf("%s scored %d\n", name, score)
}

Dot-Access Syntax

Maps support dot-notation as syntactic sugar for bracket access:

Dot-Access Syntax
package main
var user = {"name": "Alice", "age": 30}

// Dot-access (sugar for bracket notation)
var name = user.name     // Same as user["name"]
var age = user.age       // Same as user["age"]

// Works with nested maps
var config = {"server": {"host": "localhost", "port": 8080}}
var host = config.server.host    // "localhost"
var port = config.server.port    // 8080

// Missing keys return None
var missing = user.email  // None

Note: Dot-access is only syntactic sugar. Use bracket notation when: - Key names contain special characters or spaces - Key names are computed dynamically - Key names conflict with method names

Method lookup precedence and conflicts

When using obj.name on a map, the language first checks if name is a built-in map method. If a method exists (e.g., keys, values, has, contains, delete, deleteAll, putAll, entries), you'll get a bound method value which must be called: obj.keys().

If no method matches, obj.name is treated as sugar for obj["name"].

This means method names take precedence over string keys with the same name. To access a key named "keys" (instead of the method), use bracket notation:

Method Lookup Precedence
1
2
3
4
5
var m = {"keys": "literal-value"}

var method = m.keys      // <bound method>
var list = m.keys()      // array of all keys
var literal = m["keys"] // "literal-value"

Method error cases

  • keys() / values() expect 0 arguments.
  • has(key) / contains(key) / delete(key) expect 1 argument.
  • Map keys must be hashable for has/contains/delete.
Method Error Cases
1
2
3
4
5
6
// Wrong arity
m.keys(1)         // runtime error: map.keys() expected 0 arguments
m.delete()        // runtime error: map.delete(key) expected 1 argument

// Non-hashable key example (using a function or a non-hashable composite)
m.has(function() {})  // runtime error: map key must be hashable

Ordering and complexity

  • keys() and values() return arrays with unspecified order.
  • Average time complexity:
  • has/contains: O(1)
  • delete: O(1)
  • keys()/values(): O(n)
  • Iteration: O(n)

Clearing Maps

Use the built-in clear(m) to remove all entries from a map in-place (Go-like):

clear() builtin
1
2
3
4
5
6
7
package main
import fmt

var m = {"k1": 7, "k2": 13}
fmt.Println("before:", len(m))  // e.g., 2
clear(m)
fmt.Println("after:", len(m))   // 0

Notes: - clear(m) mutates the map and returns None. - Runtime validates the argument is a map. - Alternatively, you can reassign a new empty map: m = {}.

Equality & Comparison

Harneet supports deep structural equality for maps with the == operator. Equality rules:

  • Keys are compared by identity (hash key), and values are compared deeply.
  • Works for primitives, arrays, typed arrays (matching element type), and maps.
  • None == None is true.
Deep equality with ==
1
2
3
4
5
6
7
8
9
package main
import fmt

var a = {"foo": 1, "bar": [1, 2, 3], "user": {"name": "Alice"}}
var b = {"foo": 1, "bar": [1, 2, 3], "user": {"name": "Alice"}}
var c = {"foo": 1, "bar": [1, 2, 3], "user": {"name": "Bob"}}

fmt.Println(a == b) // true
fmt.Println(a == c) // false

maps.Equal

The standard library also provides maps.Equal(a, b) which follows the same deep-equality semantics as == for maps:

maps.Equal
1
2
3
4
5
6
package main
import fmt, maps

var a = {"x": 1, "y": {"n": 2}}
var b = {"x": 1, "y": {"n": 2}}
fmt.Println(maps.Equal(a, b)) // true

Use == for idiomatic comparisons; maps.Equal is available when an explicit function is preferred (e.g., passing as a callback).

Advanced Key Types

Harneet maps support any comparable (hashable) type as keys:

Float Keys

Float Keys
1
2
3
package main
var constants = {3.14: "pi", 2.71: "e", 1.41: "sqrt(2)"}
var pi = constants[3.14]  // "pi"

Array Keys

Arrays can be used as keys if all elements are hashable:

Array Keys
package main
var coordinates = {
    [0, 0]: "origin",
    [1, 2]: "point A",
    [3, 4]: "point B"
}
var origin = coordinates[[0, 0]]  // "origin"

// Nested arrays work too
var nested = {
    [[1, 2], [3, 4]]: "matrix A",
    [[5, 6], [7, 8]]: "matrix B"
}

Struct Keys

Structs can be used as keys if all fields are comparable:

Struct Keys
package main
type Point struct {
    x int
    y int
}

var locations = {
    Point{x: 0, y: 0}: "origin",
    Point{x: 10, y: 20}: "home",
    Point{x: -5, y: 15}: "work"
}

var home = locations[Point{x: 10, y: 20}]  // "home"

// Complex structs with nested hashable fields
type Address struct {
    city string
    zip int
}

type Person struct {
    name string
    age int
    address Address
}

var people = {
    Person{name: "Alice", age: 30, address: Address{city: "NYC", zip: 10001}}: "employee_001"
}

Mixed Key Types

Untyped maps can have mixed key types:

Mixed Key Types
1
2
3
4
5
6
7
8
package main
var mixed = {
    "string": 1,
    42: 2,
    true: 3,
    3.14: 4,
    [1, 2]: 5
}

Map Initialization

Multiple ways to initialize maps:

Empty Map Literal

Empty Map Literal
1
2
3
package main
var m = {}
m["key"] = "value"

Map Literal with Values

Map Literal with Values
package main
var scores = {"math": 95, "science": 87}

Typed Map Declaration

Typed Map Declaration
1
2
3
package main
var m map[string]int
m = {"a": 1, "b": 2}

Iteration

Maps can be iterated using for-in loops:

Iterate Over Keys

Iterate Over Keys
1
2
3
4
5
6
package main
import fmt
var scores = {"Alice": 95, "Bob": 87, "Carol": 92}
for name in scores {
    fmt.Printf("Student: %s\n", name)
}

Iterate Over Key-Value Pairs

Iterate Over Key-Value Pairs
1
2
3
4
5
6
package main
import fmt
var scores = {"Alice": 95, "Bob": 87, "Carol": 92}
for name, score in scores {
    fmt.Printf("%s: %d\n", name, score)
}

Complete Example

Comprehensive example using all map features:

Complete Example
package main
import fmt

type Point struct {
    x int
    y int
}

function main() {
    // Create map with various key types
    var data = {
        "name": "Alice",
        "age": 30,
        "active": true
    }

    // Dot-access
    fmt.Printf("Name: %s\n", data.name)

    // Add new key
    data["email"] = "alice@example.com"

    // Check existence
    if data.has("email") {
        fmt.Println("Email exists")
    }

    // Get all keys and values
    var keys = data.keys()
    var values = data.values()
    fmt.Println("Keys:", keys)
    fmt.Println("Values:", values)

    // Iterate over map
    for key, value in data {
        fmt.Printf("%s: %v\n", key, value)
    }

    // Delete key
    data.delete("age")

    // Struct keys
    var locations = {
        Point{x: 0, y: 0}: "origin",
        Point{x: 10, y: 20}: "destination"
    }

    var dest = locations[Point{x: 10, y: 20}]
    fmt.Println("Destination:", dest)

    // Array keys
    var grid = {
        [0, 0]: "top-left",
        [0, 1]: "top-right",
        [1, 0]: "bottom-left",
        [1, 1]: "bottom-right"
    }

    var topLeft = grid[[0, 0]]
    fmt.Println("Top-left:", topLeft)
}

See Also