April 19, 2019

FP for OO - Part 2

FP intuitions from the OO world.

(Or monoids in the category of endofunctors)

Previous Post: What is FP - https://blog.marktranter.com/what-is-functional-programming

In this post we'll look at some functional programming constructs from popular OO languages. I'll use these familiar constructs to prime some FP intuitions. I'll use Java for the examples, but this should be pretty familiar to C# and JS devs.

Functional Collections

To kick this off, lets imagine some requirement

  • Given a timestamp
  • For all users created after this timestamp
  • Fetch all user transactions

Our imperative Java/C# code might look something like this.

interface AccountsRepository {
    List<User> getUsersSince(Long timestamp);
    List<Account> getUserAccounts(User user);
    List<Transaction> getUserTransactions(Account account);
}

class DependentCalculationsList {

    private final AccountsRepository accountRepo = null;
    public List<Transaction> getTransactionsFromNewCustomers(Long since) {
        List<Transaction> tx = new ArrayList<>();
        for(User u : accountRepo.getUsersSince(since)) {
            for(Account a : accountRepo.getUserAccounts(u)) {
                tx.addAll(accountRepo.getAccountTransactions(a));
            }
        }
        return tx;
    }
}

Notice that we are following a fairly common pattern here. Do X and then Do Y with each result of X.

What we really want to do is:

getUsersSince andThen getUserAccounts andThen getAccountTransactions

But we can't, because we have to extract each user out of a list before we can call getUserAccounts.

Ditto with extracting each account out of a list before we can call getAccountTransactions.

So lets rewrite this "function" in a more functional style.

interface AccountsRepository {
    Stream<User> getUsersSince(Long timestamp);
    Stream<Account> getUserAccounts(User user);
    Stream<Transaction> getUserTransactions(Account account);
}

class DependentCalculationsStream {

    private final AccountsRepository accountRepo = null;
    public Stream<Transaction> getTransactionsFromNewCustomers(Long since) {
       return accountRepo
               .getUsersSince(since)
               .flatMap(accountRepo::getUserAccounts)
               .flatMap(accountRepo::getUserTransactions);
    }
}

To devs familiar with Streams or LINQ, this feels pretty natural. And - for me - is a big improvement over the nested for loops.

