Oct 24

My F# Notes

Building LINQ Queries in F# and C#

My last post at the hubFS was an article that I wrote earlier to my personal blog and I wrote one more article some time ago that is related to F# and I wasn’t posted here. The article is a part of a short series that discusses how the same LINQ-related problem can be solved in F# and in C# 3.0 (and in fact, my implementation of the C# version was largely motivated by the F# solution). Because I think that the articles are closely related, I’ll write just a short summary here and add links to both articles available in my other blog.

The articles deal with a problem how can one build a LINQ (database) query at runtime. A typical example of a query that developers want to build at runtime is a query where the user can modify the filtering condition (where clause) - for example the application may want to allow combining several conditions, like c.Country == with c.Name.StartsWith(). In addition you may want to allow combining these single conditions using various operators (at least or and and).

The article about the F# version also shortly introduces the F# comprehensions and quotations that are used when writing LINQ queries using F#. Then it gives a few examples of using LINQ for database queries and finally, it fully describes the example that is shortly introduced below. In the article about the C# version, I described several extensions that are very useful when writing the queries in the F#-style as well as a solution that I use to allow a mechanism similar to F# holes (that are shortly introduced below). Finally, the F# article also contains a version of the FLINQ sample that is compatible with Visual Studio 2008 Beta 2.

Building LINQ Queries at Runtime in F#

In F#, the queries are represented using quotations which can be easily combined thanks to the support for holes (written as underscore). The following example first defines two basic conditions (one of them always returns true and the second returns false). Later, we define two combinators for combining conditions using || and && operators. Finally, we build a condition that is composed from several elementary conditions (stored in a dictionary called dict). To do this we can use the Fold function which takes an initial condition and combines it with all the conditions in the dictionary:

// Basic conditions that always return true/false
let falseCond = « fun (c:Customer) -> false »
let trueCond  = « fun (c:Customer) -> true  »

// Condition combinators
let (||*) f g = « fun (c:Customer) -> (_ c) || (_ c) » f g
let (&&*) f g = « fun (c:Customer) -> (_ c) && (_ c) » f g

// Which combinator will we use?
let (^^) = if generateOr then (||*) else (&&*)

// Build the expression by folding all items in a dictionary  
let expr =
  dict.Fold(fun key propSelector e ->
      Console.Write(“Enter value for ‘{0}’:n> “, key);
      let enteredVal = Console.ReadLine();

      let currentCond =
        « fun (c:Customer) -> ((_ c):string).IndexOf(_:string)  -1 »
          propSelector « §enteredVal »
      (currentExpr ^^ e))
    (if generateOr then falseCond else trueCond)

Building LINQ Queries at Runtime in C#

In C# the representation of the lambda expression can be either a delegate (Func<...>) or an expression tree (Expression>) and the decision whether to generate a delegate or an expression tree depends on the expected type, which means that the code needs to define a type alias. This is possible thanks to the using statement (though this can be avoided by using helper functions, but in this example I’ll instead use aliases). Primitive conditions are easy to define once we have a type alias CustomerCondition (which stands for an expression tree of a lambda expression taking a Customer as an argument and returning a bool). When defining a combinators, we need a way for calling an expression tree in another expression tree and this is done by the method Expand (which is not part of LINQ and is further discussed in the article). Finally, the code that generates the result uses similar technique as the previous F# version:

// Basic conditions that always return true/false
CustomerCondition trueCond  = (c) => true;
CustomerCondition falseCond = (c) => false;

// Condition combinators
CustomerConditionCombinator combineOr =
  (f, g) => (c) => f.Expand(c) || g.Expand(c);
CustomerConditionCombinator combineAnd =
  (f, g) => (c) => f.Expand(c) && g.Expand(c);

// Which combinator will we use?
CustomerConditionCombinator combinator =
  generateOr ? combineOr : combineAnd;

// Build the expression by folding all items in a dictionary  
var expr = dict.Fold((e, item) => {
    Console.Write(“Enter value for ‘{0}’:n> “, item.Key);
    string enteredVal = Console.ReadLine();
    CustomerPropSelector propSelector = item.Value;    

    CustomerCondition currentCond = (c) =>
      propSelector.Expand(c).IndexOf(enteredVal) != -1;

    return combinator(e, currentCond);
  }, generateOr ? falseCond : trueCond);
Jun 26

F# Tool Support

F# runs on the .NET platform. This means that nearly all the major tools for the platform can be easily reused for F# code. For example, F# produces intermediary language code, with a close correspondence to the source text, and with sensible generated identifier names. This means debuggers, profilers and other tools not only work without any problems, but also that their results are just as intelligible as when applied to C# code. Furthermore, F#’s excellent bi-directional language interoperability means that you can even use code generation tools such as parser-generators.

Because of this, F# has a surprisingly powerful and complete set of tools for a research language. Traditionally, functional languages have not had good profilers or graphical debuggers. With F# these tools essentially come for free.

The most important tools supported by F# are:

  • A command line compiler (fsc.exe) supporting separate compilation.
  • Graphical interactive debugging (via Visual Studio).
  • Parsing and Lexing (fslex.exe and fsyacc.exe).

Debugging

Debugging with Visual Studio.NET is well supported. Debugging is supported through any .NET debugger, e.g. the command line “cordbg” tool that comes with the .NET Framework SDK, or the graphical debugger “DbgClr”.