Code blocks in Python
mtomassoli.wordpress.com
mtomassoli.wordpress.com
def sorted_by(xs, attr):
def cmp_attr(a, b):
return cmp(getattr(a, attr), getattr(b, attr))
return sorted(xs, cmp_attr)
It takes a little more vertical space and requires you to give a name to something that might otherwise be anonymous, but in the long term it's actually beneficial in my opinion. I've actually taken to using the same pattern in my JavaScript development, giving all of my callbacks names, because it makes maintenance so much easier later. var load_into = function (url, elem) {
var handle_response = function (response) {
$(elem).html(response);
};
$.get(url, handle_response);
};Doing this without code blocks adds extra code like launching another thread, connecting the scope to the new thread.
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))Why wouldn't you just create another function in the same class?
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...
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
People have been investing a lot of time in this. That certainly indicates someone considers it a worthwhile endeavor.
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.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.
For example, consider the function "forever" in Haskell:
forever action = do
action
forever action
Now you can use it as a new control structure, e.g: forever $ do
(sock, addr) <- accept listener
forkIO $ handleClient sock
The $ simply means "apply" and is low-precedence, so it removes the need to put () around the entire argument to be applied.In Python, you could define:
def forever(action):
action()
forever(action)
but then, to use it, you have to give a name to your function, so: def accept_once():
(sock, addr) = listener.accept()
fork(partial(handleClient, sock))
forever(accept_once)
This makes "forever" much less useful as a new control structure/looping primitive.In this sense, Python makes DSLs less usable. The built-in primitives are first-class, and can have code directly within their use. Library functions are second-class, and can only have code passed by name which must then fully appear before the use.
Another example is callbacks. The reason "twisted" is probably called "twisted", and that people hate callbacks so much, is that it forces writing the code in backwards order, precisely because of this problem. For example:
def handle_result(result):
print "Done:", result
def connection_started(conn):
conn.request(SomeRequest(), handle_result)
def start():
start_connecting(connection_started)
Compare this with Haskell, as an example: start = startConnecting $ \conn -> do
request conn SomeRequest $ \result -> do
putStrLn $ "Done: " ++ show result
Note, in Haskell, this would actually be worked out to be (by overloading the semicolon): start = do
conn <- startConnecting
result <- request conn SomeRequest
putStrLn $ "Done: " ++ show result
But even the former nested representation is better than the backwards ("twisted") representation that makes people hate callbacks so much. while True:
action()
I know what you mean about generalising this to other control structures, but it looks like the whole concept starts from "I want to write 'forever' that resembles something from other languages", rather than "I want to write a loop". There are existing ways to write things like that, so is do we really need to force something from other languages into Python? What about some examples which cannot be easily handled - maybe the solution for them is something completely different than porting codeblocks. replicateM 10 . forkIO . forever $ do
..
Which is basically the same as: replicateM 10 (forkIO (forever (do ..)))
replicateM 10 executes its code block argument 10 times. forkIO executes its code block argument in a new thread. forever loops forever.So this line basically creates a thread pool with 10 threads, all infinitely executing the given code block.
How would you solve this in Python?
The question is how does codeblocks solve this problem in Python, not how does Haskell solve this problem in Haskell.
times(10, forkIO(forever(block:
...
)))I'm not sure that codeblocks actually solves the problem.
It all just becomes a giant clusterfuck way too quickly. Small set of well-defined semantics, please.
Specific examples?
I've actually often found the opposite to be true, given a good (meta) syntax for handling this.
I've often seen patterns in Smalltalk like:
myTransaction
commit: [
"Stuff happens here"
]
onRollback: [
"handles rollback"
].
Semaphores are used for critical sections like so: (mySemaphore)
critical: [
"critical section code goes here"
].
I think these examples show how such a thing can increase readability.As far as the portability thing goes, I've never heard of that being a terrible problem in Smalltalk. If code from one environment has some custom method like "isNilOrFalse:" or something like "ifNotNilDo:" then it's a simple matter to port that code over another environment, or have it syntactically rewritten by the browser to make a portable library. The sorts of patterns I list above are generally for DSLs that are only used internally in a given library or for code that uses the particular library, so portability isn't a problem. The structures are carried where needed by the library.
It all just becomes a giant clusterfuck way too quickly. Small set of well-defined semantics, please.
Smalltalk does have a small set of well-defined semantics, just not at the level you're used to. I have never heard of modifications that break the control flow standard library. My experience is that good programmers don't change how the "standard" behaviors work, because those changes tend to break the rest of the code base.
>>> def fight(batman_wins):
... def t(): return "pow"
... def f(): return "oof"
... def b(msg): print msg
... # if_but not defined at this point
... if_but(lambda: batman_wins, t, f, b)
...
>>> def if_but(cond, true, false, but):
... if cond():
... v = true()
... else:
... v = false()
... but(v)
...
>>> fight(True)
pow
>>> fight(False)
oof
Sure, if you want to call if_but outside a function, it has to be defined, but inside a function (as in your example, and in 99% of python development that doesn't occur in a REPL) x() is effectively globals()['x'](). Have I totally misunderstood your point?But callback-style often appeals to defining very-local callback code. In fact, it would be anonymous in languages that support it, but it is forced to not only be given a name, but also potentially clutter some namespace (or be written in reverse order).
Consider having every for/while loop, or every "if" require giving a name to the code block[s] within it. It sounds insane. The same doesn't sound insane for library functions, but in my opinion that's really just because everybody's used to this limitation.
When I moved from Python to Haskell, one of the many joys was that I could stick an anonymous code block anywhere so easily.
If you really need new control structures, Python syntax isn't all that hard to hack.
But blocks? They're a misfeature of Ruby, badly copied from Smalltalk.
[edit: added "return"]
How about new keyword(s)? "begin" and "end"? I imagine this has thoroughly been discussed ad-nauseum. Is there a good synopsis?
CS has ambiguous cases because of optional ( ), but that wouldn't be the case with Python anyway.
Like I said, I'm just learning... so I'd love counterexamples and reasons why I'm wrong.
So your named functions, functions, lambdas, code blocks, etc, become.. well.. equivalent.
They can also become so lexically awkward as to be unusable. For example, one code a toy "fuzzy logic" system in Smalltalk with a handful of methods. The result looks like a 1st class member of the language, just like the control structures that are already there. (Same goes for the loops)
"Here is the standard if-else"
(condition)
ifTrue: [ ... ]
ifFalse: [ ... ]
"Here is what my DSL can look like"
(condition)
ifTrue: [ ... ]
ifFalse: [ ... ]
ifMaybe: [ ... ]
Doing this with named functions is going to scatter code between different functions. It's semantically equivalent, but harder to read. Write anything involved in such a way, and it becomes untenable. Blocks can make such DSLs an order of magnitude more readable.Hipsters, don't confuse the young ones with your 30 ways of jumping into a block of code. Cheers!
A good heaping fraction of hipsters don't understand what's good about code blocks.
"Doing this with named functions is going to scatter code between different functions". Please show an example.
"A good heaping fraction of hipsters don't understand what's good about code blocks." That's one way of phrasing it.
At the very least, the semantics of this need to be explained better. When you give a new code example, you always need to say "And this is the result." Otherwise, I can't close the loop; I have new code with new and unknown semantics, and an unknown result. I need to have a known result to figure out the new semantics. (Much like you can't solve a single equation with two unknowns; you either need to have one unknown, or two equations.)
As for mixing strings with code, I still need to adhere to Python's syntax in order to avoid syntax errors. Anyway, syntax errors in strings are caught at "rewriting time". Using words instead of operators would also cause some problems.
I understand why you had to use strings as code. I had two concerns about that. One, you didn't explain it - I had to figure it out on my own. And two, if a solution is supposed to make things cleaner, but ends up introducing warts like that... maybe it's not worth it. What you implemented is interesting, no doubt, but I wouldn't want to use it for that reason.
However, I think you would benefit greatly from having improved examples. Even if people don't use your module, they can still build on your ideas if it's well explained.
1 click --> bitbucket
1 click --> source
1 click --> codeblocks.py
It's that too much to ask?
I'm assuming that you want people to use your work. People need convincing. There are thousands and thousands of ideas and projects out there. The ones that are well explained are the ones that will get attention. I do research for a living. The research itself is only half the job. Presenting the research - convincing people that what I did is important and useful - is the other half.
Basically, what is your objective: do you want to be right, or do you want to be heard? You said you welcome constructive criticism. That is what I'm trying to provide - except it's not about your work itself, but about better ways to present it.
I'm not trying to convince anyone that my "project" is useful because that's not my project anymore. Anyone can look at it, dissect it, propose new feature, etc... Now that the thrill of the exploration is almost over, I'm losing interest... but I'll keep an eye on my repository on bitbucket.
By the way, if someone could provide better examples, documentation, etc... I'd be grateful. Let's just say I'm not much into the "convincing thing". If you need convincing, don't look at me. I gave you an object. Now it's your job to figure out what to do with it (or just toss it away) ;)