On null
sicpers.info
sicpers.info
reference undefined state ... check! contain no useful information ... check! causes confusion in programmer ... check! are defined differently in each instance ... check!
I think I could go on, but perhaps you get my point.
I think this goes the same for a lot of unfortunate design decisions throughout computing that like to get bikeshed with the benifit of hindsight. Like, yeah, sure, there's this foot-gun. And, yeah, we've probably found better options in the intervening years, or ways to use the feature more delicately where it is the right option.
But hot takes make for better conversation than reasonable discussion. Even on Hacker News, the person I want to hear from most is the one rolling their eyes and closing the tab without taking the time to write a comment.
Conceptually, null is a sentinel value representing nonexistence within a value domain. Memory pointers/object references are the archetype of null's use, but as others have pointed out SQL's null support is another. The key takeaway, though, is the concept of "sentinel value." For a (pardon the pun) non-null example, consider the sentinel value "-1" in the domain of integer file descriptors returned by the "open" POSIX function[0].
Sentinel values, by definition, put the onus of discovery on the code which receives it since they can be the result of any computation which can produce a respective domain value. This complicates usage but must be done for correct programs. As a result, it is very common to normalize sentinel value discovery through the constructs available in the language being used in an attempt to minimize defects related to "missing a sentinel."
In my experience, this naturally led to modelling the expectation of sentinel values in its own container type, one where the cardinality is either 0 or 1. Common names for a container with 0/1 cardinality are "Option", "Maybe", and "Nullable." Again, the key takeaway here is that these types in this context serve to normalize the detection of sentinel values wherever a domain value can be produced.
Depending on the language used, "lifting" sentinel values into an option type might be not worth the effort. But the handling of sentinels must always be addressed.
0 - https://en.wikipedia.org/wiki/Open_%28system_call%29#C_libra...
The only way I can think of making this sound and well-typed, is that null supports any fields and any methods, returning always null. This doesn't fit well how many of the languages actually do.
In C++ references fill that role but it's not very difficult to get a reference to a NULL or stray pointer by mistake, unless you make sure to forbid raw pointers altogether. NULL effectively ruins your type system by making all your reference types de-facto optional.
TFA points that out but concludes by saying that "Either approach will work", that's a bit curt I think. It's like saying that if the seatbelt doesn't stop you the windshield will, "either approach will work".
You generally want problems to be noticed close to their origin, and not hide latently in lists and maps, waiting to be tripped over.
This is what Clojure does to an extent - in Clojure, it is called nil-punning and implicitly handling nil values by using the functions that return null on null input makes the code concise and is considered idiomatic practice. In other Lisp dialects, this is even more common, as far as I know.
That sounds a lot like NaN, which is often conspicuously absent from diatribes against NULL.
(And it's not just because of "lazy programmers"; it truly lacks the requisite structure. Even a very Haskell-y Haskell program in the "normal programming" space doesn't have something that looks like a complicated numeric stack... this is where the applicatives and monads and profunctors come in. Well-structured "normal programming", even very "mathematical" "normal programming", is not structured like heavily-numeric code. That's a sort of "complicated stack" of its own, but even then I'd characterize those abstractions as having a structure where you are almost always building properties up as you go "up" the stack. Contrast with the numeric tower where as you build up, you are generally losing properties; numbers are fully ordered, until you add complex numbers. Complex numbers multiply commutatively, until they become quaternions. Any number can be multiplied by any other until the "numbers" become matrices. Etc. The numeric tower builds in the opposite "direction" from the "normal programming" tower. There's a sense in which you could turn it upside down, but it's not one most people are even going to wrap their head around, let alone want to use.)
Delphi's RTL set the FPU flags to default to signalling NaNs, causing some havoc when linked against C and C++ libraries (usually gaming libraries) that assumed otherwise.
You usually don't want a divide by zero poisoning your data, for the same reasons you don't want unnoticed nulls infecting your data structures. Exceptions can be made for scoped blocks of code, but it's not a good default.
If one cares to make the analysis, one could say that it's messy encoding of the None of the maybe monad, e.g. at the call site you need to do some reasoning, but it effectively encodes Some / None.
It simply does not communicate anything to me to hear someone say "logically-speaking, there is no such thing as null" - such a statement really does appear to be word games. It has no explanatory power, and it cannot ever be categorically true.
People want to Get Stuff Done, and preferably without getting all math-y about it. Compile now plz.
Add the null-reference to your spec and Presto! the problem is now confined to userspace.
it can be done, but it's Not Fun, and definetly mathy, e.g. Predicate Transformers or ß-reductions
This is what you call "paper overing" but I don't think it is fundamentally wrong, I think it's just a different way of reasoning about the data. It's also why I think the functional programming community prizes maybe monads more than the object orientated community. Functional programming is fundamentally about managing streams of data with functions that match up types, so the distinction between none and null matters because the data doesn't drag along it's methods from place to place.
I've seen tons of complex and unwieldy code in my day. Often the complexity comes from illogic and incorrect modeling of how things are. So when someone points out a logical unnecessity, for a programmer to dismiss it as meaningless is to miss out.
(In some logics,) there is no such thing as null and therefore you don't need it's representation to write effective programs. That's actually a valuable thing to say. Because from there you can start asking questions like why does it exist here or there? And then you can proceed to write more reasonable, less complex programs.
I've had to fight ill-devised null semantics too many times to count in my programming life.
To get concrete, there are things we know and things we don't. Most often null is used as a placeholder to say "I don't know." This leads to complexity because I'll then have to write code to branch on this one other value case (or at minimum defend, like the article points out). But if it's not logical per se, then what else can we do. What other way can I program that more reflects logic and our reasoning processes? That's a path to writing better, more maintainable programs.
One revelation I got when having to model data in NoSQL after years of SQL is that while SQL makes you decide and write down NULL values for entity properties, NoSQL did not. If you don't know it, you don't write it down. It's that simple. And that is much more in line with the way that I contend with the things I don't know in my life. I don't go around re-iterating my lack of knowledge by referring to some logically non-existent value. I generally simple don't say things I don't know. Following this reasoning to a degree, and then I am writing programs that are much more ergonomic to the way people actually think.
A null value means something in that context. I don't see how it means nothing.
If I have a tail-recursive function, I probably do not need null. But if I have a loop which may run 0 or more times, and need to catch the absence of runs, I need to model a None result.
In that instance, the null has semantic meaning. I can use an algebraic type of course, but for the task I am doing, that may not be practical.
Saying that null logically doesn't exist is not true if my context is the Linux kernel. The unqualified statement of such seems to be overly categorical, to me.
I do like your analogy with NoSQL, I think it's an excellent point. Nullability in a JSON document for instance is better modelled with absence - however, with a language which is compile-time type-checked, those absent values may need to be present in materialised classes as nullable values (options if you like) - unless you're dictionaries all the way down like Python.
Of course there are alternative ways of doing it:
"A statement either does hold (it has value 1, with some probability p) or it does not (it has value 0, with some probability 1-p; the Law of the Excluded Middle means that there is no probability that the statement has any other value)."
This holds for example in classical logic, but it is not a universal principle in logic. For example, the statement does not apply to intuitionistic logic:
https://en.wikipedia.org/wiki/Intuitionistic_logic
The fact that (A ∨ ¬A) is not a tautology in intuitionistic logic reflects the limits of logical proof: By Gödel's first incompleteness theorem, if a consistent formal system is sufficiently powerful, then it can express statements that can be neither proven nor refuted within the system. Intuitionistic logic lets you reason about such situations.
If you are interested in learning a programming language where you can conveniently express "anything at all" without even a trace of "null", a logic programming language may be suitable. For example, in Prolog, a logical variable stands for anything at all, and as such is truly a variable as in predicate logic.
But if you don’t explicitly expose that additional entropy/information to the programmer and type system, the meaning vanishes. If every Value can have this additional type, we lose the semantic understanding we would get by seeing that a particular method returns a “bool?” rather than a “bool”, and instead we are forced to either assume everything is a “bool?” and add a bunch of unnecessary checks, or assume nothing is, and miss out on whatever extra value a nullable give us. This is again very uncontroversial.
What this really comes down to is, do you prefer to have guarantees, or do you think that "less code"/"less useless checks" is better.
In my mind, the only way to write robust, maintainable code is to not use null and use Option/Maybe.
> I disagreed with the statement in the post that:
>> Logically-speaking, there is no such thing as Null
> [..]
> Boolean logic
> [..]
> It isn’t true [..] it isn’t false either. We need another value, that means “this cannot be decided given the current state of our knowledge”.
Null as used by many computer languages is not some logical choice. As in the example, sometimes a value has possibly more choices than just True | False. Logically-speaking that means the value has three choices: True | False | Undecided.
Some computer languages let you encode this more convenient that others. Even falling back to Null to represent the possibility isn't necessarily bad. However, that doesn't make Null a logical thing, but just the representation of this choice for a computer.
In that sense, "True" as encoded in programming languages isn't a "logical thing" either.
Programming languages are their own domain of syntax/semantics, which can encode other domains (eg. mathematics).
The type, Trivalent = { True, False, Null } is a perfectly reasonable encoding of a system of logic.
enum Bool
{
True,
False,
FileNotFound
};This author is ultimately making the point you are making, which is that there's a concept that every programming language has to grapple with. Some call it Null. Some use more formalized means.
Sybase, at least back then, stored BLOBs as 8k chunks. So when creating a row, if you passed NULL (in C) for the pointer to the data to store in the BLOB, then the BLOB became null. But if you passed 0 as the pointer, then Sybase allocated 8k and stored an empty value.
For years, we had stored data in BLOB fields, sometimes empty. We'd never noticed the 8k allocation into empty fields, because it was swamped by the ones with real data. But we pulled the data out into files and added a file name field to find it (for performance reasons), and adjusted the size of the databases, because disk was expensive back then!
I had implemented the transition, and had argued for removal of the BLOB column entirely, but our cautious lead dev rejected it, out of fear the missing column would break other things. Testing showed everything working perfectly. Then we put it into production...
...and saw that we had about four hours before we ran out of rapidly-filling database space. And we could not gracefully roll back, for business reasons. So I had four hours to find and fix the bug.
The real problem here wasn't NULL itself. That was a reasonable way to communicate a state to Sybase. But other problems made it a problem. First, the C programmer's habit of treating 0 and null pointers interchangeably. They are usually but not always equivalent! This bug had actually been sitting latent in our system for five years at that point - we just never noticed it.
The other problem was the lava flow antipattern, not removing a clearly unnecessary database field out of fear of downstream repercussions. That one bothers me more, and to this day, I try to be good about not keeping apparently-dead code out of fear.
Does anyone else immediately lost interest in the article at the word "mansplaining"? Like, why do we need tp bring this in a discussion about logic and theoretical cs? Also if you post something in a public manner and people state their disagreement, that's not mansplaining...
People who disagree with you arent personally insulting you. And even, people who are condescending online arent personally insulting you.
The self-esteem hack psychology of the 60s-80s has created a generation of people who haven't learnt how to tolerate insults and disagreement, ie., a sub-clinical narcissism.
This was a pithy summary. Yes, all the languages discussed (Ruby, ObjC, Java, Haskell) are all indeed Turing-complete and hence can express what any other one can express.
What the article didn't discuss is ... basically everything there is about the "nullness" discussion going on in the software engineering community.
I use nullable languages a lot (Ruby, Java, C#, Clojure, Common Lisp, etc.) and null-free languages a little, but for a long time (Haskell, more recently others). If I absolutely want to make correct code without writing more tests than code and using extensive code coverage tools and other external support, I'd rather do it in Haskell. Then I don't have to worry about nullness except when I want to, and then the type system handles checking that I properly checked for it.
unit has a cardinality of 1, it has a single value (). Option types have a cardinality of 1 (if you ignore the sub-value): an empty value and a full value. This can trivially be shown by the bijection between boolean and unit option.
> In an implementation language like Java, Objective-C or Ruby, a null value is supplied as a bottom type
1. Ruby's nil is just a singleton instance of NilClass, and it's not a subtype of "all other types in the system" anymore (or less) than Fixnum or String is.
2. a bottom type is empty by definition, null can't be a "bottom type" since it's a value (of sorts). `void` would be a bottom if it were something approaching a proper type.
But I think - beyond having wildly inconsistent notions of null - many languages do a much worse job of dealing with null than they could, which is why it causes so many errors in the wild. Java is obviously the archetypical example. It sounds from the comments, though, like Objective-C and Swift and Ruby have interesting strategies which I didn't know about.
TUPLES SETS
NULL=⧼⧽ ∅={}
combinations collections
Obviously, there is duality between tuples and sets but currently there is no theory which would be based on this property by treating them as equal constructs (tuples are defined as ordered sets by simply ignoring their duality to sets).Since variables need to allocate some memory to store their values, it is necessary to introduce a mechanism to deal with empty values and then somehow interpret them.
There was another discussion of nulls some time ago: https://news.ycombinator.com/item?id=17028878 (The Logical Disaster of Null)
Suppose we have a permission system for some webapp. With using Null as permission to access a user profile we can express three end states; Return 403 Unauthoried (FALSE), Return 404 Not Found (NULL) and Return 200 OK (TRUE).
That way a user not logged in or with no specific permissions will not know a profile exists. However if they have been added to the deny list they get a deny error. Otherwise they see the profile.
It's quite neat in that way.
Live in C (or assembly) and there are no issues with what is NULL.
There are two kinds of types:
1) Exposed data like numbers, strings, structs or containers. These will often go on the disk or wire. Best practice says such types shouldn't have any required parts, otherwise updating the code in production becomes hell. For example, see how Protocol Buffers V3 bans "required" fields and uses default values everywhere.
2) Opaque objects with behavior. Best practice says such objects should be mockable, which makes them de facto nullable, because you can write a mock that behaves like null (throwing an exception on every method, or even looping forever).
This is the same as the old distinction between types with deep equality and types with pointer equality (or no equality operation), or the distinction between serializable and non-serializable types, etc.
So the idea is that we shouldn't have null pointers per se, but we should allow programmers to construct a default value for any type, which could be spelled like {} or null. Its implementation should vary depending on type. For data-like types the default value should be something like 0, "" or [], and for behavior-like types it should throw an exception on every method. That would work in both static and dynamic languages and cover all uses of null that I've seen, in both production and test code, without losing any convenience or semantic niceness.