The Fractor Model, inspired by BitC and Jonathan Shapiro, refines the Actor paradigm for better control over shared mutable state. Fractors offer explicit state and effect handling, ideal for low-level concurrent programming. The key innovation is fractal-like, fine-grained control over mutable state at the method level, improving programmer ergonomics and enabling automated type-checking.
Crucially, the Fractor Model introduces a sound and complete type system for effects that:
- Controls side effects: Only mutable handles (
name!,NAME!) can invoke mutating methods. - Ensures safe reassignment: Handles distinguish between reassignable and fixed references.
- Provides concurrency safety: By enforcing clear rules for state mutation and access, it prevents race conditions and unintended state changes.
These conventions make it easier to reason about state transitions, especially in concurrent environments, while enhancing safety and predictability.
1. From Actors to Fractors: Explicit State & Effects
While the Actor model excels at isolating state, it struggles with shared resources. The Fractor Model introduces Fractors, which encapsulate state and provide methods for reading and mutating it, based on effect typing similar to BitC.
2. Handle Types: Fine-Grained Control
Fractors introduce four handle types for explicit control over mutability and reassignment, visualized in a 2×2 matrix:
| Immutable | Mutable! | |
| CONSTANT | NAME | NAME! |
| variable | name | name! |
Four Quadrants
- Non-Reassignable, Immutable (
NAME): Fixed reference, no mutation. - Non-Reassignable, Mutable (
NAME!): Fixed reference, allows mutation. - Reassignable, Immutable (
name): Can be reassigned, but not mutated. - Reassignable, Mutable (
name!): Can be reassigned and mutated.
3. Methods: Access & Mutation
Fractors encapsulate mutable state and differentiate mutating methods with a : convention. Only mutable handles can invoke mutating methods.
Example: Bank Account Fractor
fractor BankAccount {
balance: int;
get_balance() { return read_fractor(BankAccount).balance; }
deposit:(amount: int) {
let account = read_fractor(BankAccount);
write_fractor(BankAccount, account.balance + amount);
}
withdraw:(amount: int) {
let account = read_fractor(BankAccount);
if (account.balance >= amount) {
write_fractor(BankAccount, account.balance - amount);
} else {
throw_error("Insufficient funds");
}
}
has_funds(amount: int) {
return read_fractor(BankAccount).balance >= amount;
}
}
REPL Example of Handle Types and Usage
; let account! = new BankAccount(100) # Mutable variable (balance = 100)
; account!.get_balance()
# 100
; account!.deposit:(50)
; account!.get_balance()
# 150
; account!.withdraw:(200)
@error "Insufficient funds"
; let ACCOUNT! = new BankAccount(200) # Mutable constant (balance = 200)
; ACCOUNT!.withdraw:(100)
; ACCOUNT!.get_balance()
# 100
; let ACCOUNT = new BankAccount(500) # Immutable constant
; ACCOUNT.withdraw:(50)
@error "Cannot mutate immutable reference"
In this REPL example:
- Accessor methods like
get_balanceandhas_fundscan be invoked on any handle type. - Mutating methods like
deposit:andwithdraw:can only be invoked on mutable handles (name!orNAME!).
4. Conclusion: Elevating Systems Programming with Fractors
The Fractor Model advances the Actor model by making state and effects explicit. It brings the rigor of effect typing and fine-grained control, enhancing both safety and predictability, especially in concurrent systems programming.
By distinguishing between mutability and reassignment, Fractors offer a sound and complete type system for effects, enabling clearer reasoning about state transitions, particularly in multi-threaded systems. This makes Fractors an important evolution for systems programming, simplifying complex state management while maintaining safety and precision.
