Local reasoning analyzes a component from its definition and inputs, without tracking unrelated execution history or state changes. For a pure function, substitute its arguments into its body: the result depends only on that local computation. section 1.3 section 1.5
Immutable values versus mutation
Repeated evaluation of "Hello, World".reverse produces the same result, so each expression can be understood independently. A mutable StringBuilder behaves differently: evaluating x.append(", World").toString twice changes the second result to "Hello, World, World". Understanding it requires tracking the earlier mutation. section 1.3
Side effects such as mutation, exceptions, network access, and file access make meaning context-dependent. Moving effects outward leaves a pure core whose logic can be understood, tested, reused, and composed independently. section 1.3 section 1.5
Exceptions particularly obstruct local reasoning: the meaning of a throw changes according to the surrounding try block, while the function type does not communicate that failure is possible. Modeling errors with Option, Either, Try, or Validated makes failure explicit and lets map and flatMap preserve compositional reasoning. section 4.1 section 4.6
def buyCoffee(cc: CreditCard): Coffee = val cup = Coffee() cc.charge(cup.price) cup
Use the impure buyCoffee definition to identify payment as an effect entangled with computation, then separate the pure result from effect handling. section 1.3
