ima-claude 2.20.0 → 2.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -9
- package/dist/cli.js +1 -1
- package/package.json +1 -1
- package/plugins/ima-claude/.claude-plugin/plugin.json +1 -1
- package/plugins/ima-claude/agents/explorer.md +29 -15
- package/plugins/ima-claude/agents/implementer.md +58 -13
- package/plugins/ima-claude/agents/memory.md +19 -19
- package/plugins/ima-claude/agents/reviewer.md +56 -34
- package/plugins/ima-claude/agents/tester.md +59 -16
- package/plugins/ima-claude/agents/wp-developer.md +66 -21
- package/plugins/ima-claude/hooks/bootstrap.sh +42 -44
- package/plugins/ima-claude/hooks/prompt_coach_digest.md +14 -17
- package/plugins/ima-claude/hooks/prompt_coach_system.md +10 -12
- package/plugins/ima-claude/personalities/README.md +17 -6
- package/plugins/ima-claude/personalities/enable-efficient.md +61 -0
- package/plugins/ima-claude/personalities/enable-terse.md +71 -0
- package/plugins/ima-claude/skills/agentic-workflows/SKILL.md +35 -71
- package/plugins/ima-claude/skills/architect/SKILL.md +54 -168
- package/plugins/ima-claude/skills/compound-bridge/SKILL.md +41 -94
- package/plugins/ima-claude/skills/design-to-code/SKILL.md +43 -78
- package/plugins/ima-claude/skills/discourse/SKILL.md +79 -194
- package/plugins/ima-claude/skills/discourse-admin/SKILL.md +41 -103
- package/plugins/ima-claude/skills/docs-organize/SKILL.md +63 -203
- package/plugins/ima-claude/skills/ember-discourse/SKILL.md +90 -200
- package/plugins/ima-claude/skills/espocrm/SKILL.md +14 -23
- package/plugins/ima-claude/skills/espocrm-api/SKILL.md +79 -192
- package/plugins/ima-claude/skills/functional-programmer/SKILL.md +33 -237
- package/plugins/ima-claude/skills/gh-cli/SKILL.md +26 -65
- package/plugins/ima-claude/skills/ima-bootstrap/SKILL.md +71 -104
- package/plugins/ima-claude/skills/ima-bootstrap/references/ima-brand.md +32 -22
- package/plugins/ima-claude/skills/ima-brand/SKILL.md +18 -23
- package/plugins/ima-claude/skills/ima-copywriting/SKILL.md +68 -179
- package/plugins/ima-claude/skills/ima-doc2pdf/SKILL.md +32 -102
- package/plugins/ima-claude/skills/ima-editorial-scorecard/SKILL.md +38 -63
- package/plugins/ima-claude/skills/ima-editorial-workflow/SKILL.md +69 -114
- package/plugins/ima-claude/skills/ima-email-creator/SKILL.md +16 -22
- package/plugins/ima-claude/skills/ima-forms-expert/SKILL.md +21 -37
- package/plugins/ima-claude/skills/jira-checkpoint/SKILL.md +39 -120
- package/plugins/ima-claude/skills/jquery/SKILL.md +107 -233
- package/plugins/ima-claude/skills/js-fp/SKILL.md +75 -296
- package/plugins/ima-claude/skills/js-fp-api/SKILL.md +52 -162
- package/plugins/ima-claude/skills/js-fp-react/SKILL.md +47 -270
- package/plugins/ima-claude/skills/js-fp-vue/SKILL.md +55 -209
- package/plugins/ima-claude/skills/js-fp-wordpress/SKILL.md +59 -204
- package/plugins/ima-claude/skills/livecanvas/SKILL.md +19 -32
- package/plugins/ima-claude/skills/mcp-atlassian/SKILL.md +92 -162
- package/plugins/ima-claude/skills/mcp-context7/SKILL.md +32 -64
- package/plugins/ima-claude/skills/mcp-gitea/SKILL.md +98 -188
- package/plugins/ima-claude/skills/mcp-github/SKILL.md +60 -124
- package/plugins/ima-claude/skills/mcp-memory/SKILL.md +1 -177
- package/plugins/ima-claude/skills/mcp-qdrant/SKILL.md +58 -115
- package/plugins/ima-claude/skills/mcp-sequential/SKILL.md +32 -87
- package/plugins/ima-claude/skills/mcp-serena/SKILL.md +54 -80
- package/plugins/ima-claude/skills/mcp-tavily/SKILL.md +40 -63
- package/plugins/ima-claude/skills/mcp-vestige/SKILL.md +75 -116
- package/plugins/ima-claude/skills/php-authnet/SKILL.md +32 -65
- package/plugins/ima-claude/skills/php-fp/SKILL.md +50 -129
- package/plugins/ima-claude/skills/php-fp-wordpress/SKILL.md +25 -73
- package/plugins/ima-claude/skills/phpunit-wp/SKILL.md +103 -463
- package/plugins/ima-claude/skills/playwright/SKILL.md +69 -220
- package/plugins/ima-claude/skills/prompt-starter/SKILL.md +33 -83
- package/plugins/ima-claude/skills/prompt-starter/references/code-review.md +38 -0
- package/plugins/ima-claude/skills/py-fp/SKILL.md +78 -384
- package/plugins/ima-claude/skills/quasar-fp/SKILL.md +54 -255
- package/plugins/ima-claude/skills/quickstart/SKILL.md +7 -11
- package/plugins/ima-claude/skills/rails/SKILL.md +63 -184
- package/plugins/ima-claude/skills/resume-session/SKILL.md +14 -35
- package/plugins/ima-claude/skills/rg/SKILL.md +61 -146
- package/plugins/ima-claude/skills/ruby-fp/SKILL.md +66 -163
- package/plugins/ima-claude/skills/save-session/SKILL.md +10 -39
- package/plugins/ima-claude/skills/scorecard/SKILL.md +24 -38
- package/plugins/ima-claude/skills/skill-analyzer/SKILL.md +42 -71
- package/plugins/ima-claude/skills/skill-creator/SKILL.md +79 -250
- package/plugins/ima-claude/skills/task-master/SKILL.md +11 -31
- package/plugins/ima-claude/skills/task-planner/SKILL.md +44 -153
- package/plugins/ima-claude/skills/task-runner/SKILL.md +61 -143
- package/plugins/ima-claude/skills/unit-testing/SKILL.md +59 -134
- package/plugins/ima-claude/skills/wp-ddev/SKILL.md +38 -120
- package/plugins/ima-claude/skills/wp-local/SKILL.md +26 -108
|
@@ -5,158 +5,68 @@ description: "Python FP principles with anti-over-engineering focus - Simple > C
|
|
|
5
5
|
|
|
6
6
|
# Python Functional Programming
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
FP in spirit, Pythonic in expression. Python is not Haskell — don't fight the language.
|
|
9
9
|
|
|
10
|
-
##
|
|
10
|
+
## CRITICAL: Anti-Over-Engineering
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
- Data transformation pipelines (pandas, polars, ML preprocessing)
|
|
14
|
-
- Need FP architectural guidance for Python
|
|
15
|
-
- Preventing over-engineering and custom FP utility creation
|
|
16
|
-
- Comprehensive testing strategies with pytest
|
|
17
|
-
- Evidence-based performance optimization
|
|
18
|
-
|
|
19
|
-
## CRITICAL: Anti-Over-Engineering (PRIMARY FOCUS)
|
|
20
|
-
|
|
21
|
-
**Core Principle**: "Simple > Complex | Evidence > Assumptions"
|
|
22
|
-
|
|
23
|
-
> **Clarification**: This skill prevents CREATING custom FP utility functions (pipe, compose, curry, monads) to make Python "feel" like Haskell. Using established libraries (toolz, pandas, itertools, etc.) is perfectly fine. FP is a mindset — pure functions, immutability, composition — not a rigid API signature.
|
|
24
|
-
|
|
25
|
-
### Don't Create Custom FP Utilities
|
|
12
|
+
**Never create custom FP utilities** (`pipe`, `compose`, `curry`, custom monads). Use native patterns or established libraries (`toolz`, `itertools`, `functools`).
|
|
26
13
|
|
|
27
14
|
```python
|
|
28
|
-
# DON'T
|
|
15
|
+
# DON'T: pipe() utility
|
|
29
16
|
def pipe(value, *functions):
|
|
30
17
|
for fn in functions:
|
|
31
18
|
value = fn(value)
|
|
32
19
|
return value
|
|
33
20
|
|
|
34
|
-
#
|
|
21
|
+
# DO: native early returns
|
|
35
22
|
def validate_user(user_data: dict) -> dict:
|
|
36
|
-
|
|
37
|
-
if not
|
|
38
|
-
return
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return email_check
|
|
43
|
-
|
|
23
|
+
required = validate_required(["email", "name"], user_data)
|
|
24
|
+
if not required["valid"]:
|
|
25
|
+
return required
|
|
26
|
+
email = validate_email(user_data)
|
|
27
|
+
if not email["valid"]:
|
|
28
|
+
return email
|
|
44
29
|
return validate_name_length(user_data)
|
|
45
30
|
|
|
46
|
-
# DON'T
|
|
47
|
-
|
|
48
|
-
def composed(x):
|
|
49
|
-
for fn in reversed(fns):
|
|
50
|
-
x = fn(x)
|
|
51
|
-
return x
|
|
52
|
-
return composed
|
|
53
|
-
|
|
54
|
-
# INSTEAD: Direct function calls
|
|
55
|
-
def process_data(raw: dict) -> dict:
|
|
56
|
-
normalized = normalize(raw)
|
|
57
|
-
validated = validate(normalized)
|
|
58
|
-
return transform(validated)
|
|
59
|
-
|
|
60
|
-
# DON'T CREATE: curry() utility
|
|
61
|
-
# INSTEAD: functools.partial or closures
|
|
31
|
+
# DON'T: compose() or curry() utilities
|
|
32
|
+
# DO: direct calls or functools.partial
|
|
62
33
|
from functools import partial
|
|
63
|
-
|
|
64
34
|
validate_min_length = partial(validate_length, min_len=3)
|
|
65
35
|
|
|
66
|
-
#
|
|
67
|
-
|
|
68
|
-
def validate(value):
|
|
69
|
-
errors = [r["message"] for r in rules if not r["check"](value)]
|
|
70
|
-
return {"valid": True} if not errors else {"valid": False, "errors": errors}
|
|
71
|
-
return validate
|
|
72
|
-
|
|
73
|
-
# DON'T CREATE: Custom monads (Maybe, Either, IO)
|
|
74
|
-
# INSTEAD: Native error handling with result dicts
|
|
36
|
+
# DON'T: Custom monads (Maybe, Either, IO)
|
|
37
|
+
# DO: result dicts
|
|
75
38
|
def get_user(user_id: int, db) -> dict:
|
|
76
39
|
try:
|
|
77
|
-
|
|
78
|
-
return {"success": True, "data": user}
|
|
40
|
+
return {"success": True, "data": db.find(user_id)}
|
|
79
41
|
except Exception as e:
|
|
80
42
|
return {"success": False, "error": str(e)}
|
|
81
43
|
```
|
|
82
44
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
```python
|
|
86
|
-
# CLI Script: Simple and direct
|
|
87
|
-
def process_file(file_path: str) -> list[str]:
|
|
88
|
-
with open(file_path) as f:
|
|
89
|
-
return [line.upper() for line in f if line.strip()]
|
|
45
|
+
Match complexity to context: CLI script ≠ production service ≠ data pipeline.
|
|
90
46
|
|
|
91
|
-
|
|
92
|
-
def process_file(file_path: str, logger) -> dict:
|
|
93
|
-
try:
|
|
94
|
-
with open(file_path) as f:
|
|
95
|
-
lines = [line.strip() for line in f if line.strip()]
|
|
96
|
-
logger.info("File processed", extra={"path": file_path, "lines": len(lines)})
|
|
97
|
-
return {"success": True, "data": [line.upper() for line in lines]}
|
|
98
|
-
except OSError as e:
|
|
99
|
-
logger.error("File processing failed", extra={"path": file_path, "error": str(e)})
|
|
100
|
-
return {"success": False, "error": str(e)}
|
|
101
|
-
|
|
102
|
-
# Data Pipeline: Composable transformations
|
|
103
|
-
def create_file_processor(transforms: list[callable]):
|
|
104
|
-
compiled = [compile_transform(t) for t in transforms]
|
|
105
|
-
|
|
106
|
-
def process(file_path: str, logger) -> dict:
|
|
107
|
-
with open(file_path) as f:
|
|
108
|
-
data = f.read()
|
|
109
|
-
for transform in compiled:
|
|
110
|
-
data = transform(data)
|
|
111
|
-
return {"success": True, "data": data}
|
|
112
|
-
|
|
113
|
-
return process
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
## Core FP Patterns (Error-Preventing Essentials)
|
|
47
|
+
## Core Patterns
|
|
117
48
|
|
|
118
49
|
### 1. Purity and Side Effect Isolation
|
|
119
50
|
|
|
120
|
-
|
|
51
|
+
Separate business logic from side effects.
|
|
121
52
|
|
|
122
53
|
```python
|
|
123
|
-
#
|
|
124
|
-
def calculate_total(items: list[dict]) -> float:
|
|
125
|
-
print("Processing items") # Side effect
|
|
126
|
-
global running_total
|
|
127
|
-
running_total += sum(i["price"] for i in items) # Mutation
|
|
128
|
-
return running_total
|
|
129
|
-
|
|
130
|
-
# PURE: business logic
|
|
54
|
+
# Pure: business logic
|
|
131
55
|
def calculate_total(items: list[dict]) -> float:
|
|
132
56
|
return sum(item["price"] for item in items)
|
|
133
57
|
|
|
134
|
-
#
|
|
58
|
+
# Side effects separate
|
|
135
59
|
def log_and_calculate(items: list[dict], logger) -> float:
|
|
136
|
-
total = calculate_total(items)
|
|
137
|
-
logger.info(f"Total: {total}")
|
|
60
|
+
total = calculate_total(items)
|
|
61
|
+
logger.info(f"Total: {total}")
|
|
138
62
|
return total
|
|
139
63
|
```
|
|
140
64
|
|
|
141
|
-
|
|
142
|
-
- 100% testable with all edge cases
|
|
143
|
-
- Predictable behavior and debugging
|
|
144
|
-
- Safe for `multiprocessing` (pure functions serialize trivially)
|
|
145
|
-
- Enables memoization via `@lru_cache`
|
|
65
|
+
Pure functions: 100% testable, `@lru_cache`-able, safe for `multiprocessing`.
|
|
146
66
|
|
|
147
67
|
### 2. Composition Over Inheritance
|
|
148
68
|
|
|
149
|
-
**Rule**: Build complex behavior from simple functions.
|
|
150
|
-
|
|
151
69
|
```python
|
|
152
|
-
# Class hierarchy approach (avoid)
|
|
153
|
-
class BaseValidator:
|
|
154
|
-
def validate(self, value): raise NotImplementedError
|
|
155
|
-
|
|
156
|
-
class EmailValidator(BaseValidator):
|
|
157
|
-
def validate(self, value): ...
|
|
158
|
-
|
|
159
|
-
# Function composition (no utilities needed)
|
|
160
70
|
def validate_required(value) -> bool:
|
|
161
71
|
return value is not None and value != ""
|
|
162
72
|
|
|
@@ -166,7 +76,6 @@ def validate_email(value: str) -> bool:
|
|
|
166
76
|
def validate_length(min_len: int, max_len: int):
|
|
167
77
|
return lambda value: min_len <= len(value) <= max_len
|
|
168
78
|
|
|
169
|
-
# Simple composition without pipe() utility
|
|
170
79
|
def validate_user_email(email: str) -> dict:
|
|
171
80
|
if not validate_required(email):
|
|
172
81
|
return {"valid": False, "error": "Required"}
|
|
@@ -177,101 +86,67 @@ def validate_user_email(email: str) -> dict:
|
|
|
177
86
|
return {"valid": True}
|
|
178
87
|
```
|
|
179
88
|
|
|
180
|
-
### 3. Dependency Injection
|
|
181
|
-
|
|
182
|
-
**Rule**: Pass dependencies explicitly, avoid global state.
|
|
89
|
+
### 3. Dependency Injection via Parameters
|
|
183
90
|
|
|
184
91
|
```python
|
|
185
|
-
# Hidden
|
|
92
|
+
# Hidden deps — untestable
|
|
186
93
|
def save_user(user_data: dict) -> dict:
|
|
187
|
-
hashed = bcrypt.hash(user_data["password"])
|
|
188
|
-
return database.save({**user_data, "password": hashed})
|
|
94
|
+
hashed = bcrypt.hash(user_data["password"])
|
|
95
|
+
return database.save({**user_data, "password": hashed})
|
|
189
96
|
|
|
190
|
-
# Explicit
|
|
97
|
+
# Explicit deps — fully testable
|
|
191
98
|
def save_user(user_data: dict, hasher, database) -> dict:
|
|
192
99
|
hashed = hasher.hash(user_data["password"])
|
|
193
100
|
return database.save({**user_data, "password": hashed})
|
|
194
101
|
|
|
195
|
-
#
|
|
102
|
+
# Closure-based DI for repeated use
|
|
196
103
|
def create_user_service(hasher, database):
|
|
197
104
|
def save(user_data: dict) -> dict:
|
|
198
105
|
return save_user(user_data, hasher, database)
|
|
199
|
-
|
|
200
106
|
def find(user_id: int) -> dict:
|
|
201
107
|
return database.find_by_id(user_id)
|
|
202
|
-
|
|
203
108
|
return {"save": save, "find": find}
|
|
204
109
|
```
|
|
205
110
|
|
|
206
|
-
### 4. Immutability
|
|
207
|
-
|
|
208
|
-
**Rule**: Don't mutate input data. Create new structures.
|
|
111
|
+
### 4. Immutability
|
|
209
112
|
|
|
210
113
|
```python
|
|
211
114
|
from dataclasses import dataclass, replace, field
|
|
212
115
|
from typing import NamedTuple
|
|
213
116
|
|
|
214
|
-
# Frozen dataclasses — the primary immutable record
|
|
215
117
|
@dataclass(frozen=True)
|
|
216
118
|
class User:
|
|
217
119
|
name: str
|
|
218
120
|
email: str
|
|
219
121
|
settings: dict = field(default_factory=dict)
|
|
220
122
|
|
|
221
|
-
# "Update" via replace() — returns new instance
|
|
222
123
|
def update_email(user: User, new_email: str) -> User:
|
|
223
|
-
return replace(user, email=new_email)
|
|
224
|
-
|
|
225
|
-
# NamedTuple — lighter weight alternative
|
|
226
|
-
class Point(NamedTuple):
|
|
227
|
-
x: float
|
|
228
|
-
y: float
|
|
229
|
-
|
|
230
|
-
def translate(point: Point, dx: float, dy: float) -> Point:
|
|
231
|
-
return Point(point.x + dx, point.y + dy)
|
|
124
|
+
return replace(user, email=new_email) # new instance
|
|
232
125
|
|
|
233
|
-
# Dict
|
|
126
|
+
# Dict/list — spread, don't mutate
|
|
234
127
|
def update_settings(user: dict, settings: dict) -> dict:
|
|
235
128
|
return {**user, "settings": {**user.get("settings", {}), **settings}}
|
|
236
129
|
|
|
237
|
-
# List operations without mutation
|
|
238
|
-
def add_item(items: list, new_item) -> list:
|
|
239
|
-
return [*items, new_item]
|
|
240
|
-
|
|
241
|
-
def remove_item(items: list[dict], item_id: int) -> list[dict]:
|
|
242
|
-
return [i for i in items if i["id"] != item_id]
|
|
243
|
-
|
|
244
130
|
def update_item(items: list[dict], item_id: int, updates: dict) -> list[dict]:
|
|
245
131
|
return [{**i, **updates} if i["id"] == item_id else i for i in items]
|
|
246
132
|
```
|
|
247
133
|
|
|
248
|
-
>
|
|
134
|
+
> `frozen=True` is shallow — nested dicts/lists remain mutable. Use `pyrsistent` or `MappingProxyType` for deep immutability.
|
|
249
135
|
|
|
250
136
|
## Python-Specific Patterns
|
|
251
137
|
|
|
252
|
-
### Comprehensions Over map/filter
|
|
138
|
+
### Comprehensions (Prefer Over map/filter)
|
|
253
139
|
|
|
254
140
|
```python
|
|
255
|
-
#
|
|
256
|
-
active_names = list(map(
|
|
257
|
-
lambda u: u["name"],
|
|
258
|
-
filter(lambda u: u["active"], users)
|
|
259
|
-
))
|
|
260
|
-
|
|
261
|
-
# Comprehension style (Pythonic FP)
|
|
141
|
+
# Pythonic FP
|
|
262
142
|
active_names = [u["name"] for u in users if u["active"]]
|
|
263
|
-
|
|
264
|
-
# Dict comprehension for transformations
|
|
265
143
|
name_by_id = {u["id"]: u["name"] for u in users}
|
|
266
|
-
|
|
267
|
-
# Generator expression for lazy evaluation (large data)
|
|
268
144
|
totals = sum(item["price"] for item in items if item["taxable"])
|
|
269
145
|
```
|
|
270
146
|
|
|
271
147
|
### Generators for Lazy Pipelines
|
|
272
148
|
|
|
273
149
|
```python
|
|
274
|
-
# Generators are Python's answer to lazy evaluation
|
|
275
150
|
def read_records(path: str):
|
|
276
151
|
with open(path) as f:
|
|
277
152
|
for line in f:
|
|
@@ -288,47 +163,34 @@ def transform(records):
|
|
|
288
163
|
|
|
289
164
|
# Compose lazily — no intermediate lists
|
|
290
165
|
pipeline = transform(filter_valid(read_records("data.csv")))
|
|
291
|
-
results = list(pipeline) # Materializes only when needed
|
|
292
166
|
```
|
|
293
167
|
|
|
294
|
-
### itertools
|
|
168
|
+
### itertools / functools
|
|
295
169
|
|
|
296
170
|
```python
|
|
297
171
|
from functools import partial, lru_cache, reduce
|
|
298
|
-
from itertools import
|
|
299
|
-
from operator import itemgetter
|
|
172
|
+
from itertools import groupby
|
|
173
|
+
from operator import itemgetter, mul
|
|
300
174
|
|
|
301
|
-
# partial —
|
|
175
|
+
# partial — Pythonic curry
|
|
302
176
|
multiply_by_tax = partial(lambda rate, price: price * (1 + rate), 0.08)
|
|
303
177
|
|
|
304
|
-
# lru_cache — memoize pure functions
|
|
178
|
+
# lru_cache — memoize pure functions only
|
|
305
179
|
@lru_cache(maxsize=256)
|
|
306
180
|
def fibonacci(n: int) -> int:
|
|
307
|
-
if n < 2
|
|
308
|
-
return n
|
|
309
|
-
return fibonacci(n - 1) + fibonacci(n - 2)
|
|
310
|
-
|
|
311
|
-
# operator module — eliminate trivial lambdas
|
|
312
|
-
from operator import itemgetter, attrgetter
|
|
181
|
+
return n if n < 2 else fibonacci(n - 1) + fibonacci(n - 2)
|
|
313
182
|
|
|
183
|
+
# operator — eliminate trivial lambdas
|
|
314
184
|
sorted_by_name = sorted(users, key=itemgetter("name"))
|
|
315
|
-
# vs: sorted(users, key=lambda u: u["name"])
|
|
316
|
-
|
|
317
|
-
# itertools for complex transformations
|
|
318
|
-
from itertools import groupby
|
|
319
185
|
|
|
320
186
|
def group_by_category(items: list[dict]) -> dict:
|
|
321
187
|
sorted_items = sorted(items, key=itemgetter("category"))
|
|
322
|
-
return {
|
|
323
|
-
k: list(v)
|
|
324
|
-
for k, v in groupby(sorted_items, key=itemgetter("category"))
|
|
325
|
-
}
|
|
188
|
+
return {k: list(v) for k, v in groupby(sorted_items, key=itemgetter("category"))}
|
|
326
189
|
```
|
|
327
190
|
|
|
328
|
-
### Pattern Matching (
|
|
191
|
+
### Pattern Matching (3.10+)
|
|
329
192
|
|
|
330
193
|
```python
|
|
331
|
-
# Structural pattern matching for clean dispatch
|
|
332
194
|
def process_event(event: dict) -> dict:
|
|
333
195
|
match event:
|
|
334
196
|
case {"type": "click", "target": target}:
|
|
@@ -339,48 +201,31 @@ def process_event(event: dict) -> dict:
|
|
|
339
201
|
return handle_error(code, msg)
|
|
340
202
|
case _:
|
|
341
203
|
return {"error": f"Unknown event type: {event.get('type')}"}
|
|
342
|
-
|
|
343
|
-
# Pattern matching with guards
|
|
344
|
-
def categorize_score(score: int) -> str:
|
|
345
|
-
match score:
|
|
346
|
-
case n if n >= 90: return "A"
|
|
347
|
-
case n if n >= 80: return "B"
|
|
348
|
-
case n if n >= 70: return "C"
|
|
349
|
-
case _: return "F"
|
|
350
204
|
```
|
|
351
205
|
|
|
352
|
-
### Type Hints
|
|
206
|
+
### Type Hints
|
|
353
207
|
|
|
354
208
|
```python
|
|
355
209
|
from typing import TypeVar, Callable, Optional
|
|
356
|
-
from collections.abc import Iterable
|
|
210
|
+
from collections.abc import Iterable
|
|
357
211
|
|
|
358
212
|
T = TypeVar("T")
|
|
359
213
|
U = TypeVar("U")
|
|
360
214
|
|
|
361
|
-
# Type hints make pure function contracts explicit
|
|
362
215
|
def transform_all(items: Iterable[T], fn: Callable[[T], U]) -> list[U]:
|
|
363
216
|
return [fn(item) for item in items]
|
|
364
217
|
|
|
365
|
-
# Union types (3.10+) for result patterns
|
|
366
|
-
def divide(a: float, b: float) -> dict:
|
|
367
|
-
if b == 0:
|
|
368
|
-
return {"success": False, "error": "Division by zero"}
|
|
369
|
-
return {"success": True, "data": a / b}
|
|
370
|
-
|
|
371
|
-
# Optional signals nullable return
|
|
372
218
|
def find_user(users: list[dict], user_id: int) -> Optional[dict]:
|
|
373
219
|
return next((u for u in users if u["id"] == user_id), None)
|
|
374
220
|
```
|
|
375
221
|
|
|
376
|
-
## Data Science / ML
|
|
222
|
+
## Data Science / ML
|
|
377
223
|
|
|
378
|
-
### pandas pipe()
|
|
224
|
+
### pandas pipe()
|
|
379
225
|
|
|
380
226
|
```python
|
|
381
227
|
import pandas as pd
|
|
382
228
|
|
|
383
|
-
# Each function: DataFrame in, DataFrame out (pure transforms)
|
|
384
229
|
def clean_nulls(df: pd.DataFrame) -> pd.DataFrame:
|
|
385
230
|
return df.dropna(subset=["email", "name"])
|
|
386
231
|
|
|
@@ -392,272 +237,121 @@ def add_age_group(df: pd.DataFrame) -> pd.DataFrame:
|
|
|
392
237
|
labels = ["youth", "young_adult", "adult", "senior"]
|
|
393
238
|
return df.assign(age_group=pd.cut(df["age"], bins=bins, labels=labels))
|
|
394
239
|
|
|
395
|
-
|
|
396
|
-
result = (
|
|
397
|
-
raw_df
|
|
398
|
-
.pipe(clean_nulls)
|
|
399
|
-
.pipe(normalize_names)
|
|
400
|
-
.pipe(add_age_group)
|
|
401
|
-
)
|
|
240
|
+
result = raw_df.pipe(clean_nulls).pipe(normalize_names).pipe(add_age_group)
|
|
402
241
|
```
|
|
403
242
|
|
|
404
|
-
###
|
|
405
|
-
|
|
406
|
-
```python
|
|
407
|
-
# Pure transformation functions for ML pipelines
|
|
408
|
-
def extract_features(df: pd.DataFrame) -> pd.DataFrame:
|
|
409
|
-
return df.assign(
|
|
410
|
-
word_count=df["text"].str.split().str.len(),
|
|
411
|
-
has_url=df["text"].str.contains(r"https?://", regex=True),
|
|
412
|
-
text_length=df["text"].str.len(),
|
|
413
|
-
)
|
|
414
|
-
|
|
415
|
-
# scikit-learn FunctionTransformer for pipeline integration
|
|
416
|
-
from sklearn.preprocessing import FunctionTransformer
|
|
417
|
-
from sklearn.pipeline import Pipeline
|
|
418
|
-
|
|
419
|
-
feature_extractor = FunctionTransformer(extract_features)
|
|
420
|
-
|
|
421
|
-
pipeline = Pipeline([
|
|
422
|
-
("features", feature_extractor),
|
|
423
|
-
("scaler", StandardScaler()),
|
|
424
|
-
("model", LogisticRegression()),
|
|
425
|
-
])
|
|
426
|
-
```
|
|
427
|
-
|
|
428
|
-
### Polars (FP-Friendly Alternative to pandas)
|
|
243
|
+
### Polars (FP-native)
|
|
429
244
|
|
|
430
245
|
```python
|
|
431
246
|
import polars as pl
|
|
432
247
|
|
|
433
|
-
# Polars expressions are lazy and composable by design
|
|
434
248
|
result = (
|
|
435
|
-
pl.scan_csv("data.csv")
|
|
249
|
+
pl.scan_csv("data.csv")
|
|
436
250
|
.filter(pl.col("status") == "active")
|
|
437
251
|
.with_columns(
|
|
438
252
|
full_name=pl.col("first_name") + " " + pl.col("last_name"),
|
|
439
|
-
age_group=pl.when(pl.col("age") < 30).then(pl.lit("young"))
|
|
440
|
-
.otherwise(pl.lit("senior")),
|
|
253
|
+
age_group=pl.when(pl.col("age") < 30).then(pl.lit("young")).otherwise(pl.lit("senior")),
|
|
441
254
|
)
|
|
442
255
|
.group_by("department")
|
|
443
256
|
.agg(pl.col("salary").mean().alias("avg_salary"))
|
|
444
|
-
.collect()
|
|
257
|
+
.collect()
|
|
445
258
|
)
|
|
446
259
|
```
|
|
447
260
|
|
|
448
261
|
## Result Type Pattern
|
|
449
262
|
|
|
450
263
|
```python
|
|
451
|
-
# Standard result shape — consistent across the codebase
|
|
452
264
|
def success(data=None) -> dict:
|
|
453
265
|
return {"success": True, "data": data}
|
|
454
266
|
|
|
455
267
|
def failure(error: str) -> dict:
|
|
456
268
|
return {"success": False, "error": error}
|
|
457
269
|
|
|
458
|
-
# Chain results with early return
|
|
459
270
|
def process_order(order: dict) -> dict:
|
|
460
271
|
validated = validate_order(order)
|
|
461
272
|
if not validated["success"]:
|
|
462
273
|
return validated
|
|
463
|
-
|
|
464
274
|
priced = calculate_pricing(validated["data"])
|
|
465
275
|
if not priced["success"]:
|
|
466
276
|
return priced
|
|
467
|
-
|
|
468
277
|
return submit_order(priced["data"])
|
|
469
278
|
```
|
|
470
279
|
|
|
471
|
-
## Testing
|
|
472
|
-
|
|
473
|
-
**Philosophy**: Pure functions enable testing all edge cases systematically.
|
|
280
|
+
## Testing
|
|
474
281
|
|
|
475
282
|
```python
|
|
476
283
|
import pytest
|
|
477
284
|
|
|
478
|
-
# Parametrized testing — the FP way
|
|
479
285
|
@pytest.mark.parametrize("price,rate,expected", [
|
|
480
286
|
(100.0, 0.1, 10.0),
|
|
481
287
|
(50.0, 0.2, 10.0),
|
|
482
288
|
(0.0, 0.1, 0.0),
|
|
483
|
-
(100.0, 0.0, 0.0),
|
|
484
289
|
])
|
|
485
290
|
def test_calculate_discount(price, rate, expected):
|
|
486
291
|
assert calculate_discount(price, rate) == expected
|
|
487
292
|
|
|
488
|
-
# Edge case testing — pure functions handle all inputs
|
|
489
|
-
@pytest.mark.parametrize("invalid_input", [None, "", [], {}, 0])
|
|
490
|
-
def test_validate_required_rejects_invalid(invalid_input):
|
|
491
|
-
assert not validate_required(invalid_input)
|
|
492
|
-
|
|
493
|
-
# Testing result chains
|
|
494
|
-
def test_process_order_validation_failure():
|
|
495
|
-
result = process_order({"items": []})
|
|
496
|
-
assert not result["success"]
|
|
497
|
-
assert "empty" in result["error"].lower()
|
|
498
|
-
|
|
499
|
-
# Frozen dataclass testability
|
|
500
293
|
def test_update_email_returns_new_instance():
|
|
501
294
|
user = User(name="Alice", email="old@test.com")
|
|
502
295
|
updated = update_email(user, "new@test.com")
|
|
503
296
|
assert updated.email == "new@test.com"
|
|
504
|
-
assert user.email == "old@test.com" #
|
|
297
|
+
assert user.email == "old@test.com" # original unchanged
|
|
505
298
|
```
|
|
506
299
|
|
|
507
|
-
## Performance
|
|
508
|
-
|
|
509
|
-
**IMPORTANT**: Optimize only when needed with evidence.
|
|
510
|
-
|
|
511
|
-
### Memoization for Expensive Pure Functions
|
|
300
|
+
## Performance (Evidence-Based Only)
|
|
512
301
|
|
|
513
302
|
```python
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
# ONLY memoize pure functions with hashable arguments
|
|
303
|
+
# Memoize pure functions with hashable args
|
|
517
304
|
@lru_cache(maxsize=128)
|
|
518
305
|
def parse_template(template: str) -> dict:
|
|
519
|
-
# Expensive parsing — cached after first call
|
|
520
306
|
return heavy_parse(template)
|
|
521
307
|
|
|
522
|
-
#
|
|
523
|
-
_cache = {}
|
|
524
|
-
def get_config(key: str) -> dict:
|
|
525
|
-
if key not in _cache:
|
|
526
|
-
_cache[key] = load_config(key)
|
|
527
|
-
return _cache[key]
|
|
528
|
-
```
|
|
529
|
-
|
|
530
|
-
### Generator Pipelines for Large Data
|
|
531
|
-
|
|
532
|
-
```python
|
|
533
|
-
# Memory-efficient: processes one record at a time
|
|
308
|
+
# Batch generator for large data
|
|
534
309
|
def process_large_file(path: str):
|
|
535
|
-
|
|
536
|
-
valid = filter_valid(records) # Generator
|
|
537
|
-
transformed = transform(valid) # Generator
|
|
538
|
-
|
|
539
|
-
# Only materializes in batches
|
|
310
|
+
pipeline = transform(filter_valid(read_records(path)))
|
|
540
311
|
batch = []
|
|
541
|
-
for record in
|
|
312
|
+
for record in pipeline:
|
|
542
313
|
batch.append(record)
|
|
543
314
|
if len(batch) >= 1000:
|
|
544
315
|
yield batch
|
|
545
316
|
batch = []
|
|
546
317
|
if batch:
|
|
547
318
|
yield batch
|
|
548
|
-
```
|
|
549
|
-
|
|
550
|
-
### multiprocessing with Pure Functions
|
|
551
|
-
|
|
552
|
-
```python
|
|
553
|
-
from multiprocessing import Pool
|
|
554
319
|
|
|
555
320
|
# Pure functions parallelize trivially
|
|
556
|
-
|
|
557
|
-
return {**item, "score": compute_score(item)}
|
|
558
|
-
|
|
559
|
-
# No shared state, no locks, no bugs
|
|
321
|
+
from multiprocessing import Pool
|
|
560
322
|
with Pool(4) as pool:
|
|
561
323
|
results = pool.map(process_item, items)
|
|
562
324
|
```
|
|
563
325
|
|
|
564
|
-
##
|
|
565
|
-
|
|
566
|
-
### Recursion Limit
|
|
326
|
+
## Gotchas
|
|
567
327
|
|
|
328
|
+
**Recursion limit** — no tail call optimization. Use `reduce` for large inputs:
|
|
568
329
|
```python
|
|
569
|
-
# Python has a 1000-call recursion limit. No tail call optimization.
|
|
570
|
-
# DON'T: recursive algorithms for large inputs
|
|
571
|
-
def factorial(n):
|
|
572
|
-
return 1 if n <= 1 else n * factorial(n - 1) # Blows up at n > 1000
|
|
573
|
-
|
|
574
|
-
# DO: iterative or reduce
|
|
575
330
|
from functools import reduce
|
|
576
331
|
from operator import mul
|
|
577
|
-
|
|
578
332
|
def factorial(n: int) -> int:
|
|
579
333
|
return reduce(mul, range(1, n + 1), 1)
|
|
580
334
|
```
|
|
581
335
|
|
|
582
|
-
|
|
583
|
-
|
|
336
|
+
**Mutable defaults** — never use mutable default args:
|
|
584
337
|
```python
|
|
585
|
-
# NEVER use mutable defaults
|
|
586
|
-
def add_item(item, items=[]): # BUG: shared mutable default
|
|
587
|
-
items.append(item)
|
|
588
|
-
return items
|
|
589
|
-
|
|
590
|
-
# INSTEAD: None sentinel
|
|
591
338
|
def add_item(item, items=None):
|
|
592
|
-
|
|
593
|
-
return [*items, item] # Return new list, don't mutate
|
|
594
|
-
```
|
|
595
|
-
|
|
596
|
-
### Shallow vs Deep Copy
|
|
597
|
-
|
|
598
|
-
```python
|
|
599
|
-
# frozen=True is SHALLOW
|
|
600
|
-
@dataclass(frozen=True)
|
|
601
|
-
class Config:
|
|
602
|
-
settings: dict # This dict is still mutable!
|
|
603
|
-
|
|
604
|
-
config = Config(settings={"debug": True})
|
|
605
|
-
# config.settings = {} # FrozenInstanceError
|
|
606
|
-
config.settings["debug"] = False # This WORKS — shallow freeze
|
|
607
|
-
|
|
608
|
-
# For deep immutability: use tuples, frozensets, or pyrsistent
|
|
609
|
-
from types import MappingProxyType
|
|
610
|
-
|
|
611
|
-
def freeze_dict(d: dict):
|
|
612
|
-
return MappingProxyType(d) # Read-only view
|
|
339
|
+
return [*(items or []), item]
|
|
613
340
|
```
|
|
614
341
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
1. **"Can this be pure?"** - Separate business logic from side effects
|
|
618
|
-
2. **"Can this use native patterns?"** - Comprehensions, generators, functools, itertools
|
|
619
|
-
3. **"Can this be simplified?"** - Choose simple solution over complex abstraction
|
|
620
|
-
4. **"Is this complexity justified?"** - Evidence-based complexity decisions
|
|
621
|
-
5. **"Is this testable?"** - Pure functions enable comprehensive testing
|
|
622
|
-
6. **"Are type hints used?"** - Type hints on all public function signatures
|
|
623
|
-
|
|
624
|
-
## When to Load Reference Files
|
|
625
|
-
|
|
626
|
-
### Deep Principles and Explanations
|
|
627
|
-
**File**: `references/core-principles.md`
|
|
628
|
-
**Load when**:
|
|
629
|
-
- Learning mode or explaining WHY behind patterns
|
|
630
|
-
- Making architectural decisions
|
|
631
|
-
- Need complete Result Type patterns
|
|
632
|
-
- Anti-pattern recognition details
|
|
633
|
-
- Python-specific FP philosophy deep-dive
|
|
634
|
-
|
|
635
|
-
### Testing Methodology
|
|
636
|
-
**File**: `references/testing-patterns.md`
|
|
637
|
-
**Load when**:
|
|
638
|
-
- Building comprehensive test suites with pytest
|
|
639
|
-
- Improving test coverage
|
|
640
|
-
- Edge case analysis and boundary testing
|
|
641
|
-
- Testing data pipelines and ML code
|
|
642
|
-
- Property-based testing with Hypothesis
|
|
643
|
-
|
|
644
|
-
### Working Examples
|
|
645
|
-
**Directory**: `examples/`
|
|
646
|
-
**Load when**:
|
|
647
|
-
- Need complete working code
|
|
648
|
-
- Integration examples
|
|
649
|
-
- Learning implementation patterns
|
|
650
|
-
|
|
651
|
-
## Integration with Domain Skills
|
|
652
|
-
|
|
653
|
-
This core skill provides the foundation for:
|
|
342
|
+
**Shallow freeze** — `frozen=True` doesn't freeze nested dicts. Use `MappingProxyType` or `pyrsistent` for deep immutability.
|
|
654
343
|
|
|
655
|
-
|
|
656
|
-
- **py-fp-fastapi**: FastAPI patterns with FP principles (future)
|
|
657
|
-
- **py-fp-ml**: ML/data science patterns with FP principles (future)
|
|
344
|
+
## Quality Gates
|
|
658
345
|
|
|
659
|
-
|
|
346
|
+
1. Can this be pure? — separate logic from side effects
|
|
347
|
+
2. Can this use native patterns? — comprehensions, generators, functools, itertools
|
|
348
|
+
3. Is this complexity justified? — evidence-based decisions only
|
|
349
|
+
4. Are type hints used? — all public function signatures
|
|
660
350
|
|
|
661
|
-
##
|
|
351
|
+
## References
|
|
662
352
|
|
|
663
|
-
|
|
353
|
+
| Resource | Load when |
|
|
354
|
+
|----------|-----------|
|
|
355
|
+
| `references/core-principles.md` | Learning mode, architecture decisions, anti-pattern recognition |
|
|
356
|
+
| `references/testing-patterns.md` | Building test suites, Hypothesis property-based testing |
|
|
357
|
+
| `examples/` | Complete working code, integration examples |
|