What is this flatMap thing doing though? (In C# the method is SelectMany)

Semantically, we could replace this flatMap/SelectMany with andThen and it would make sense. Get Users, and then get their accounts and then get the transactions. It wouldn't compile, but it would make logical sense.

So then flatMap/SelectMany is a slightly smarter andThen function, which logically does the exact same thing: composition.

It acts something like ifSomeThen.

Also notice that we also get early bail-out semantics.

If getUsersSince returns an empty stream, then the entire function just returns an empty Stream<Transaction>. Same goes with and empty getUserAccounts.

Lets see a similar example. In this case, getting all the transactions for some user.

Functional Null

Imperative first:

interface AccountsRepository {
    User getUserByEmail(String email);
    Account getUserAccount(User user);
    List<Transaction> getUserTransactions(Account account);
}

class DependentCalculationsNull {

    private final AccountsRepository accountRepo = null;
    public List<Transaction> getAccountsFromEmail(String email) {
        User user = accountRepo.getUserByEmail(email);
        if(user == null) {
            return null;
        }

        Account userAccount = accountRepo.getUserAccount(user);
        if(userAccount == null) {
            return null;
        }

        return accountRepo.getUserTransactions(userAccount);
    }
}

This isn't that pretty. We have these nulls floating around. We have conditional null checking. We have lost the ability to compose our getAccountsFromEmail "function" with any kind of safety.

However using Java's Optional<T> we can do something sexier.1

interface AccountsRepository {
    Optional<User> getUserByEmail(String email);
    Optional<Account> getUserAccount(User user);
    Optional<List<Transaction>> getUserTransactions(Account account);
}

class DependentCalculationsOpt {

    private final AccountsRepository accountRepo = null;
    public Optional<List<Transaction>> getAccountsFromEmail(String email) {
        return accountRepo
                .getUserByEmail(email)
                .flatMap(accountRepo::getUserAccount)
                .flatMap(accountRepo::getUserTransactions);
    }
}

Now we have no nulls. Just functions.

And it looks a lot like the previous functional solution with Streams.

We have flatMap doing composition again. In this case, rather than ifSomeThen, it's semantics are ifExistsThen.

We also have the same early bail-out semantics. If getUserByEmail returns an empty Optional<User>, getAccountsFromEmail will just return an empty Optional<List<Transaction>>.

Last example. This time with an asynchronous API.

Functional Futures

Imperative version first:

interface AccountsRepository {
    Future<User> getUserByEmail(String email);
    Future<Account> getUserAccount(User user);
    Future<List<Account>> getUserTransactions(Account account);
}

class DependentCalculationsFuture {
    private AccountsRepository accountRepo = null;
    public Future<List<Account>> getAccountsFromEmail(String email) {
        Future<User> userByEmail = accountRepo.getUserByEmail(email);
        User user;
        try {
            user = userByEmail.get();
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }

        Future<Account> userAccountF = accountRepo.getUserAccount(user);
        Account account;
        try {
            account = userAccountF.get();
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }

        return accountRepo.getUserTransactions(account);
    }
}

This is a mess. Its ugly to look at for starters. The intention of this "function" is completely obfuscated by all error handling.

So can we can clean up this mess with Java Completable Futures (or JavaScript promises)?2

interface AccountsRepository {
    CompletableFuture<User> getUserByEmail(String email);
    CompletableFuture<Account> getUserAccount(User user);
    CompletableFuture<List<Transaction>> getUserTransactions(Account account);
}
class DependentCalculationsCompletableFuture {

    private final AccountsRepository accountRepo = null;
    public CompletableFuture<List<Transaction>> getAccountsFromEmail(String email) {
        return accountRepo.getUserByEmail(email)
                .thenCompose(accountRepo::getUserAccount)
                .thenCompose(accountRepo::getUserTransactions);
    }
}

Much better.

And almost exactly the same as the last few functional examples.

(In JavaScript, thenCompose would be replaced by then.)

Here though, the flatMap has been replaced by thenCompose. Its obvious from the similarity to the previous examples that the thenCompose function serves the same role as flatMap: it gives us waitForSuccessThen semantics.

Or - as the name thenCompose suggests - it allows composition of functions that return Futures. The same way flatMap gave us the ability to compose functions returning List<> and Optional<>.

The early bail-out scenario here is for failed futures. If the getUserByEmail returns a future with a failed state, the entire function completes with a CompletableFuture<List<Transaction>> in a failed state.

The M Word

To pull it all together: structures like Streams and Optionals and CompletableFutures can all be viewed as containers for values.

Stream is a container that can hold multuple values. Optional<> is a container they can contain zero or one value. And CompletableFuture is a container that holds some value available at a future time.3

  • Functions returning these structures can be composed using flatMap style operators.
  • The composition of these functions also allows for early bail-out.

Using these structures, we can build entire programs using only function composition. Entire programs that the compiler can help us check for correctness!

In fact these structures are so common that they have a special name in functional programming.

Monads.

Monads. Composible containers. Whats the problem? Thats one important piece of jargon out of the way. While we're in the weeds, lets take a quick look at a second one.

The F Word

A simpler concept is the transformation of the values inside these container structures.

In Java, the map function is used with Stream<> and Optional<>, and the thenApply function is used with CompletableFuture. For example

Optional.of(10).map(n -> (n * 2).toString()) == Optional<String>("20")

In C#, the Select functions performs this transformation IEnumerable<>. i.e.

new List<int>{ 10 }.Select(n => (n * 2).ToString()) == new List { "20" };

 
Container types that allow transformation like this are called Functors in FP jargon.

All Monads are Functors. But not all Functors are Monads.

Monads and Functors form the backbone of building programs in functional programming. Transformation and Composition gives us almost everything we need to build programs. Whats left is mostly syntax sugar and exception handling.

So now we know the name of the things - Monad - and we know a bit about the things themselves: that they allow the composition of functions that return container-like structures.

Most of us are probably already using strucutres like this to program with collections. Maybe even to handle Nulls (Optionals) and Futures.

So next up lets have a look at how programming with these structures can help us solve one of the problems we mentioned in part one: Exceptions.

Next Up: https://blog.marktranter.com/popular-scala-monads/


1 C# has Nullable<T>. But it does not come with Select/SelectMany. Its easy enough to create extension methods for this though.
2 C# has the Task<> type. Unfortunately this type doesn't follow the pattern described in this post. I knocked up a Gist over here: https://gist.github.com/mtranter/d778cd313fd2c2358fd9a23bf80eac5d demonstrating how to make Task<> fit the pattern described.
3 The Scala version of these monadic types are as below:

Below are the Scala versions of the types mentioned in the previous post. Collections, Optionals and Futures.

Collections
   def getUsers(email: String): List[User] = ???
   def getAccount(u: User): List[Account] = ???
   def getUserAccountIds(email: String) =
       getUser(email).flatMap(getAccount).map(_.accountId)
Options
   def getUser(id: String): Option[User] = ???
   def getAccount(u: User): Option[Account] = ???
   def getUserAccountIds(userId: String) =
       getUser(userId).flatMap(getAccount).map(_.accountId)
Futures
   def getUser(id: String): Future[User] = ???
   def getAccount(u: User): Future[Account] = ???
   def getUserAccountId(userId: String) =
       getUser(userId).flatMap(getAccount).map(_.accountId)
Contents

Intro - https://blog.marktranter.com/fp-introduction/
What is FP - https://blog.marktranter.com/what-is-functional-programming/
The M Word - https://blog.marktranter.com/fp-in-oo/