| name | write-docstring-first |
| description | For writing clean functions: start with docstring, iterate until code matches docstring exactly, achieve docstring-code isomorphism. |
write-docstring-first
When to Use
- Writing any new function
- When clarity of purpose matters
- Complex logic that needs explanation
- Functions that will be reused
- Teaching or educational code
When NOT to Use
- Trivial one-liners (
return x + 1)
- Private implementation details
- Rapidly prototyping (add later)
The Pattern
Docstring Fixpoint Theory: Iterate between docstring and code until they are isomorphic - each reads like a translation of the other.
def rainfall(numbers):
"""Return the mean of the non-negative values in a list,
up to the first -999 (if it shows up)."""
...
def rainfall(numbers):
"""Return the mean of the non-negative values in a list,
up to the first -999 (if it shows up)."""
return mean(non_negative(upto(-999, numbers)))
Example (from pytudes DocstringFixpoint.ipynb)
def rainfall(numbers):
"""Design a program called rainfall that consumes a list of numbers
representing daily rainfall amounts. The list may contain -999
indicating end of data. Produce the average of non-negative values
up to the first -999."""
...
def rainfall(numbers):
"""Produce the average of the non-negative values in a list,
up to the first -999 (if it shows up)."""
...
def rainfall(numbers):
"""Return the mean of the non-negative values in a list,
up to the first -999 (if it shows up)."""
return mean(non_negative(upto(-999, numbers)))
def upto(sentinel, items):
"""Return items that appear before sentinel,
or all items if sentinel doesn't appear."""
return items[:items.index(sentinel)] if sentinel in items else items
def non_negative(numbers):
"""The numbers that are >= 0."""
return [x for x in numbers if x >= 0]
Key Principles
- Docstring first: Specification before implementation
- Iterate both ways: Edit docstring, then code, then docstring...
- Match vocabulary: Use same words in both
- Helpers inherit pattern: Each has its own docstring-code match
- Fixpoint = done: When neither needs changing, you're done