PEP 308 and why I still hate Python
dvt.name
dvt.name
Substitute {{x}} for Python and you have a template for a Hitchhiker's Guide entry for just about every programming language, ever.
"There are only two kinds of languages: the ones people complain about and the ones nobody uses." - Bjarne Stroustrup
(occasional lisper)
(constant lisper)
Even PHP, which is not a well designed language at all, has at least attempted to make core language improvements in PHP7 and has supersets like Hiphop/Hack which use the same syntax and avoid most of the insane typing pitfalls.
Python tried to make it more english-like just like many other of it's syntax (using and/or/not instead of && || !) and in this case it feels strange to experienced programmers who are not used to it. That being said, if you show this and javascript one to someone who's new to programming, they'll for sure understand the Python one better.
Still, again that's where style guides come in. I've been coding Python for years and rarely ever used the ternary.
As a practical matter, only use Python's functional features in one-liners.
I don't get the ideal of saving space and writing huge one-liners (that in Python you'd have to break up over more lines). As they say on IRC: "if you're worried about using too many lines, I can send you a file full of blank ones."
...as are many others with the conditional operator's operands in the normal (condition, true-case, false-case) order:
https://en.wikipedia.org/wiki/%3F:
I think being "functional" or otherwise has nothing to do with it. Even in imperative languages you must use expressions, and it is there that the conditional operator is most useful.
I think the cause has everything to do with some poor choices around the parser and syntax, however, and nothing to do with its imperative nature as such.
I'm sure that nearly everyone reading the article knows how an if/else works, so that naming won't actually lead them astray, but it just feels ugly.
consequent-expr if antecedent-expr
...which has all the same problems as the PEP308 example, as far as I can tell. Its (implicit) else-clause is just always nil.I can't tell from the author's argument whether this condemns Ruby as well, or if something about the PEP308 style was uniquely bad.
I could see something definitely worse about the readability of this code:
{long expr} if x else {another long expr}
but the author didn't discuss that case.Python does force you to use their awkward conditional expressions; there is no normal ternary operator.
There's a big difference between a language which lets you write hideous code, and a language which forces you to write hideous code.
In any case where you can use PEP 308, you can use a boring traditional "if else".
More seriously: Yes, you are obviously correct. But while you can use if/else, you can't use a ternary, and you can in Ruby. That's why people complain about the lack in Python but don't wrote the same article about Ruby.
Though I don't really dislike Python's approach so what do I know.
print "true" if (a < b);
I recall seeing dialects of BASIC back in the 70s which supported it as well. Who knows, maybe this is where Perl got it from.I use the post conditional most often in perl in this way:
foreach my $foo (@collection) {
next if (I-dont-care-about-this-$foo);
next if (another-reason-for-ignoring-$foo);
do_something($foo);
} def do_something(arg1, arg2)
return false if arg1.is_invalid
return false if arg2 < sane
...
end
The trailing `unless` is also handy, and I find it a bit nicer to read compared to a negated `if`.Having said that, I often use the "else" as a "this should never happen" clause that raises an error.
"I'm going to die ... (gasp) ... if you don't grab me some Subway. I'm starving"
The sentence is funny because the apodosis is misleading. Even thought that might be a valid narrative approach when writing a comedy, I don't think it's an appropriate coding one.
Code should have literary value. ;-)
foo = [transform(bar) for bar in baz.list() if condition(bar)] ?
Seems very readable and expressive. Or are you complaining about how they execute?Also \ used judiciously with proper indentation can break statements over multiple lines without too much pain.
foo = baz.list().filter{ bar => condition(bar) }.map(transform(_)).toList
If it's your first time looking at Scala, you'd probably be like, "What the fuck is that shit?" but as you learn the language, these types of patterns become common and make a lot of sense.I mean it's not like weird rules in natural language. At least the grammars for computer languages are strict, with very few edge cases (compared to human/spoken language).
foo = baz.list().filter(condition).map(transform).toList
No need to create the extra anonymous functions. foo = baz.\
list().\
filter{ bar => condition(bar) }.\
map(transform(_)).\
toList
IMO this is about as readable as it gets. It's like bullet points enumerating each step in the "algorithm" that produces a `foo`.AFAIK Javascript also uses this pattern a lot.
foo = baz
.list()
.filter{ bar => condition(bar) }
.map(transform(_))
.toList;Edit: and yes, that line separation works fine, both compiled and in the REPL.
2: When I hit `transform(bar)`, I don't know what `bar` is either. I have to jump forward to disambiguate again.
3: Once I hit `for bar in` I realize I'm in a comprehension, but that's still not enough to figure out what `transform(bar)` is acting on. I have to read to the end of the `if` condition, then backtrack to the beginning.
bonus reason: why not `list(baz)`? Is there actually some logic to when Python uses methods and when it uses global functions? If so, please tell me.
another bonus reason: when I was learning Python, I found the keyword reuse genuinely confusing. I would try to write for loops like `for x in my_list if condition(x):` and couldn't figure out why it didn't work because I knew I had seen that syntax somewhere before.
Now, your particular example isn't that bad, but if you make it just a little more complicated you get something like this aberration:
foo = [transform(bar) if bar.property else bar for bar in baz.list() if condition(bar)]
Nothing will convince me that you're not a mutant if you think that's clear and concise.Meanwhile, over in Ruby-land, we write this:
baz.each.select{ |bar|
condition(bar)
}.map{ |bar|
bar.property ? transform(bar) : bar
}
Which to me parses very easily into meat-brain logic, in small chunks, with a single pass:-Start with `baz`
-Consider each item
-Select only those `bar`s that meet `condition`
-Map each `bar` to...
-`transform(bar)` when `property` is true; itself otherwise (okay, natural language actually more closely resembles Python-style ternaries here. Sue me).
P.S. apologies for the multiple edits; I am an HN formatting noob.
All research I have seen says that proficient readers of a language read closer to a line at a time than a token at a time, so I suspect that not really a problem so much as a rationalization of aesthetic preference.
> bonus reason: why not `list(baz)`? Is there actually some logic to when Python uses methods and when it uses global functions?
list is a built-in type, and (as is usually the case for Python types) list() is the type constructor. There are other global functions in Python and there is room for debate over whether they make sense over methods, but constructors for built-types are pretty clear.
baz.list() is a method on baz. Assuming baz is iterable, list(baz) is valid, it's just different than baz.list().
foo = [transform(bar) for bar in baz.list()]
Adding an if condition looks like this: foo = [transform(bar) for bar in baz.list() if condition(bar)]
But an if/else expression looks like this: foo = [if condition(bar) transform(bar) else other_transform(bar) for bar in baz.list()]
Adding an else requires completely rewriting the line! Super ugly and inconsistent.Edit: formatting... don't know why my the 'list()' in my last example is disappearing
foo = [
if condition(bar) transform(bar) else other_transform(bar) # 1: storage
for bar in base.list() # 2: iteration
if bar_tester(bar). # 3: filtering
]
to each his own I suppose, until the PEP8 police come and get us all!!!I think this would be easier to read, given that what's inside the [] now looks almost exactly like the equivalent code without using a list comprehension:
foo = [for bar in baz.list() if condition(bar) transform(bar)]
(Disclaimer: I am not a regular Python user. Maybe that ordering conflicts with a syntax for something else.)But my problem with
true if cond else false
is it makes impossible to chain fast choices.I thought it was a bug, then I understood it was a feature (after talking to him).
GvR hate ternary operators, the python 3nary is midfix, it sux, but it removes the possibility for people to do long chained if/then/else in unreadable ways.
If you think I am right and it sux, understand I think forbidding this kind of operator is like hating shortcuts and goto.
Since I love shortcuts, goto and chained ift and python, I logically found ways to not care of PEP308 because it is a non problem.
Dispatch tables, and/or used wisely ... there are a lot of ways to not care about PEP308.
And my point is that going against the standard (for over 100 years) way of reading conditionals is quite the opposite.
I would have prefered a ?: but I can replace it with
# requires non null default else there is a bug
value = arg is MARKER and default or arg
So ... I don't botherI don't find the syntax adopted in PEP 308 to be readable.
x = if condition a else b
? checkSomeValue() && doOtherThing()Variable x should be assigned if condition a true, otherwise b.
msg = if (status == 0) "done" else "error"msg = if status == 0 "done" else "error"
From a compiler perspective that's a bear to parse.
x = -1 if y else -2
However, with your syntax, it would look like: x = if y -1 else -2
Which could be possibly confused with trying to subtract 1 from y.And don't get me started with Java's verbosity. Half the time Java seems completely unusable without an auto-completing IDE.
And in all honesty I am a big Python fan, but only because it seems like the lesser of all evils in programming. There are some great designs out there, but Python is that toolkit that just seems to be pragmatic enough to be useful and idealistic enough (hello, significant whitespace) to encourage expressive algorithm design.
That being said, some things in Scala are difficult to wrap your head around, and anything made by TypeSafe/Lightbend should die in a fire, but overall I still enjoy coding in it.
> {} + []
<- 0
> ({} + [])
<- "[object Object]"
or how the identity x|0 == x doesn't hold for all "integers"[2]: > 4294967296 | 0
<- 0
or how function definitions aren't grammatically statements, making this[3]: function foo() {
function bar() {} // legal
if(1) {
function baz() {} // illegal!
}
}
this just always bites me: > "a string" instanceof String
<- false
(but seriously, I don't understand why JavaScript needed the object/primitive distinction. I get it for Java, but not in JS.)> sometimes I feel that the language works against you, and not with you.
JavaScript is the language where you ask it, "here, I want to know which of these guns most adequately shoots my foot."
ES6 improves things considerably; in 10 years we'll have good browser support for basic data structures, like Map and Set.
[1]: I'm joking. Read it first, then return to this footnote. The sleight of hand is this: the first expression parses as an empty code block, followed by a unary plus taking an empty list. The second parses as binary addition. There's also a terrible amount of coercion taking place. This also "means" that binary addition "isn't" commutative: {}+[] and []+{} evaluate to different values; the same trickery is involved.
[2]: the binary or operator, |, silently converts the operands to a signed 32bit integer for the duration of the expression. Note that signed 32bit integer is a type not available to the JavaScript programmer — it only arises internally! (The only numerical type available is "Number", which is an IEEE double.)
[3]: the grammar for functions is essentially "list of statements or function definitions", so function definitions are only valid at the "top level" of a function.