Hacker News new | ask | show | jobs
by lee 5169 days ago
Can someone give me an example where using a code block is better than using a simple function? And is the added complexity and loss of readability worth it?
5 comments

Some people may consider:

    def is_prime(n):
        if n % 2 == 0:
            return False

        sqrt_n = int(math.floor(math.sqrt(n)))
        for i in range(3, sqrt_n + 1, 2):
            if n % i == 0:
                return False
        return True

    def main():
        with concurrent.futures.ProcessPoolExecutor() as executor:
            for number, prime in zip(PRIMES, executor.map(is_prime, PRIMES)):
                print('%d is prime: %s' % (number, prime))

uglier than (borrowing some JavaScript syntax):

    def main():
        with concurrent.futures.ProcessPoolExecutor() as executor:
            for number, prime in zip(PRIMES, executor.map(
                    function(n) {
                        if n % 2 == 0:
                            return False

                        sqrt_n = int(math.floor(math.sqrt(n)))
                        for i in range(3, sqrt_n + 1, 2):
                            if n % i == 0:
                                return False
                        return True
                    }
                    , PRIMES)):

                print('%d is prime: %s' % (number, prime))
I don't, mostly because in the second example, I cannot unit test the anon version of is_prime in isolation. This is a rather trivial case, but I've seen much more complex and enigmatic anon functions in Node applications.

Note: the original comes from http://docs.python.org/dev/library/concurrent.futures.html#p...

"Some people may consider: [example A] uglier than: [example B]."

Do people really think that? I ask in all seriousness. I am a hardware developer by profession and don't often dive very deep in software, though I love exploring software space.

I find example A to be very clear and readable. I like to look at things in bite sized chunks.

I also like to create things in bite sized chunks. In hardware design, specifically digital chip design, I will know what my overall problem is to solve (aka the requirements), but I don't always have a clear view of what pieces I need to get there.

In the cases where I'm not sure about the smaller pieces, I'll write the larger overall structure and when I get to a spot where I'll need a block of smaller functionality, I'll just make up a function name on the spot and pretend it's already written and does the right thing.

Then I take a second pass, implementing those smaller blocks, and keep going down. I guess that's a typical top-down approach? I don't know, but it is what I do.

But I also do bottom up. Even without knowing all the steps between, I'll often have some idea that there are some basic building blocks I'm going to need, and I'll implement those.

So I iterate with some steps top-down, others bottom-up, until I meet in the middle.

All that is a long winded way for me to say that your example A fits both approaches for me. I can start with the overall structure and implement "main" first, or know what my basis will need and start with "is_prime" first.

Whereas example B seems like an all at once approach that is both more difficult to write and read. At least, that is how it seems to me.

edit: wording

> Do people really think that?

People have been investing a lot of time in this. That certainly indicates someone considers it a worthwhile endeavor.

I'm not sure there is one. They're useful in C# as a way of defining a function within another function (which sometimes makes for more readable code). For example:

    public int CountRelevantItems(IEnumerable<Thing> things)
    {
      // Non-trivial filter that you don't want in a where lamdba
      Func<Thing, bool> isRelevant = (t) =>
      {
        ...
      }
      
      return things.Where(t => isRelevant(t)).Count()
    }
But in Python you can already define functions inside of functions so you have a better solution:

    def CountRelevantThings(things):
      def isRelevant(thing):
        ...
      return len(filter(isRelevant, things))
> They're useful in C# as a way of defining a function within another function (which sometimes makes for more readable code)

Why wouldn't you just create another function in the same class?

Just personal preference really, if your "isRelevant" filter was only ever going to be used by your "CountRelevantItems" method you might consider it more readable to have the logic of isRelevant right there in the CountRelevantItems method rather than elsewhere in the class.
If the code block can run in a different thread (like in Grand Central Dispatch) it is very useful to do stuff in the background without blocking the GUI (that executes in a specific thread) and using the current scope variables. A progress bar is a clear example of this.

Doing this without code blocks adds extra code like launching another thread, connecting the scope to the new thread.

Sorry, I'm not following. Why couldn't you use a function here instead of a code block to do the exact same thing?
I think that "readability" also depends on what you're used to reading and writing. To me, my code blocks are quite readable, but that's only true because I've seen a lot of them in these last two weeks.

Code blocks let you write

  with myfunc(iter) << 'x, y':
    code which uses x and y
instead of

  def some_func_name(x, y):
    code which uses x and y
  myfunc(iter, some_func_name)
If you prefer the second form, then you don't need code blocks. I think the first form is much better. It's not just a matter of saving one single line: you don't have to define a function which you'll never use again, so the first code is actually more readable.

    "Here is what my DSL can look like with Blocks in Smalltalk"
    (condition)
        ifTrue: [ ... ]
        ifFalse: [ ... ]
        ifMaybe: [ ... ]
You can accomplish the same thing with named functions and function pointers, but it makes the DSL very awkward to use. Imagine if you had to write all of your conditional logic (if-then and if-then-else and switch) using the names of functions defined elsewhere. That would be much less readable than being able to write block of code inline.
Surely that depends on how well you name the functions?
If by that, you mean that the intention revealing name of the function is always as good as being able to read their source code, then I'm a bit dubious. That's a little better than assuming the comments are never outdated or outright lies. The thing is, naming really well is often hard. By requiring so many more names, I think you've added a whole lot of workload to the developer.

In the right environment, that might actually encourage developers to write short methods. In the wrong one, it might encourage longer ones. It would discourage me from writing DSL control structures.

Gedankenexperiment. Imagine this: An IDE that quickly lets you browse the code for a function by pressing a key, or hovering over its name invoked in code. Some smart cookie then comes up with an add-on that provides a switchable view with in-lines for the function code, as if in a code block.