Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Why Python doesn't need blocks (stupidpythonideas.blogspot.com)
40 points by ceronman on June 30, 2014 | hide | past | favorite | 46 comments


Guido himself has said no multiline lambdas is an explicit design choice, and it's not really a technical issue:

"But the complexity of any proposed solution for this puzzle is immense, to me: it requires the parser (or more precisely, the lexer) to be able to switch back and forth between indent-sensitive and indent-insensitive modes, keeping a stack of previous modes and indentation level. Technically that can all be solved (there's already a stack of indentation levels that could be generalized). But none of that takes away my gut feeling that it is all an elaborate Rube Goldberg contraption."

source: http://www.artima.com/weblogs/viewpost.jsp?thread=147358

EDIT: This was from an earlier proposal in 2006.


It definitely is a technical issue: Python simply doesn't support multiline expressions.

Anything utilizing an indent block must be a statement by the nature of the Python grammar, and since lambdas pretty much require returning a value (the new lambda) to be useful (and are thus expressions), they cannot be modeled as statements in Python, and therefore can't utilize indent blocks.

This is definitely an oddity of the Python grammar, where statements vs. expressions are entangled into how Python models indent blocks. Statements can contain expressions but expressions cannot contain statements.

Compare to Haskell, an everything-is-an-expression language which still supports indent sensitivity and multiline lambdas.

Guido just chose a wacky way to implement an indent-sensitive grammar.


This is the only case where I think that using whitespace indentation for delimiting blocks of code is not a good idea. It makes this particular problem very difficult to solve elegantly. In languages where blocks are delimited by brackets is easer.


...perhaps this is a hint that significant whitespace is a bad idea?


It's an argument to weigh in the balance, yes. Given that Python is more readable than languages without significant whitespace (even when those languages have the advantage of easy multiline lambdas), I don't find it convincing.


