C# 泛型方法类型参数推断

8

有没有办法可以概括这里的类型定义? 理想情况下,我希望能够更改“testInput”的类型,并且在编译时正确地推断出测试类型。

public static void Run()
{
    var testInput = 3;
    var test = ((Func<int, int>) Identity).Compose<int,int,int>(n => n)(testInput);
    Console.WriteLine(test);
}

public static Func<T, V> Compose<T, U, V>(this Func<U, V> f, Func<T, U> g)
{
    return x => f(g(x));
}

public static T Identity<T> (this T value)
{
    return value;
}

更新: 我可以指定传递给Compose的函数类型,但这仍然在该行中指定了类型。
public static void Run()
{
    var testInput = 3;
    var identity = (Func<int, int>) Identity;
    var test = identity.Compose((int n) => n)(testInput);
    Console.WriteLine(test);
}

一些背景信息:我正在学习Wes Dyer的Monad奇迹


注:Monad是一种编程范式,用于处理副作用和非确定性计算。

你能提供更具体的信息,说明你想要实现什么吗?目前你的Compose函数过于宽泛,无法从lambda中推断出T的类型。它只是无法从使用中获取足够的信息。 - jeffora
4个回答

3
由于我今晚已经在不停地输入文本,所以我也想试着回答一下。需要说明的是,我不是C#编译器的专家,我没有阅读过规范(任何规范……对于任何东西),尽管你提供的那篇文章真的很有趣,但如果我说我是其中的任何一种专家(甚至完全理解了它),那我就是在撒谎。

除此之外,我对你的问题的看法是这样的:

是否有任何方法可以将此处的类型定义概括起来?

我认为简短的答案是否定的。根据提供的信息,C#编译器中的类型推断部分无法从各种变量的使用中推断出足够的信息。

正如其他答案所示,它可以被简化。您可以使用@Lee的IdentityFunc来允许var identity进行类型推断。然而,即使加上这个,仍然不能在您的示例代码中推断出所有Compose的类型变量。

想象一下以下情况:

public static Func<T, V> Compose<T, U, V>(this Func<U, V> f, Func<T, U> g)
{
    return x => f(g(x));
}

public static T Identity<T> (this T value)
{
    return value;
}

public static Func<T, T> IdentityFunc<T>(this T value)
{
    return (Func<T, T>)Identity;
}

并且

public static void Run()
{
    var a = 3; // a is int
    var i = a.IdentityFunc(); // i is Func<int, int>;
    var test = i.Compose(n => n)(a) // test is expected to be int
}

一开始,这似乎可以将test轻松推断为int。然而,i.Compose的返回类型只能在使用后才能推断出来。C#编译器显然不会允许这种情况。

public static void Run()
{
    var a = 3; // a is int
    var i = a.IdentityFunc(); // i is Func<int, int>;
    var c = i.Compose(n => n) // c is Func<T, int> - T cannot be resolved without knowledge of n
    var test = c(a); // ideally have type inference infer c (Func<T, int>) as Func<int, int>
}

在这个例子中,使用ca时,编译器必须回顾性地推断调用i.Compose<T, U, V>(n => n)的返回类型为Func<int, int>。显然,在C#编译器中不可能做到这一点。去掉调用c(a),编译器就无法知道对c的使用,这将消除推断T的任何可能性(即使它也无法推断)。可能有一个更先进的类型推断系统能够根据通用返回值的使用来进行这种推断(可能是F# - 另一个我不是专家的话题)。
由于Wes Dyer没有提供特定示例的使用情况,因此不知道他是否使用了其他魔法来实现您试图实现的类型推断程度。
更有资质的人,如Eric Lippert,能够为您提供更详细(和技术上准确/敏锐)的信息。我在这里读到了他对类型推断问题的出色回答,但我找不到它了。他的博客有很多优秀的信息。如果您有兴趣,可以尝试联系他。此外,他对这个问题的回答讨论了单子(最终链接至Wes Dyer的文章),但您可能会对阅读它感兴趣:纯英语中的Monad?(对于没有FP背景的OOP程序员)

2
实际上,我没有太多要补充你的答案。正如你正确指出的那样,在 lambda 参数上没有类型时,我们没有足够的信息可以从 Compose 的调用站点中推断出 T。而且正如你所指出的,如果我们能够推迟分析直到使用组合结果,那么我们将能够取得更大的进展,但我们没有这样做。 - Eric Lippert
感谢您对问题的出色回答。这个答案没有得到它应该得到的赞同。 - CaptainCasey

2
你可以编写一个扩展方法,返回指定类型的 Identity 函数:
public static Func<T, T> IdentityFunc<T>(this T value)
{
    return (Func<T, T>)Identity;
}

(或者只是return v => value;
你的测试就变成了:
var testInput = 3; 
var identity = testInput.IdentityFunc();
test = identity.Compose((int n) => n)(testInput);

1

我能做到的最接近的方法是显式地为 n => n lambda 参数进行类型定义:

var test = ((Func<int, int>)Identity).Compose((int n) => n)(testInput);

1

我认为你无法实现你的理想;C#类型推断不是这样工作的。

你可能会喜欢F#。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接