Providing Stronger GuaranteesRelaxing Requirements
Take a look at the following code:
function bar(x: string): Option<string> {...} let y = bar('sdf'); if (y.isEmpty()) { console.log('No result!'); } else { console.log(y); }
Looks good. Bar expresses that it may or may not return a string. Callers of bar are forced to handle both cases -- when a string is returned and when a string is not. And better yet, the compiler forces us to do this checking since Option is part of the type system.
This code gets released into the wild and tons of people are calling bar. Great success.
Now, as the author of bar, I want to provide a stronger guarantee to my users. I can now guarantee that bar will always return a string in all cases.
function bar(x: string): string;
Shit. Well what just happened to all of the callers of bar? They BROKE! They no longer compile because I'm returning string rather than Option<string>.
However, if the language understood nullable types we wouldn't have this problem.
function bar(x: string): ?string; let y = bar('sdf'); if (y != null) { console.log('No result!'); } else { console.log(y); }
?string has all the safety as Option but when changing ?string to string in a return type, we do not break any callers. Everything continues to work as expected.
Now lets look at the reverse.
Relaxing Requirements
This same problem occurs when taking in arguments to a function.
function foo(x: string): string {...}
Callers doing foo('sdf'); must be updated to foo(Option('sdf'));. Pretty problematic if this is a library released publicly.
However, this isn't an issue in TypeScript and other languages that understand null references. In those languages I would do:
?string is a supertype of string and thus no existing callers break. I can release an update to my library and not break any of its consumers.
foo('sdf'); // requires no change as ?string is a supertype of string.
The same problem with Option also exists in the reverse -- when making return types provide stronger guarantees.