N

Nanyx

Documentation

Records and Tuples

Records and tuples are fundamental data structures in many languages; in Nanyx they are a single feature. They allow you to group related values together with or without names.

Records

Records are structural types with named fields enclosed in parentheses:

-- Record creation
def user = (
  name = "Alice"
  age = 30
  email = "alice@example.com"
)

Record Types

Define record types to describe the shape of your data:

type Person = (
  name: string
  age: int
  email: string
)

-- Using the type
def alice: Person = (
  name = "Alice"
  age = 30
  email = "alice@example.com"
)

Accessing Record Fields

Access fields using dot notation:

def user = (name = "Bob", age = 25)

def userName = user.name  -- "Bob"
def userAge = user.age    -- 25

Record Updates (Non-destructive mutation / copy-and-update)

In a language where immutability is the default, it is almost required that there is a way to make a copy of a value with some of its properties changed. This is called "copy-and-update" or "non-destructive mutation". Nanyx has two special syntactical forms for this:

  1. The with keyword
  2. The spread operator ...

The important difference between these two forms is that the with keyword works on exactly one input object, and the result of the with expression will always be the same type as the input object. The spread operator, meanwhile, can be used on any number of input objects and the resulting object may well be a different type from any of the input objects.

def x = (a = 1, b = 2)
def y = x with (a = 3)
def z = x with (d = 4) --  Error: the `with` keyword cannot be used to add new properties
def x = (a = 1, b = 2)
def y = (...x, a = 3) -- `y` is now (a = 3, b = 2)
def z = (...x, c = 4) -- `z` is now `(a = 1, b = 2, c = 4)`. Note the fact that a new property has been added

Unlike with, the spread operator can be used on multiple input objects, to create a mix of properties

def x = (a = 1, b = 2)
def y = (c = 3, d = 4)
def z = (...x, ...y) -- `z` is now `(a = 1, b = 2, c = 3, d = 4)`

Note that the order of the input objects is important. If two input objects have the same property, the last one will be used.

Nested Records

Records can contain other records:

type Address = (
  street: string
  city: string
  zipCode: string
)

type Person = (
  name: string
  age: int
  address: Address
)

def person = (
  name = "Alice"
  age = 30
  address = (
    street = "123 Main St"
    city = "Springfield"
    zipCode = "12345"
  )
)

-- Access nested fields
def city = person.address.city  -- "Springfield"

Record Destructuring

Extract fields from records with pattern matching:

def user = (name = "Bob", age = 25)

-- Destructure in definition
def (name = userName, age = userAge) = user

-- Destructure in function parameter
def greet: Person -> string = { (name = n, age = a) ->
  "Hello {n}, age {a}"
}

-- Partial destructuring
def (name = name, _) = user  -- only extract name

Tuples

Tuples are anonymous records with numbered fields:

-- Tuple creation
def point = (10, 20)

-- Access by position
def (x, y) = point

-- Tuple type
def origin: (int, int) = (0, 0)

Named Tuples

You can name tuple fields, making them equivalent to records:

-- Named tuple
def coords = (x = 10, y = 20)

-- Access by name
def x = coords.x  -- 10

-- Or destructure
def (x = xVal, y = yVal) = coords

Record Patterns in Matching

Use records in pattern matching:

type Point = (x: int, y: int)

def classify: Point -> string = { point ->
  match point
    | (x = 0, y = 0) -> "origin"
    | (x = 0, y = _) -> "on y-axis"
    | (x = _, y = 0) -> "on x-axis"
    | (x = x, y = y) if x == y -> "on diagonal"
    | _ -> "general point"
}

Records as Function Parameters

Records make multi-parameter functions natural:

-- Function taking multiple parameters
def calculateArea: (width: int, height: int) -> int = { width, height ->
  width * height
}

-- Call with named arguments
def area = calculateArea(width = 10, height = 20)

-- Or with a record
def dimensions = (width = 10, height = 20)
def area2 = calculateArea(dimensions)

Structural Typing

Records use structural typing - they're compatible based on shape, not name:

type Point2D = (x: int, y: int)
type Coord = (x: int, y: int)

-- These are the same type!
def point: Point2D = (x = 10, y = 20)
def coord: Coord = point  -- OK!