SRFI-9: Record Types

Table of Contents

0.1. Tutorial: Comparing Record Types in Guile, Clojure, Python, and TypeScript

0.1.1. Objective:

0.1.2. Contents:

  • Introduction to Record Types
  • Guile SRFI-9 Records
  • Clojure defrecord
  • Python Data Classes
  • TypeScript Classes
  • Comparison and Summary

0.1.3. Part 1: Introduction to Record Types

Duration: 5 minutes

0.1.4. Part 2: Guile SRFI-9 Records

reproduced · pilot-annotation-pass · 2026-10-09

Duration: 20 minutes

0.1.4.1. Example:
  (use-modules (srfi srfi-9))

  (define-record-type <employee>
    (make-employee name age salary)
    employee?
    (name    employee-name)
    (age     employee-age    set-employee-age!)
    (salary  employee-salary set-employee-salary!))

  (define fred (make-employee "Fred" 45 20000.00))

  (employee? fred)        ;; #t
  (employee-age fred)     ;; 45
  (set-employee-salary! fred 25000.00)  ;; pay rise

0.1.5. Part 3: Clojure defrecord

Duration: 20 minutes

0.1.5.1. Example:
  (defrecord Employee [name age salary])

  (def fred (->Employee "Fred" 45 20000.00))

  (:age fred)          ;; 45
  (assoc fred :salary 25000.00)  ;; pay rise, creates a new record with updated salary
  • Explanation:
    • defrecord defines a new record type Employee.
    • ->Employee is the constructor.
    • :age is the accessor.
    • assoc is used to create a new record with updated fields (immutable update).

0.1.6. Part 4: Python Data Classes

Duration: 20 minutes

0.1.6.1. Example:
  from dataclasses import dataclass

  @dataclass
  class Employee:
      name: str
      age: int
      salary: float

  fred = Employee("Fred", 45, 20000.00)

  print(fred.age)        # 45
  fred.salary = 25000.00  # pay rise

  print(fred)
  • Explanation:
    • @dataclass is used to define a data class.
    • Fields are defined with type annotations.
    • Fields can be accessed and modified directly.

0.1.7. Part 5: TypeScript Classes

Duration: 20 minutes

0.1.7.1. Example:
  class Employee {
    constructor(public name: string, public age: number, public salary: number) {}

    setSalary(newSalary: number) {
      this.salary = newSalary;
    }
  }

  let fred = new Employee("Fred", 45, 20000.00);

  console.log(fred.age);  // 45
  fred.setSalary(25000.00);  // pay rise
  • Explanation:
    • class defines a class with a constructor.
    • Fields are defined in the constructor.
    • Methods can be added for field modifications.

0.1.8. Part 6: Comparison and Summary

Duration: 25 minutes

0.1.8.1. Syntax Comparison
  • Guile (SRFI-9):

        (define-record-type <employee> ...)
    
  • Clojure (defrecord):

        (defrecord Employee [name age salary])
    
  • Python (data classes):

        @dataclass
        class Employee:
    
  • TypeScript (classes):

        class Employee {
          constructor(public name: string, public age: number, public salary: number) {}
        }
    
0.1.8.2. Field Access and Modification
  • Guile:

        (employee-age fred)
        (set-employee-salary! fred 25000.00)
    
  • Clojure:

        (:age fred)
        (assoc fred :salary 25000.00)
    
  • Python:

        fred.age
        fred.salary = 25000.00
    
  • TypeScript:

        fred.age
        fred.setSalary(25000.00)
    
0.1.8.3. Immutability
  • Guile: Mutable by default, can be made immutable with define-immutable-record-type.
  • Clojure: Immutable by default, use assoc for updates.
  • Python: Mutable by default, use frozen=True in @dataclass for immutability.
  • TypeScript: Mutable by default, use techniques like Readonly for immutability.

0.1.9. Summary

  • Guile (SRFI-9): Strong syntax for functional programming, requires explicit accessors and modifiers.
  • Clojure (defrecord): Emphasizes immutability and functional updates.
  • Python (data classes): Simple and flexible, easy to use with direct field access.
  • TypeScript (classes): Combines object-oriented and functional programming features, supports methods for field manipulation.

0.1.10. Conclusion

0.2. Related Languages and Record Types

0.2.1. Rust Structs

