Closures & private state
Falx structs have no private keyword - any field can be read or written from outside. When you actually need to hide state, a closure does the job instead.
Step 1: An independent counter
A function returned from another function keeps access to that function's local variables, even after the outer call has finished - see Closures:
func makeCounter = define() {
let count = 0;
func increment = define() {
count = count + 1;
return count;
}
return increment;
}
let counterA = makeCounter();
let counterB = makeCounter();
output(counterA()); // 1
output(counterA()); // 2
output(counterB()); // 1 - counterB's `count` is a completely separate variable
There is no way to reach into counterA and read or reset count directly - count only exists inside the closure. That's the "private state" a struct field can't give you.
Step 2: A bank account with several operations
One closure only gives you one function. For something with several operations sharing one hidden balance - deposit, withdraw, check balance - declare all three as nested functions over the same balance, then hand them back together in a plain map, using the map purely as a bundle of functions:
func makeAccount = define(startingBalance) {
let balance = startingBalance;
func deposit = define(amount) {
balance += amount;
return balance;
}
func withdraw = define(amount) {
if (amount > balance) {
output("Insufficient funds.");
return balance;
}
balance -= amount;
return balance;
}
func checkBalance = define() {
return balance;
}
let account = {};
account.deposit = deposit;
account.withdraw = withdraw;
account.checkBalance = checkBalance;
return account;
}
Notice the map is built with {} and then has keys added with dot-assignment (account.deposit = deposit;) - a plain, direct use of the dot-access-as-key-access behaviour maps support.
let acct = makeAccount(100);
output(acct.checkBalance()); // 100
acct.deposit(50);
output(acct.checkBalance()); // 150
acct.withdraw(30);
output(acct.checkBalance()); // 120
acct.withdraw(1000); // prints "Insufficient funds."
output(acct.checkBalance()); // 120 - unchanged
There's no way to write acct.balance = 1000000; from outside - balance was never put on the map, only the three functions that close over it were. The bank account manager example in the test suite builds on exactly this pattern.
Structs or closures?
Reach for a struct when you want named, freely-readable/writable fields and don't need to hide anything. Reach for a closure like this when a piece of state genuinely shouldn't be reachable from outside - there's no in-between "private field" option on a struct today.