1"""
2Functional Programming Patterns in Python
3
4Key concepts:
51. Pure Functions - no side effects, same input = same output
62. Higher-Order Functions - functions that take/return functions
73. Closures - functions that capture variables from outer scope
84. Function Composition - combining simple functions to build complex ones
95. Currying - transforming multi-argument function to chain of single-argument
106. Map/Filter/Reduce - fundamental data transformation operations
11"""
12
13from typing import Callable, List, TypeVar, Any
14from functools import reduce, partial
15import operator
16
17T = TypeVar('T')
18U = TypeVar('U')
19
20
21# =============================================================================
22# 1. PURE FUNCTIONS
23# No side effects, deterministic output
24# =============================================================================
25
26print("=" * 70)
27print("1. PURE FUNCTIONS")
28print("=" * 70)
29
30
31# ❌ IMPURE: Has side effects, depends on external state
32counter = 0
33
34
35def impure_increment(x: int) -> int:
36 """Impure: modifies global state"""
37 global counter
38 counter += 1 # Side effect!
39 return x + counter # Depends on external state!
40
41
42# ✅ PURE: No side effects, deterministic
43def pure_add(x: int, y: int) -> int:
44 """Pure: same inputs always give same output"""
45 return x + y
46
47
48def pure_square(x: float) -> float:
49 """Pure: mathematical function"""
50 return x * x
51
52
53def pure_capitalize_words(text: str) -> str:
54 """Pure: transforms input without side effects"""
55 return ' '.join(word.capitalize() for word in text.split())
56
57
58# =============================================================================
59# 2. HIGHER-ORDER FUNCTIONS
60# Functions that take or return other functions
61# =============================================================================
62
63print("\n" + "=" * 70)
64print("2. HIGHER-ORDER FUNCTIONS")
65print("=" * 70)
66
67
68# Function that takes another function as argument
69def apply_twice(func: Callable[[T], T], value: T) -> T:
70 """Apply function twice to a value"""
71 return func(func(value))
72
73
74def apply_n_times(n: int) -> Callable[[Callable[[T], T], T], T]:
75 """Returns a function that applies func n times"""
76
77 def applier(func: Callable[[T], T], value: T) -> T:
78 result = value
79 for _ in range(n):
80 result = func(result)
81 return result
82
83 return applier
84
85
86# Function that returns another function
87def make_multiplier(factor: int) -> Callable[[int], int]:
88 """Returns a function that multiplies by factor"""
89
90 def multiplier(x: int) -> int:
91 return x * factor
92
93 return multiplier
94
95
96def make_adder(n: int) -> Callable[[int], int]:
97 """Returns a function that adds n"""
98
99 def adder(x: int) -> int:
100 return x + n
101
102 return adder
103
104
105# =============================================================================
106# 3. CLOSURES
107# Functions that capture variables from their enclosing scope
108# =============================================================================
109
110print("\n" + "=" * 70)
111print("3. CLOSURES")
112print("=" * 70)
113
114
115def make_counter(start: int = 0) -> Callable[[], int]:
116 """Returns a counter function with private state"""
117 count = start # Captured by closure
118
119 def counter() -> int:
120 nonlocal count # Allows modification of captured variable
121 count += 1
122 return count
123
124 return counter
125
126
127def make_greeting(greeting: str) -> Callable[[str], str]:
128 """Returns a greeting function that remembers the greeting"""
129
130 def greet(name: str) -> str:
131 return f"{greeting}, {name}!"
132
133 return greet
134
135
136def make_accumulator(initial: float = 0.0) -> Callable[[float], float]:
137 """Returns a function that accumulates values"""
138 total = initial
139
140 def accumulate(value: float) -> float:
141 nonlocal total
142 total += value
143 return total
144
145 return accumulate
146
147
148# =============================================================================
149# 4. FUNCTION COMPOSITION
150# Building complex functions from simple ones
151# =============================================================================
152
153print("\n" + "=" * 70)
154print("4. FUNCTION COMPOSITION")
155print("=" * 70)
156
157
158def compose(*functions: Callable) -> Callable:
159 """
160 Compose functions right to left: compose(f, g, h)(x) = f(g(h(x)))
161 """
162
163 def inner(arg):
164 result = arg
165 for func in reversed(functions):
166 result = func(result)
167 return result
168
169 return inner
170
171
172def pipe(*functions: Callable) -> Callable:
173 """
174 Compose functions left to right: pipe(f, g, h)(x) = h(g(f(x)))
175 More intuitive for data pipelines
176 """
177
178 def inner(arg):
179 result = arg
180 for func in functions:
181 result = func(result)
182 return result
183
184 return inner
185
186
187# Example transformation functions
188def remove_spaces(text: str) -> str:
189 return text.replace(" ", "")
190
191
192def to_uppercase(text: str) -> str:
193 return text.upper()
194
195
196def add_exclamation(text: str) -> str:
197 return text + "!"
198
199
200def double(x: int) -> int:
201 return x * 2
202
203
204def increment(x: int) -> int:
205 return x + 1
206
207
208# =============================================================================
209# 5. CURRYING
210# Transform multi-argument function to chain of single-argument functions
211# =============================================================================
212
213print("\n" + "=" * 70)
214print("5. CURRYING")
215print("=" * 70)
216
217
218# Regular function
219def add_three_numbers(a: int, b: int, c: int) -> int:
220 return a + b + c
221
222
223# Curried version
224def curried_add(a: int) -> Callable[[int], Callable[[int], int]]:
225 def add_b(b: int) -> Callable[[int], int]:
226 def add_c(c: int) -> int:
227 return a + b + c
228
229 return add_c
230
231 return add_b
232
233
234# Generic curry function
235def curry(func: Callable) -> Callable:
236 """
237 Automatically curry a function
238 (simplified version for demonstration)
239 """
240
241 def curried(*args):
242 if len(args) >= func.__code__.co_argcount:
243 return func(*args)
244 return lambda *more_args: curried(*(args + more_args))
245
246 return curried
247
248
249# Using functools.partial (Pythonic currying)
250def multiply(a: int, b: int, c: int) -> int:
251 return a * b * c
252
253
254# Create specialized functions using partial
255double_then_multiply = partial(multiply, 2)
256triple_then_multiply = partial(multiply, 3)
257
258
259# =============================================================================
260# 6. MAP / FILTER / REDUCE
261# Fundamental functional operations
262# =============================================================================
263
264print("\n" + "=" * 70)
265print("6. MAP / FILTER / REDUCE")
266print("=" * 70)
267
268
269def demonstrate_map():
270 """Map: Transform each element"""
271 numbers = [1, 2, 3, 4, 5]
272
273 # Using built-in map
274 squared = list(map(lambda x: x ** 2, numbers))
275
276 # List comprehension (more Pythonic)
277 squared_comp = [x ** 2 for x in numbers]
278
279 return squared, squared_comp
280
281
282def demonstrate_filter():
283 """Filter: Keep elements matching predicate"""
284 numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
285
286 # Using built-in filter
287 evens = list(filter(lambda x: x % 2 == 0, numbers))
288
289 # List comprehension (more Pythonic)
290 evens_comp = [x for x in numbers if x % 2 == 0]
291
292 return evens, evens_comp
293
294
295def demonstrate_reduce():
296 """Reduce: Combine all elements into single value"""
297 numbers = [1, 2, 3, 4, 5]
298
299 # Sum using reduce
300 total = reduce(lambda acc, x: acc + x, numbers, 0)
301
302 # Product using reduce
303 product = reduce(lambda acc, x: acc * x, numbers, 1)
304
305 # More Pythonic: use operator module
306 total_op = reduce(operator.add, numbers, 0)
307 product_op = reduce(operator.mul, numbers, 1)
308
309 return total, product, total_op, product_op
310
311
312# =============================================================================
313# PRACTICAL EXAMPLES
314# =============================================================================
315
316def practical_pipeline_example():
317 """Real-world data transformation pipeline"""
318
319 # Data pipeline: process user data
320 users = [
321 {"name": "alice jones", "age": 25, "salary": 50000},
322 {"name": "bob smith", "age": 17, "salary": 0},
323 {"name": "charlie brown", "age": 35, "salary": 75000},
324 {"name": "diana prince", "age": 30, "salary": 90000},
325 {"name": "eve taylor", "age": 16, "salary": 0},
326 ]
327
328 # Functional pipeline
329 pipeline = pipe(
330 # Filter adults
331 lambda users: filter(lambda u: u["age"] >= 18, users),
332 # Extract names and capitalize
333 lambda users: map(
334 lambda u: {**u, "name": u["name"].title()},
335 users
336 ),
337 # Calculate bonus (10% of salary)
338 lambda users: map(
339 lambda u: {**u, "bonus": u["salary"] * 0.1},
340 users
341 ),
342 # Convert to list
343 list,
344 )
345
346 return pipeline(users)
347
348
349def practical_function_factory():
350 """Function factory for validation"""
351
352 def make_validator(
353 min_value: float,
354 max_value: float
355 ) -> Callable[[float], bool]:
356 """Create range validator"""
357
358 def validate(value: float) -> bool:
359 return min_value <= value <= max_value
360
361 return validate
362
363 # Create specialized validators
364 is_percentage = make_validator(0, 100)
365 is_temperature = make_validator(-273.15, float('inf'))
366 is_grade = make_validator(0, 100)
367
368 return is_percentage, is_temperature, is_grade
369
370
371# =============================================================================
372# DEMONSTRATION
373# =============================================================================
374
375def main():
376 print("\n[PURE FUNCTIONS]")
377 print("-" * 50)
378 print(f"pure_add(5, 3) = {pure_add(5, 3)}")
379 print(f"pure_square(4) = {pure_square(4)}")
380 print(f"pure_capitalize_words('hello world') = {pure_capitalize_words('hello world')}")
381
382 # Impure function produces different results
383 print("\nImpure function (different results):")
384 print(f"impure_increment(5) = {impure_increment(5)}")
385 print(f"impure_increment(5) = {impure_increment(5)}") # Different!
386
387 print("\n[HIGHER-ORDER FUNCTIONS]")
388 print("-" * 50)
389 multiply_by_3 = make_multiplier(3)
390 print(f"multiply_by_3(5) = {multiply_by_3(5)}")
391
392 add_10 = make_adder(10)
393 print(f"add_10(5) = {add_10(5)}")
394
395 print(f"apply_twice(lambda x: x * 2, 5) = {apply_twice(lambda x: x * 2, 5)}")
396
397 apply_3_times = apply_n_times(3)
398 print(f"apply_3_times(double, 2) = {apply_3_times(double, 2)}")
399
400 print("\n[CLOSURES]")
401 print("-" * 50)
402 counter1 = make_counter(0)
403 counter2 = make_counter(100)
404
405 print(f"counter1(): {counter1()}, {counter1()}, {counter1()}")
406 print(f"counter2(): {counter2()}, {counter2()}")
407
408 say_hello = make_greeting("Hello")
409 say_bonjour = make_greeting("Bonjour")
410 print(f"say_hello('Alice') = {say_hello('Alice')}")
411 print(f"say_bonjour('Bob') = {say_bonjour('Bob')}")
412
413 acc = make_accumulator()
414 print(f"Accumulator: {acc(10)}, {acc(5)}, {acc(3)}")
415
416 print("\n[FUNCTION COMPOSITION]")
417 print("-" * 50)
418
419 # Compose text transformations
420 process_text = compose(add_exclamation, to_uppercase, remove_spaces)
421 print(f"process_text('hello world') = {process_text('hello world')}")
422
423 # Pipe is more intuitive
424 process_text_pipe = pipe(remove_spaces, to_uppercase, add_exclamation)
425 print(f"process_text_pipe('hello world') = {process_text_pipe('hello world')}")
426
427 # Compose numeric operations
428 process_number = pipe(increment, double, increment)
429 print(f"process_number(5) = {process_number(5)}") # (5+1)*2+1 = 13
430
431 print("\n[CURRYING]")
432 print("-" * 50)
433
434 # Regular call
435 print(f"add_three_numbers(1, 2, 3) = {add_three_numbers(1, 2, 3)}")
436
437 # Curried call
438 print(f"curried_add(1)(2)(3) = {curried_add(1)(2)(3)}")
439
440 # Partial application
441 add_5 = curried_add(5)
442 add_5_and_10 = add_5(10)
443 print(f"curried_add(5)(10)(3) = {add_5_and_10(3)}")
444
445 # Using partial
446 print(f"double_then_multiply(3, 4) = {double_then_multiply(3, 4)}")
447 print(f"triple_then_multiply(3, 4) = {triple_then_multiply(3, 4)}")
448
449 print("\n[MAP / FILTER / REDUCE]")
450 print("-" * 50)
451
452 squared, squared_comp = demonstrate_map()
453 print(f"Map - Squared: {squared}")
454
455 evens, evens_comp = demonstrate_filter()
456 print(f"Filter - Evens: {evens}")
457
458 total, product, total_op, product_op = demonstrate_reduce()
459 print(f"Reduce - Sum: {total}, Product: {product}")
460
461 print("\n[PRACTICAL PIPELINE]")
462 print("-" * 50)
463 processed_users = practical_pipeline_example()
464 for user in processed_users:
465 print(f" {user}")
466
467 print("\n[PRACTICAL FUNCTION FACTORY]")
468 print("-" * 50)
469 is_percentage, is_temperature, is_grade = practical_function_factory()
470
471 print(f"is_percentage(50) = {is_percentage(50)}")
472 print(f"is_percentage(150) = {is_percentage(150)}")
473 print(f"is_temperature(-300) = {is_temperature(-300)}")
474 print(f"is_grade(85) = {is_grade(85)}")
475
476
477def print_summary():
478 print("\n" + "=" * 70)
479 print("FUNCTIONAL PROGRAMMING PATTERNS SUMMARY")
480 print("=" * 70)
481
482 print("""
4831. PURE FUNCTIONS
484 ✓ No side effects
485 ✓ Deterministic (same input → same output)
486 ✓ Easy to test and reason about
487 ✓ Cacheable/memoizable
488
4892. HIGHER-ORDER FUNCTIONS
490 ✓ Functions as first-class citizens
491 ✓ Can take functions as arguments
492 ✓ Can return functions
493 ✓ Enables abstraction and reusability
494
4953. CLOSURES
496 ✓ Functions that capture variables
497 ✓ Private state without classes
498 ✓ Function factories
499 ✓ Data hiding and encapsulation
500
5014. FUNCTION COMPOSITION
502 ✓ Build complex from simple
503 ✓ Compose/pipe for readability
504 ✓ Reusable transformation chains
505 ✓ Declarative style
506
5075. CURRYING
508 ✓ Transform multi-arg → single-arg chain
509 ✓ Partial application
510 ✓ Specialized functions from generic ones
511 ✓ Better function reuse
512
5136. MAP/FILTER/REDUCE
514 ✓ Fundamental transformations
515 ✓ Declarative data processing
516 ✓ Composable operations
517 ✓ Works with any iterable
518
519BENEFITS:
520 • More predictable code
521 • Easier to test
522 • Better concurrency (immutability)
523 • Composability
524 • Declarative style
525
526PYTHON TIPS:
527 • Use list comprehensions instead of map/filter when simple
528 • functools module for partial, reduce
529 • operator module for common operations
530 • itertools for advanced iteration
531 • toolz/fn.py libraries for more FP tools
532""")
533
534
535if __name__ == "__main__":
536 main()
537 print_summary()