From East-Oriented Programming to Functional Bind

null

East-Oriented programming can, for example, be implemented through delegates or callback blocks. “East” is all about Tell, Don’t Ask: don’t query for values; instead, pass a handler around so the flow of information doesn’t return to your current scope.

When you adopt the habit of reasoning about your code in a “tell, don’t ask” manner, then you’ll probably discover that chaining successful actions would be cool.

With closures as handlers, chaining turns into nesting, though:

let amount = ...
let fromAccount = ...
let toAccount = ...

withdrawCash(amount, fromAccount) { 
  depositCash(amount, toAccount) { 
    addTransferToLog(amount, fromAccount, toAccount)
  }
}

Nesting follow-up actions upon success sucks. The level of indentation increases with every method.

These three example functions could be free functions. The result is East-Oriented. But it doesn’t read too well. It’s clumsy.

knot
Photo credit: beautiful [but deadly] square knot by woodleywonderworks. License: CC-BY 2.0

Chaining calls through returning an object which responds to the next action works better. It certainly reads nicer and is also East-Oriented:

amount.withdrawCash(fromAccount)
    .depositCash(toAccount)
    .addTransferToLog(fromAccount, toAccount)

That’s the usual object-oriented way. Here, we don’t implement handlers but return self, which is the amount instance. The return value is meaningless in respect to the method names, but the trick of returning self makes chaining possible.

Functional programmers will come up with a yet different route. They will probably utilize a free function called bind to chain functions together. Then, for readablity’s sake and to avoid nesting function calls, define the operator >>= to use bind.

I imagine the functional result to look like this:

withdrawCash(amount, fromAccount) 
  >>= depositCash(toAccount)
  >>= addTransferToLog

This way, there’s no need to create an object which implements the methods. They could be part of another object encapsulating the sequence, or free functions themselves, encapsulating the parts of the algorithm only.

I’m not a fan of creating fantasy operators. The bind operator is pretty common among those who read about functional programming, so it’s a pretty safe addition to your toolkit.

Notice that in order for this to work, the signature of two of these three functions have to change dramatically:

func addTransferToLog(amount: Money, from: Account, to: Account) {
    // ...
}

func depositCash(to: Account)(amount: Money, from: Account) 
    -> (Money, Account, Account)? {
        
    // ...
    return (amount, from, to)
}

func withdrawCash(amount: Money, from: Account) -> (Money, Account)? {
    // ...
    
    if unsuccessful {
        return nil
    }
    
    return (amount, from)
}

For a working example including the bind implementation and the >>= operator, see the Gist I created.

Update 2015-12-23: See this follow-up post with another example to chain transformations.

These free functions have to obtain all context through their input. It would work just as well to create a simple data transfer object: a mutable struct which gets populated along the way. This way, the input can be optional (No struct incoming? Create a new one!) and the output is a simple single value.

func addTransferToLog(transfer: Transfer) {
    // ...
}

func depositCash(to: Account)(transfer: Transfer?) -> Transfer {
    // ...
}

func withdrawCash(amount: Money, from: Account, transfer: Transfer? = nil) -> Transfer {
    // ...
}

Summarizing

“East” helps us clean up sequences in an object-oriented way. It requires we bend our minds a bit and return the callee on every method to chain calls or ignore the return value completely otherwise.

Swift allows us to adopt functional patterns. I think they sometimes suite the way we reason about sequences better. Even though the function’s signatures become more complex when you pass multiple values around, the resultingchain itself is very concise.

Especially if you encounter if let a lot, think about chaining the values:

  • Using bind, one functions output has to match another’s input. That looks weird, but the result can become very readable.
  • Adopting “East”, you’ll return an object which can handle the next step. That may result in a weird API, but again the result is very concise.

Watch Saul Mora’s AltConf presentation “Object-Oriented Functional Programming: The Best of Both Worlds!” to learn more about real world use of bind and other functional concepts in object-oriented programming. His examples are really good.

Both “East” and using bind to chain operations is compatible with “Tell, Don’ Ask”. Have both in your toolkit to respond to new challenges appropriately.

Update 2015-09-02: In a follow-up post, I show an object-oriented alternative to the mess of this particular example code.