... like $[ in Perl?


Fortunately $[ was disabled from v5.16.0 (released in 2012).

ref: http://perldoc.perl.org/perl5160delta.html


Doesn't make it any less annoying to not have them.


Language design, especially syntax design, is always a series of trade-offs. Aside from relatively rare outright mistakes (bitwise precedence in C, associativity of ?: in PHP), most syntax choices that people don't like are done to make something else easier.

I really like blocks, but Python deliberately makes it harder to support those in return for not needing scope delimiters in the majority of code that isn't using blocks. It seems like a reasonable choice to me.


>Language design, especially syntax design, is always a series of trade-offs.

Sure, but then again not all trade-offs make the right trade, err, off.


Even saying "right trade-off" goes against what I'm trying to say. It may be the right trade-off for some users and the wrong trade-off for others. Most of the time, when people say, "this programming language is doing this thing wrong," all it really means is "this isn't the right language for me for this problem."


That means any set of tradeoffs is valid. It's not. Tradeoffs are only OK if what you get back in each option is balanced. In some cases you're trading something very valuable for diminishing returns.

>It may be the right trade-off for some users and the wrong trade-off for others.

Well, then it matters how big are those groups relative to each other, and perhaps which one does something that's more important with the language and more tied to its core principles.


Honestly, if I had to choose between things I'd like to have in Python that exist in other languages, I'd choose improving the protocol supporting comprehension syntax improved to be more general (like Scala's) over blocks.

While blocks are nice to have, I've yet to see a good proposed syntax for them in Python.


associativity of ?: in PHP

TIL that PHP is even scarier than I thought.


Blocks could be allowed in one liners. That would make Python a credible solution in that use case.

(And yes, not all one liners need it. I know.)

And list comprehensions seems like a kludge from a lack of real lambdas (map). So that is an extra concept.

Edit: Ruby doesn't have closures?!

Edit 2: 2nd paragraph of the linked article: "In Ruby, Objective-C, and C, functions are not first-class objects, they don't capture closures, and they can't be defined inline." For Ruby, that don't claim there are no closures at all. Got it.


Methods aren't closures in Ruby (though we have procs and lambdas for that), but you certainly can pass methods around. Methods are objects, as are unbound methods. Both of them can be passed as arguments or returned as values. That's the definition of a first-class object.

You don't see the pattern often, as it's far less idiomatic than passing procs as a block, but you can do it.

Also, def is an expression in recent versions of Ruby. I don't remember exactly which version introduced it, but def returns the method that it created.


> Also, def is an expression in recent versions of Ruby. I don't remember exactly which version introduced it, but def returns the method that it created.

They added that in 2.1 Well, it was always an expression... in Ruby, every statement is an expression. But it always used to return nil. Now it returns the symbol of the method name. Still not as good as returning a method object, but since you can easily get the method object from the symbol, in practice there's no difference.


Ruby definitely does have closures.


The proposed solution in the original post doesn't require an indent-insensitive mode.


This is one of those pieces that is simultaneously very correct and very wrong. Technically, it's mostly correct, but the fact is Ruby has blocks, lambdas, and the ability to pass around a method attached to an object instance. That gets you a superset of functionality really. It's just all syntactic sugar.

As a lisper I could easily point my finger at ruby blocks and point out they're more complicated than just always using a function, but the fact is, in practice, it just doesn't matter. Ruby blocks cover 95% of the cases you hit in a pretty way, and when they don't you can still use lambdas!

Now macros, those are a better solution to what blocks, but it's a 95% solution, so I've stopped caring.


> It's just all syntactic sugar.

I disagree. Blocks are the fundamental building blocks (!) of various constructs that are derived from them. They're 'thinner' than lambdas, procs and methods. Blocks alone are only part of a system, which, together with binding allows one to build lambdas, iteration constructs, and various other features. Comparing them head-on to functions or lambdas therefore makes no sense. For example, where Python has 'hardcoded' generator expressions, a Ruby developer can leverage blocks to make his own generators. The same goes for features such as with, which are syntactically defined in Python, but which are customary to simply implement with blocks in Ruby. Combining instructions such as next and continue with yield return values and arguments, along with rebinding allows one to define new constructs and syntaxes without paying the price of Lisp's purity (which basically mandates that the language is made of sexprs).


Sure, once you understand the differences between bound and unbound methods, blocks, lambdas and Proc's. That's a lot to understand for something that's fairly simple in many other languages.


You don't need understand that much. If all you know is "a block is just fancy syntax for passing an optional lambda into a method", that will suit you just fine. Does it capture 100% of the nuances of a block? No. But it's a fine bit of functional knowledge. If you throw in calling Object#method(:foo) returns what amounts to lambda {|args| Object.foo|args| } you have 99% of what you'll ever need.


> That's a lot to understand for something that's fairly simple in many other languages.

That's what's so nice about how Ruby does it; you don't need to understand any of that to use blocks. As you learn the language, you learn those things, but none of them are required to get started.


Surely that's not really that difficult to understand?


This article doesn't seem to address continue, return, and break. These are control-flow features that are often allowed in blocks (though not all languages) that can't be done in a function. That is, in a block "return" will return from the enclosing function, and continue/break are a sort of signal to code calling the block. You can simulate the second, like jQuery.each(function (item) {if (timeToStop) return false}); but I don't know of good conventions for this so typically the functionality is skipped. But it's demonstrably useful functionality because it is so widely present in languages.


It seems the author overlooked how `return` works in a block, compared to a function. It was one of the main reasons why Yehuda Katz proposed adding blocks to javascript (I see the reasoning behind it, but I think javascript doesn't need more concepts).


This. That the author doesn't understand this completely undermines the article.


That's hardly fair to use the term "any decent language". Is the author really saying that SmallTalk is not a decent language?

Of course Python actually does have blocks (lambda), they're just very limited, so it's a strange article none-the-less and to a non-Python user come across as a bit of a hack so that you aren't constantly passing method pointers around. But maybe that's just me.


Lambdas and blocks are different things.


I think the author's correct here, there's really nothing uniquely wonderful about blocks, but he's railing against people that don't exist. Pythonistas would like some sort of way to do inline functions, beyond one-liner lambdas, in Python. Whether those are just generic functions or Ruby-inspired blocks is really not all that important. As long as we can inline 'em.


Err, no, such people definitely exist. Whenever a language debate comes up there's always people complaining on one side or another about how language X doesn't have exactly the same feature as language Y.

That's the wrong question to ask in a language debate. The question should be "This thing I can do in language X, how would I do it best in language Y, and how do they compare in tradeoffs?"

I've seen Rubyists complain about blocks a number of times. I've also seen complaints about how Python isn't truly object oriented because not everything is an object (false, actually) and because "len" is a function instead of a method (true technically, but len itself is actually a generic function that uses various OO protocols to determine the answer), though I haven't seen this in some years now.

In reality Ruby and Python are so close to each other on the grand scale of languages in the world that you can barely slide a piece of paper between them... the syntaxes are pretty different but the capabilities are almost identical.


I don't understand this. You can just define a function anywhere in python and use it (in local scope). I do this all the time, and it's a lot more clear than an unnamed lambda function. What's the use case here?


It's nice for chaining. E.g. in CoffeeScript:

  list
    .map (el) ->
      el * 2
    .filter (el) ->
      el % 2 == 0
I suppose in this case a list comprehension would work too:

  list = [el * 2 for el in list if el % 2 == 0]
... but as these things get more complex, inline functions become really useful.

It's also nice for various DSL purposes, e.g. unit tests with Mocha

  it 'should be able to multiply a number', ->
    a = 2
    b = 3
    (product a, b).should.eql 6
Which in Python would become

  def test():
      a = 2
      b = 3
      assert product(a, b) == 6

  it('should be able to multiply a number', test)
Probably doesn't look that impressive, but once you're used to them from other languages, it gets to be really frustrating to not have them in Python.

You also start noticing that Python has a couple of features that are actually entirely unnecessary if only you'd have multiline inline functions. For example, you could do away with decorators and `with` blocks.

  view = authenticate (req, res) ->
      res.send 'hello world'


"Which in Python would become ... assert product(a, b) == 6"

Python's got a lot more capacity for "DSLs" than it may immediately seem. Writing "it.shouldBeEqual(product(2, 3))(6)" would be quite easy in Python, actually; it's got all sorts of overloads and you can return all sorts of excitingly overloaded values. You don't see it because it isn't considered "Pythonic", not because Python can't do that.

(And I'm with Python on this. Those "fluent" APIs spend way too much effort being cutesy and verbose and not enough effort being simple, transparent, and composable. It looks good in the common case but when you write good test code it stops looking so good when you're writing map(it.shouldBe.eq, testTable), and the fact that the cutesy "fluent" style takes even a baby step in the direction of discouraging anything like that is an immediate disqualification in my book. Oh, don't try to defend it by saying you can still do the map I said... I've got enough experience in such things myself to know that it's actually a bigger problem than I'm making it out to be, not a smaller one.)


Kind of besides the point though -- that line in particular had nothing to do with blocks/inline functions.


Promises show a good use case. It's common to have many promise-laden bits of code and it's clear that the first callback is success, second, error, and third progress.

Having to do "def promise1_success" ... "def promise1_error" ... etc all the way up through 5 promises is a pain.

For event-emitter like things, it's also great to have anonymous functions.

For example, in nodejs, you might have reader.on('data', function(chunk) {}); .... You gain no further insight into what that function does if you have "def handle_reader_data" and then use it once there. There are many cases where you don't need the function name for clarify, and it just clutters the current scope to have it not be anonymous.

Code is also easier to follow if functions like that, used once only as a callback, are immediately defined where they are used, not lines above. When reading code that has a named function above, I'd have to skim the function the first time, see it used, and then trace back through it above knowing what was calling it.


Part of the reason why functional languages like Scheme have anonymous functions (lambdas) is so that you can encode various language features in them.

function definition: (define (f x) (+ x x)) => (define f (lambda (x) (+ x x)))

variable binding: (let ((x 1) (y 2)) (+ x y)) => ((lambda (x y) (+ x y)) 1 2)

delayed evaluation: (delay (display "foo")) => (lambda () (display "foo"))

and lots more...

But they also sometimes are convenient when you have a function or procedure that expects the same as one of its parameters, but said function doesn't already exist, and there isn't a simple composition of higher-order (functions that take or return functions) functions that does what you want.

For example, say you wanted to multiply a list of numbers by their squares (this is in Racket):

(define (sqrmult n) (foldr (lambda (x y) (* (sqr x) (sqr y))) 1 (stream->list (in-range 1 n))))

See you don't have to name the function?


> Promises/A has been ported to Ruby, with basically the same API…

This is, to me, the core of the problem.

> The Promises/A spec from CommonJS defines Promise.then as taking three (optional) callbacks: fulfilledHandler, errorHandler, and progressHandler. The code above is passing two of them at once.

Shoehorning things from different realms (e.g desperately trying to write JS in Ruby or vice-versa) is not a good idea.

Instead of either:

    db.select_one(sql, thingid)
        .then((proc {|rowset| rowset[0]['foo'}), (proc {|err| log_error(err)}))
or:

    db.select_one(sql, thingid)
        .then {|rowset| rowset[0]['foo'}
        .then(nil) {|err| log_error(err)}
Why not do:

    db.select_one(sql, thingid)
        .then {|rowset| rowset[0]['foo'}
        .else {|err| log_error(err)}
        .each {|whatever| the_progress(callback_is)}
Also, method(:log_error) will happily turn the method into a proc.

This last case is actually extremely important. Contrary to what the author claims, functions and methods are first class in Ruby. The problem is that in Ruby's grammar an identifier without parentheses is a method call if that identifier does not resolve to a variable. Therefore, you cannot refer to an existing function by its name, merely because it would resolve to a call.

Therefore the difference between those is purely syntactic:

    Python  Ruby
    foo     method(:foo)
    foo()   foo
Also, this[0] explains how &:to_i works, which can be leveraged with great success, should one implement the promise class in a sufficiently idiomatic way (turning the above log_error reference into &:log_error)

[0]: http://ablogaboutcode.com/2012/01/04/the-ampersand-operator-...


...huh? Having piece-of-code A call piece-of-code B which in turn calls some other piece of code as determined by A is useful. Whether you call this idea a "block" or a "lambda" or a "function pointer" or a "first-class function" is an implementation detail.


Next up: Why we don't need higher level abstractions because we can do it all in assembly.


Python already has multi-line lambdas, you just need to be creative http://ideone.com/ybf63a


If anyone has used block-happy smaltalk, you know how amazing it can be to do dependency injection easily and arbitarily. Almost function you write can be generic, and only become specific when it needs to in the business code.


Hmm...I am not sure what difference the author is making between closures and blocks, the terms at least overlap substantially.

For example, Smalltalk blocks have been "upgraded" to closures in most dialects (and not implementing them as closures in the first place was not a choice of semantics, but one of implementation simplicity).

Also, Swift closures are the same as Objective-C blocks.

   +(void)logMe: (void (^)(void))block
   {
       NSLog(@"object of class: %@",[(id)block class]);
   }
Called from Swift with a closure:

    func applicationDidFinishLaunching(aNotification: NSNotification?) {
        logger.logMe( { } );
    }
Results in:

    2014-06-30 19:55:37.973 SwifTest[94782:303] object of class: __NSMallocBlock__
So much ado about nothing?


I was a ruby developer, who then had to do a lot of C and am writing CL/Clojure for fun.

Then I went back to a ruby project, and realised - the language I used to adore kinda sucks, because it's _so_ un-functional.


> JavaScript, for example...

What an odd appeal to authority...




Consider applying for YC's Summer 2026 batch! Applications are open till May 4

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: