On Thu, 2008-09-11 at 16:21 -0400, Mike Meyer wrote:
On Thu, 11 Sep 2008 12:45:06 -0700 Cliff Wells cliff@develix.com wrote:
The thought of running into something like the above in production code is enough to make me bounce pretty much any proposal that would allow it. Compared to that,
a = (18 if x < 23 else 24) + (29 if y < 23 else 81)
a = ( if x < 23: 18 else: 24 ) + ( if y < 23: 29 else: 81 )
And you ignored the constraint that you had to leave the newlines and indentations in the statement.
Sorry, I'm not going to satisfy "now do it with one hand behind your back" sort of challenges. This is the syntax I'm proposing, so why would I provide it any other way?
Also, you've claimed a number of times that you didn't want to change the syntax of python, but this clearly requires a syntax change, as current python syntax doesn't allow multiple colon-separated statements on a line.
No, I stated I didn't want to *add* any additional syntax (i.e. block delimiters, keywords or whatnot), nor do I want to break existing code. I certainly propose changing the grammar (albeit in a demonstrably backwards-compatible way).
Granted, today this is not valid Python, but supporting it doesn't seem to break existing Python code either. This is precisely how Logix does it (from the page I linked you to earlier):
x = if 2+2==4: "I thought so" else: "hmmm..."
In any case, your above code seems intentionally formatted to be unreadable, something that could be achieved under any circumstances. For example, here's your code reformatted in a fashion similar to your first example:
Yup, and if that was what someone was proposing, it'd be a reason to reject it. But nobody is proposing turning expressions into statements, which is what you just did.
No, I turned statements into expressions, which is what I've been proposing since the very first post. I cannot possibly fathom where you came up with the exact opposite.
You continue to be stuck in a rut of demanding that something be a statement when this is simply not a requirement. Perfectly legal (although useless) Python:
1 2 3
Note that this does not somehow magically turn numbers into statements. It affirms that expressions can be used as statements (something that is already true in Python). Ask yourself, is a function call a statement or an expression? It's an expression (a function call *always* returns a value), and yet it can be used alone:
def foo(x): return x**2
foo(2) # expression used as a statement y = foo(3) # expression used as an expression
What we do gain with the idea I've forwarded is a single, more flexible if-expression in place of an if-statement and an if-operator with inverted syntax.
But that's not what you've been claiming you wanted. You've been claiming you wanted statements to be expressions.
Uh... yeah. Seriously: which part of "this is an example of turning a particular Python statement into an expression" am I not conveying to you?
I claim that doing that makes for ugly code, because statements - with newlines and whitespace as a critical part of the syntax - are ugly to embed in expressions. I've asked for examples and not gotten them. I provide an ugly example of statements embedded in an expressions, and you respond by providing an alternative trinary operator to rewrite it.
I did not. I'm going to let you try to understand that one line of code better after you've realized you wouldn't need an operator if the if-statement were an if-expression.
I still want to see how you propose to embed *statements* into arbitrary expressions, complete with newlines and proper indentation. Say... a = ( if x < 24: go_make_some_side_effects() a_nice_long_function() * another_nice_long_function() else: go_make_different_side_effects() a_twisty_little_function() * a_nice_long_function() ) \ + ( if y < 23: go_make_some_different_side_effects() a_twisty_little_function() * another_nice_long_function() else: go_make_different_side_effects() a_nice_long_function() * a_twisty_little_function() )
This is still simply bletcherous compared to the obvious legal rewrite.
Ignoring your superfluous backslash, the above is a perfectly valid example. Of course, like all contrived examples, it doesn't necessarily convey *why* you'd want to do this, but the syntax is correct. Also, go_make_some_side_effects() is probably ill-advised, and not something usually done in an FP style which you claim to be familiar with. Imperative programming is all about side-effects whereas functional programming is all about avoiding them.
But it's about the cleanest way I've seen to deal with the issue. Using operator.add might be a bit cleaner, but not enough so to justify using operator.add.
And we haven't gotten *really* warped yet, with statements appearing as controlling expressions inside of other statements. While making this "simple" changes allows you to make a few use cases simpler, it also makes some truly ugly things seem reasonable. Frankly, I'd rather let the experts deal with those use cases individually with well-thought out extensions than open this can of worms - unless you can show how to do those kinds of embeddings in ways that *aren't* ugly.
I point you to the entire history of FP programming and we can leave it at that. If you've programmed in Lisp as you claim then I cannot see any reason to rehash what you, by definition, must already know, but keep pretending not to.
Unless you're willing to admit you simply don't know FP or that you are being intentionally obtuse for some unfathomable reason, then I'm not interested in pursuing this discussion with you any further. If it's a matter of me not being clear, then I apologize, but frankly I've been as clear as I am able so further discussion would still certainly seem to be fruitless.
Regards, Cliff
On Thu, Sep 11, 2008 at 3:06 PM, Cliff Wells cliff@develix.com wrote:
On Thu, 2008-09-11 at 16:21 -0400, Mike Meyer wrote:
Also, you've claimed a number of times that you didn't want to change the syntax of python, but this clearly requires a syntax change, as current python syntax doesn't allow multiple colon-separated statements on a line.
No, I stated I didn't want to *add* any additional syntax (i.e. block delimiters, keywords or whatnot), nor do I want to break existing code. I certainly propose changing the grammar (albeit in a demonstrably backwards-compatible way).
We keep disagreeing on this point. You're not adding any new tokens, but you are adding a ton of new syntax!
Not to mention that doing it generically doesn't work (a for-loop needs to evaluate eagerly, but mustn't retain all the values it contained), and your primary example (dispatch-dict) only needs lambda to allow statements, rather than the broad changes you suggest.
On Thu, Sep 11, 2008 at 3:06 PM, Cliff Wells cliff@develix.com wrote:
Also, go_make_some_side_effects() is probably ill-advised, and not something usually done in an FP style which you claim to be familiar with. Imperative programming is all about side-effects whereas functional programming is all about avoiding them.
This isn't true when applied to python. We're about halfway in between, regularly avoiding side effects (immutable int/str, sorted(), iteration is generic and only covers reading), while happily doing side-effects when appropriate.
Moreover, although iteration is built on mutation (the iterator object's state changes as you go through it), good style is to contain that in a single function. The end result is often directly translatable into a side-effect-free language, only our version is easier to read; all they offer is a guarantee of no side effects.
On Thu, 2008-09-11 at 15:53 -0600, Adam Olsen wrote:
On Thu, Sep 11, 2008 at 3:06 PM, Cliff Wells cliff@develix.com wrote:
Also, go_make_some_side_effects() is probably ill-advised, and not something usually done in an FP style which you claim to be familiar with. Imperative programming is all about side-effects whereas functional programming is all about avoiding them.
This isn't true when applied to python. We're about halfway in between, regularly avoiding side effects (immutable int/str, sorted(), iteration is generic and only covers reading), while happily doing side-effects when appropriate.
Well, I'd argue that *any* statement is causing a side-effect as it affects the flow of control or values of objects outside its own scope.
Even the following represents one side-effect (defining a class in the outer scope):
class Foo(object): pass
Expressions on the other hand, *evaluate* into the outer scope. They must be explicitly assigned to have any long-term effect once they've completed.
One way to look at it is that statements "push" values into the enclosing scope whereas expressions "offer" values to it. I'd call the "pushing" a side-effect. Note that I am not claiming side-effects to be universally bad things (they often are, but they are just as often unavoidable to do real work, i.e. printing to the terminal).
Moreover, although iteration is built on mutation (the iterator object's state changes as you go through it), good style is to contain that in a single function. The end result is often directly translatable into a side-effect-free language, only our version is easier to read; all they offer is a guarantee of no side effects.
Not 100% I'm sure I follow this, but I think I might agree ;-)
Cliff
On Thu, Sep 11, 2008 at 7:16 PM, Cliff Wells cliff@develix.com wrote:
On Thu, 2008-09-11 at 15:53 -0600, Adam Olsen wrote:
On Thu, Sep 11, 2008 at 3:06 PM, Cliff Wells cliff@develix.com wrote:
Also, go_make_some_side_effects() is probably ill-advised, and not something usually done in an FP style which you claim to be familiar with. Imperative programming is all about side-effects whereas functional programming is all about avoiding them.
This isn't true when applied to python. We're about halfway in between, regularly avoiding side effects (immutable int/str, sorted(), iteration is generic and only covers reading), while happily doing side-effects when appropriate.
Well, I'd argue that *any* statement is causing a side-effect as it affects the flow of control or values of objects outside its own scope.
In a strict technical sense, sure. Perhaps it's better to say that python encourages *containing* side-effects.
I agree that making every statement also an expression has merits, as does FP. However, you're handwaving about how this fits into python. The scope of an if is defined syntactically by indentation. Your proposal changes that in some way that I can't quite tell. Is it that you can put a single expression between the if and the else? Or something else? When would line breaks be optional where they are now required? Simply saying "make everything an expression" is insufficient. Saying "it's just lifting a restriction" is also insufficient. In many languages there are restrictions on using the + operators with non-numbers. Why can't we just remove that restriction? Well, we have to define the precise semantics in order to do that, e.g., what does []+{} mean?
You said that the value of a for loop would have the value [] if it didn't include yield, i.e., for x in y: foo(x) evaluates to [] while you said if x: foo(x) else: foo(0) evaluates to foo(something). That seems inconsistent. Is that inconsistency a good thing? For that matter, why return a list rather than () or an empty generator? [[As an aside: why can't I write {foo(x) for x in y} which is the equivalent of doing d[x] = foo(x) for all x. I've just written the equivalent loop several times recently.]]
To answer these questions, the right thing to do is to write a list of exactly what new syntax and semantics you are proposing. I'm not saying that I (or anyone else) will agree with what you propose but I certainly can't agree with the vague proposal you've made so far.
--- Bruce
On Thu, 2008-09-11 at 15:14 -0700, Bruce Leban wrote:
I agree that making every statement also an expression has merits, as does FP. However, you're handwaving about how this fits into python.
Sure. I intentionally handwave for two reasons:
1) it's a bit of a waste of time to detail something like this when it's universally rejected 2) I didn't want to bicker over details. I wanted to forward the concept.
The scope of an if is defined syntactically by indentation. Your proposal changes that in some way that I can't quite tell. Is it that you can put a single expression between the if and the else? Or something else? When would line breaks be optional where they are now required?
I'd say anytime it's ambiguous (i.e. much like you would for parenthesizing any other group of expressions).
Logix manages some of the ambiguity by defining ';' as an operator that simply evaluates to the rightmost value. I think this makes a lot of sense.
if x: y else: z # seems obvious
if x: y; y+1 else: z; z+1 # evaluates to y+1 or z+1 depending on x
and is equivalent to
if x: y y+1 else: z z+1
A the primary question that arises is what to make of assignment. GvR is notoriously against allowing constructs such as:
if ( x=1 ): y
and I think his reasoning is sound (this is usually a programming error). I tend to think it should allowed (for consistency), but it could be defined as a syntax error in this context. At any rate, I'll resort to the same argument as is used against people who demand static type declarations: unit testing =). Frankly, either is acceptable to me.
One thing that would have to change about assignment is it would definitely *have* to be an expression (and evaluate to the assigned value). Whether it's allowed in any particular context is open.
Simply saying "make everything an expression" is insufficient. Saying "it's just lifting a restriction" is also insufficient. In many languages there are restrictions on using the + operators with non-numbers. Why can't we just remove that restriction? Well, we have to define the precise semantics in order to do that, e.g., what does []+{} mean?
You said that the value of a for loop would have the value [] if it didn't include yield, i.e., for x in y: foo(x)
evaluates to [] while you said if x: foo(x) else: foo(0) evaluates to foo(something). That seems inconsistent.
Is that inconsistency a good thing? For that matter, why return a list rather than () or an empty generator? [[As an aside: why can't I write {foo(x) for x in y} which is the equivalent of doing d[x] = foo(x) for all x. I've just written the equivalent loop several times recently.]]
Both evaluate to *something*, just that the non-yielding for-loop evaluates to an empty list (probably a good indication it's not being used in an enclosing expression). It could probably evaluate to None, if that seemed more reasonable. I tend to prefer an empty list as it allows for looping without testing for an iterable. Incidentally, how would an empty generator differ from []? I'll concede it might make more logical sense, but would it differ in application?
To answer these questions, the right thing to do is to write a list of exactly what new syntax and semantics you are proposing. I'm not saying that I (or anyone else) will agree with what you propose but I certainly can't agree with the vague proposal you've made so far.
I'd probably defer heavily to an existing implementation that does exactly what I'm referring to (except I'm not espousing macros or other metaprogramming facilities Logix provides):
http://www.livelogix.net/logix/index.html
Whether or not this is the ideal situation is arguable, but those arguments would need to be much further down the road. Right now there's not even agreement on whether the overarching concept is even appropriate for Python.
Cliff
Cliff Wells wrote:
Sure. I intentionally handwave for two reasons:
- it's a bit of a waste of time to detail something like this when it's
universally rejected
And yet this thread, persists.. *sigh*
-Scott