PEP 505 – None-aware operators (2015)
peps.python.org
peps.python.org
I'd like to use 'or' more often, but it always makes me stop to think "Could this get passed a falsey value?" I've seen a lot of Python bugs because people used "x or 5" when they needed the much uglier "5 if x is None else x".
Yes. That's how you end up with messes like this one: https://lwn.net/Articles/590299/
In most people's Haskell code, you can only use actual Booleans when you need true or false. (As you might expect from a well-disciplined, strongly typed language.)
But it's actually really easy to add your own version of eg `if` etc that accept nearly anything, and automatically convert it to True or False. You can do this as a normal library for use in anyone's code.
If your language supports a genuine nil and not just a distinguished singleton token (AFAIK only Lua does that consistently, though perhaps “undefined” in non-strict-mode JavaScript also counts), I’m actually leaning towards having operators for dealing with it specifically. Icon does something like that, arguably, although its behaviour (for a “failing” expression that yields no values) is more like a propagating NaN / relational NULL / poison value than a nil.
Nix (though it has a faux null like everybody else, as well as a poisoning bottom typical of lazy languages) has a slick partial solution: the field access operator is optionally ternary, expr.name [or expr], so you can write things like
systems.flakeExposed or systems.supported.hydra
when you want to fall back from one to the other, even though in the fallback case the value of systems.flakeExposed by itself is not nil, it’s an error. (Using or outside of field access is a syntax error, the Boolean operator is spelled ||.)I liked Kotlin's null-handling when I worked in it because there were some tools there to handle nulls in a reasonable manner. I think the `something?.prop` would do null checking on `something` before trying to access `prop` and then you could use `?:` (Elvis operator, haha) to check a null and do something if it was null, like `something ?: executed_if_null` and combining those would effectively deal with nulls in a simple and readable way.
Lua is JavaScript-level stupid, minus the useful ecosystem.
Which is irrelevant, as the author mentioned a specific feature of Lua that's designed better than Python's same feature, didn't say everything is better in Lua.
(Not to mention it's not really true anyway, Lua is fine).
As far as zero and one; if you compare equality on a machine you’re generally doing an xor type operation between the two values and if they’re equal you get zero, so that’s flipped. If you’re comparing two numbers it might even result in a ternary value of LT, EQ, and GT.
I think true and false are high level programming language or mathematical concepts that don’t map directly to the low level testing results. So, I don’t really consider the traditional “true is 1, false is 0” to be anything more than convenient abstractions in programming based on things that some folks did for convenience at the beginning of software development.
1 doesn't mean much regarding true, if every other non-zero integer is also true.
And my point is that since 0 alone is treated as false among integers in those cases, it can be considered as iconic or "standard". But since any other integer is true, 1 is not much of the "canonical" true, but just one of billions of others.
In binary it's 0 and 1 alone, so the comment doesn't apply there.
If you don’t want to test an integer, if it doesn’t meet your needs, don’t use it. There are an unlimited number of other choices for comparisons.
First class bools alone being logic-checked, is a better design.
The problem is sum types. Specifically, languages which "don't have" sum types often secretly do have sum types, absolutely everywhere, but no language facilities to deal with them because they "don't have" sum types. That's what the billion dollar mistake is, it's sum types (thing | NULL) with no language facilities. In Python that's (thing | None) but the same penalty accrues.
Now, PEP 505 introduces a bunch of actual language facilities. They might be a good idea for Python, other languages successfully did this, but I think as a lesson going forward what we should take away is that we do want Sum types and we should stop pretending we (sometimes?) don't, which means new languages need to be designed for them.
Certainly Kotlin, Swift, Rust, etc. do have them. Even Java, I believe, is planning to add them, though I suspect that many people won't necessarily understand why they're useful even when they land.
It's probably helped by functional programming becoming more mainstream and "obvious" sum types needing to be implemented (Optional and Result, mostly).
something['nested']['blah']['bloop']
some.namespace.with['stuff']
safe with None. Your options are using jmespath, your own similar function, or wrap it in a try block. Otherwise the whole syntax is essentially unusable. And because of the quirks of how __getitem__ is evaluated you can't even hack an infinite defaultdict to do the right thing because you can't detect the "last" access to return None so it's only good for when you want to assign to it. something.get('nested', {}).get('blah', {}).get('bloop')
works just fine but is mighty ugly....which is a hint to use something else in this case. dataclass, typed dictionary, deserialize properly, whatever.
Lisp: (or x 5)
Scheme: (if (null? x) 5 x)
https://github.com/TC39/proposal-optional-chaining
https://github.com/tc39/proposal-nullish-coalescing
IMO optional chaining is a lifesaver when dealing with deeply nested JSON, and it's become indispensable in our Typescript code. But we also deal with JSON from various third-party endpoints in Python, and far too often we resort to an inefficient deep_get utility to make our code sensical.
> if json?.get("foo")?.get("bar")?[0]?.get("baz")
may seem messy but would be infinitely better than the fragile (note the nested list-of-a-single-dict, and how bugs can appear if it's not there):
> if json.get("foo", {}).get("bar", [{}])[0].get("baz")
or the inefficient
> if deep_get(json, "foo.bar.0.baz")
It's not nearly as good as the Typescript json.foo?.bar?.[0]?.baz - but it's good enough!
if hi is None:
hi = len(a)
With: hi ??= len(a)
Just sounds gratuitous. I'm happy this was deferred. hi = len(a) if hi is None else hi(In the concrete example, both versions use mutation.)
You can get examples equivalent to the proposed None-coalescing ?? operator does that don't use mutation, though.
They are both basically what you get when you retrofit something a bit like Haskell's typesystem onto dynamic languages.
Remember the Zen of Python: “Readability counts”.
In a language which considers `and` and `or` more readable than `&&` and `||`, surely `is not None` is more readable than `??`.
That said, I really loved using null coalescing/propagation in C#, so I have to assume my bias is coming from wanting python to be more readable.
In any case, this feels like something that belongs in Ruby more than Python. The designers of Python made the deliberate choice of sacrificing conciseness for readability in certain cases. If we give that up, I think Python will end up feeling like something that doesn't know what it wants to be.
They can also be shadowed by your own code.
If you don't like `<>`, you can also easily make your own function and use that instead:
my_function a b = a <*> b
I agree that (infix) operator syntax can make some code hard to read, but it can also make some code easier to read. I do feel strongly that allowing people to make up new operators is preferable to undisciplined overloading of existing operators:Eg in Haskell when you see `<>` at least you know that something unusual might be going on. In C++, when you see `>>` (or even `+`) you have no clue whether it's just plain old bit-shifting, or whether someone overloaded it to do IO or perhaps launch the missiles.
bar if foo is None else foo
or perhaps more clearly: foo if foo is not None else bar
Seems like it would save some typing.`str?` would be much nicer than `Optional[str]` or (new in 3.10) `str | None`