In this post, I'm going to write a couple of active patterns to access parts of LINQ expression trees. As a little experiment I'm going to change the implementation of the Linq To Google Desktop expression tree handling from C# to F#.
The following definitions of active patterns provide access to sections of expression trees.
let (|BinaryExpression|_|) (x:Expression) =
if (x :? BinaryExpression)
then let be = (x :?> BinaryExpression)
in Some (be.Left,be.Right)
else None
let (|AndAlso|_|) (x:Expression) =
if (x.NodeType = ExpressionType.AndAlso)
then match x with
| BinaryExpression(l,r) -> Some (l,r)
| _ -> None
else None
let (|Equal|_|) (x:Expression) =
if (x.NodeType = ExpressionType.Equal)
then match x with
| BinaryExpression(l,r) -> Some (l,r)
| _ -> None
else None
let (|IsExpression|_|) (x:Expression) =
if (x :? TypeBinaryExpression)
then let be = (x :?> TypeBinaryExpression)
in Some (be.Expression,be.TypeOperand)
else None
let (|MethodCall|_|) (x:Expression) =
if (x.NodeType = ExpressionType.Call &&
(x :? MethodCallExpression))
then
let mc = x :?> MethodCallExpression
in Some (mc.Object,
mc.Method,
IEnumerable.to_list(mc.Arguments))
else None
let (|Cast|_|) (x:Expression) =
if (x.NodeType = ExpressionType.Convert
&& (x :? UnaryExpression))
then let ue = (x :?> UnaryExpression)
in Some (ue.Type,ue.Operand)
else None
let (|Lambda|_|) (x:Expression) =
if ( (x :? UnaryExpression) &&
((x :?> UnaryExpression).Operand :? LambdaExpression))
then let ue = (x :?> UnaryExpression)
in Some (ue.Operand :?> LambdaExpression).Body
else None
let (|MemberAccess|_|) (x:Expression) =
if (x.NodeType = ExpressionType.MemberAccess
&& (x :? MemberExpression))
then let ue = (x :?> MemberExpression)
in Some (ue.Expression,ue.Member)
else None
let (|MethodName|) (m:MethodInfo) = m.Name
let (|TypeName|) (t:Type) = t.Name
let (|PropertyWithName|_|) (m:MemberInfo) =
if (m :? PropertyInfo)
then Some (m.Name)
else None
let (|ExpressionType|) (x:Expression) = x.Type
As you can see, most of the active patterns are Partial Recognizers (see here), for example
(|Equal|_|)
. This is because of all the dynamic type tests that we need to do.The C# code for expression tree handling presented on the Linq to Google Desktop post, is located in the
GDesktop.CollectQueryInfo
method. By using these new active pattern definitions the code could be rewritten in F# as follows:
let rec CollectQueryInfoFS (e:Expression, qi:GDQueryInfo) =
match e with
| Lambda(body) -> CollectQueryInfoFS (body,qi)
| AndAlso(l,r) -> CollectQueryInfoFS(l,qi);
CollectQueryInfoFS(r,qi)
| MethodCall(_,MethodName("Contains"),[argument]) ->
qi.AddTerm(GetStringFromArgument(argument))
| Equal(MemberAccess(ExpressionType(TypeName("GDFileResult")),
PropertyWithName("FileType")),
value) ->
qi.FileType <- GetStringFromArgument(value)
| Equal(MemberAccess(ExpressionType(TypeName("GDEmailResult")),
PropertyWithName(pName)),
value) ->
CollectEmailProperty(pName,GetStringFromArgument(value),qi)
| IsExpression(_,TypeName("GDFileResult")) ->
qi.ElementType <- new Nullable<GDElementType>( GDElementType.File )
| IsExpression(_,TypeName("GDEmailResult")) ->
qi.ElementType <- new Nullable<GDElementType>( GDElementType.Email )
| _ -> raise (new NotSupportedException(e.ToString()))
By using the active pattern definitions, the code is much smaller and easer to read and modify.
Right now only the expression tree handling part was translated to F#. The point where we call F# looks like this:
private GDQueryInfo ProcessWhereMethodCall(MethodCallExpression mcExpression)
{
Expression theObjectArgument = mcExpression.Arguments[0];
Expression whereExpressionTree = mcExpression.Arguments[1];
GDesktop provider =
(GDesktop)((ConstantExpression)theObjectArgument).Value;
GDQueryInfo qi = new GDQueryInfo();
//CollectQueryInfo(whereExpressionTree, qi);
Langexplr.FsharpTests.Langexplr.FsharpTests.CollectQueryInfoFS(
whereExpressionTree, qi);
return qi;
}
For future posts I'm going to try to rewrite the entire implementation.