The many faces of undefined in JavaScript
mortoray.com
mortoray.com
We've generally adopted strict mode. I think it is now time to come up with and adopt a new no-undefined mode. And yes, it should break the standard library. It is worth it.
And on the other hand sql made the opposite mistake where NULL means both "this field has an empty value" and "this field does not have a value"
https://medium.com/@hbarcelos/why-i-banned-null-from-my-js-c...
Brendan Eich has elaborated on why the decision was made, whether it was a good one or not - null is an empty object pointer.
Idiomatically, I'd argue that `null` shouldn't exist in Javascript, since undefined is the global "default empty" value.
It's not very common but not unique either. Perl has `undef` that roughly works identically, which became generalized into the "undefinedness" concept in Raku [1]. Ruby doesn't have `undef` value, but it could have been in the alternative universe [2]. Even more languages have multiple absent values which are not necessarily compatible to the null-undef distinction (e.g. Objective-C).
---
I think the separate `undef` value was mainly regarded as a solution to the apparent problem of detecting the absence in general, for example the absence of index or argument. Consider the following Python program for example:
def foo(obj=None): ...
It is clear that the optional `obj` argument cannot alone distinguish `foo(obj=None)` from `foo()`. A common idiom is to have a private object in place of `None`: _NOT_GIVEN = object()
def foo(obj=_NOT_GIVEN): ...
It is still possible to somehow obtain a reference to `_NOT_GIVEN` and therefore use `foo(obj=_NOT_GIVEN)` which is indistinguishable from `foo()`, but why would you do that? `None` is sometimes a valid argument to the optional argument, but `_NOT_GIVEN` is clearly designated to be invalid for that. Now rename `_NOT_GIVEN` and make it a language construct---voila, you've got `undef`.`Undef` might have been a working solution a decade ago, when we were still struggling with dynamically typed languages in general and systematic approaches were less common. Lua for example uses `nil` for both purposes; `t[key] = nil` is a valid way to remove given key from the table `t` (with a caveat that it doesn't shift any subsequent keys if the key was an integer) and an excess argument is filled with `nil` [3]. This is painful from time to time, say, a table of optional integers is not straightforward. `Undef` might have been a good compromise under this observation... if we didn't have any algebraic/sum data type like today.
[1] https://docs.raku.org/language/typesystem#Undefinedness
[2] https://stackoverflow.com/questions/6975266/what-is-the-unde...
[3] Lua even tried hard to remove any visible distinction between the actual `nil` and real absence of value! But it's still not perfect, and the discrepancy is much easier to detect from the C API.
None signifies absence, and if you need to pass a special value, then you might define a special object for that.
You can't distinguish `foo(obj=None)` from `foo()` because they are the same thing. You probably want something like `foo(obj=UNSET)`
I could add that in my mental model, the reason that an object property with an undefined value is different from an actually undefined property, is that since JS objects are basically key/value maps, there is a difference between a key existing with an undefined value and the key itself not existing at all.
The remaining caveat is that when serializing an object, attributes of type undefined need to be skipped, and some libraries get that wrong. So sometimes it is necessary to delete them from objects before serialization.
So in my view, things would be better if null didn't exist at all and if assigning an undefined value to an object attribute would actually delete the attribute from the object. Of course the language can't be changed now, but that is the philosophy I try to use in code design.
I do realize there is the use case of passing an update object e.g. with HTTP PUT/PATCH and wanting to make a distinction between "don't update an attribute" and "remove an attribute". In that case, null kind of makes sense as a designator to remove an attribute.
I'm sure the author is most likely aware of this plus the context is around TypeScript but I think it can still be misunderstood and if you haven't been around when IIFE were very common you might not be aware that:
`void` is a javascript operator and it does have semantic meaning, it evaluates an expression and returns undefined.
const a = void 1;
console.log(a); // undefined
see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...`void 0` ensures that you get the `undefined` value regardless of any shadowing that may be happening with the `undefined` binding.
If you don't want the result, just call
someFunction()
instead of
const a = void someFunction()
import { produce } from 'immer'
let i = 1
const a = produce({ x: 0 }, draftState => i++)
const b = produce({ x: 0 }, draftState => void i++)
const c = produce({ x: 0 }, draftState => {
i++
})
console.log({ a, b, c }) // Logs: { a: 1, b: { x: 0 }, c: { x: 0 } }
The immer docs have a section about this pattern: https://immerjs.github.io/immer/return#inline-shortcuts-usin...That was in C, release 52 years ago.
> JavaScript [...] doubled it by having yet another null-ish value.
And did so 24 years later!
In defense of JS they supposedly designed and implemented the language in 10 days.
I find "implicit nulls" less defensible for Golang (38 years after C) and Java (23 years after C), as they had plenty of time to learn from other's mistakes.
The mistake was allowing nulls to be a valid value for all pointers/reference at the type level.
You cannot make this mistake in an untyped language like JavaScript*
The solution to this is not eliminating nulls but rather separating nullable pointers/references from non-nullable ones
* Ok ironically JavaScript has an actual historical implementation bug where typeof null === "object" (basically Netscape used tagged pointers, the object tag was 0, and null was represented as a null pointer, so typeof would read the tag and return "object"
Hence I call it "implicit nulls" in my post. I know `null` as a concept is important, even if you have it as `Unit`, `Void` or `Nothing`.
You are totally right thought that in languages that do not force you to specify types it is basically needed as you cannot hint a type to begin with.
No, it was in ALGOL W. The "billion dollar mistake" is what Tony Hoare - one of the designers of the language - called it (later on, of course).
would be damning but also fun if it ever turned out they planted this as a practical joke, fully knowing what they were doing
So you could return something from a void function and use it and that will compile to JS and work but only the compile time type check would fail.
Highlighting that of course TS has no runtime checks of types.
It's a bit annoying, but there's good reason for it. However, it can be controlled with compilerOptions.noEmitOnError (can be passed as a flag too).
https://www.typescriptlang.org/tsconfig/#noEmitOnError
That being said, if using something like SWC or wrappers that's less useful, since they don't type check.
TypeScript is great, but setting up projects that use it often come with some tradeoff (but I still think TS is worth it)
My biggest pet peeve is the lack of decimal in the main language.
Most languages aren’t infinitely backwards-compatible so unless you want to be running Python 2.7 forever or whatever…
It would have been nice if a cleaner, stricter and more "designed" alternative with full access to browser features had emerged in the meantime. I'd even accept Dart if it had managed to get broad browser support.
Instead we're in a waiting game where we're all forced to use Javascript for any real work until the horizon is crossed and we can finally use a dozen of wasm-targeted languages.
Are you aware if AssemblyScript?
https://www.assemblyscript.org/
But arguably, the main purpose of WebAssembly is to use existing languages on the web, not necessarily invent new ones or even to replace Javascript.
I'm arguing we should have invented a new native language to replace javascript, which could have provided a stop-gap solution until wasm targeted languages are viable.
I use WASM everyday and I don't agree that this is anywhere near the top of the things that need to be fixed. The DOM is inherently slow by design, accessing it through a JS shim or "directly" from WASM really wouldn't make any difference in performance.
And on the language level, the DOM is just another web API accessed through a wrapper library like https://rustwasm.github.io/wasm-bindgen/examples/dom.html.
Also the "fixed" Javascript is called Typescript (maybe combined with a very strict linter).
This is an important point that I think gets overlooked quite a bit in these discussions. Web tech is essentially append only — you can add things but it's incredibly difficult for a browser vendor to make a breaking change because no vendor wants to be perceived as the one that "breaks the Web". Even technologies that were never technically standardized like Web SQL end up sticking around for far too long. I'm not sure if many programming language communities would like to be constrained by this and the slow TC39 language proposal process.
not if you implement backwards-compatible opt-outs for which see my reply concerning pragmas a la `'use strict'`
And may they do it, I don't care except for the weight of the little-used lines of code that are carried forward indefinitely. And it's not only 'append'; appending stuff we can without breaking backwards compatibility, it's the 'update' operations that curiously many view as infeasible despite there already being `'use strict'`.
Server side developers yearn for their favorite language in the browser, but are used to living in a world where language deprecations mean "this feature will disappear from all modern version of the language eventually" not "you can opt-out of this feature by placing a string somewhere in your code". To me that's a hollow definition of 'update'. Golang for example completely changed the semantics of for loops this year [1]. Moving forward, if you use a modern version of Go, you will accept the way for loops work now — there's no magical 'use old for loops' string. It's awesome that they were able to unilaterally make a breaking change to the language and force users to accept it moving forward. We can't do that in JS, that's the point I'm making.
On the other hand, I understand why not: You ship Dart and Python folk will be WTF, you ship Python and that would annoy the Java/.NET folk, and so on...
https://www.google.com/search?q=web+assembly+forth
I think the parent post wanted a more easily composed, modular sources of a whole, but better than JavaScript / ECMAScript. Intended to also have something similar to modern dev consoles attached with ease.
Not that it’d help much. Browsers aren’t 100% compatible between even their own releases with the one runtime they ship.
Regardless, there is an active proposal to add a 128-bit decimal type to JS: https://github.com/tc39/proposal-decimal
1234nEasy.
I agree that if it did not exist it would be not worth to be added and that almost every other use is bad, but != null and == null are very easy to learn idioms.
* 0
* null
* false
* empty String/collection
It may seem like too many rules, but it's very intuitive. For example, it's common in Java to do:
if (str != null && str.length > 0)
Or: if (str != null && !str.isEmpty())
There's even a apache.commons helper function for that: if (!StringUtils.isNullOrEmpty(str))
While in Groovy that's just: if (str)
It's just handy. It also reminds me of Common Lisp, where `false` is defined to be the empty list itself! It just makes sense and in Common Lisp code the same idiom can thus be used, which makes it very easy to read and write.If you have only one false value, it means that this test
if (str)
can mean "we have a string object here". If we make str take on the false value, such as nil, then that is not a string.If an empty string is false, then we cannot use this test.
An empty string being false encourages the poor practice of using empty strings to mean "there is no value here".
You might as well code in Bash.
That is not bad practice, as I said before, you almost always want to only process non-empty Strings, but sometimes an empty String comes in from user input or whatever and you either have to check it everywhere like in Java, or in Groovy that is just the default... you can still do 'if (str != null)' to be more explicit, but in my experience that is almost never what you want. Bad practice is making the more usual case look more unusual.
Also, it's nothing like JS because in JS empty lists and maps are `true`! The commonality with Common Lisp is exactly that "not being empty" is what the concept of true represents.
if ( list_of_names ) { ... }
doesn't make any sense at all. For one thing, `list_of_names` should only be allowed to be `null` in exceptional cases; disallowing it removes a whole class of errors.Second, imagine you have two handy operators / functions `not` and `is_empty`, then the above becomes either
if ( not is_empty list_of_names ) { ... }
or if ( is_empty list_of_names ) { ... }
as the case may be. In which universe is that too long for a load-bearing program that people depend on?