Not as such; functional languages use "pattern matching", so you are forced to match each possible outcome. This way, it explicitly brings the issue of nulls out into the open; if you have a Maybe it might be null, if you have a String, or Number, or anything else, it is never null!
This means the language allows you to define areas of your program that are null-safe, and areas where nulls are expected.
Compare to languages where you must remember to check null on every use of a null variable. I guarantee I can pick any Java codebase and find a method where arguments to that method, or a class's properties, are not checked for null on every use. How do I know that the use is safe? Usually by coding conventions; maybe immutable instances represented by values set only in a constructor, although what's to stop null being passed in?
90% of the time you don't need to check the value for null, because it won't be null and that will be reflected in the type. In langauges where all types contain null, there is no way for a function to communicate that it always returns a non-null value, which forces the caller to make redundant checks. If the value can be null, then they are at risk of NullReferenceExceptions if they fail to check.
In cases where the value could be missing, the type is change to Maybe and the caller is forced to handle the null case. They can still avoid explicit checks using various combinators (>>=, fmap, liftA2 etc.) which propagate empty Maybe values. Explicit matching on Maybe is fairly uncommon in languages which support it.
1. the large number of values that you now know definitely have a value, and you can access without fear that they don't.
2. the helper functions for Maybe that let you deal with optionality in various ways; the possibilities are extensive and useful. null doesn't give you any help, and generally any helper syntax is limited to one or two possibilities.
3. Code cannot fail due to trying to access a null pointer accidentally. Functions that definitely return a value (ignoring severe issues like OOM, etc.) is a somewhat useful property.
No. When you get passed a "Foo", you know for a fact that it's non-null. When you get passed a "Maybe Foo", you know for a fact that there are intentional cases where this thing will be null. In Java, you just have one type for both purposes, so you end up checking non-nullness every time (or just blindly rely on non-nullness having been checked elsewhere before).
Here’s my real-world example for why type-level null checking is a good idea.
Let’s say your application tracks users, and each user needs to have at least one address. On top of that, they must flag one address as their default address, which is used when sending them formal communication, but usually they can pick which address they want to use. (Most people have one address which is their default).
The Java code to pick out a user’s default address is simple enough:
class User {
// ...
public Address getDefaultAddress() {
for (Address address : getAddresses()) {
if (address.isDefault()) {
return address;
}
}
throw new AssertionError("User has no default address");
}
}
We throw an AssertionError here instead of returning null because if a user has no default address, then that’s a bug in the program, and we want to fix it. And because it throws, we can safely assume that any address coming out of getDefaultAddress is not null.
Then one day we receive a change request from a client. Some users move around a lot, and have no permanent address, and they’d like it to be changed from at least one address to at least zero addresses. Basically, make it so users don’t have to have an address anymore.
How does this affect our getDefaultAddress function? It has to handle the case where users have no addresses, because if that’s the case, then they’ll have no default address, either.
The easiest way to do it is just return null instead:
public Address getDefaultAddress() {
for (Address address : getAddresses()) {
if (address.isDefault()) {
return address;
}
}
// no default address
return null;
}
With this in place can no longer assume that the Address returned from getDefaultAddress() is an actual Address object. Which means that all the code we’ve written so far -- the code that assumes that the method doesn’t return null -- will break. So we have to find every place in the code where this method is called, and make sure that it does a null check. IDEs can help here, but they can’t help with developers who hadn’t noticed the change and continue using the method in the old way, or places where the method is called with reflection... and do you really want to test every call site to make sure you’re handling it correctly?
Or we can use Optionals:
public Optional<Address> getDefaultAddress() {
for (Address address : getAddresses()) {
if (address.isDefault()) {
return Optional.of(address);
}
}
// I know I could use Java 8 streams here, just go with it :)
return Optional.empty();
}
The Optionals aren’t the most important part of this example -- it’s that the method signature changed. Our program will now no longer compile until each and every place in the code that gets a default address has been updated to specifically handle the case where there is none, because the compiler won’t accept an Optional<Address> where it expects an Address. A developer who hadn’t seen the change won’t be able to use the function in the old way by accident, as it won’t compile.
This is precisely what happened to us. Changing the method to return null resulted in lots of breakage from code that we forgot to add null checks to and code written afterwards using the old signature by accident. Changing it to return an Optional meant we were forced to update everything, otherwise it wouldn’t compile. No runtime bugs. No developer slip-ups. That’s why I’m in Team Optional.
I hope you can see how this is pretty much the same as checking if a variable is null. It takes just as much work, and has the same outcome.