> Different people understand null differently (it might mean "error", "value not in map", "invalid user input", ...)
Null only has one meaning, null. That's the point.
As soon as you start applying more to it than that you get problems.
> and there's never been a clear consensus.
This is simply not true. Null is null. That is all. Period. End of story. It has never been more than that.
If you have libraries, functions or existing code that ignores this, then that's on you, the developer, to reason about.
I guess I'm taking your meaning a bit out of context, I do understand years of common practice have resulted in much abuse but I don't think the language designers would have ever denoted double meanig in null values.
> If you only have one cache, sure
I feel like you're missing my point. If you need to handle more meaning than null == notset/unset/absent then you need to resort to a new data type. Null only has one meaning, null. You can't get two meanings out of one.
You, as the consumer of the cache must then decide on how to represent or encode further meaning. Either using the Some pattern or an empty string or something like that.
This serialisation can easily be wrapped around a base cache class that just deals with simple storage where null == not set or unset (absent value.)
But the underlying pattern shouldn't involve itself with further concerns than it needs to.
This opinion is precisely because I've seen this sort of oh I'll just add a has function, oh and then I'll add a sub par serialisation library. Ok now I need a sometimes unserialize. Oh some legacy? Ok now I need to deserialize once to one level and twice to all levels. Oh yea, now I should throw not found.
Ok now every single call to cache.get must be wrapped in try catch and we must cast some values to false and others to empty string, oh yea and you have to call has before every get even if you just want to take advantage of a dynamically typed language and test for that falsely value. Cache.get(test) && okdothing();
It's two functions set sets the thing. Get gets it. If it's not there it returns null. That is the whole contract. Why do people try to over complicate the base contract? It's just crazy over engineering.
> which is precisely when you most need everything to work the way you'd expect
Indeed. And I expect null == null.
Not null == not yet set, set but cleared, set but empty, false, error, not found, or anything else for that matter.
I can use null to represent that my cache does not have a value for that key because that is the design I chose that