Understanding functional style versus imperative

I have recently started writing my library of business applications written in C# into F# in order to determine if the language can handle the sort of business applications that I write that use a third-party Windows .NET Framework library. So far everything I have tried has worked, although some things with using C# class libraries are strange, but what I struggle with is that my code looks very imperative in style and try as I might, I can’t always find a way to break out of it.

Here is an example, in pseudo-code.

var session = new Session
session.Start server user password
var loan = session.Loans.Open loanGuid false false
var dec1 = if loan.Fields[“field1”].IsEmpty then 0.00M else (cast to decimal) loan.Fields[“field1”].Value
var dec2 = if loan.Fields[“field2”].IsEmpty then 0.00M else (cast to decimal) loan.Fields[“field2”].Value
// Print out the field values
loan.Close
session.End

Pretty simple stuff. But how does that translate to a functional style, trying to follow the principles in “Grokking Functional Programming” of a trying to make as many functions “pure”, with a function:

  • Returns only a single value
  • Calculates the return value based only on its argument
  • Doesn’t mutate any existing values

There are three basic steps to the program: 1) open a session to the server; 2) open a loan given the loan’s GUID; and extract the data from the fields of the loan.

Step 1 requires you pass the server, user, and password data then return the session object. So isolating that into a function seems pretty straightforward, unless I have more than one function that needs that session.

Step 2 requires the session object, so I can see passing that in, but it is the caller of those two functions that start to look like imperative coding.

Step 3 requires the loan object, so again I can see passing that in as an argument, but again the caller now looks like imperative (step-by-step) code.

I can see how the books define how to do it, for simplistic examples that have no side effects, but I am just not getting it for real-world programming.

I haven’t finished the book, to be honest, so I am hoping it becomes clearer, but I was hoping for a pointer to some example F# code that might illustrate how a traditional program like the above would be written in a functional style.

Right now my style is a bit of a hybrid. I break things down into small, single-purpose functions as much as I can, but generally end up with a big function that consists of step-by-step instructions using all the little functions.

Sometimes, simple imperative code is just to best way to compose functions and I would not necessarily avoid it. Sometimes, restructuring the problem can lead to more elegant solutions. Have a look at the examples in the README of this project (which I accidentally came across a few minutes ago) for some inspiration.

2 Likes

Welcome @dalehurtt!

Everything I write here is pretty opinionated, so take it for what it’s worth.
There’s two ways to look at this: 1) What’s a more F#-styled approach? 2) What’s a more functional programming approach?
For #1 I really like @Martin521’s answer. The link provided has a lot of nice examples of using |> pipelines and functionality in module functions instead of class members and other things that I tend to associate with typical F# code.
For #2 there’s not a whole lot you can do for the example you gave. The main idea with functional programming is that “pure” functions are easier to test, to understand, to reuse, and so we should prefer “pure” functions - but you can’t make all functions pure, because then your program wouldn’t do anything. So the next best thing is to separate out any “pure” logic from any side effects and put the pure stuff in its own function. The example you gave has almost no logic that could be separated out though. Just about the only thing you could do is make a pure function for

let asDecimal field = if field.IsEmpty then 0.00M else (cast to decimal) field.Value

and change your example to

let session = new Session
session.Start server user password
let loan = session.Loans.Open loanGuid false false
let dec1 = asDecimal loan.Fields[“field1”]
let dec2 = asDecimal loan.Fields[“field2”]
// Print out the field values
loan.Close
session.End

but that’s a pretty minor change. If working with a program that does more logic (calculations, decision branching, etc.), then you’ll probably be able to use a lot more of what you’re reading about.

1 Like

In addition to the answers given above, I’ve given my version of something you could try.

I’ve wrapped the original methods in functions and separated those functions into two modules: Session and Loan. (The original method calls are in the commented out code, and the DateTime in Session is only used to show the ‘status’ of the Session.)

The wrapping functions in Session and Loan can be unit tested separately as they are ‘atomic’ (I think that’s the right word).

Then, in the doIt function, I’ve adapted those functions, very simply, using other functions so that they can be called in a pipeline.

Some extra types have been used to pass the necessary information between the adaptor functions.

Basically, the pipeline ‘loads-up’ on information as it progresses to printing the values and then it starts to ‘dump’ information as it ‘winds down’. (In this particular case you probably don’t need to keep the Loan information once the modified values have been found but I’ve left it in to show that you could then pass the Loan information into other pipelined functions.)

Hopefully, the code remains easy to understand while still being ‘proper’ F#.

As with the original version, I’ve not handled any errors or exceptions, but my code should be easily-enough adapted to use Results instead for an even safer pipeline.

This probably isn’t the best way to do this – and I’m open to suggestions for changes – but I thought it might be of interest.

(This code can be executed – as is – via the REPL/FSI, so you can see how it works.)

open System 

type Session = Session of DateTime option 

type Loan = { Field1 : string ; Field2 : string } 

type SessionAndLoan = { Session : Session ; Loan : Loan } 

type ModifiedValues = { Dec1 : string ; Dec2 : string } 

type SessionAndLoanAndValues = { Session : Session ; Loan : Loan ; Values : ModifiedValues }

module Session = 

    let create = 
        // new Session 
        let session = Session None 
        printfn "Session Created - %A" session 
        session 

    let start server user password session = 
        // session.Start server user password 
        let startedSession = Session (Some DateTime.Now)
        printfn "Session Started - %A" startedSession 
        startedSession 

    let disconnect session : unit = 
        // session.End
        printfn "Session Disconnected" 

module Loan = 

    let find loanGuid booleanOne booleanTwo session = 
        // session.Loans.Open loanGuid booleanOne booleanTwo 
        let loan = { Field1 = "value 1" ; Field2 = "value 2" } 
        printfn "Loan Found - %A" loan 
        loan 

    let getModifiedValues loan = 
        // Actual modification needs to be done here.
        let dec1 = sprintf "modified %s" loan.Field1 
        let dec2 = sprintf "modified %s" loan.Field2 
        printfn "Modified Values Calculated" 
        { Dec1 = dec1 ; Dec2 = dec2 } 

    let printValues values : unit = 
        printfn "Printing - %s and %s" values.Dec1 values.Dec2 
        printfn "Values Printed"

    let close loan : unit = 
        // loan.Close 
        printfn "Loan Closed" 

let doIt = 

    let findLoan loanGuid booleanOne booleanTwo session = 
        { Session = session 
          Loan = Loan.find loanGuid booleanOne booleanTwo session } 

    let getValues (sessionAndLoan : SessionAndLoan) = 
        { Session = sessionAndLoan.Session 
          Loan = sessionAndLoan.Loan 
          Values = Loan.getModifiedValues sessionAndLoan.Loan } 

    let printValues (sessionAndLoanAndValues : SessionAndLoanAndValues) = 
        do Loan.printValues sessionAndLoanAndValues.Values 
        { Session = sessionAndLoanAndValues.Session 
          Loan = sessionAndLoanAndValues.Loan } 

    let closeLoan (sessionAndLoan : SessionAndLoan) = 
        do Loan.close sessionAndLoan.Loan 
        sessionAndLoan.Session 

    let disconnectSession session = 
        do Session.disconnect session 
        Session None 

    Session.create 
    |> Session.start "server" "user" "password" 
    |> findLoan "LoanGuid" "BooleanOne" "BooleanTwo" 
    |> getValues 
    |> printValues 
    |> closeLoan 
    |> disconnectSession 

doIt
1 Like