5

How to properly use Monads

 2 years ago
source link: https://dev.to/oscarablinger/how-to-properly-use-monads-49ch
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

I've previously explained what a monad is and why I think that they offer a better way to handle exceptional states than exceptions.

But whenever Monads come up (whether in a blog post or on stackoverflow), there is a comment talking about how that would fix any of the proposed problems.
And that confusion usually stems from using them wrong.
In this blog post, I hope I can explain how you can effectively use them.

The code looks the same

As an example let's use the Optional<T> class from Java.
It's essentially just a wrapper around a type and does null-checks for you.
This is a (heavily) stripped down version of the class implementation:

public final class Optional<T> {
    private final T value;

    // this constructor is used by static factory methods
    private Optional(T value) {
        this.value = value;
    }

    public T get() {
        if (value == null) {
            throw new NoSuchElementException("No value present");
        }
        return value;
    }

    public boolean isPresent() {
        return value != null;
    }

    public <U> Optional<U> map(Function<? super T, ? extends U> mapper) {
        Objects.requireNonNull(mapper);
        if (!isPresent()) {
            return empty();
        } else {
            return Optional.ofNullable(mapper.apply(value));
        }
    }
}

Enter fullscreen mode

Exit fullscreen mode

As you can see, the implementation only really abstracts away some null checks.
So why not use null directly?
Why write this

public String getNameOrDefault() {
    Optional<String> optName = getName();
    String name;
    if (optName.isPresent()) {
        name = optName.get();
    } else {
        name = "<no name found>";
    }
}

Enter fullscreen mode

Exit fullscreen mode

when you could just write

public String getNameOrDefault() {
    String name = getName();
    if (name == null) {
        name = "<no name found>";
    }
}

Enter fullscreen mode

Exit fullscreen mode

And the answer is of course that you shouldn't – the second way is nicer.
But that's not the only way that you can use Optional.
Let's try to use one of the other methods that it has: orElse

public String getNameOrDefault() {
    return getName().orElse("<no name found>");
}

Enter fullscreen mode

Exit fullscreen mode

Now the code is nicer than both versions before (imo).
Not only because it's shorter, it's also more descriptive and less error-prone, since I can't forget to check for null.
Without external tools and Optional a simple return getName(); would compile just the same and you'd have to check the method on whether or not it can return null.

Let's look at some longer example.
Let's say we want to either greet our user if they are logged in or else display a generic greeting:

interface User {
    /** Might return null */
    String getFirstName();
    String getUsername();
}

public User getCurrentUser() { return user; }

public String getGreeting() {
    User user = getCurrentUser();
    if (user == null) {
        return "Hello anonymous";
    } else {
        String firstName = user.getFirstName();
        if (firstName != null) {
            return "Hello " + firstName;
        } else {
            return "Hello " + user.getUsername();
        }
    }
}

Enter fullscreen mode

Exit fullscreen mode

Now let's replace the "nullable" Strings with Optionals.
If we change the code 1:1 then it will look like this:

interface User {
    Optional<String> getFirstName();
    String getUsername();
}

public Optional<User> getCurrentUser() { … }

public String getGreeting() {
    Optional<User> userOptional = getCurrentUser();
    if (userOptional.isEmpty()) {
        return "Hello anonymous";
    } else {
        User user = userOptional.get();
        Optional<String> firstName = user.getFirstName();
        if (firstName.isPresent()) {
            return "Hello " + firstName.get();
        } else {
            return "Hello " + user.getUsername();
        }
    }
}

Enter fullscreen mode

Exit fullscreen mode

I don't think many people would disagree when I say that the code became a lot harder to read.
But of course, there is a better way to use Optional:

public String getGreeting() {
    return "Hello" + getCurrentUser()
            .map(user -> user.getFirstName().orElseGet(user::getUsername))
            .orElse("anonymous");
}

Enter fullscreen mode

Exit fullscreen mode


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK