-
Website
http://tav.espians.com/ -
Original page
http://www.asktav.com/ruby-style-blocks-in-python.html -
Subscribe
All Comments -
Community
-
Top Commenters
-
stayce
1 comment · 1 points
-
Wybo_Wiersma
1 comment · 1 points
-
chenz
1 comment · 1 points
-
holgerkrekel
1 comment · 1 points
-
Philip Hofstetter
2 comments · 2 points
-
-
Popular Threads
-
Ciao Python, Hola Go!
3 weeks ago · 7 comments
-
Ciao Python, Hola Go!
Suggestions from everyone very welcome!
I suggest the word "on" instead of "with" for the moment.
on employees.select do (emp):
if emp.salary > developer.salary:
return fireEmployee(emp)
else:
return extendContract(emp)
--Bill
Will update this proposal now.
As for a decorator, yes, we could do a do() decorator, I could code one up if it would help make the case better...
I believe it could be similarly popular in the same way that @decorators have been tremendously popular despite just saving a few bytes.
Ruby has already proven the merits of a block syntax -- check out Rake and Rails for examples of how blocks are used to increase productivity!
Lambda functions are useful simply because they are *short*: short enough to take in at a glance, and short enough not to require documentation, or testing. By allowing multi-line lambdas, you encourage people to write complex, long functions that can't have doc strings and can't (easily? or at all?) be tested. Use-once-and-throw-away code blocks discourage code reuse and DRY. These criticisms also apply to lambda, of course, but with lambda the harm is minimised because the functions are small and easily understood. That anonymous blocks are very popular among Ruby programmers doesn't mean much to me. GOTOs were hugely popular among Basic programmers too, and look at how many people use XML for supposedly human-editable config files.
But Ruby has shown that anonymous blocks can be used to improve the elegance of the code as well as increase the productivity of the developer.
As for testing, I don't see why such an approach precludes either testing or documentation.
In fact, it could make testing more pleasant than the java-inspired unittest framework we currently have.
See this http://blog.excastle.com/2005/05/18/ruby-coolne... for
CheckThrows(EInvalidOperation) do
DoSomething
end
I can even see interesting ways that __do__ could be used to create nice doctests.
If your lambda is complicated enough to need testing, and for some reason you don't want to (or can't) move it out to the top level into a def, it's still a first-class object so you can do this:
func = lambda x,y,z: something(x)+something(y)+another(z)
if testing: test(func)
use(func)
Unless I've misunderstood something, your multi-line lambdas are second-class functions, so the only way to do the same would be:
if testing: with test do (x,y,z): something(x)+something(y)+another(z)
with use do (x,y,z): something(x)+something(y)+another(z)
You have to repeat yourself twice, which leads to copy-and-paste programming, code duplication, and other Bad Things.
One thing I _do_ see as problematic is something else: functions are first-class objects in Python, but this "syntactic sugar" would remove the first-class-ness of that anonymous block in a way. If you do define the local function, you can pass it along just as any other value, while with the suggestion it wouldn't. And that just for the sake of getting rid of a function definition. Not too sure about the value of that.
Think about refactoring your code and discovering that it would be nice to pass the "anonymous block" function as argument - you would have to change the code quite drastically if you used the suggested "do" block, while with the classic approach, not much changes - you still have your local function and it's cl osure and just pass that on elswhere.
An analogy might be that functions are molecules while single elements are atoms. The semantics of the atom "if x > y: x += y" depends on the other atoms around it, while ideally the function sin() is always the trigonometric sine no matter what other functions are around it. So you should be able to test sin() in isolation, which you can't do with the if block.
(Besides, you can perform limited, ad hoc testing of individual elements simply by sprinkling asserts after each element. Not a good solution, but it is something.)
The whole purpose of functions (procedures or sub-routines) is to lessen the coupling between parts of code, to partially isolate chunks of code for ease of re-use and testing. If a code block is tightly coupled like a while or if, then it is a step backwards towards spaghetti code; and if it is loosely coupled, like a function, then there's no benefit to that looseness if you can't test it or re-use it.
Unit testing, although nice, falls out of this "unitizing". It's not the original motivation.
(Although that may also harm performance, compilers could be written to recognize all the duplication and merge common bits. Functions are quite entrenched in common styles though, so there's little motivation to do this today.)
with FUNCTION do (MAGIC):
ARG
where ARG is a code block, and MAGIC is actually part of the code block but appears outside of it. And what happens when you have multiple arguments to FUNCTION? How do you combine multi-line anonymous blocks with other arguments? Or do you make multi-line lambdas a second-class citizen by prohibiting them from being passed to functions of two or more arguments?
In your post, you say "This is also possible in Python but at the needless cost of naming and defining a function first". I dispute that it is "needless cost". The programmer's effort in creating the code block, versus the effort in creating a function, is precisely the same except for a single line: "def throwaway_function(emp)" in your example. Your proposed implementation also creates a function object. So there's unlikely to be any performance boost at compile time, and probably a performance cost at runtime due to the extra redirection with your __do__ function.
As far as I can see, the only advantages for anonymous blocks are that the programmer doesn't have to think of a name, and that you define the function where you need it instead of immediately prior to where you need it. Both are tiny advantages, as far as I can see. I'm not convinced that multi-line lambdas are needed at all.
@func
def foo(): ...
(which is:)
def foo(): ...
foo = func(foo)
So no it is not unique.
Also, code flows better this way, especially with e.g. callbacks.
sorted_stuff = stuff.sort_by do |s|
s.name
end
do_something_with(sorted_stuff)
or
useful_infos = stuff.map do |x|
x.useful_info.to_s
end.join(', ')
...which isn't possible with blocks-as-statements.
I thought (wrongly?) that the real advantage of blocks was that they shared scope with the funciton where they're used, instead of with the location where they're defined.
defop add(a, b):
# Today, a and b would be undefined
sum=a+b
def caller():
a=5
b=6
defop()
# today, sum would still be undefined,
# -- even if defop "worked"
print(sum)
(I actually really like anonymous functions -- I've done a lot of work in javascript and Lua, and they're very useful.)
I think for this to work well, you really need to first have Python return the last value of a block, like Ruby and Lisp do. (Or you could disambiguate by adding a RETURN-FROM form like Lisp has, but that I *really* can't imagine ever being adopted in Python!) Then you need to retrofit values to statements (like "if"), and that's a can of worms Guido doesn't seem likely to ever want to reopen.
It also seems to go counter to Python's "one way to do things" mantra.
I'm not saying it's a bad feature idea. (I'm a Lisp programmer and we're not even happy with Perl's 27 ways to do it. We need access to the machine shop so we can build a new language from scratch ourselves.) I just don't see it being very Pythonic on its own, or being very Pythonic with respect to other features, or resulting in more Pythonic code to be written. It's really more of a cultural change than a syntactic one.
http://www.python.org/dev/peps/pep-0359/
As another commenter noted, this can be done with decorators. For example,
@employees.select
def emp (emp):
if emp.salary > developer.salary:
return fireEmployee(emp)
else:
return extendContract(emp)
The downside of this is that "emp" ends up being assigned the result rather than a function, which is a little confusing.
Besides that, injecting globals and locals is horrible. __do__ should take a single argument, a callback, that's a rather mundane function.
records = employee.select(@):
def @(employee):
return do_something(employee) if cond(employee) else None
It didn't get much traction on Python-ideas, unfortunately.
def records():
records = employee.select(lambda employee):
return do_something(employee) if cond(employee) else None
def foo():
yield "hello"
yield "world"
foo() do(arg1)
print arg1
would print "hello" and then "\world", maybe it introduces some ambiguity I'm not aware of?
The real advantage I see with blocks is (and I use {} most of the time actually) that it leads to very short code.
However I think python is too far away from ruby to allow blocks. The syntax differences are very large.
Ruby has no problem to use a notifier like @ but python seems to rather use __ or other solutions to not introduce "new" key-symbols like @
For me, shorter code is always better. Really. This is btw what I like in python, that I can omit end.
(I however cant stand implicit self, required () and the needed : after statements. On the other hand, I think ruby can be beat - a better language would be one that SIMPLIFIES ruby heavily. I dont like the module vs class separation at all for example. I also dont really like lambda. I can see the advantages of lambda and procs, but I think they should LIVE in a world full of objects, not in a world full of messages - messages are more important in the end, but well defined objects help my brain model everything easier IMHO)... I digress...
(Btw you made a minor mistake... else: is not needed in ruby, the : seems to be pythonic )
I agree that {} and shorter code would be better. Unfortunately I haven't been able to come with a Pythonic way of doing that.
This proposal was about trying to come with a Pythonic equivalent.
As for a better language, I agree -- and perhaps I should just do that -- there would be a lot less resistance! =)
I also like the blocks in Ruby and it's right now the only advantage I see in Ruby over Python. But with multi-line lambdas the same would be achievable.
Oh, and make this a 2.x upgrade :)
And thanks for the blog article!
Specifically, what does employees.select _do_? It seems to be either filter or map, but neither really. The multiline example seems a particularly bad example of Ruby code. I'd imagine you'd probably use #each instead of #select.
If select is like filter, then you could rewrite the first example as
[e for e in employees if e.salary > developer.salary]
The latter example is very confusing. I have no idea what I'd want to do with a list of return values from two different functions, both of
which obviously have side-effects. Do they return boolean values? True on success, false on failure? Or true if we keep the employee?
I'd rather see that written out explicitly as a for-loop:
remaining_employees = set()
for emp in employees:
if emp.salary > developer.salary:
fireEmployee(emp)
else:
extendContract(emp)
remaining_employees.add(emp)
There is no mention in the proposal about how the alternative
__do__ functions are specified.
Finally, as you said, it only implements a portion of the features of Ruby blocks.
comprehensions and generators. But for the simple case of entering a block of code as one does with @contextmanager I suppose it would be nice to make a generator with a single yield statement a contextmanager by default such that:
>>> def g():
... print "a"
... yield
... print "b"
...
>>> with g():
... print "c"
...
a
c
b
would be equivalent to
>>> from __future__ import with_statement
>>> from contextlib import contextmanager
>>> @contextmanager
... def g():
... print "a"
... yield
... print "b"
...
>>> with g():
... print "c"
...
a
c
b
http://www.pluginaweek.org/2009/03/08/state_mac...
Without solving that, not much use in putting together a PEP =(