Expression monad

Developer
Aug 24, 2010 at 1:54 PM

My final task is to create a library for building C# expression (namespace System.Linq.Expressions) which I’m intending to use in real-world application. I’ve decided to use monadic interface because I want to employ both dynamic expressions generation (to perform such tasks as accessing field by name, provided in runtime) and static expression composition (as with regular LINQ queries) with type checking, standard C# syntax and other cool things. The result could looks like this:

using (var context = new DataContext())

{

   var expr = from fullName in PrimExpressions.PropertyOrField<Employee, string>("FullName")

               select PrimExpressions.Citation<Func<Employee, bool>>(e => fullName(e).StartsWith("Bill Gates"));

 

   var emp = context.Employees.FirstOrDefault(expr);

}

 

Here I’ve acquired FullName property dynamically (by name) but then used It in static context with other functions to assemble filter predicate. This is a working sample, you can compile run it using following code:

public static class ExpressionMonad

{

   public static Expression<TResult> Select<TSource, TResult>(

           this Expression<TSource> source,

           Expression<Func<TSource, TResult>> projector)

   {

       if (source == null) throw new ArgumentNullException("source");

       if (projector == null) throw new ArgumentNullException("projector");

 

       switch (projector.NodeType)

       {

           case ExpressionType.Lambda:

               Func<Expression<TResult>, TResult> citationFunc = PrimExpressions.Citation;

 

               Expression projectorExpression;

 

               var citationFuncCall = projector.Body as MethodCallExpression;

               if ((citationFuncCall != null) && (citationFuncCall.Method == citationFunc.Method))

                   projectorExpression = citationFuncCall.Arguments[0]; // remove citation

               else

                   projectorExpression = projector;

 

               if (projectorExpression.NodeType == ExpressionType.Quote) // remove quote

                   projectorExpression = ((UnaryExpression)projectorExpression).Operand;

 

               var binder = new ReplaceParameter(projector.Parameters[0], source);

               var expression = (LambdaExpression)binder.Visit(projectorExpression);

               var lambda = Expression.Lambda<TResult>(expression.Body, source.Parameters);

 

                return lambda;

 

           default:

               throw new InvalidOperationException(string.Format("Unsupported projector node type: {0}", projector.NodeType));

       }

   }

 

   internal sealed class ReplaceParameter : ExpressionVisitor

   {

     public ReplaceParameter(ParameterExpression parameter, LambdaExpression parameterLambda)

       {

           if (parameter == null) throw new ArgumentNullException("parameter");

           if (parameterLambda == null) throw new ArgumentNullException("parameterLambda");

 

           m_Parameter = parameter;

           m_ParameterLambda = parameterLambda;

       }

 

       protected override Expression VisitInvocation(InvocationExpression node)

       {

           if (node.Expression == Parameter)

               return ParameterLambda.Body;

           else

               return base.VisitInvocation(node);

       }

 

       private readonly ParameterExpression m_Parameter;

 

       private ParameterExpression Parameter

       {

           get

           {

               return m_Parameter;

           }

       }

 

       private readonly LambdaExpression m_ParameterLambda;

 

       private LambdaExpression ParameterLambda

       {

           get

           {

               return m_ParameterLambda;

           }

       }

   }

}

 

public static class PrimExpressions

{

   public static Expression<Func<TEntity, TResult>> PropertyOrField<TEntity, TResult>(string propertyOrFieldName)

   {

       var entityParameter = Expression.Parameter(typeof(TEntity));

       var propertyOrField = Expression.PropertyOrField(entityParameter, propertyOrFieldName);

       var lambda = Expression.Lambda<Func<TEntity, TResult>>(propertyOrField, entityParameter);

 

        return lambda;

   }

 

   public static T Citation<T>(Expression<T> expr)

   {

       throw new InvalidOperationException("This method should be used within expression monad only and shouldn't be called directly.");

   }

}

 

I think the implementation will not be too large and I don’t want to start separate project. Probably it would not harm if I’ll put it in the SharpMaLib. This library if very C# specific and probably completely useless in F#, so I’d like to put in the separate library but under the SharpMaLib project. And probably it will relay on some SharpMaLib functions. I haven’t try to implement it in F# yet, but I think it’s quite possible. What is your opinion about all of this?

 

Coordinator
Aug 24, 2010 at 5:34 PM
Edited Aug 24, 2010 at 5:41 PM

My opinion is that it doesn't belong in this project.

I think a better place for it is http://functionalcsharp.codeplex.com/. (I owned that project too)

Also it could be greatly simplified using C# Pattern Matching (which is part of that project) like:

Func<Expression, string> toString = null;
toString = exp =>
exp.Match()
.With<LambdaExpression>(l => toString(l.Body))
.With<ParameterExpression>(p => p.Name)
.With<BinaryExpression>(b => String.Format("{0} {1} {2}", toString(b.Left), Op[b.NodeType], toString(b.Right)))
.Return<string>();
Developer
Aug 25, 2010 at 4:46 AM
Edited Aug 27, 2010 at 10:49 AM

Thanks, I’ll check it.

UP: I've created new project for Expression’s stuff. It located here http://lexpress.codeplex.com/ and relies on sharpmalib (I use maybe monad in active patterns). Functionalcsharp is nice but I’ve decided to implement library in F#, so I have most of its functionality out of the box.