The point is that option types let you explicitly state which values are nullable, but null references mean that every value is potentially null even if it doesn't make sense.
And that having everything being 'implicitly nullable' is a categorically worse for maintainability, readability and comprehensibility than using explicit Option<T> types at the boundaries; such as the implementation in F# [1], with an expressive matching syntax.
It is now common when creating a library to rely on manual checking and coders explicitly writing code to check and throw NullArgument exceptions for each object parameter in every public method, and then writing dozens or hundreds more pointless unit tests to ensure this null exception behaviour works as expected; this is totally unnecessary in languages that demand explicitly stated Option<T> when an Option is meaningful.
Instead of requiring all the above for safety -an approach which will notably fail to report any errors if the null argument checks are partially or completely omitted- we have -and we should prefer- languages where the compiler or interpreter will not let us pass null into a function where null makes no sense, because that fact is encoded in the function definition.
At the library boundaries, which are comparatively rare to internal code, we can simply use the explicit Option type.
I should note I have one objeciton with the F# implementation - in that I think that ideally the properties { .IsSome, .IsNone, .Value } should not exist, as they promote bad programming practices.
That is only the case in reference-centric languages such as Java, C#, JS, etc. In value-based languages, "values" can't be null, while "references" can.
For instance a `std::string` or a `double` in C++ can never be null ; a pointer to either can be but that's far from idiomatic.
That approach doesn't really work, because C++ conflates where a value is stored with whether the value can semantically be null. Sometimes you want a nullable value that's on the stack. Sometimes you want a non-null value in the heap. Both these things are hard to do in C++.
Neither of those is hard to do in C++. The first one is std::optional. The second one is a reference or a non-nullable smart pointer like https://github.com/dropbox/nn
You don't need option types for the compiler to enforce safety around nulls. Its possible to model null as its own type in the type system to enforce safety.
In such a type system, is every variable and function potentially null, or does its type have to be declared as being potentially null (or not declared as not null)? Would the latter case not be, in practice, just like using option types, and would the former case require ubiquitous null checking?
My understanding so far is that option types have two advantages over the way current mainstream languages handle the null case: a) you can find the potential uses of null values statically, and also ensure that the programmer writes some code that at least nominally handles the case; b) you can avoid this burden in cases where null is not an option. Does null-as-its-own-type improve on this?
I mean the latter. And in practice, it's not just like using option types because it works well with flow typing. Being able to express your nil checking in ordinary control flow instead of special optional methods is often cleaner (think `raise "foo" if bar.nil?` as a guard statement and for the rest of the method body bar is no longer nil). All absolutely safe and checked at compile-time. If you then allow calling methods on nil you can even implement the "try" method that most Optional implementations have, which is pretty nice sometimes even when you have flow typing.
In Crystal we go a step further and allow type unions between arbitrary types (of which nil is just an ordinary empty struct in the stdlib which you can call methods on). I love how it works in practice.
It's the same mistake as checked exceptions all over again. If you make null a special case in the type system then anything that interacts with the type system has to know about null or will handle it wrong. Far better to have the type system work with plain (non-nullable) values, and implement a plain old library type like Maybe/Option for use in places that need absence semantics; that way your concerns are properly separated and you can spend your type system complexity budget in more generally applicable ways.
That's the only way I've ever seen it implemented. If you're proposing some idea for having null in the type system as a normal type (that doesn't boil down to just being equivalent to Option/Maybe), can you be more concrete?
It is now common when creating a library to rely on manual checking and coders explicitly writing code to check and throw NullArgument exceptions for each object parameter in every public method, and then writing dozens or hundreds more pointless unit tests to ensure this null exception behaviour works as expected; this is totally unnecessary in languages that demand explicitly stated Option<T> when an Option is meaningful.
Instead of requiring all the above for safety -an approach which will notably fail to report any errors if the null argument checks are partially or completely omitted- we have -and we should prefer- languages where the compiler or interpreter will not let us pass null into a function where null makes no sense, because that fact is encoded in the function definition.
At the library boundaries, which are comparatively rare to internal code, we can simply use the explicit Option type.
I should note I have one objeciton with the F# implementation - in that I think that ideally the properties { .IsSome, .IsNone, .Value } should not exist, as they promote bad programming practices.
[1] https://fsharpforfunandprofit.com/posts/the-option-type/