Saturday, June 16, 2012

F# Type Provider as wrapper class

I still remember those days when I have to change behavior from a sealed class. I have to use a wrapper class and rewrite each method in the sealed class. When I find out the type provider can inherit an non-object class (see this post), I decide to find a way to simplify this task on this Friday night. The source code is here.

The idea is simple, I need to do a wrapper class around the existing sealed class. In addition, logging function is executed before and after each method call. The equivalence C# code is like:

public sealed class A
    public void F1() { ... }

public class MyClass
    private A a = new A();
    public void F1() { a.F1(); }

The type provider generate methods with the same name as the base class method, take same parameter, pass the parameter to base class method, and return values from base method invoke. In addition to the base class invoke, logging methods are invoked before and after.

the base class definition is:

[< Sealed >]
type BaseType2() =  
     member this.F1(s) = printfn "%s" s
     member this.F2(i) = printfn "%d" i 

the code to invoke the type provider is:

#r @".\bin\Debug\ShareInfoSampleTypeProvider.dll"
type T = Samples.ShareInfo.TPTest.TPTestType
let t = T()
t.F1("hello")  //invoke F1 and also output logging info
t.F2(2)         //invoke F2 and also output logging info
Because we have the logging function defined as:

type InsertFunctions = class
    static member LogBeforeExecution(obj:BaseType2, methodName:string) = printfn "log before %A %A" obj methodName
    static member LogAfterExecution(obj:BaseType2, methodName:string) = printfn "log after %A %A" obj methodName

the final execution is:

log before Samples.FSharp.ShareInfoProvider.BaseType2 "F1"
log after Samples.FSharp.ShareInfoProvider.BaseType2 "F1" 
log before Samples.FSharp.ShareInfoProvider.BaseType2 "F2"
log after Samples.FSharp.ShareInfoProvider.BaseType2 "F2"
If you understand the previous post, the only change for current version is how to use Quotation to invoke the code.

  • The first barrier is to how to invoke a method.
the method call is Expr.Call, which takes three parameters if not a static method and will take only two parameters if it is a static method. If you are familiar about how to get quotations, you can refer to MSDN document. The parameter to the Expr.Call is created by using Expr.Value.
  • The second one is how to invoke two or more statements
Invoking statements is handled by Expr.Sequential. The problem it only takes two elements, seems a problem when we want to invoke more than two statements. Actually this is not a problem at all. If the second parameter is a Expr.Sequential, you can have another space to hold your statement. The following code is the what is inside the InvokeCode.

let baseTExpression = <@@ (%%args.[0]:BaseType2) @@>
let mi = baseTy.GetMethod(methodName)
let logExpr = Expr.Call(typeof.GetMethod("LogBeforeExecution"), [ baseTExpression; Expr.Value(methodName) ])
let invokeExpr = Expr.Call(baseTExpression, mi, args.Tail)
let logAfterExpr = Expr.Call(typeof.GetMethod("LogAfterExecution"), [ baseTExpression; Expr.Value(methodName) ])
Expr.Sequential(logExpr, Expr.Sequential(invokeExpr, logAfterExpr) 
Hopefully this can inspire you to explore more about the F# type provider and apply it to your daily coding adventure. :-)

No comments: