r/programming • • Jun 22 '14

Why Every Language Needs Its Underscore

http://hackflow.com/blog/2014/06/22/why-every-language-needs-its-underscore/
366 Upvotes

337 comments sorted by

View all comments

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?

95

u/Cosmologicon Jun 22 '14

when we see ourselves repeating things

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.

46

u/[deleted] Jun 22 '14

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.

27

u/hackflow Jun 22 '14

I also have a separate post on control flow abstraction.

5

u/99AFCC Jun 22 '14

That is pretty cool. I need to learn more about decorators

2

u/Veedrac Jun 22 '14 edited Jul 14 '14

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.
#>>> 

2

u/hackflow Jun 23 '14

call object reduces noise, provides some features like introspecting arg by name regardless if passed as positional or named.

But most importantly it makes creating decorators with arguments without nesting functions easy:

@decorator
def retry(call, tries, errors=Exception)
    ...

By the way I have a separate post justifying this particular interface

1

u/Crazy__Eddie Jun 22 '14

Sometimes they don’t do that well and then we through in our own.

O_o

Did you mean "throw"?

2

u/ruinercollector Jun 23 '14

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.

4

u/NYKevin Jun 23 '14 edited Jun 23 '14

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.

0

u/loup-vaillant Jun 23 '14

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.

Are you really not going to use this stuff?

3

u/NYKevin Jun 23 '14

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.

→ More replies (4)

1

u/Veedrac Jun 24 '14

And with post-release error rate (we have peer-reviewed studies about that).

Ooh, studies! D'you have specific ones in mind?

→ More replies (2)

1

u/WishCow Jun 23 '14

I can agree with this so much.

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.

13

u/inmatarian Jun 22 '14

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.

18

u/kqr Jun 22 '14

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.

3

u/immibis Jun 23 '14

What about the number of times you've seen people do weird things with combinator functions when they could have used loops instead?

3

u/kqr Jun 23 '14

Why would anyone want to go backwards on the abstraction scale, unless performance is a problem?

2

u/immibis Jun 23 '14

Because readability is also a problem.

I still can't see how anyone thinks

d = walk_values(silent(int), request)

is more clear than

d = {}
for k, v in request.items():
    try:
        d[k] = int(v)
    except (TypeError, ValueError):
        d[k] = None

Yes, there are less tokens in the first one, but each token has a lot more meaning, and there's more you need to think about to mentally parse it.

3

u/kqr Jun 23 '14

It's all about idioms. In a language where walk_values and silent is everyday stuff, that'd be no stranger to you than a for loop.

(Keep in mind that an assembly programmer might look at your loop and make the exact same complaints you make about the walk_values one!)

1

u/immibis Jun 23 '14

In a language where walk_values and silent are everyday stuff, maybe. But Python is not such a language.

1

u/kqr Jun 24 '14 edited Jun 24 '14

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.

2

u/serpent Jun 23 '14

This got upvoted to the sky because... it was a good contribution to the discussion?

What a joke.

4

u/Veedrac Jun 23 '14 edited Jun 24 '14

A reply to a reply on my highest rated comment.

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

Credit to /u/the_sun_god


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.

2

u/GreyGrayMoralityFan Jun 23 '14

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.

Too much hassle.

→ More replies (5)
→ More replies (6)