我正试图了解如何实现类型推断。 特别是,我不太清楚“统一”的繁重发挥在何处/为何发挥作用。
我将举一个“伪C#”的例子来帮助澄清:
这种天真的方式是这样的:
假设您将程序“解析”到表达式树中,以便可以使用以下命令执行:
interface IEnvironment
{
object lookup(string name);
}
interface IExpression
{
// Evaluate this program in this environment
object Evaluate(IEnvironment e);
}
因此可以通过以下方式实现“乘法”之类的内容:
class Multiply : IExpression
{
IExpression lhs;
IExpression rhs;
// etc.
public object Evaluate(IEnvironment e)
{
// assume for the moment C# has polymorphic multiplication
return lhs.Evaluate(e) * rhs.Evaluate(e);
}
}
然后要“实现”类型推断,您可以执行以下操作:
interface ITypeEnvironment
{
Type getType(string name);
}
interface IExpression
{
//as before
object Evaluate(IEnvironment e);
// infer type
Type inferType(ITypeEnvironment typeEnvironment);
}
然后“乘法”的类型推断可能就像:
class Multiply : IExpression
{
IExpression lhs;
IExpression rhs;
// ...
public Type inferType(ITypeEnvironment typeEnvironment)
{
Type lhsType = lhs.inferType(typeEnvironment);
Type rhsType = rhs.inferType(typeEnvironment);
if(lhsType != rhsType)
throw new Exception("lhs and rhs types do not match");
// you could also check here that lhs/rhs are one of double/int/float etc.
return lhsType;
}
}
lhs和rhs可能是简单的常量,或者是在环境中查找的“变量”:
class Constant : IExpression
{
object value;
public Type inferType(ITypeEnvironment typeEnvironment)
{
return value.GetType(); // The type of the value;
}
public object Evaluate(IEnvironment environment)
{
return value;
}
}
class Variable : IExpression
{
string name;
public Type inferType(ITypeEnvironment typeEnvironment)
{
return typeEnvironment.getType(name);
}
public object Evaluate(IEnvironment environment)
{
return environment.lookup(name);
}
}
但在这方面我们最终无需“统一”算法。
所以,显然,我的例子不够复杂。它需要更高阶的功能吗?我们需要“参数多态”吗?
实际需要“统一”来正确推断表达式类型的最简单的例子是什么。
Scheme中的一个例子是理想的(即一个非常小的Scheme程序的例子,你需要统一来正确地推断出s表达式的类型)。
答案 0 :(得分:5)
让我完全忽略您的示例,并举例说明我们在C#中进行方法类型推断的位置。 (如果您对此主题感兴趣,那么我建议您阅读我博客的“type inference”存档。)
考虑:
void M<T>(IDictionary<string, List<T>> x) {}
这里我们有一个通用方法M,它采用一个将字符串映射到T列表的字典。假设你有一个变量:
var d = new Dictionary<string, List<int>>() { ...initializers here... };
M(d);
我们在没有提供类型参数的情况下调用M<T>
,因此编译器必须推断它。
编译器是通过Dictionary<string, List<int>>
“统一”IDictionary<string, List<T>>
来实现的。
首先,它确定Dictionary<K, V>
实现IDictionary<K, V>
。
由此我们推断出Dictionary<string, List<int>>
实现了IDictionary<string, List<int>>
。
现在我们在IDictionary
部分进行了匹配。我们用字符串统一字符串,这显然都很好但我们从中学不到任何东西。
然后我们将List与List统一起来,并意识到我们必须再次递归。
然后我们用T统一int,并意识到int是T的绑定。
类型推断算法逐渐消失,直到它不再有进展,然后它开始从推论中进一步推断。 T上唯一的绑定是int,所以我们推断出调用者必须要T为int。所以我们打电话给M<int>
。
这是清楚的吗?
答案 1 :(得分:2)
假设我们有一个功能
f(x,y)
其中x可能是例如FunctionOfTwoFunctionsOfInteger 或者它可能是FunctionOfInteger。
假设我们传入
f(g(u,2),g(1,z))
现在统一,u绑定为1,z绑定为2,因此第一个参数是FunctionOfTwoFunctionsOfInteger。
所以,我们必须推断出x和y的类型都是FunctionOfTwoFunctionsOfInteger
我对C#不太熟悉,但是对于lambda表达式(或等效的委托或其他),这应该是可能的。
有关类型推理在提高定理证明速度方面非常有用的示例,请查看“Schubert的Steamroller”
http://www.rpi.edu/~brings/PAI/schub.steam/node1.html
“自动推理期刊”有一个问题,专门讨论这个问题的解决方案和公式,其中大部分涉及定理证明系统中的类型推理:
答案 2 :(得分:2)
public Type inferType(ITypeEnvironment typeEnvironment) { return typeEnvironment.getType(name); }
如果您不知道变量的类型怎么办?这就是类型推断的重点,对吗?像这样非常简单(用某种伪代码语言):
function foo(x) { return x + 5; }
你不知道x
的类型,直到你推断添加,并意识到它必须是一个整数,因为它被添加到一个整数,或类似的东西。
如果你有另外一个这样的功能怎么办:
function bar(x) { return foo(x); }
在找出x
的类型之前,您无法确定foo
的类型,等等。
因此,当您第一次看到变量时,您必须为变量指定一些占位符类型,然后,当该变量传递给某种函数或某物时,您必须使用参数“统一”它功能的类型。