Hacker News new | past | comments | ask | show | jobs | submit login
Ruby-style Blocks in Python (espians.com)
46 points by tav on March 8, 2009 | hide | past | favorite | 42 comments



"Ruby does it this way and people like it" isn't an argument that will work. You can accomplish the same things in Python with an extra line of overhead.

If you want to get this past python-dev you'll need to explain the benefits, here's a start:

1) Lower cognitive load: Users can understand blocks but the extra naming/reference is one too many things for their brain stack to handle.

2) More descriptive: with blocks the first line says "I'm about to define a function for use with X" instead of the existing way which says "I'm defining a function. Now I'm using that function with X."

3) Show me: take a chunk of an existing project (stdlib is best) and rewrite it with the proposed block syntax. You get to cherry pick the example so if the new version doesn't read much cleaner than the old version maybe the idea isn't so hot.

I'm -1 on the idea but if you want to change my mind you have to make an argument that doesn't start with "In Ruby..."


@jackdied that's brilliant advice!! thank you -- will bear in mind for the future.


Never gonna happen. As GvR said, 'with' already means something completely different.

Besides, it's not the Python style to have function calls without the () syntax.

I think you could do something interesting with decorators, though.


Ditto on that. The two uses of with have completely different meanings. The was an interesting discussion of the use of 'there' as a keyword that ferreted out a lot of issues: http://groups.google.com/group/comp.lang.python/browse_threa...

Sorry about the url. Tinyurl couldnt digest it. :-)

I think 'there' could have good traction, but nobody has made a PEP (see python.org). If a proposal doesnt have a champion, it isn't going anywhere.


I wish that thread had progressed to a more complete conclusion - I've been developing software with Twisted Python recently (a notoriously callback-heavy framework) and that syntax would clean up an awful lot of code in our code-base. In particular, I like that it's not a knee-jerk emulation of a particular syntax-feature of another language, but a general solution that works well for Ruby-style maps, defining property getters and setters, and setting up design-by-contract semantics.

I'd happily swap that syntax for some of the other features modern Python has grown, like the "with" statement, or decorators.


A similar feature was already proposed and rejected: http://www.python.org/dev/peps/pep-0340/

Anyway, I'm kind of sad that people seem to want a straight copy of Ruby's blocks. I'd prefer syntax that allowed passing more than one block to a function, like the C-like syntax I proposed here: http://www.hackerdashery.com/2006/10/code-blocks-and-c-like-...


I was wondering if the one-block limit bothered anyone else. Maybe something like Haskell's where clause:

  map(foo, pairs) where:
    pairs = [(0,1), (20, 10), (30, 10)]
    def foo(pair):
      a = pair[1] * 2
      b = pair[0] * a
      return (b, a)
I'm not terribly familiar with Python's grammar, so it might be difficult to introduce a closing where-clause without ambiguity. In that case, it might be useful to use a do clause as well:

  do:
    <statements>
  where:
    <statements>
