Skip to content

Latest commit

 

History

History
220 lines (153 loc) · 3.77 KB

File metadata and controls

220 lines (153 loc) · 3.77 KB

Immutability with Data

By Brett Slatkin

Pure function

Recursive sum_up function

Test

Advantages

  • Completely deterministic
  • no mutable state
  • Data-driven tests are easy
  • Debugging: easy to reproduce production issues

Functional Sandwich

Split up program into three distinct phases:

  1. Input phase
    • Reading input
  2. Do stuff phase
    • Pure business logic
  3. Output phase
    • "dirty work"
    • E.g., writing stuff back to a file

Problems: Stack space

Python not technically "functional". Recursion can typically break early. Python is technically more imperative.

"Stackless Python," not well-maintained.

Python recursive economics can be unintuitive.

Is name reassignment really mutability?

Example: sum_up_imperative vs sum_up_unrolled

  • Not the type of mutability that he's worried about

Fibonacci example

Stateful Fibonacci sequence:

class Fib:
    ...

def fib_up(n, Fib: fib)
    ...

Fib.: Another function that steps downward

Example: fib_down

Intially:

fib = Fib()
result = fib_up(8, fib)
print(result)

Later developer:

fib = Fib()
result = fib_up(8, fib)
result2 = fib_down(3, fib)
print(result)   # current == 5
print(result2)  # current == 5

Fix with immutability

@dataclass(frozen=True)
class FibFrozen:
    current: int = 0
    next: int = 1

def fib_up(n, fib):
    for i in range(n)
        fib = FibFrozen(
            current=fib.next,
            next = fib.current + fib.next)
    return fib

Testing easier and predictable.

Referential transparency

Basic example

def my_func(n):
    return n * 2

No caching example

Transparency cached func

Big speedups available for pure functions with functools.cache:

import functools

@functools.cache
def ...:
    ...

Composition

Partial applicaiton

Assigning a parameter in advance. Convenient with functools.partial.

def apply_tax(total, rate):
    ...

from functools import partial

my_apply_tax = partial(apply_tax, rate=0.095)

Without composition

Readability issue

def finalize_cart(total, coupon, tax_rate, shipping):
    ...

print(finalize_cart(...)) # 129.40

def fnlcart_customt(total, apply_shipping, apply_tax, apply_coupon):
    return apply_shipping(apply_tax(apply_coupon(total)))

linear ordering

def fnlcart_customt(total, apply_shipping, apply_tax, apply_coupon):
    ... = apply_coupon(total)
    ... = apply_tax(...)
    ... = apply_shipping(...)
    return finished

Pipe operator in R... Might go through in Python with some PEP...

Higher-order functions

def many_fibonaccis(items):
    ```
    fibonacci for each input element
    ```
    ...

many_fibonaccis([1,3,5])
# returns 1, 2, 5 )

map built-in

Mclaren F1 example:

  • Clay versus Lego
  • Clay: customized
  • Lego: replaceable parts, etc.

Why map?

Less bespoke stuff. How do I take this problem and solve it immutably with map.

Get bonuses for writing things functionally.

Parallelization and Concurrentization for free/cheap

from concurrent.futures import ThreadPoolExecutor
from concurrent.futures import ProcessPoolExecutor

Free threading is coming Immutable = sharable

  • Free threading (no GIL)
  • multiproccessing (IPC)
  • Threading (parallel I/O)

Frozen types

Read-only stuff

frozenset

frozendict

tuple

Why?

  • Stable hashing
  • Tracking lineage
  • Sharing

Conclusion

  • help you avoid certain errors
  • easier: datadriven tests, bug reproducbility
  • wrap pure logic in functional sandiwch
  • composition for pluggability and extensibility
  • metaprogramming with higher order functions
  • AI: tell LLM to use immutability except when necessary for performance or clarity

Book

Effective Python