3

我试图弄清楚类型推断是如何实现的。特别是,我不太明白“统一”的繁重工作在哪里/为什么发挥作用。

我将在“伪 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 表达式的类型)。

4

3 回答 3

5

让我完全忽略您的示例,并举一个我们在 C# 中进行方法类型推断的示例。(如果您对这个主题感兴趣,那么我鼓励您阅读我博客的“类型推断”存档。)

考虑:

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 统一起来,意识到我们必须再次递归。

然后我们将 int 与 T 统一,并意识到 int 是 T 上的一个界。

类型推断算法会一直持续下去,直到它无法再取得进展,然后它开始从其推断中进行进一步的推论。T 的唯一界限是 int,因此我们推断调用者一定希望 T 为 int。所以我们调用M<int>.

明白了吗?

于 2009-07-15T19:57:34.267 回答
2

假设我们有一个函数

f(x, y)

其中 x 可能是例如 FunctionOfTwoFunctionsOfInteger 或者它可能是 FunctionOfInteger。

假设我们通过

f(g(u, 2), g(1, z))

现在统一了,u 绑定到 1,z 绑定到 2,所以第一个参数是 FunctionOfTwoFunctionsOfInteger。

所以,我们要推断x和y的类型都是FunctionOfTwoFunctionsOfInteger

我对 C# 不太熟悉,但是对于 lambda 表达式(或等效的委托或其他),这应该是可能的。

有关类型推断在提高定理证明速度方面非常有用的示例,请查看“舒伯特的压路机”

http://www.rpi.edu/~brings/PAI/schub.steam/node1.html

Journal of Automated Reasoning 有一期专门讨论这个问题的解决方案和公式,其中大部分涉及定理证明系统中的类型推理:

http://www.springerlink.com/content/k1425x112w6671g0/

于 2009-07-15T19:13:29.707 回答
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,依此类推。

因此,当您第一次看到一个变量时,您只需为变量分配一些占位符类型,然后,当该变量传递给某种函数或其他东西时,您必须将其与参数类型“统一”功能。

于 2009-07-15T19:19:13.300 回答