2D Head with a clock as an eyeball.
 Thursday, October 16, 2008

Jason recently produced this post where he demonstrates some lambda expression tree parsing and extension methods in C# 3.0 to do windows forms data binding sans strings:

nameTextBox.Bind(t => t.Text, aBindingSource, (Customer c) => c.FirstName);

I happen to like the idea so much that I thought about how this might look in F#. There are some juicy language features in F# sharp that lap this stuff up. Take Jason's C# implementation:

public static class ControlExtensions

    public static Binding Bind<TControl, TDataSourceItem>
        (this TControl control,  
        Expression<Func<TControl, object>> controlProperty, 
        object dataSource,  
        Expression<Func<TDataSourceItem, object>> dataSourceProperty) where TControl: Control
    { 
        return control.DataBindings.Add( 
            PropertyName.For(controlProperty),  
            dataSource,  
            PropertyName.For(dataSourceProperty)); 
    } 


public static class PropertyName

    public static string For<T>(Expression<Func<T, object>> property)
    { 
        var member = property.Body as MemberExpression; 
        if (null == member)
        { 
            var unary = property.Body as UnaryExpression; 
            if (null != unary) member = unary.Operand as MemberExpression;
        } 
        return null != member ? member.Member.Name : string.Empty; 
    } 
}

While this gets the job done, the fact that every last type needs explicit declaration gets in the way of some otherwise damn elegant thinking. The signature for the Bind extension is crazy long, and the phrase "angle bracket tax" comes to mind.

What if our compiler would infer these types for us while preserving compile time type checking? I still want to provide some generic type constraints but beyond that, I don't care.  Using F# we can have these all of these things to give us readable solutions to difficult problems. Also note the extension method syntax:

#light 

open System.Windows.Forms
     
type Control with 
    member this.Bind (control_property : #Control->'b) datasource datasource_property = 
        this.DataBindings.Add(
            property_name_for control_property,  
            datasource,  
            property_name_for datasource_property 
        ) |> ignore

I've deliberately left the implementation to property_name_for till now because it uses some little known constructs. Quotations in F# allow the developer to inform the compiler that a particular piece of code is to be dealt with as an expression tree. To quote code we use the <@ expression @> construct. Being a functional language (amongst other paradigms) F# also allows us to pass lambdas around as variables and parameters unevaluated without any ceremony, thus yielding a readable approach to finding the name of a property in a lambda expression body:

#light 

open Microsoft.FSharp.Quotations.Patterns
     
let property_name_for (p:'x->'y) =  
    let rec parse_expression e =
        match e with 
        | Lambda(var, body) -> parse_expression body 
        | Let(var, lhs, rhs) -> parse_expression rhs 
        | PropGet(opt, info,  expr) -> info.Name 
        | _ -> "" 
    parse_expression <@ p @>

Note the usage of active patterns Lambda, Let &  PropGet provided by the latest CTP of F# in Microsoft.FSharp.Quotations.Patterns we can analyse the expression p to recursively hunt property gets in the lambda body. The Quotations.Expr class conversely, allows us to construct these patterns. The latest CTP has simplified the Quotations namespace dramatically and now utilizes the standard .NET reflection libraries consistently. The list of patterns and constructors is extensive so rather than going through them all here, here's the link to the docs.

Full listing with example usage:

#light 

open System.Windows.Forms
open Microsoft.FSharp.Quotations.Patterns
     
let property_name_for (p:'x->'y) =  
    let rec parse_expression e =
        match e with 
        | Lambda(var, body) -> parse_expression body 
        | Let(var, lhs, rhs) -> parse_expression rhs 
        | PropGet(opt, info,  expr) -> info.Name 
        | _ -> "" 
    parse_expression <@ p @> 
       
type Control with 
    member this.Bind (control_property : #Control->'b) datasource datasource_property = 
        this.DataBindings.Add(
            property_name_for control_property,  
            datasource,  
            property_name_for datasource_property 
        ) |> ignore 

(* EXAMPLE ONLY *) 
type Customer = { 
    FirstName   : string
    Age         : int
} 
             
let nameTextBox = new TextBox()
let ageSelect = new NumericUpDown()
let bs = new BindingSource()
bs.DataSource <- { FirstName = "Jim"; Age = 29 }

nameTextBox.Bind (fun ctrl -> ctrl.Text) bs (fun c -> c.FirstName) 
ageSelect.Bind (fun ctrl -> ctrl.Text) bs (fun c -> c.Age)

Filed under:
Comments are closed.
© Copyright 2009 Jim Burger