Duration: 20 minutes

0.2.1.1. Example:
  struct Employee {
      name: String,
      age: u32,
      salary: f64,
  }

  fn main() {
      let mut fred = Employee {
          name: String::from("Fred"),
          age: 45,
          salary: 20000.00,
      };

      println!("{}", fred.age);  // 45
      fred.salary = 25000.00;    // pay rise
  }
  • Explanation:
    • struct defines a new struct type.
    • Fields are defined with types.
    • Fields can be accessed and modified directly if mutable.

0.2.2. Haskell Data Types

Duration: 20 minutes

0.2.2.1. Example:
  data Employee = Employee {
      name :: String,
      age :: Int,
      salary :: Float
  } deriving (Show)

  let fred = Employee "Fred" 45 20000.00

  age fred               -- 45
  fred {salary = 25000}  -- pay rise, creates a new record with updated salary
  • Explanation:
    • data keyword defines a new record type.
    • Fields are defined with types.
    • Records are immutable, creating a new record for updates.

0.2.3. Swift Structs

Duration: 20 minutes

0.2.3.1. Example:
  struct Employee {
      var name: String
      var age: Int
      var salary: Double
  }

  var fred = Employee(name: "Fred", age: 45, salary: 20000.00)

  print(fred.age)        // 45
  fred.salary = 25000.00 // pay rise

  • Explanation:
    • struct defines a new struct type.
    • Fields are defined with types.
    • Fields can be accessed and modified directly.

0.2.4. Elm Records

Duration: 20 minutes

0.2.4.1. Example:
  type alias Employee =
      { name : String
      , age : Int
      , salary : Float
      }

  fred : Employee
  fred =
      { name = "Fred"
      , age = 45
      , salary = 20000.00
      }

  fred.age                  -- 45
  { fred | salary = 25000 } -- pay rise, creates a new record with updated salary
  • Explanation:
    • type alias defines a new record type.
    • Fields are defined with types.
    • Records are immutable, creating a new record for updates.

0.2.5. JavaScript (ES6 Classes)

Duration: 20 minutes

0.2.5.1. Example:
  class Employee {
    constructor(name, age, salary) {
      this.name = name;
      this.age = age;
      this.salary = salary;
    }

    setSalary(newSalary) {
      this.salary = newSalary;
    }
  }

  let fred = new Employee("Fred", 45, 20000.00);

  console.log(fred.age);  // 45
  fred.setSalary(25000.00);  // pay rise
  console.log(fred);

  • Explanation:
    • class defines a new class type.
    • Fields are defined in the constructor.
    • Methods can be added for field modifications.
  1. Person
    class Person {
      constructor(name, age) {
        this.name = name;
        this.age = age;
      }
    }
    
    class Employee extends Person {
      constructor(name, age, salary) {
        super(name, age); // Call parent constructor
        this.salary = salary;
      }
    }
    
    let bob = new Employee("Bob", 30, 50000);
    console.log(bob.name); // "Bob" (Inherited from Person)
    console.log(bob.salary); // 50000 
    
    
  2. Point
    class Point {
      constructor(x, y) {
        Object.defineProperty(this, 'x', { value: x, writable: false });
        Object.defineProperty(this, 'y', { value: y, writable: false });
      }
    }
    
    let p1 = new Point(3, 4);
    // p1.x = 5; // Throws an error, as 'x' is not writable
    console.log(p1);
    
    
  3. Color
    class Color {
      constructor(red, green, blue) {
        this.red = red;
        this.green = green;
        this.blue = blue;
      }
    
      equals(otherColor) {
        return (
          this.red === otherColor.red &&
          this.green === otherColor.green &&
          this.blue === otherColor.blue
        );
      }
    }
    
    let c1 = new Color(255, 0, 0); // Red
    let c2 = new Color(255, 0, 0); // Red
    let c3 = new Color(0, 255, 0); // Green
    
    console.log(c1.equals(c2)); // true
    console.log(c1.equals(c3)); // false
    
    

0.2.6. Comparison and Summary

