Everything that makes Harding special
Harding preserves the essence of Smalltalk:
^ return3 + 4 # binary message
obj size # unary message
dict at: key put: value # keyword message
# Blocks with non-local return
findPositive := [:arr |
arr do: [:n |
(n > 0) ifTrue: [^ n]
].
^ nil
]
Optional periods, hash comments, double-quoted strings:
# for comments# This is a comment
x := 1 # No period needed
y := 2
z := x + y # But periods work too
"Double quotes for strings"
Create classes dynamically with slots (instance variables) and methods:
derive: for class creation with slots (instance variables)deriveWithAccessors: for automatic getter/setter generation# Declare a temporary variable to hold an instance
| p |
# Create a new class with two slots (instance variables)
Point := Object derive: #(x y)
# Add methods using >> syntax and direct slot access
Point >> x: val [ x := val ]
Point >> y: val [ y := val ]
# Add multiple methods at a time
Point extend: [
self >> moveBy: dx and: dy [
x := x + dx.
y := y + dy
]
self >> distanceFromOrigin [
^ ((x * x) + (y * y)) sqrt
]
]
# Create and use a Point
p := Point new
p x: 100; y: 200
p distanceFromOrigin println # 223.6068...
Inherit from multiple parents with conflict detection:
derive: to create a subclassaddSuperclass: to add additional parents# Inherit from multiple parents
ColoredPoint := Point derive: #(color)
ColoredPoint addSuperclass: Comparable
ColoredPoint addSuperclass: Printable
# ColoredPoint now has methods from:
# - Point (distanceFromOrigin, etc)
# - Comparable (>, <, =, etc)
# - Printable (asString, print, etc)
Create classes with auto-generated getters and setters:
deriveWithAccessors: - Generate getters and setters for all slots (instance variables)variableName returns the current valuevariableName: updates the valueFor complete control over which slots (instance variables) get accessors:
derive:getters:setters: - Specify which slots get getters/settersWhen multiple parents define the same method, Harding detects conflicts at class definition time. Override conflicting methods in the child class to resolve.
# Automatic accessors
Person := Object deriveWithAccessors: #(name age)
p := Person new
p name: "Alice" # Auto-generated setter
p age: 30
p name # Auto-generated getter
# Selective accessors
Account := Object derive: #(balance owner)
getters: #(balance owner)
setters: #(balance)
# Conflict detection
Parent1 := Object derive: #(a)
Parent1 >> foo [ ^ "foo1" ]
Parent2 := Object derive: #(b)
Parent2 >> foo [ ^ "foo2" ]
# Override first, then add parents
Child := Object derive: #(x)
Child >> foo [ ^ "child" ]
Child addSuperclass: Parent1
Child addSuperclass: Parent2
Mixin is a state-free class designed for behavior composition. Mixins derive from Root (sibling to Object) and avoid the diamond problem since they carry no slots (instance variables):
| Mixin | Requires | Provides |
|---|---|---|
Comparable |
compareTo: |
<, <=, >, >=, between:and:, min:, max: |
Iterable |
do: |
collect:, select:, reject:, detect:, inject:into: |
Printable |
printOn: |
printString, print, printCr |
Synchronizable |
— | critical:, acquire, release |
# Create a mixin
Comparable := Mixin derive.
Comparable >> < other [ ^ (self compareTo: other) < 0 ]
Comparable >> > other [ ^ (self compareTo: other) > 0 ]
Comparable >> between: min and: max [
^ (self >= min) and: [ self <= max ]
]
# Add mixin to any class
Point := Object derive: #(x y)
Point addSuperclass: Comparable
Point >> compareTo: other [
^ ((x * x) + (y * y)) - ((other x * other x) + (other y * other y))
]
# Now Point supports <, >, between:and:, etc.
Harding implements Smalltalk-style resumable exceptions with full signal point preservation. Unlike traditional exception handling that unwinds the stack, Harding's exceptions can be resumed, retried, or delegated.
| Message | Effect |
|---|---|
ex resume |
Restore signal point, signal returns nil |
ex resume: value |
Restore signal point, signal returns the given value |
ex retry |
Re-execute the protected block from the beginning |
ex pass |
Delegate to the next outer matching handler |
ex return: value |
Return value from the on:do: expression |
# Basic exception handling
result := [
riskyOperation value
] on: Error do: [:ex |
Transcript showCr: "Caught: " + ex message.
ex resume: 42 # Resume with a value
]
# Resume execution
result := [
10 // 0
] on: DivisionByZero do: [:ex |
ex resume: 0
]
# Retry the operation
[
attemptOperation
] on: RetryableError do: [:ex |
fixTheIssue.
ex retry
]
# Arithmetic exceptions
result := [ 10 // 0 ] on: DivisionByZero do: [:ex |
ex resume: 0 # Return 0 instead
]
Division by zero signals a DivisionByZero exception that can be caught and resumed:
// (integer division) - Signals on zero divisor/ (float division) - Signals on zero divisor% (modulo) - Signals on zero divisorHandlers can resume with a default value to continue execution.
# Integer division by zero
result := [ 10 // 0 ] on: DivisionByZero do: [:ex |
ex resume: 0
]
# result is 0
# Float division by zero
result := [ 10.0 / 0.0 ] on: DivisionByZero do: [:ex |
"Cannot divide!" println.
ex resume: 42
]
# result is 42
Direct access to VM-level operations and Nim interop:
primitiveClone - Shallow copyprimitiveAt: - Access elementprimitiveAt:put: - Set elementprimitiveIdentity: - Object identityprimitiveCCall:with: - Call C libraries# Basic primitive syntax
Array>>at: index <primitive primitiveAt: index>
# With validation wrapper
Array>>at: index [
(index < 1 or: [index > self size]) ifTrue: [
self error: "Index out of bounds"
].
^ <primitive primitiveAt: index>
]
Both unqualified and qualified super sends for method overriding:
super methodName - Unqualified supersuper<ParentClass> methodName - Qualified superQualified super is essential when using multiple inheritance to specify which parent's implementation to call.
# Unqualified super
ColoredRectangle>>area [
baseArea := super area.
^ baseArea + colorAdjustment
]
# Qualified super (for multiple inheritance)
C>>foo [
^ super<A> foo, " from C"
]
C>>bar [
^ super<B> bar, " from C"
]
Define methods on the class itself using class>> syntax:
selector:put: on the class# Instance method
Person>>greet [
^ "Hello, I am " + name
]
# Class-side method
Person class>>newNamed: n aged: a [
| p |
p := self new.
p name: n.
p age: a.
^ p
]
# Usage
alice := Person newNamed: "Alice" aged: 30
Send messages and inspect objects at runtime:
perform: - Dynamic message sendperform:with: - With one argumentperform:with:with: - With two argumentssuperclassNames - Get inheritance chainclass, slotNames - Object inspection# Dynamic dispatch
obj perform: #description
obj perform: #at: with: 5
obj perform: #at:put: with: 5 with: 'value'
# Introspection
Point superclassNames # #(Object)
obj class # Get object's class
obj slotNames # List instance variables
obj respondsTo: #do: # Check method existence
Harding includes practical process/file utilities in the stdlib:
System arguments for CLI/runtime argumentsSystem cwd, System stdin, System stdout, System stderrFile class convenience messages (readAll:, write:to:, append:to:, exists:)FileStream for stream-style open/read/write# File helpers
content := File readAll: "README.md"
File write: content to: "README.copy.md"
# Process info and stdio
System stdout writeline: ("args: " , (System arguments size) asString)
Cooperative multitasking with first-class Process objects:
worker := Processor fork: [
1 to: 10 do: [:i |
i println
Processor yield
]
]
worker suspend
worker resume
worker terminate
Process States: ready, running, blocked, suspended, terminated
Scheduler: Processor yield, Scheduler listProcesses, process priority:
Coordinate between green processes using built-in primitives:
# Monitor - mutual exclusion
monitor := Monitor new
monitor critical: [
sharedResource := sharedResource + 1
]
# SharedQueue - producer/consumer
queue := SharedQueue new
queue nextPut: "item" # Producer
item := queue next # Consumer (blocks if empty)
# Semaphore - counting/binary locks
sem := Semaphore forMutualExclusion
sem wait
sem signal
Each Process runs in its own stackless VM, enabling lightweight processes and a path to true parallelism.
Interactive development with immediate feedback:
$ harding
Harding REPL (:help for commands, :quit to exit)
harding> 3 + 4
7
harding> "Hello, World!" println
Hello, World!
harding> numbers := #(1 2 3 4 5)
#(1 2 3 4 5)
harding> numbers collect: [:n | n * n]
#(1 4 9 16 25)
Arrays, Tables, and all the classic Smalltalk collection methods:
#(1 2 3)#{"key" -> "value"}do:, collect:, select:inject:into:, detect:# Array literal
numbers := #(1 2 3 4 5)
# Table literal
scores := #{"Alice" -> 95, "Bob" -> 87}
# Collection methods
numbers do: [:n | n println]
squares := numbers collect: [:n | n * n]
evens := numbers select: [:n | (n % 2) = 0]
sum := numbers inject: 0 into: [:acc :n | acc + n]
Call Nim code directly via primitives. Access the entire Nim ecosystem including libraries, packages, and system APIs.
Objects can hold references to Nim values:
isNimProxy - True if object wraps a Nim valuehardingType - Get the Harding type namenimValue - Access underlying Nim value# Access Nim functions via primitives
Array>>at: index <primitive primitiveAt: index>
# Objects can wrap Nim values
obj isNimProxy # true if wraps Nim value
obj hardingType # Get Harding type name
obj nimValue # Access Nim value
# C library access via Nim FFI
<primitive primitiveCCall: function with: args>
Harding supports packaging Nim primitives and Harding source files together:
For a full walkthrough, see the Nim Package Tutorial.
Compile Harding code to native binaries via Nim → C:
granite compile program.hrd -o program
granite build program.hrd --release
granite run program.hrd
Harding source (.hrd)
↓
Parser (AST)
↓
Granite Compiler
↓
Nim source (.nim)
↓
C Compiler
↓
Machine code
↓
Native binary
# Compile to native binary
$ granite compile app.hrd -o myapp
# Result: standalone executable
$ ./myapp
Control verbosity of execution output:
harding --loglevel DEBUG script.hrd
Levels: DEBUG, INFO, WARN, ERROR, FATAL
DEBUG shows AST evaluation, message sends, method lookups, and activation stack operations.
View the parsed Abstract Syntax Tree:
harding --ast script.hrd
Shows the hierarchical structure of parsed expressions before execution.
Inspect running processes:
Scheduler listProcesses
process state # ready, running, etc.
process pid # Unique process ID
process name # Process name
process priority # Scheduling priority
deriveWithAccessors:)System, File, FileStream, stdio globals)System arguments (including harding ... -- args)