Blogs

Gary's Blog

July 2010 - Posts

  • XPO – F# Lists and Seqs Oh My!

         

    As we developers move to doing less OO programming and more functional programming – and we will be making that move over time, trust me – then we’ll look to that paradigm’s best practices to help us make the transition smoothly. One of the best practices of functional programming is to “always be lazy”. What is meant by this is that a programmer should, where possible, strive to use a collection that will return the next element in a lazy or, “only when you ask for it” way. Of course, as with all general advice, sometimes it can fall down when you look at a specific example, so to follow this advice and not have it bite you in the ass, you really need an understanding of what is happening “under the hood”. Take the following example for instance:

    Let’s assume we have a small database of people and we wish to calculate the combined age. The functional method of doing that would be to “fold” the elements of a collection of People over a function that accumulates the age of each person, in an accumulator value, which is threaded through the fold. Of course, following best practice, you will wish to make the collection lazy, by using a Seq instead of a List – but would that be the best policy? Let’s have a look. The following code can be executed in the F# interactive window:

    Firstly, let’s register and open the XPO DLLs that we need:

    #r "C:\Program Files\DevExpress 2010.1\Components\Sources\DevExpress.DLL\DevExpress.Data.v10.1.dll"
    #r "C:\Program Files\DevExpress 2010.1\Components\Sources\DevExpress.DLL\DevExpress.Xpo.v10.1.dll"
    
    open DevExpress.Data
    open DevExpress.Data.Filtering
    open DevExpress.Xpo
    open DevExpress.Xpo.DB

    Now, let’s define a Person type:

    //GS - Define the Person class as a descendant of XPObject
    [<AllowNullLiteral>]
    type Person(session:Session) = 
        inherit XPObject(session:Session)
        let mutable name = ""
        let mutable age = 0
        member public this.Name with get() = name and set newName = name <- newName
        member this.Age with get() = age and set newAge = age <- newAge

    Then we’ll let XPO know which database we want to use and add a few rows to it:

    //GS - Store a connection string
    let sqlConn =
         MSSqlConnectionProvider.GetConnectionString(".", "People")
    
    //GS - Add a few Rows
    for i = 1 to 100 do
        use uow = new UnitOfWork()
        uow.ConnectionString <- sqlConn
        let p = new Person(uow)
        p.Name <- "Person " + i.ToString()
        p.Age <- (new System.Random()).Next(18,25)
        uow.CommitChanges()

    Being lazy, we’ll create a Seq of all the rows in the database:

    //GS - GS - Create a lazy seq over the records in the database
    let people =
        seq {
             use uow = new UnitOfWork()
             uow.ConnectionString <- sqlConn
             let people = (new XPCollection<Person>(uow))
             for p in people -> p
        }

    And, as expected, the SQL Profiler shows that no requests have been sent to the database:

    image

    Now, let’s find out what the combined total age is:

    //GS - Create a fold over the seq to calculate combined age
    Seq.fold (fun a (p : Person) -> a + p.Age) 0 people 

    And we can see that there were the expected requests made to the database:

    image

    Let’s do the same with a List now, firstly building a List:

    //GS - Create a list over the records in the database
    let peopleList = 
       [
           use uow = new UnitOfWork()
           uow.ConnectionString <- sqlConn
           let people = (new XPCollection<Person>(uow))
           for p in people -> p
       ]

    Which sent the expected requests to the database (note the requests appear accumulatively in the profiler screen shots):

    image

    And then doing the calculation:

    //GS - Create a fold over the list to calculate combined age
    List.fold (fun a (p : Person) -> a + p.Age) 0 peopleList

    Which added no more requests to the database:

    image

    So, as you can see, when you are dealing with a function that operates on the whole collection, the only difference between a List and Seq is one of timing. With the Seq the list comprehension is only executed when the function is executed, and with the List the comprehension is executed immediately, when the List is created.

    But, that is not the the full story. What would happen if our function did not operate over the entire collection, what if we picked out the elements we wanted in single operations? Well with the List it makes no difference, as we have already built the List, so calling for the nth element doesn’t send any further requests to the database:

    //GS - Get the second Person in the collection
    List.nth peopleList 2

    image

    However, calling the same function on the Seq does cause the comprehension to be re-executed, returning all the rows form the database again:

    //GS - Get the second Person in the collection
    Seq.nth people 2

    image

    In fact, every time you access the Seq, for anything, the comprehension will be re-executed and all the rows will be pulled back from the database, the exact opposite of what you wanted to happen! In this case, the better collection to use to be “lazy” would be the List, which might be surprising when you first find out. So, it pays to check when you think you are being “lazy”.

    That’s all for this post, until next time, happy XPOing! :-)

More from DevExpress
Live Chat
Have a pre-sales question?
Need assistance with your evaluation?
We are here to help.
Chat is one of the many ways you can contact members of the DevExpress Team. We are available Monday-Friday between 8:30am and 5:00pm Pacific Time.
If you need additional product information, require pre-sales assistance, or want help with your order, write to us at info@devexpress.com or call us at
+1 (818) 844-3383.