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 |
|---|
| // 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 |
|---|
| 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 |
|---|
| 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 |
|---|
| 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 |
|---|
| 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 |
|---|
| // 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 |
|---|
| package main
var stringMap = {"name": "Alice", "city": "NYC"}
var typed map[string]string = stringMap
|
Integer Keys
| Integer Keys |
|---|
| package main
var intMap = {1: "one", 2: "two", 3: "three"}
var typed map[int]string = intMap
|
Boolean Keys
| Boolean Keys |
|---|
| package main
var boolMap = {true: "enabled", false: "disabled"}
var typed map[bool]string = boolMap
|
Mixed Keys (Untyped Maps)
| Mixed Keys |
|---|
| package main
var mixed = {
1: "integer key",
"text": "string key",
true: "boolean key"
}
|
Complex Examples
Configuration Management
| Configuration Management |
|---|
| 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 |
|---|
| 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 |
|---|
| // 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 |
|---|
| package main
var m map[string]int
if m == None {
m = {"default": 0}
}
|
3. Handle Missing Keys
| Handle Missing Keys |
|---|
| 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 |
|---|
| // Good: Descriptive keys
package main
var userProfile = {"firstName": "Alice", "lastName": "Smith", "isActive": true}
// Avoid: Cryptic keys
var profile = {"fn": "Alice", "ln": "Smith", "act": true}
|
- 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() |
|---|
| 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() |
|---|
| 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() |
|---|
| 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() |
|---|
| 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() |
|---|
| 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 |
|---|
| 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 |
|---|
| // 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 |
|---|
| 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 == |
|---|
| 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 |
|---|
| 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 |
|---|
| 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 |
|---|
| 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 |
|---|
| 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 |
|---|
| 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 |
|---|
| 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 |
|---|
| 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