2 ms·
That's an anti-pattern. In a properly lexically scoped language, an undefined identifier is erroneous, diagnosable before you even run the code. If optional dy
by kazinator 2y ago
That's an anti-pattern. In a properly lexically scoped language, an undefined identifier is erroneous, diagnosable before you even run the code.
If optional dynamic scoping is supported, it can make sense to deal with an undefined variable, but is still something you would want to avoid. E.g. Common Lisp programs almost always assidiously define dynamic variables via a top-level defvar.
Testing for definedness is usually part of some hack that doesn't deserve its own syntax as a predefined language feature.
The basic idea is that programs should usually know which of their variables are defined, at what point in the program. It should be statically obvious, or failing that, at least dynamically obvious. The remaining situations can be valid, but are low-priority hacks.
(You can always have your macro for it, if you want it badly enough.)
For the environment var example, we can use the regular or (which is much like ||), if the lookup yields nil, which is false:
1> (or (getenv "HOME") (getenv "LOGDIR") (error "You're homeless!"))
"/Users/kazinator"
2> (or (getenv "XXXHOME") (getenv "LOGDIR") (error "You're homeless!"))
** You're homeless!
** during evaluation at expr-2:1 of form (error "You're homeless!")
Looks like Perl made a mess of this simple thing, too.
I understand some languages have an "undefined" value for a variable that is actually defined, but that's pretty lame too; it gives rise to multiple ways of being false. Awk is like this. Awk knows every variable in the program before executing it. GNU Awk builds a global symbol table of all variables at compile time. So every variable is considered defined in the sense of being known to the implementation and existing. It just has a kind of undefined value initially that serves as an empty string or zero, and tests false, but is neither a number nor string. It's a pretty cockamamie design from the POV of larger programs, but does lead to very short one-liners.
- JadeNB 2y agoThese thoughts on proper typing discipline are a valuable perspective on good programming-language design, but I think that they probably don't help much for writing code in Ruby, which, I believe, doesn't behave this way. (I'm not a Ruby-ist, so can't speak authoritatively to it.) Nonetheless, supposing that we're working in a language that behaves as you suggest—if memoization isn't built in, how would a user build it in a way that allows caching of the return value `false`? (For example, for testing whether a positive integer is prime, where getting that `false` value could be very expensive.)
- kazinator 2y agoThe problem is identical to how do we have an hash table (or other associative array or dictionary) in which we distinguish the presence of a false value from the absence of a value. There are solutions for that. For instance, a lookup operation that returns the underlying dictionary entry, or nil if there is no entry. If there is an entry, we can dereference it: entry.value has the value. That's a solution not requiring any special type system constructs like Optional or sum types. We can have a double lookup: if the lookup yields the ambiguous false value, then we can do another lookup like contains(hash, key) that has a boolean result. Some languages like Common Lisp have multiple return values. The gethash function in Common Lisp returns a second value which indicates whether the key is present. If the first value is nil, but the second value is t, then it indicates that the nil was actually pulled from the hash table.
- JadeNB 2y ago> Some languages like Common Lisp have multiple return values. The gethash function in Common Lisp returns a second value which indicates whether the key is present. If the first value is nil, but the second value is t, then it indicates that the nil was actually pulled from the hash table. But this seems to be what you said was an anti-pattern: > That's an anti-pattern. In a properly lexically scoped language, an undefined identifier is erroneous, diagnosable before you even run the code. You were speaking there, presumably, of bare variables, not of hash entries, but Perl's defined-or works just as well on lookups, like `$a{ key } //= "Hi there!"`, in which case it is just a less-verbose version of the two-step lookup you propose.