This series explores F# pattern matching and active patterns, two useful tools for working with F# quotations. As an example, we’ll build a set of functions similar in purpose to typeof<T>, returning instances of System.Reflection.MemberInfo subclasses for properties, methods, functions, constructors, and other code elements. In effect, we’ll implement an infoof() operation, pronounced info-of. Similar helpers are often written in C# using expression trees, as in this example. Eric Lippert discusses the idea here.

Let’s start with a function that accepts a quoted F# expression and returns a System.Reflection.PropertyInfo when the expression represents a property access:

open Microsoft.FSharp.Quotations.Patterns

let propertyof expr =
  match expr with
    | PropertyGet(_, info, _) -> info
    | _ -> failwith "Not a property expression"

The function uses the PropertyGet active pattern from Microsoft.FSharp.Quotations.Patterns to recognize a property access and extract its metadata. It works for static properties and for properties accessed through variables or literals available at the call site:

propertyof<@ (null : string).Length @>

val it : System.Reflection.PropertyInfo =
  Int32 Length {Name = "Length";
                CanRead = true;
                CanWrite = false;
                DeclaringType = System.String;
                PropertyType = System.Int32; ...}

propertyof<@ System.Console.CapsLock @>

val it : System.Reflection.PropertyInfo =
  Boolean CapsLock {Name = "CapsLock";
                    CanRead = true;
                    CanWrite = false;
                    DeclaringType = System.Console;
                    PropertyType = System.Boolean; ...}

Accessing an instance property this way is less convenient when no instance is available. We can support that case by also accepting a quoted lambda whose body accesses a property through the lambda parameter:

<@ fun(s: string) -> s.Length @>

val it : Quotations.Expr<(string -> int)> =
  Lambda (s, PropertyGet (Some (s), Int32 Length, []))

The revised propertyof function becomes:

let propertyof expr =
  match expr with
    | PropertyGet(_, info, _) -> info
    | Lambda(arg, PropertyGet(Some(Var var), info, _))
        when arg = var -> info
    | _ -> failwith "Not a property expression"

This demonstrates an important feature of pattern matching: patterns can be nested, allowing complex structures to be recognized directly. If expr is a lambda, its parameter is bound to arg, and its body is matched against PropertyGet(Some(Var var), info, _). The Some requires an instance property access; a static property would have None in that position. The receiver must itself be a variable, matched by Var var. Finally, the when guard checks that var is the same variable as the lambda parameter arg. This rejects expressions such as fun x -> someOtherVar.Property, where the property is accessed through a different variable.

Now consider a property access on a different kind of literal. In F#, 123I is a numeric literal of type BigInteger:

propertyof<@ 123I.IsZero @>

System.Exception: Not a property expression
   at FSI_0045.propertyof(FSharpExpr expr)
   at <StartupCode$FSI_0049>.$FSI_0049.main@()

Looking at the quotation for a similar property access reveals why this does not match:

<@ 123I.IsOne @>

val it : Quotations.Expr<bool> =
  Let (copyOfStruct,
     Call (None, BigInteger FromInt32[BigInteger](Int32), [Value 123]),
     PropertyGet (Some copyOfStruct, Boolean IsOne, []))

F# introduces a let binding, initializes it with the BigInteger value, and then accesses a property through that binding. In other words, 123I.IsOne is quoted as let copyOfStruct = 123I in copyOfStruct.IsOne. Adding a pattern for this form gives:

let propertyof expr =
  match expr with
    | PropertyGet(_, info, _) -> info
    | Lambda(arg, PropertyGet(Some(Var var), info, _))
    | Let(arg, _, PropertyGet(Some(Var var), info, _))
        when arg = var -> info
    | _ -> failwith "Not a property expression"

The Lambda and Let cases are combined using an OR pattern, written with |, as in match x with 1 | 2 | 3 -> true | _ -> false. This is possible because both alternatives bind the same names (arg, var, and info) with the same types. The when guard applies to both alternatives. Here are several examples:

[ propertyof<@ System.Console.Out @>
  propertyof<@ (null: Type).IsClass @>
  propertyof<@ "someStringLiteral".Length @>
  propertyof<@ fun(x: string) -> x.Length @> ]

|> List.iter (printfn "%A")

The output is:

System.IO.TextWriter Out
Boolean IsClass
Int32 Length
Int32 Length

This version is sufficient for now. In the next post, we’ll build the more involved methodof function.