HigherLogics 是我的博客,我花了很多时间研究这个问题。限制确实是对类型构造函数的抽象,也就是“泛型优于泛型”。模仿 ML 模块和仿函数的最佳方法似乎需要至少一个(半安全)演员表。
它基本上归结为定义一个抽象类型,以及一个对应于在该类型上运行的模块签名的接口。抽象类型和接口共享一个类型参数 B,我称之为“品牌”;品牌一般只是实现模块接口的子类型。品牌确保传入的类型是模块期望的正确子类型。
// 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; }; }
}
}
如您所见,强制转换大多是安全的,因为类型系统确保表达式类型的品牌与解释器的品牌相匹配。解决这个问题的唯一方法是,如果客户创建自己的 Exp 类并指定 Interpreter 品牌。有一种更安全的编码也可以避免这个问题,但是对于普通编程来说太笨拙了。
后来我使用了这种编码,并翻译了Oleg 用 MetaOCaml 编写的一篇论文中的示例,以使用 C# 和 Linq。解释器可以透明地运行使用这种嵌入式语言在 ASP.NET 中的服务器端或作为 JavaScript 的客户端编写的程序。
这种对解释器的抽象是 Oleg 的最终无标记编码的一个特征。博客文章中提供了他论文的链接。
接口在 .NET 中是一等的,由于我们使用接口对模块签名进行编码,因此模块和模块签名在这种编码中也是一等的。因此,函子直接使用接口代替模块签名,即。他们将接受 ISymantics<B> 的实例并将任何调用委托给它。