4 ms·
Empirically, having an “unset” value is pretty important. It’s not nil or null or undefined, but rather unbound. A sentinel value. It pops up everywhere in pyt
by sillysaurusx 4y ago
Empirically, having an “unset” value is pretty important. It’s not nil or null or undefined, but rather unbound. A sentinel value.
It pops up everywhere in python libraries. Core libs, like inspect.py.
I’ve never seen a good treatment of unset. There are a bunch of interesting corner cases, like how to store an unset value, or whether setting a hash map entry to unset should delete the entry, or whether it’s falsey. (Falsy?)
You want it so that you can tell whether an argument to a function wasn’t passed at all, vs whether someone explicitly set it to nil.
- masklinn 4y ago> It pops up everywhere in python libraries. It pops up everywhere because Python has default values and sentinel objects. In a different language with a different logic, it wouldn't pop up anywhere as much. And usually (though not always) it doesn't overlap with the need for a null (and / or the distinction doesn't matter). When it does, you can just add a layer of indirection e.g. `Maybe (Maybe a)`. That's what you get if you store an option in a map, in a language with option types: lookup :: Ord k => k -> Map k a -> Maybe a so if you have a Map k (Maybe a) (a map from any key to an optional value, aka your standard Java or JS or Python map) lookup will return a `Maybe (Maybe a)`, the outer Maybe is the result of the lookup, and the inner Maybe is the stored value. There is no information loss, and if you don't care about the distinction you can just `join` the result in order to collapse it.
- dragonwriter 4y ago> Empirically, having an “unset” value is pretty important. Being able to check if some identifier isn’t bound is useful, but as soon aa you do so by means of an “unset value”, you need abother way to detect if it was really unset or just set to the unset value. > It pops up everywhere in python libraries It pops up everywhere in Python libraries as a means of hacking around the fact that function defaults are evaluated at compile time, so, particularly with mutable defaults, you end up with a shared object rather than a fresh one with each default invocation, so a sentinel value is used to know you need to create a fresh value in the function body rather than using an default thar gets improperly shared. Arguably, what Python mostly needs to streamline this isn’t a systematic handling of sentinel values but syntax for specifying that the default value is the return value of a specified callable rather than a specific function-definition-time value.
- Dylan16807 4y ago> Being able to check if some identifier isn’t bound is useful, but as soon aa you do so by means of an “unset value”, you need abother way to detect if it was really unset or just set to the unset value. "Unset" has that problem. But a sentinel value for "unbound" is very doable under certain limitations. A language can have "being unbound" be exclusive to data structures, and "containing the sentinel value for being unbound" be exclusive to local values. Then it's never ambiguous. This also implies you won't have a "locals()" equivalent.
- wongarsu 4y agoThe concept of "there is no value here" is very useful. For example when trying to get a value from a hash map, or having optional parameters. But nil/null/undefined doesn't seem like the best way to go about this when designing a language. Not every value is optional, but if anything can be nil then you will spend a lot of unit tests and mental overhead on managing that disconnect. Systems that allow you to explicitly specify what can have no value (like SQLs NotNull, or Rust's or c++'s Optional) seem to do much better in terms of preventing bugs.
- int_19h 4y agoThe problem with "unset" being a value is that it can then get propagated very far (so long as it just gets copied and not used) before blowing up on access. And then you're looking at the value and have no clue as to what logic error produced it in the first place.
- patrec 4y agoThis is easily accomplished in python: _DEFAULT = object() # bonus points: make this reload safe def frobnicate(bar=_DEFAULT): if bar is _DEFAULT: ... But really the main reason this comes up is because python went with the dumb decision to evaluate default args at defintion and not call time (unlike, say, common lisp etc.).
- kazinator 4y agoBut in this instance, Python's dumb evaluation evaluation strategy for default expressions makes your code unnecessary, because you could just have: def frobnicate(bar = object()): # object() evaluated once, at definition time Python behaves as if by a transformation to: __hidden_global_42 = object(); # evaluation at definition time def frobnicate(bar = __hidden_global_42): which is exactly what you have done manually.
- DonHopkins 4y agoBut then how does the body of the frobnicate function tell if bar is the default sentinel object() value or not (if bar is ???), without somehow using reflection to fish out a reference to the default value of bar from the function definition? Or is there an easy and efficient way to do that? Add yet another optional-but-not-meant-to-be-passed parameter bar_default=bar maybe? Yuck.
- kazinator 4y agoLooks like you can get at that value in frobnicate.__defaults__. Having spread that, I have to go wash my hands now.
- DonHopkins 4y agohttps://en.wikipedia.org/wiki/Falsies https://en.wikipedia.org/wiki/Falsies