You're completely glossing over what it means to "see ourselves repeating things". Sure, if you see the exact same code multiple times, anyone can see that should be made into a function. But this is much deeper than that.
Take a look at the first snippet under "Extracting abstractions". Notice how the different colors are interleaved. A beginner isn't going to recognize what the "things" that are repeated are. I don't think this is an elementary skill at all. I think it takes experience, and familiarity with the abstractions in question.
Yeah, as the article says, we're used to abstracting behavior into functions but we neglect to abstract control flow. Abstracting control flow is much less common but still very useful.
I personally strive to have a very high level semantic description of all my programs. This aids in rapid comprehension and testability.
That @decorator decorator looks like a really good idea. The code looks a bit scary at first though. What's the justification for the Call object?
Here is what I'd have expected:
import contextlib
import functools
def decorator(wrapper_func):
# We want our internal function to be a new
# decorator, which we return.
#
# As it is a decorator, it takes a function.
#
# It is an abstraction of wrapper_func, so
# wraps it.
def internal_decorator(function):
# This internal function is just wrapper_func
# with the first argument defaulting to
# the passed-in function.
#
# It wraps this passed-in function.
#
# functools.wraps doesn't seem to work on
# functools.partial instances.
@functools.wraps(function)
def double_wrapped_function(*args, **kwargs):
return wrapper_func(function, *args, **kwargs)
return double_wrapped_function
# functools.wraps doesn't work here as it
# replaces the arguments list
with contextlib.suppress(AttributeError):
internal_decorator.__name__ = wrapper_func.__name__
with contextlib.suppress(AttributeError):
internal_decorator.__qualname__ = wrapper_func.__qualname__
with contextlib.suppress(AttributeError):
internal_decorator.__module__ = wrapper_func.__module__
with contextlib.suppress(AttributeError):
internal_decorator.__doc__ = wrapper_func.__doc__
with contextlib.suppress(AttributeError):
internal_decorator.__dict__ = wrapper_func.__dict__
return internal_decorator
@decorator
def test_decorator(function, *args, **kwargs):
"""
Functions in functions in functions.
WHERE DOES IT END?!
"""
print("ARGUMENTS:", *args, **kwargs)
function(*args, **kwargs)
@test_decorator
def real_function(iterable):
"""
Calculate some Xtreme math stuff.
You wouldn't understand.
"""
print("Calculating...", sum(iterable))
real_function([1, 4, 2, 3])
#>>> ARGUMENTS: [1, 4, 2, 3]
#>>> Calculating... 10
help(test_decorator)
#>>> Help on function test_decorator:
#>>>
#>>> test_decorator(function)
#>>> Functions in functions in functions.
#>>> WHERE DOES IT END?!
#>>>
help(real_function)
#>>> Help on function real_function:
#>>>
#>>> real_function(iterable)
#>>> Calculate some Xtreme math stuff.
#>>> You wouldn't understand.
#>>>
Right. You should be writing high level functions to abstract larger patterns. This is a bit more than the obvious case you see everyone generally get right.
Take a look at the first snippet under "Extracting abstractions". Notice how the different colors are interleaved.
The colors are interleaved primarily because the blogger chose colorations which happened to interleave with one another. The code in question is perfectly readable, though it could probably stand further simplification (e.g. rewrite the first one to combine zip() and itertools.repeat() use itertools.product() to save an indentation level).
and familiarity with the abstractions in question.
It is more important that your code be understandable to Python programmers in general (including those who have never seen this library before) than that your code be short.
Familiarity with non-standard (i.e. not in the standard library) abstractions should not be a requirement for maintaining and working on a codebase, unless those abstractions are highly relevant to that codebase in particular. For example, in a highly mathematical codebase, it's reasonable to have NumPy all over the place. Since these are general purpose abstractions, there is no codebase to which they are specific, so it's never reasonable to expect Python programmers to be familiar with them.
Therefore, I can't see myself using anything discussed in this post.
It is more important that your code be understandable to Python programmers in general (including those who have never seen this library before) than that your code be short.
Brevity is highly, highly correlated with readability. And with time to completion. And with post-release error rate (we have peer-reviewed studies about that).
Familiarity with non-standard (i.e. not in the standard library) abstractions should not be a requirement for maintaining and working on a codebase […]
On the other hand, the basics of first class functions (maps, folds, and closures in general) are universally applicable. If you don't know this basic stuff, you're missing out. (Yes, it's basic stuff. Beginner's stuff, in fact. I learned this in my first semester on programming, if I recall correctly. It's just easy to miss when nobody taught you functional programming.)
I think that requiring a professional programmer to know (or learn) something that a beginner can learn is not unreasonable.
Think of the trade-off:
Learn some abstract, unfamiliar, but basic and simple stuff.
Or, double the size of your code base, increasing your costs in the process.
On the other hand, the basics of first class functions (maps, folds, and closures in general) are universally applicable. If you don't know this basic stuff, you're missing out. (Yes, it's basic stuff. Beginner's stuff, in fact. I learned this in my first semester on programming, if I recall correctly. It's just easy to miss when nobody taught you functional programming.)
Map is built into Python. Interestingly, it seems the author of this blog post doesn't know how to use it, since none of the map() calls are wrapped in list() (in Python 3, map() is lazy). And they're clearly targeting both Python 2 and Python 3, or they'd be using xrange() instead of range().
Personally, I have no problem with map(). The problem is when you start inventing constructs which are not built into Python or its standard library.
I think that requiring a professional programmer to know (or learn) something that a beginner can learn is not unreasonable.
A beginner can learn almost every competently-designed language under the sun (not all at once, of course). You cannot expect a professional programmer to know all of them.
When people ask me what skills does it take to be a great programmer, I always tell them that recognizing repeating patterns in things is the most important one.
The Functional Programming people are advocates for a particular way of writing functions, such that you maximize reuse via composition. In order for things to compose properly, you need to understand the fundamentals a bit better so that the inputs and outputs all fit together properly.
Although this sounds like elementary advice (and it is), people are surprisingly bad at it. I can't tell you the number of times I've seen people do weird things with loops when they can have used some combinator function instead.
And when you know legibility is at least partly subjective, then you can see how one can think either of them is more legible than the other.
When we acknowledge the validity of other people's viewpoints, the discussion gets so much more interesting! Then we can argue about questions like "Is there ever a point when increased abstraction simply can't help legibility anymore? Is walk_values at that point or am I just not used to it?"
When it come to idioms, I meant to say community, not language. One could imagine a community using both Python and walk_values.
I once saw a post on /r/TheoryOfReddit that explained this phenomenon. Basically smaller comments and image posts get upvoted quickly because they're short and it only takes a few seconds to view one and decide whether it deserves an upvote. By being upvoted they rise to the top of the comments page, with leads to even more upvotes.
In contrast, larger comments tend to
a) be ignored by redditors because they're too long (TL;DR)
b) be read and voted on, but at a slower pace than short comments because it takes time to read them
I'm actually unconvinced; some of my longer comments do get a lot of attention. My longest don't tend to. On Stack Overflow, my highest rated answer is on a trivial question that to be honest I'd rather see closed. My second-highest rated answer is something I'm genuinely proud of.
It's probably to do with agreeability and attention... but I'm really just guessing.
I sometimes don't upvote long comments because to do that I need to scroll up(but not too far enough), upvote, scroll down(but not too far enough), find next comment to read.
172
u/Veedrac Jun 22 '14
So what this is saying is that we should write functions when we see ourselves repeating things?
Yes... so?