Using CodeDOM.

Dynamic currying: part 2

When we left last time, we were just preparing to start using CodeDOM. So, let's put our sleeves and do it:

CodeDOM has classes to represent parts of code we write, from compile unit (roughly the equivalent of Visual Studio project) down to statement, all in the System.CodeDom namespace.

We start simple, by declaring the compile unit, namespace and class:

var compileUnit = new CodeCompileUnit();

var curryingNamespace = new CodeNamespace("Currying");
compileUnit.Namespaces.Add(curryingNamespace);

var funcExtensionsClass = new CodeTypeDeclaration("FuncExtensions");
curryingNamespace.Types.Add(funcExtensionsClass);

This is where thighs start to get somewhat complicated, because now we want to add the method from the last part:

public static Func<T1, Func<T2, TResult>> Curry<T1, T2, TResult>(Func<T1, T2, TResult> func)
{
    return p1 => p2 => func(p1, p2);
}

Because we'll be working with types like this a lot, I have written a bunch of functions to create them. They are in the source code repository, but I won't show their implementation here. This is the code to declare the function:

var curryMethod =
    new CodeMemberMethod
    {
        Attributes = MemberAttributes.Public | MemberAttributes.Static,
        ReturnType = CreateCurriedFuncType(1, i),
        Name = "Curry",
        Parameters =
            { new CodeParameterDeclarationExpression(CreateFuncType(i), funcParameterName) }
    };
curryMethod.TypeParameters.AddRange(CreateFuncTypeParameters(i).ToArray());

This does exactly what we do when we type the code by hand, except it's more verbose. We state that it's a public static method called Curry with return type, parameters and type parameters generated by helper methods. We have to specify TypeParameters separately, because it doesn't have public setter and it's not possible to use collection initializer with existing collection.

Now we want to write the method body. The problem is that CodeDOM tries to be language-agnostic and lambdas are feature of C#, not .Net. Fortunately, we can use so called snippets, which are strings containing any code, which can be language specific:

var parameters = Enumerable.Range(1, i).Select(j => "p" + j).ToArray();

string lambda = string.Format("{0} => {1}({2})", string.Join(" => ", parameters),
                                funcParameterName, string.Join(", ", parameters));

var lambdaExpression = new CodeSnippetExpression(lambda);

We can the use this expression in a return statement, and we are done with the method:

var returnStatement = new CodeMethodReturnStatement(lambdaExpression);

curryMethod.Statements.Add(returnStatement);

funcExtensionsClass.Members.Add(curryMethod);

We can now compile the assembly and we are done:

var provider = new CSharpCodeProvider();

provider.CompileAssemblyFromDom(
    new CompilerParameters(new[] { "System.Core.dll" }, "Currying.dll") { GenerateInMemory = false },
    compileUnit);

Or are we? Tune in next time to see, why we aren't done yet.

dotnet c# codedom
Posted by: Petr Onderka
Last revised: 22 Jul, 2014 02:59 PM History

Discussion

Matt Smith
Matt Smith
04 Jun, 2014 12:55 AM

Thanks. The lambda creation via CodeSnippet was just what I needed.

Your Comments

Used for your gravatar. Not required. Will not be public.
Posting code? Indent it by four spaces to make it look nice. Learn more about Markdown.

Preview