Generic pattern matching in a non-native method that implements an interface

I have an unpleasant problem. I create a view engine in ASP.NET MVC and implements the IViewEngine interface. In one of the methods, I try to dynamically determine the type of the result of the view. Sometimes the result is a template (with type Template <'key>). The keys are used to target the placeholder in the template, and the idea is to use a discriminatory association, potentially unique to each website. It might look like this:

type MasterKey = | HeadContent | HeaderContent | MainContent | FooterContent let MasterTemplate : Template<MasterKeys> = ... 

Now the problem is this: since I am implementing the interface, I have no control over the method signature. Since I cannot add a parameter of a general type, 'a will be converted to obj, and the template will not match below:

  match result with | :? foo -> ... | :? bar -> ... | :? Template<'a> -> ... 

Any ideas?

+1
pattern-matching f #
source share
2 answers

Can you make the generic class of the view class generic, depending on the type of 'key it uses? Indivudual projects should inherit from your viewing engine class and indicate the type of key in the process.

+1
source share

Unfortunately, there is no way to make it beautiful. If you have control over the Template<'T> , the best option is to create a non-generic interface (for example, ITemplate ) and implement it in the Template<'T> . Then you can just check the interface:

 | :? ITemplate as t -> ... 

If not, then your only option is to use reflection magic. You can implement an active template that matches when the type is some value of Template<'T> , and returns a list of types ( System.Type objects) that were used as general arguments. In your pseudo-code, you wanted to get this as a parameter of type type 'a - it is impossible to get it as a parameter of type compilation time, but you can get it as information like runtime type:

 let (|GenericTemplate|_|) l = let lty = typeof<list<int>>.GetGenericTypeDefinition() let aty = l.GetType() if aty.IsGenericType && aty.GetGenericTypeDefinition() = lty then Some(aty.GetGenericArguments()) else None 

Now you can write the following pattern matching code:

 match result with | GenericTemplate tys -> // ... 

The final issue is how you can use this runtime type information to run some common code. The best option I can come up with is to call a common method (or function) using reflection - then you can specify information such as runtime as a general parameter, and therefore the code can be shared. The easiest option is to call a static member of the type:

  type TemplateHandler = static member Handle<'T>(arg:Template<'T>) = // This is a standard generic method that will be // called from the pattern matching - you can write generic // body of the case here... "aaa" | :? GenericTemplate tys -> // Invoke generic method dynamically using reflection let gmet = typeof<TemplateHandler>.GetMethod("Handle").MakeGenericMethod(tys) gmet.Invoke(null, [| result |]) :?> string // Cast the result to some type 

The basic idea is that you move the pattern matching body (which cannot have type type parameters) to the method (which can have common type parameters) and dynamically run the method using reflection.

You can change the code to use the let function instead of the static member , but finding a function that uses reflection is a bit more complicated.

+6
source share

All Articles