0.2.6.1. Syntax Comparison
  • Rust:

        struct Employee {
            name: String,
            age: u32,
            salary: f64,
        }
    
  • Haskell:

        data Employee = Employee {
            name :: String,
            age :: Int,
            salary :: Float
        }
    
  • Swift:

        struct Employee {
            var name: String
            var age: Int
            var salary: Double
        }
    
  • Elm:

        type alias Employee = { name : String, age : Int, salary : Float }
    
  • JavaScript (ES6):

        class Employee {
          constructor(name, age, salary) {
            this.name = name;
            this.age = age;
            this.salary = salary;
          }
        }
    
0.2.6.2. Field Access and Modification
  • Rust:

        fred.age
        fred.salary = 25000.00
    
  • Haskell:

        age fred
        fred {salary = 25000}
    
  • Swift:

        fred.age
        fred.salary = 25000.00
    
  • Elm:

        fred.age
        { fred | salary = 25000 }
    
  • JavaScript (ES6):

        fred.age
        fred.setSalary(25000.00)
    
0.2.6.3. Immutability
  • Rust: Mutable by default, but can enforce immutability.
  • Haskell: Immutable by default.
  • Swift: Mutable by default, use let for immutability.
  • Elm: Immutable by default.
  • JavaScript: Mutable by default, use libraries like Immutable.js for immutability.

0.2.7. Summary

  • Rust Structs: Provide a balance between performance and safety with strong typing and mutability control.
  • Haskell Data Types: Emphasize immutability and functional programming.
  • Swift Structs: Flexible and easy to use with both mutability and immutability options.
  • Elm Records: Strongly typed and immutable by default, suited for functional programming.
  • JavaScript (ES6 Classes): Object-oriented approach with flexible mutability, widely used in web development.

0.3. SRFI-9 in 2026: Implementation Status

verified · pilot-annotation-pass · 2026-10-09

SRFI-9 ("Defining Record Types") was finalized in 1999 and is one of the most universally adopted SRFIs. It is included verbatim in R7RS-small (Section 5.5), making it a de facto baseline for portable Scheme code.

0.3.1. Native Support Matrix (April 2026)

corrected · pilot-annotation-pass · 2026-10-09
Implementation SRFI-9 Status Notes
Guile 3.0.x Native (built-in) (use-modules (srfi srfi-9)) or R7RS import
Chez Scheme Partial Native define-record-type is R6RS-syntax, not SRFI-9; R7RS (scheme base) needs separate library support
Racket CS Via library #lang r7rs + (import (scheme base)); plain racket/base has struct, not define-record-type
Chibi-Scheme Native (built-in) Full R7RS-small; define-record-type in core
Chicken 5 Native (built-in) Included in (chicken base)
Gambit Native (built-in) R7RS mode via #!r7rs declaration
Kawa (JVM) Native (built-in) Maps to Java class generation
MIT Scheme Via SRFI loader (load-option 'SRFI-9)
Bigloo Partial (define-struct ...) is the idiomatic alternative

0.3.2. Key Points

  • Portability: Code using define-record-type per SRFI-9 / R7RS-small is portable across all major actively-maintained Scheme implementations as of 2026.
  • Extensions: Guile adds define-immutable-record-type beyond the SRFI-9 spec. Chicken provides (chicken record-types) with additional introspection. These are implementation-specific extensions and not portable.
  • SRFI-131 (ERR5RS Records): Provides a compatible extension adding inheritance. Adopted by Chibi and Kawa; Guile supports it via a compatibility module.
  • R7RS-large: The Red Edition includes SRFI-9 as its record-type primitive. Future ballots may add inheritance via SRFI-131 or a derivative.

0.3.3. Minimal Portable Example (R7RS-small)

corrected · pilot-annotation-pass · 2026-10-09
;; Portable R7RS-small syntax, but needs an import to run:
;; (use-modules (srfi srfi-9)) in Guile, (import (scheme base)) under R7RS

(define-record-type <point>
  (make-point x y)
  point?
  (x point-x)
  (y point-y))

(define origin (make-point 0 0))
(define p1     (make-point 3 4))

(point? origin)    ;; => #t
(point-x p1)       ;; => 3
(point-y p1)       ;; => 4

;; Euclidean distance (no mutation needed -- purely functional)
(define (point-distance a b)
  (let ((dx (- (point-x b) (point-x a)))
        (dy (- (point-y b) (point-y a))))
    (sqrt (+ (* dx dx) (* dy dy)))))

(point-distance origin p1)  ;; => 5.0

1. Related