Another alternative would be to simply create a real function literal syntax (i.e., lambda that doesn't suck).


The only time I see where Python will adopt a lambda that doesn't suck, is the day when the community itself embraces functional programming. At the rate that is going and from what I've read from the commenters of the article. I don't think that's happening anytime soon.


I think that's right. Since the early days there have been lots of people willing to say (from an academic bent) how python is basically just scheme, and (from a practical bent) how the likes of python+libraries is good enough to make scheme unnecessary. Programming in scheme feels very different to scheme, largely because of the neutered lambda.


Actually 340 was withdrawn in favor of the 'with' statement, so blocks haven't been ruled out, as far as I can see. However, it will have to be Pythonic (no braces of block terminators) and well thought out.


@sah Nice! Sadly it wouldn't ever fly in the Python context, but I like it nevertheless!


Python's lambdas aren't really functions. They're callable expressions.

Once you understand that, a lot of the "issues" go away.


Ruby style blocks would have made the with_statement (and a bunch of other stuff) obsolete. In fact most stuff can be implemented from blocks (like decorators and generators) and not the other way around.


If Python were made to look like Ruby, I'd have to find a new language. I don't want to do that. Luckily, this will never actually happen.


Obviously no reasonable person wants Python to be identical to Ruby, since those who like Ruby better can go use it. But Python has over its lifetime incorporated new ideas from other languages. Giving substantive reasons why you don't like this particular idea would be a lot more interesting than just registering your distaste for it.


This has been up so many times. It's not happening, live with it. Python "lambdas" can do most things you need anyway.Normally I don't really find the need for supercomplicated lambdas and you can do if-else in lambda s in Python with the ternary operator, see below:

####### def throwaway_function(emp): if emp.salary > developer.salary: return fireEmployee(emp) else: return extendContract(emp)

employees.select(throwaway_function) ######

Python lambda: filter(lambda emp: fireEmployee(emp) if emp.salary > developer.salary else extendContract(emp), employees)

Don't know what select does but it sounds like filter. So if that works as intended(is the ruby version both returning value and side-effecting?) or not I'm not sure but you probably could make it.


Why don't you make a PEP or suggest this to the python mailing list? Might be more effective than blogging for this sort of thing.


@lacker I did mail python-dev, but wanted to get general public criticism too...


I don't understand what the use of anonymous functions besides lambdas would be


The use is the same, it's just that the definition can now be more than one line of code. It's pretty arbitrary to say "you can have one-line anonymous functions, but for two lines, the function must have a name".

No other language does that, because it makes very little sense. This proposal will make Python behave more like every other programming language that supports anonymous functions.


The reason no other language does that is because no other mainstream language uses newline and indentation to indicate blocks, which makes all general-purpose multi-line anonymous function syntaxes inherently ugly and non-Pythonic: where should the new block's indentation begin or end? I also regard it as a relatively minor problem as there's little cognitive overhead from simply naming a multi-line function, and I can see advantages in encouraging naming for complex functions.

Having thought about it for a couple of hours, I don't think there is any good solution for adding multi-line anonymous functions to Python. The suggested syntax feels non-Pythonic, is inherently unexpressive and difficult to read, only useful in a few select use-cases while not solving the holy grail of 'pleasant anonymous functions', and the single problem it's solving isn't common enough or big enough to need dedicated syntax and could already largely be solved with a named function. The fact it enables 'misuse' via Ruby-style iteration, going against Pythonic ideals of 'one way to do things', doesn't work in its favour either.

I don't think analogies with decorators are good either. The advantages of decorators is they transform something otherwise difficult-to-read and unexpressive into something easier-to-read and more expressive, whereas this makes something that's easy-to-read less expressive with the only advantage being terseness. Python, unlike other languages, focuses primarily on clarity over code length.


The reason no other language does that is because no other mainstream language uses newline and indentation to indicate blocks, which makes all general-purpose multi-line anonymous function syntaxes inherently ugly

Haskell is newline-and-indentation-based and allows multi-line anonymous functions.

With that in mind, I often prefer to name functions, but not let them be globally callable, i.e.

    f = (g . h) where
        g = (2*)
        h = (1+)
Does Python allow something like this? (Equivalents in other languages:

    (defun f (x) 
      (flet ((g (x) (* 2 x))
             (h (x) (1+ x)))
        (g (h x))))
Or:

    sub f {
        my $g = sub { 2 * $_[0] };
        my $h = sub { 1 + $_[0] };
        return $g->($h->($_[0]));
    }
I think Haskell is prettiest, but this is not a concept unique to Haskell, and it's a technique I find useful in all three languges.)


Yeah, Python has named local functions:

  def f(x):
    def g(x): return 2 * x
    def h(x): return 1 + x
    return g(h(x))
More interesting was your point that Haskell has anonymous functions and is whitespace delimited. I don't think significant whitespace and anonymous functions have any connection at all.


It's a big deal. The holy grail here is "nice general-purpose closure syntax". So the first try is the obvious:

  function_name(
  def(e):
    print e
  def(f):
    print f
  )
Now, this isn't inherently terrible and quite easy to read (although could very easily end up in debugging hell), but the problems begin because really you'd also have to allow this:

  function_name(def(e):
    print "e", def(e):
    print "f"
  )
Which is _really_ ugly, especially as you can't indent it more as it's against Python's (sane) indentation semantics. So this is ruled out.

Then you end up with Haskell-style:

  function_blah(e, f) where:
    e = lambda e: print e
    f = lambda f: print f
This has multiple disadvantages for few advantages. Things go from "top-down" to magically changing form to what's underneath changes the statement above. This is suicide in an imperative language, least of all Python. And jumping from lambda to lambda isn't particularly readable either. And you still have to name your lambdas. The current syntax is better.

There is the alternative

  within blah def e(e):
    print e
  blah(e)
but this creates messiness where you define a function for blah before blah is defined, which is messy. And you still need a name. And you could accidentally whack out other variable names.

So Python's blocks prevent you doing nice multi-line anonymous functions.

So your only choice is to create a special-case solution, as suggested in this post. So you end up thinking:

  using blah do (e):
    print "e"
Which only passes one. Now, this is less versatile than the other solutions, only allows you to pass a single parameter or anonymous function in, and is less readable - it's simply not /obvious/ what this does unless you know beforehand.


I see your point about the bad interaction between indentation-based block delimitation and parens for function parameter delimitation.

But I think something like Ruby's block syntax should be doable, and probably you can even pass multiple blocks, with some limitations on which parameters can receive them. Here's another shot at it, just for fun: http://codepad.org/mVigj978


Arguably it's not a multi-line anonymous function, but several statement-level 'named variables' rather than block-level 'named variables'. Haskell doesn't really have the concept of "blocks" to begin with as it isn't an imperative language, so it's not really a fair comparison.

The Haskell solution is very similar to how Python currently deals with this situation, similar to your Perl and Lisp examples, by allowing named local functions within a block (rather than at statement-level), and also allowing a syntax for single-line anonymous functions (via "lambda").

I did actually ponder a Haskell-like syntax for Python, but decided it's less than ideal. Allowing assignment after a statement makes things more difficult to read, especially in imperative languages.


But how would this new syntax work when you want to pass two or more anonymous functions to another function? Or when the anonymous function is not the last argument?

Having anonymous functions only for the situation where you are passing exactly one of them to another function and it is the last argument is much more arbitrary than the current style of always temporarily binding functions to names.


This style takes care of 90 or 99% of the cases. If you have to pass 2 or more functions, revert to using named functions. The 'there' proposal that I mentioned before allows for arbitrary placement of the anonymous function, IIRC. If not, usage patterns will adjust to the new idiom.


You might say by the same argument that single-line lambda expressions take care of 90 or 99% of cases, and you should revert to using named functions for the rest.

I don't think either is true -- if you have more flexibility in the syntax, you'll do more with it.


The reason for all the discussion over the years is that a single line lambda doesn't cover 90% of the cases. If you think in terms of callbacks, I bet one liner cover less than 10% of the cases.


Python already has a solution for callbacks:

    @reg_callback(button.onclick)
    def callback():
        do_stuff()


Whilst this is more pythonic I personally prefer Javascripts use of lambdas as more than a means of expression. More like a way of life for the language :-) And that is IMHO what makes it functionally closer to Scheme. Just looking at the way one defines lambdas in Javascript and their use throughout the language is a thing of beauty.


I just don't understand where you would need an anonymous function with more then one line


You wouldn't want to have to declare named functions for the bodies of built-in control flow constructs like if/else statements and for loops, right? All of the reasons why also apply to why you might not want to declare named functions in order to pass them to other functions.

Read some Ruby code that uses blocks for an example of how passing anonymous functions can become a very clear and natural idiom. In Ruby, you can write your own simple control flow constructs in the form of functions that take a block -- in fact the idiomatic way to iterate through a collection in Ruby is with a function rather than a built-in control flow construct.

In languages like Lisp and Smalltalk, where functions can naturally take more than one in-line block, you can write any control flow construct yourself.


The usage case is the same as for a named function of more than one line.


I also don't understand why people use lambda like:

  myfunction( lambda a: a.foo > a.bar )
I think the following looks neater:

  aname = lambda a: a.foo > a.bar
  myfunction( aname )
I think that putting unnecessary clutter inside of a function call is just asking for readability problems.


If you explored outside of your boundaries and learned different ways of programming (like functional programming), you might realize that there are perfectly valid styles of programming where lambdas are as common as if-statements and for-loops. I think that it is reasonable to assume that creating named local variables for each and every one of them would be silly.


To be fair to the grandparent, Python goes out of its way to discourage use of functional programming techniques.


I don't understand why naming a function is such a problem? You can even do it in the local scope and prefix it _ if you want to.


Would you find it problematic to write if and for statements like this?

  def _forbody(x):
    def _ifbody(x):
      print x
    if x % 2: _ifbody(x)
  for x in xrange(10): _forbody(x)


But those are one-liners like you can already do in Python.


I've had a little bit of wine, so you'll have to pardon my honesty: I think Python is a crap language with a great syntax. I'd rather use C++ when performance is very important, or Haskell, when it's not.




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

Search: