State<T,S>
The State<T, T> monad represents a stateful computation that takes an initial state of type S, performs some computation, and returns a value of type T along with a new state of type S.
Operators
Implicit State<T, S>(S)
Implicitly converts a value to a State that returns that value without changing the state.
// Implicit conversion
State<int, string> state = "hello";
var (value, newState) = state.Run(42);
// value: "hello"
// newState: 42 (unchanged)
// Also works with value types
State<string, int> numState = 100;
var (num, strState) = numState.Run("initial");
// num: 100
// strState: "initial"Properties
Function
public Func<S, (T Value, S State)> Function { get; private set; }The underlying function that performs the stateful computation. Takes an initial state and returns a tuple containing:
Value- The computed result of typeTState- The new state of typeS
var state = new State<int, string>(s => ($"value:{s}", s + 1));
var result = state.Function(10);
// result.Value: "value:10"
// result.State: 11Methods
Eval
public T Eval(S initial)Executes the computation and returns only the value, discarding the final state.
Parameters:
initial- The initial state to pass to the computation
Returns: The computed value of type T
Example:
var state = new State<int, int>(s => (s * 2, s + 1));
var value = state.Eval(5);
// value: 10
// Final state (6) is discardedExec
public S Exec(S initial)Executes the computation and returns only the final state, discarding the value.
Parameters:
initial- The initial state to pass to the computation
Returns: The final state of type S
Example:
var state = new State<int, int>(s => (s * 2, s + 1));
var finalState = state.Exec(5);
// finalState: 6
// Value (10) is discardedRun
public (T, S) Run(S initial)Executes the stateful computation with the given initial state and returns both the value and the new state.
Parameters:
initial- The initial state to pass to the computation
Returns: A tuple containing the computed value and the new state
Example:
var state = new State<int, int>(s => (s * 2, s + 1));
var (value, newState) = state.Run(5);
// value: 10 (5 * 2)
// newState: 6 (5 + 1)Manipulation Methods
Get
public State<S, S> Get()Returns a State computation that produces the current state as its value without modifying the state.
Returns: A State that returns the current state as both value and state
Example:
var getState = new State<int, int>(s => (s, s)).Get();
var (value, newState) = getState.Run(10);
// value: 10
// newState: 10Gets
public State<S, T> Gets(Func<S, T> f)Returns a State that applies a function to the current state to produce a value, without modifying the state.
Parameters:
f- A function that extracts or transforms the state into a value
Returns: A State that produces the transformed value
Example:
var state = new State<int, int>(s => (s, s)).Gets(s => s * 2);
var (value, newState) = state.Run(5);
// value: 10 (5 * 2)
// newState: 5 (unchanged)
// Extracting a property from state
record GameState(int Score, int Lives);
var getScore = new State<GameState, int>(s => (0, s))
.Gets(s => s.Score);Modify
public State<S, Unit> Modify(Func<S, S> f)Returns a State that modifies the current state using the provided function, returning Unit.Value.
Parameters:
f- A function that transforms the state
Returns: A State that produces Unit.Value and the modified state
Example:
var modifyState = new State<int, int>(s => (s, s)).Modify(s => s + 5);
var (value, newState) = modifyState.Run(10);
// value: Unit.Value
// newState: 15 (10 + 5)
// Modifying complex state
record Counter(int Count);
var increment = new State<Counter, Unit>(s => (Unit.Value, s))
.Modify(c => c with { Count = c.Count + 1 });Put
public State<S, Unit> Put(S newState)Returns a State that replaces the current state with a new state, returning Unit.Value.
Parameters:
newState- The new state to set
Returns: A State that produces Unit.Value and sets the new state
Example:
var setState = new State<int, int>(s => (s, s)).Put(99);
var (value, newState) = setState.Run(10);
// value: Unit.Value
// newState: 99 (replaced from 10)Transformation Methods
Lift
public State<S, U> Lift<U>(Func<T, U> f)Lifts a pure function into the State context. Equivalent to Map(f, this).
Parameters:
f- A pure function to lift
Returns: A new State with the lifted function applied
Example:
var state = new State<int, int>(s => (s + 2, s + 3));
var lifted = state.Lift(x => x * 2);
var (value, newState) = lifted.Run(1);
// value: 6 ((1 + 2) * 2)
// newState: 4 (1 + 3)
// Lifting string operations
var strState = new State<int, string>(s => ($"Count: {s}", s + 1));
var upper = strState.Lift(s => s.ToUpper());
var (result, _) = upper.Run(5);
// result: "COUNT: 5"Lift2
public State<S, V> Lift2<U, V>(Func<T, U, V> f, State<S, T> x, State<S, U> y)Lifts a binary function into the State context. Equivalent to Map2(f, x, y).
Parameters:
f- A binary function to liftx- The first State computationy- The second State computation
Returns: A State with the lifted function applied to both values
Example:
var x = new State<int, int>(s => (s + 1, s + 2));
var y = new State<int, int>(s => (s * 2, s + 3));
var result = x.Lift2((a, b) => a * b, x, y);
var (value, newState) = result.Run(3);
// x: value=4, state=5
// y: value=10 (5*2), state=8 (5+3)
// final value: 40 (4*10), state: 8Map
public State<S, U> Map<U>(Func<T, U> f, State<S, T> state)Transforms the value produced by a State computation while preserving the state threading.
Parameters:
f- A function to transform the valuestate- The State computation to transform
Returns: A new State with the transformed value
Example:
var state = new State<int, int>(s => (s + 1, s + 2));
var mapped = state.Map(x => x * 2, state);
var (value, newState) = mapped.Run(3);
// value: 8 ((3 + 1) * 2)
// newState: 5 (3 + 2)Map2
public State<S, V> Map2<U, V>(Func<T, U, V> f, State<S, T> x, State<S, U> y)Combines two State computations using a binary function.
Parameters:
f- A function that combines two valuesx- The first State computationy- The second State computation
Returns: A State that produces the combined value
Example:
var x = new State<int, int>(s => (s + 1, s + 2));
var y = new State<int, int>(s => (s * 2, s + 3));
var result = x.Map2((a, b) => a + b, x, y);
var (value, newState) = result.Run(2);
// x: value=3, state=4
// y: value=8 (4*2), state=7 (4+3)
// final value: 11 (3+8), state: 7Map3
public State<S, W> Map3<U, V, W>(Func<T, U, V, W> f, State<S, T> x, State<S, U> y, State<S, V> z)Combines three State computations using a ternary function.
Parameters:
f- A function that combines three valuesx- The first State computationy- The second State computationz- The third State computation
Returns: A State that produces the combined value
Example:
var x = new State<int, int>(s => (s + 1, s + 2));
var y = new State<int, int>(s => (s * 2, s + 3));
var z = new State<int, int>(s => (s - 1, s + 4));
var result = x.Map3((a, b, c) => a + b + c, x, y, z);
var (value, newState) = result.Run(2);
// x: value=3, state=4
// y: value=8, state=7
// z: value=6, state=11
// final value: 17 (3+8+6), state: 11Monadic Operations
Apply
public State<S, U> Apply<U>(State<S, Func<T, U>> sf)Applies a function wrapped in a State to a value wrapped in a State, threading the state through both computations. This is the applicative functor operation.
Parameters:
sf- A State containing a function
Returns: A State with the function applied to the value
Example:
var sf = new State<int, Func<int, int>>(s => (x => x + s, s + 1));
var xv = new State<int, int>(s => (s * 2, s + 1));
var result = xv.Apply(sf);
var (value, newState) = result.Run(3);
// sf: func=(x => x+3), state=4
// xv: value=8 (4*2), state=5 (4+1)
// apply: value=11 (8+3), state=5Bind
public State<S, U> Bind<U>(Func<T, State<S, U>> f, State<S, T> state)Chains stateful computations, threading the state through each computation. This is the fundamental monadic operation for State.
Parameters:
f- A function that takes a value and returns a new State computationstate- The initial State computation
Returns: A State that sequences both computations
Example:
var state = new State<int, int>(s => (s + 1, s + 2));
State<int, int> next(int v) => new State<int, int>(s => (v * 3, s + v));
var bound = state.Bind(next, state);
var (value, newState) = bound.Run(1);
// state: value=2, state=3
// next: value=6 (2*3), state=5 (3+2)Kleisli
public Func<T, State<S, V>> Kleisli<U, V>(
Func<T, State<S, U>> f,
Func<U, State<S, V>> g)Composes two State-producing functions using Kleisli composition. This creates a pipeline of stateful computations.
Parameters:
f- The first State-producing functiong- The second State-producing function
Returns: A composed function that applies both transformations
Example:
State<int, int> f(int t) => new State<int, int>(s => (t + s, s + 1));
State<int, string> g(int u) => new State<int, string>(s => ($"{u}-{s}", s + u));
var k = new State<int, int>(s => (s, s)).Kleisli(f, g);
var result = k(5);
var (value, newState) = result.Run(3);
// f(5): value=8 (5+3), state=4 (3+1)
// g(8): value="8-4", state=12 (4+8)Sequencing Methods
SequenceLeft
public State<S, T> SequenceLeft<U>(State<S, T> x, State<S, U> y)Executes two State computations in sequence, returning the result of the first computation with the final state from the second. Often represented as <* in functional languages.
Parameters:
x- The first State computation (result returned)y- The second State computation (result discarded)
Returns: A State that produces the first computation’s value with the second’s state
Example:
var x = new State<int, string>(s => ($"x{s}", s + 1));
var y = new State<int, int>(s => (s * 3, s + 2));
var result = x.SequenceLeft(x, y);
var (value, newState) = result.Run(2);
// x: value="x2", state=3
// y: value=9 (discarded), state=5 (3+2)
// final: value="x2", state=5SequenceRight
public State<S, U> SequenceRight<U>(State<S, T> x, State<S, U> y)Executes two State computations in sequence, returning the result of the second computation. Often represented as *> in functional languages.
Parameters:
x- The first State computation (result discarded)y- The second State computation (result returned)
Returns: A State that produces the second computation’s value
Example:
var x = new State<int, int>(s => (s, s + 1));
var y = new State<int, int>(s => (s * 3, s + 2));
var result = y.SequenceRight(x, y);
var (value, newState) = result.Run(2);
// x: value=2 (discarded), state=3
// y: value=9 (3*3), state=5 (3+2)Zip
public State<S, (T, U)> Zip<U>(State<S, T> x, State<S, U> y)Runs two State computations in sequence and returns their results as a tuple.
Parameters:
x- The first State computationy- The second State computation
Returns: A State that produces a tuple of both values
Example:
var x = new State<int, string>(s => ($"x{s}", s + 1));
var y = new State<int, int>(s => (s * 2, s + 3));
var result = x.Zip(x, y);
var ((a, b), newState) = result.Run(2);
// x: a="x2", state=3
// y: b=6 (3*2), state=6 (3+3)
// final: (a,b)=("x2",6), state=6Control Flow Methods
Delay
public State<S, T> Delay(Func<State<S, T>> body)Defers the execution of a State computation until it is run. Useful for lazy evaluation or recursive computations.
Parameters:
body- A function that produces the State computation
Returns: A State that will execute the body when run
Example:
var executed = false;
State<int, int> body() => new State<int, int>(s => {
executed = true;
return (s + 10, s + 1);
});
var delayed = new State<int, int>(s => (s, s)).Delay(body);
// executed is still false here
var (value, newState) = delayed.Run(0);
// now executed is true
// value: 10, newState: 1Throw
public State<S, T> Throw(Exception ex)Returns a State that throws the specified exception when executed.
Parameters:
ex- The exception to throw
Returns: A State that throws the exception
Example:
var ex = new InvalidOperationException("Something went wrong");
var state = new State<int, int>(s => (s, s)).Throw(ex);
// Throws InvalidOperationException when Run is called
try {
state.Run(0);
} catch (InvalidOperationException e) {
Console.WriteLine(e.Message); // "Something went wrong"
}TryFinally
public State<S, T> TryFinally(State<S, T> body, Action compensation)Executes a State computation and ensures the compensation action runs afterward, similar to a finally block.
Parameters:
body- The State computation to executecompensation- An action to run after the computation (success or failure)
Returns: A State that ensures cleanup
Example:
var called = false;
var body = new State<int, int>(s => (s + 1, s + 2));
var wrapped = body.TryFinally(body, () => called = true);
var (value, newState) = wrapped.Run(1);
// value: 2, newState: 3
// called is now trueTryWith
public State<S, T> TryWith(State<S, T> body, Func<Exception, State<S, T>> handler)Executes a State computation and handles any exceptions with the provided handler function.
Parameters:
body- The State computation to executehandler- A function that handles exceptions and returns a recovery State
Returns: A State that handles exceptions
Example:
var exBody = new State<int, int>(_ => throw new InvalidOperationException("Error"));
var handled = exBody.TryWith(
exBody,
ex => new State<int, int>(s => (999, s + 1))
);
var (value, newState) = handled.Run(0);
// value: 999 (from handler)
// newState: 1Using
public State<S, T> Using<R>(R resource, Func<R, State<S, T>> body)
where R : IDisposableExecutes a State computation with a disposable resource and ensures the resource is disposed afterward.
Parameters:
resource- The disposable resource to usebody- A function that uses the resource to produce a State
Returns: A State that ensures resource disposal
Example:
class FileResource : IDisposable {
public bool Disposed { get; private set; }
public void Dispose() => Disposed = true;
}
var resource = new FileResource();
var state = new State<int, int>(s => (s + 1, s + 2));
var wrapped = state.Using(resource, _ => state);
var (value, newState) = wrapped.Run(3);
// value: 4, newState: 5
// resource.Disposed is now trueLINQ Support
The State monad supports LINQ query syntax through extension methods, enabling declarative and readable stateful computations.
Select
public static State<S, U> Select<S, T, U>(
this State<S, T> state,
Func<T, U> selector)Projects the value of a State computation. Enables select in LINQ queries.
Parameters:
state- The State to transformselector- A function to transform the value
Returns: A State with the transformed value
Example:
var state = new State<int, int>(s => (s + 1, s + 2));
var projected = state.Select(x => x * 3);
var (value, newState) = projected.Run(5);
// value: 18 ((5+1)*3)
// newState: 7 (5+2)
// Using LINQ syntax
var query = from x in state
select x * 3;SelectMany
public static State<S, U> SelectMany<S, T, U>(
this State<S, T> state,
Func<T, State<S, U>> binder)Binds State computations. Enables from ... from in LINQ queries.
Parameters:
state- The initial Statebinder- A function that produces the next State
Returns: A State that sequences both computations
Example:
var state = new State<int, int>(s => (s + 2, s + 3));
State<int, string> binder(int x) => new(s => ($"{x}-{s}", s + x));
var bound = state.SelectMany(binder);
var (value, newState) = bound.Run(4);
// value: "6-7", newState: 13
// Using LINQ syntax
var query = from x in state
from y in binder(x)
select y;SelectMany (with projection)
public static State<S, V> SelectMany<S, T, U, V>(
this State<S, T> state,
Func<T, State<S, U>> binder,
Func<T, U, V> projector)Binds State computations with a final projection. Required for full LINQ query syntax support with multiple from clauses.
Parameters:
state- The initial Statebinder- A function that produces the next Stateprojector- A function that combines both values
Returns: A State with the projected result
Example:
var state = new State<int, int>(s => (s + 1, s + 2));
var query = from x in state
from y in new State<int, int>(s => (s * 2, s + 1))
select x + y;
var (value, newState) = query.Run(3);
// x: 4, state: 5
// y: 10, state: 6
// value: 14 (4+10), newState: 6Where
public static State<S, T> Where<S, T>(
this State<S, T> state,
Func<T, bool> predicate)Filters the value of a State computation. Enables where in LINQ queries. Throws an InvalidOperationException if the predicate fails.
Parameters:
state- The State to filterpredicate- A function that tests the value
Returns: A State that validates the predicate
Example:
var state = new State<int, int>(s => (s * 2, s + 1));
var filtered = state.Where(v => v % 2 == 0);
var (value, newState) = filtered.Run(3);
// value: 6, newState: 4
// This will throw
var failing = state.Where(v => v % 2 != 0);
// Throws InvalidOperationException when run with even input
// Using LINQ syntax
var query = from x in state
where x > 10
select x;Complete Examples
Example 1: Simple Counter
using Moonad;
// Define counter operations
State<int, Unit> Increment() =>
new State<int, Unit>(count => (Unit.Value, count + 1));
State<int, Unit> Decrement() =>
new State<int, Unit>(count => (Unit.Value, count - 1));
State<int, int> GetCount() =>
new State<int, int>(count => (count, count));
// Compose operations
var computation =
from _ in Increment()
from __ in Increment()
from ___ in Increment()
from count in GetCount()
select count;
var (finalCount, _) = computation.Run(0);
Console.WriteLine(finalCount); // Output: 3Example 2: Stack Operations
using Moonad;
using System.Collections.Generic;
State<Stack<int>, Unit> Push(int value) =>
new State<Stack<int>, Unit>(stack => {
var newStack = new Stack<int>(stack);
newStack.Push(value);
return (Unit.Value, newStack);
});
State<Stack<int>, int> Pop() =>
new State<Stack<int>, int>(stack => {
var newStack = new Stack<int>(stack);
var value = newStack.Pop();
return (value, newStack);
});
State<Stack<int>, int> Peek() =>
new State<Stack<int>, int>(stack => (stack.Peek(), stack));
// Use the stack
var computation =
from _ in Push(10)
from __ in Push(20)
from ___ in Push(30)
from top in Pop()
from second in Peek()
select (top, second);
var ((topValue, secondValue), finalStack) = computation.Run(new Stack<int>());
Console.WriteLine($"Popped: {topValue}, Peek: {secondValue}");
// Output: Popped: 30, Peek: 20Example 3: Game State Management
using Moonad;
record GameState(int Score, int Lives, int Level);
State<GameState, Unit> AddScore(int points) =>
new State<GameState, Unit>(s =>
(Unit.Value, s with { Score = s.Score + points }));
State<GameState, Unit> LoseLife() =>
new State<GameState, Unit>(s =>
(Unit.Value, s with { Lives = s.Lives - 1 }));
State<GameState, Unit> NextLevel() =>
new State<GameState, Unit>(s =>
(Unit.Value, s with { Level = s.Level + 1 }));
State<GameState, bool> IsGameOver() =>
new State<GameState, bool>(s => (s.Lives <= 0, s));
State<GameState, GameState> GetState() =>
new State<GameState, GameState>(s => (s, s));
// Simulate gameplay
var gameplay =
from _ in AddScore(100)
from __ in AddScore(50)
from ___ in LoseLife()
from ____ in NextLevel()
from _____ in AddScore(200)
from gameOver in IsGameOver()
from finalState in GetState()
select (gameOver, finalState);
var initialState = new GameState(0, 3, 1);
var ((isOver, final), _) = gameplay.Run(initialState);
Console.WriteLine($"Game Over: {isOver}");
Console.WriteLine($"Score: {final.Score}, Lives: {final.Lives}, Level: {final.Level}");
// Output:
// Game Over: False
// Score: 350, Lives: 2, Level: 2Example 4: Random Number Generation
using Moonad;
using System;
State<Random, int> NextInt(int max) =>
new State<Random, int>(rng => (rng.Next(max), rng));
State<Random, double> NextDouble() =>
new State<Random, double>(rng => (rng.NextDouble(), rng));
State<Random, T> Choose<T>(params T[] options) =>
from index in NextInt(options.Length)
select options[index];
// Generate random game data
var randomGameData =
from damage in NextInt(100)
from critChance in NextDouble()
from weapon in Choose("Sword", "Axe", "Bow")
select new { Damage = damage, CritChance = critChance, Weapon = weapon };
var (result, _) = randomGameData.Run(new Random(42));
Console.WriteLine($"Weapon: {result.Weapon}, Damage: {result.Damage}, Crit: {result.CritChance:P}");Example 5: Configuration Builder
using Moonad;
using System.Collections.Generic;
record Config(Dictionary<string, string> Settings);
State<Config, Unit> SetSetting(string key, string value) =>
new State<Config, Unit>(config => {
var newSettings = new Dictionary<string, string>(config.Settings) {
[key] = value
};
return (Unit.Value, config with { Settings = newSettings });
});
State<Config, string> GetSetting(string key) =>
new State<Config, string>(config =>
(config.Settings.TryGetValue(key, out var value) ? value : "", config));
State<Config, Config> GetConfig() =>
new State<Config, Config>(config => (config, config));
// Build configuration
var buildConfig =
from _ in SetSetting("Host", "localhost")
from __ in SetSetting("Port", "8080")
from ___ in SetSetting("Protocol", "https")
from host in GetSetting("Host")
from port in GetSetting("Port")
from config in GetConfig()
select (host, port, config);
var initialConfig = new Config(new Dictionary<string, string>());
var ((h, p, finalConfig), _) = buildConfig.Run(initialConfig);
Console.WriteLine($"Server: {h}:{p}");
Console.WriteLine($"Total settings: {finalConfig.Settings.Count}");
// Output:
// Server: localhost:8080
// Total settings: 3Example 6: Parser State
using Moonad;
record ParserState(string Input, int Position);
State<ParserState, char> ReadChar() =>
new State<ParserState, char>(state => {
if (state.Position >= state.Input.Length)
throw new InvalidOperationException("End of input");
var ch = state.Input[state.Position];
var newState = state with { Position = state.Position + 1 };
return (ch, newState);
});
State<ParserState, string> ReadWhile(Func<char, bool> predicate) =>
new State<ParserState, string>(state => {
int start = state.Position;
int pos = start;
while (pos < state.Input.Length && predicate(state.Input[pos]))
pos++;
var result = state.Input.Substring(start, pos - start);
var newState = state with { Position = pos };
return (result, newState);
});
State<ParserState, int> GetPosition() =>
new State<ParserState, int>(state => (state.Position, state));
// Parse a simple expression
var parser =
from word1 in ReadWhile(char.IsLetter)
from _ in ReadWhile(char.IsWhiteSpace)
from word2 in ReadWhile(char.IsLetter)
from pos in GetPosition()
select (word1, word2, pos);
var initialState = new ParserState("Hello World", 0);
var ((w1, w2, position), finalState) = parser.Run(initialState);
Console.WriteLine($"Parsed: '{w1}' and '{w2}' at position {position}");
// Output: Parsed: 'Hello' and 'World' at position 11Example 7: Transaction Log
using Moonad;
using System.Collections.Generic;
record Transaction(string Type, decimal Amount, DateTime Timestamp);
record Account(decimal Balance, List<Transaction> History);
State<Account, Unit> Deposit(decimal amount) =>
new State<Account, Unit>(account => {
var transaction = new Transaction("Deposit", amount, DateTime.Now);
var newHistory = new List<Transaction>(account.History) { transaction };
return (Unit.Value, account with {
Balance = account.Balance + amount,
History = newHistory
});
});
State<Account, Unit> Withdraw(decimal amount) =>
new State<Account, Unit>(account => {
if (account.Balance < amount)
throw new InvalidOperationException("Insufficient funds");
var transaction = new Transaction("Withdrawal", amount, DateTime.Now);
var newHistory = new List<Transaction>(account.History) { transaction };
return (Unit.Value, account with {
Balance = account.Balance - amount,
History = newHistory
});
});
State<Account, decimal> GetBalance() =>
new State<Account, decimal>(account => (account.Balance, account));
State<Account, int> GetTransactionCount() =>
new State<Account, int>(account => (account.History.Count, account));
// Perform transactions
var transactions =
from _ in Deposit(1000m)
from __ in Withdraw(200m)
from ___ in Deposit(500m)
from ____ in Withdraw(150m)
from balance in GetBalance()
from count in GetTransactionCount()
select (balance, count);
var initialAccount = new Account(0m, new List<Transaction>());
var ((finalBalance, txCount), finalAccount) = transactions.Run(initialAccount);
Console.WriteLine($"Final Balance: ${finalBalance}");
Console.WriteLine($"Transactions: {txCount}");
// Output:
// Final Balance: $1150
// Transactions: 4Example 8: LINQ Query
var baseState = new State<int, int>(s => (s + 1, s + 2));
var query =
from a in baseState
from b in new State<int, int>(s => (s * 2, s + 3))
where a % 2 == 0
select $"{a}-{b}";
var (value, newState) = query.Run(3);
// baseState: a=4, state=5
// second: b=10 (5*2), state=8 (5+3)
// where: passes (4 % 2 == 0)
// select: value="4-10", state=8Best Practices
1. Keep State Pure and Immutable
Always use immutable state types and pure transformation functions:
// Good: Immutable record
record GameState(int Score, int Level);
State<GameState, Unit> AddScore(int points) =>
new State<GameState, Unit>(s =>
(Unit.Value, s with { Score = s.Score + points }));
// Avoid: Mutable state
class MutableState {
public int Score { get; set; }
}2. Use LINQ Query Syntax for Readability
LINQ syntax makes complex state computations more readable:
// Good: Clear and declarative
var computation =
from _ in Increment()
from count in GetCount()
where count > 0
select count;
// Less readable: Nested Bind calls
var computation2 = Increment().Bind(
_ => GetCount().Bind(
count => ...));3. Compose Small Operations
Build complex operations from small, reusable pieces:
// Small, focused operations
State<int, Unit> Increment() => /* ... */;
State<int, Unit> Double() => /* ... */;
State<int, int> GetValue() => /* ... */;
// Compose them
var complex =
from _ in Increment()
from __ in Double()
from value in GetValue()
select value;4. Handle Errors Gracefully
Use TryWith for error handling in stateful computations:
State<Account, decimal> SafeWithdraw(decimal amount) =>
new State<Account, Unit>(account => /* may throw */)
.TryWith(
Withdraw(amount),
ex => new State<Account, decimal>(s => (s.Balance, s))
);5. Minimize State Size
Keep state types focused and minimal:
// Good: Focused state
record Counter(int Value);
// Avoid: Kitchen sink state
record EverythingState(
int Counter,
string Name,
List<object> Items,
Dictionary<string, object> Cache,
// ... too much
);6. Use Gets for Read-Only Access
Use Gets when you only need to read from state without modification:
// Read state property
State<GameState, int> GetScore() =>
new State<GameState, int>(s => (0, s)).Gets(s => s.Score);
// Better than
State<GameState, int> GetScoreBad() =>
new State<GameState, int>(s => (s.Score, s));7. Leverage Type Safety
Use specific types instead of primitives:
// Good: Type-safe
record PlayerId(int Value);
record GameState(PlayerId CurrentPlayer);
// Avoid: Primitive obsession
record GameState(int CurrentPlayer);8. Document State Transitions
Make state transitions explicit and well-documented:
/// <summary>
/// Transitions from Playing to GameOver if lives reach 0
/// </summary>
State<GameState, Unit> CheckGameOver() =>
from lives in GetLives()
from _ in lives <= 0
? SetGameStatus(GameStatus.Over)
: GetState().Lift(_ => Unit.Value)
select Unit.Value;