HigherLogics is my blog, and I've spent a lot of time investigating this question. The limitation is indeed abstraction over type constructors, aka "generics over generics". It seems the best you can do to mimic ML modules and functors requires at least one (semi-safe) cast.
It basically comes down to defining an abstract type, and an interface which corresponds to the module signature that operates on that type. The abstract type and the interface share a type parameter B which I term a "brand"; the brand is generally just the subtype that implements the module interface. The brand ensures that the type passed in is the proper subtype expected by the module.
// signature
abstract class Exp<T, B> where B : ISymantics<B> { }
interface ISymantics<B> where B : ISymantics<B>
{
Exp<int, B> Int(int i);
Exp<int, B> Add(Exp<int, B> left, Exp<int, B> right);
}
// implementation
sealed class InterpreterExp<T> : Exp<T, Interpreter>
{
internal T value;
}
sealed class Interpreter : ISymantics<Interpreter>
{
Exp<int, Interpreter> Int(int i) { return new InterpreterExp<int> { value = i }; }
Exp<int, Interpreter> Add(Exp<int, Interpreter> left, Exp<int, Interpreter> right)
{
var l = left as InterpreterExp<int>; //semi-safe cast
var r = right as InterpreterExp<int>;//semi-safe cast
return new InterpreterExp<int> { value = l.value + r.value; }; }
}
}
As you can see, the cast is mostly safe, since the type system ensures the brand of the expression type matches the brand of the interpreter. The only way to screw this up, is if the client creates his own Exp class and specifies the Interpreter brand. There is a safer encoding which avoids this problem too, but it's far too unwieldy for ordinary programming.
I later used this encoding and translated the examples from one of Oleg's papers written in MetaOCaml, to use C# and Linq. The interpreter can transparently run programs written using this embedded language server-side in ASP.NET or client-side as JavaScript.
This abstraction over interpreters is a feature of Oleg's final tagless encoding. Links to his paper are provided in the blog post.
Interfaces are first-class in .NET, and since we use interfaces to encode module signatures, modules and module signatures are also first-class in this encoding. Thus, functors simply use the interface directly in place of module signatures, ie. they would accept an instance of ISymantics<B> and delegate any calls to it.