Blogs

Gary's Blog

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! :-)

Published Jul 26 2010, 03:05 PM by Gary Short (DevExpress)
Filed under:
Technorati tags: XPO
Bookmark and Share

Comments

 

Alex Hoffman said:

So XPO is so well documented and understood we are now reduced to obtuse XPO in F# posts.

July 28, 2010 8:24 AM
 

Gary Short (DevExpress) said:

Hi Alex, sorry if you didn't understand the post, it wasn't really about XPO per se, more a post pointing out a "gotcha" in F# that might catch a few devs as they move across from C# or VB.

As for the XPO documentation, AFAIK there is not a problem with it, if you have specific examples of where it is weak or lacking then please let me know and I'll take it up with the documentation team - we always strive to improve things for our customers.

Thanks again for your input, it's always welcome Alex.

July 28, 2010 1:22 PM
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.