F#: Using the Backwards Pipe Operator: “<|"

I revisited code for a bank account kata I practiced a couple weeks ago.

Take the following code:

let update account amount operator = account |>  function
           | Checking v -> Checking ( v + (operator * amount) )
           | Savings  v -> Savings  ( v + (operator * amount) )
           | Business v -> Business ( v + (operator * amount) )

The code above may look okay at first glance. However, one would argue that the parentheses are somewhat verbose.

As a result, I attempted to clarify the code by using the backwards pipe operator:

let update account amount operator = account |>  function
           | Checking v -> Checking <| v + (operator * amount)
           | Savings  v -> Savings  <| v + (operator * amount)
           | Business v -> Business <| v + (operator * amount)

The complete code with the “<|" used can be found below:

module BankAccountImpl2

type Account =        
    | Checking of decimal
    | Savings  of decimal
    | Business of decimal

type Commands =
    | Withdraw of Account * decimal
    | Deposit  of Account * decimal
    | Transfer of Account * Account * decimal

type Response =
    | Withdrawal       of State   * State
    | WithdrawalFailed of Account * decimal

    | Deposited        of State   * State             
    | DepositFailed    of Account * decimal

    | Transferred      of TransferSummary   * decimal
    | TransferFailed   of Account * Account * decimal

and State =
    | BeforeDeposit    of Account * decimal
    | AfterDeposit     of Account

    | BeforeWithdrawal of Account * decimal
    | AfterWithdrawal  of Account

and TransferSummary = { 
    FromBalanceBefore: Account ; ToBalanceBefore: Account
    FromBalanceAfter:  Account ; ToBalanceAfter:  Account }

(*Functions*)
let balanceOf = function
                | Checking v
                | Savings  v
                | Business v -> v

let update account amount operator = account |>  function
           | Checking v -> Checking <| v + (operator * amount)
           | Savings  v -> Savings  <| v + (operator * amount)
           | Business v -> Business <| v + (operator * amount)

let debit  account amount =  update account amount <| - 1m
let credit account amount =  update account amount <| + 1m

let canWithdraw account amount = 
    balanceOf account >= amount &&
    amount > 0m

let handleWithdraw = function
    | Withdraw (account , amount) when canWithdraw account amount ->
           Some <| Withdrawal (BeforeWithdrawal (account ,     amount),
                               AfterWithdrawal  (debit account amount))

    | Withdraw (account , amount) when not (canWithdraw account amount) ->
           Some <| WithdrawalFailed (account, amount)
    | _ -> None

let handleDeposit = function
    | Deposit (account , amount) when amount > 0m && amount <= 1000000000m ->
           Some <| Deposited((BeforeDeposit (account , amount),
                              AfterDeposit  (credit account amount) ))

    | Deposit (account , amount) ->
           Some <| DepositFailed (account , amount)

    | _ -> None

let handleTransfer = function
    | Transfer (fromAccount, toAccount, amount) when canWithdraw fromAccount amount ->
           Some <| Transferred ({ FromBalanceBefore=fromAccount
                                  ToBalanceBefore=  toAccount
                                  FromBalanceAfter= debit  fromAccount amount
                                  ToBalanceAfter=   credit toAccount   amount } ,
                                  amount)

    | Transfer (fromAccount, toAccount, amount) when not (canWithdraw fromAccount amount) ->
           Some <| TransferFailed (fromAccount, toAccount, amount)
    | _ -> None

(*Client*)
let deposit =    handleDeposit  <| Deposit  (Checking 100m , -10m)
let withdrawal = handleWithdraw <| Withdraw (Savings  0m   , +10m)
let transfer =   handleTransfer <| Transfer (Checking 100m , Savings 150m , -10m)

(*Tests*)
open FsCheck
open FsCheck.Xunit

type Run100KAttribute() =
    inherit PropertyAttribute(
        MaxTest = 100000,
        QuietOnSuccess = true)

[<Run100K>]
let ``deposits greater than zero AND less than 1 million always succeed`` () =

    // Setup
    let validDeposits = function
        | Deposit (acct , bal) -> bal > 0m && 
                                  bal <= 1000000m
        | _ -> false

    let isDeposited deposit = function
        | Some v -> match v with
                    | Deposited _ -> true
                    | _           -> false
        | _ -> false

    // Test
    Arb.generate<Commands> 
    |> Gen.filter validDeposits
    |> Arb.fromGen
    |> Prop.forAll 
    <| fun deposit -> handleDeposit deposit
                      |> isDeposited
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: