C# 模糊编译器';s lambda表达式翻译
我学习了Y Combinator(使用c#5.0),并对这种方法感到非常惊讶:C# 模糊编译器';s lambda表达式翻译,c#,performance,compiler-construction,lambda,C#,Performance,Compiler Construction,Lambda,我学习了Y Combinator(使用c#5.0),并对这种方法感到非常惊讶: public static Func<T1, Func<T2, TOut>> Curry<T1, T2, TOut> ( this Func<T1, T2, TOut> f) { return a => b => f(a, b); } 公共静态函数(此函数) { 返回a=>b=>f(a,b); } 。。。已由编译器翻译为: public sta
public static Func<T1, Func<T2, TOut>> Curry<T1, T2, TOut> ( this Func<T1, T2, TOut> f)
{
return a => b => f(a, b);
}
公共静态函数(此函数)
{
返回a=>b=>f(a,b);
}
。。。已由编译器翻译为:
public static Func<T1, Func<T2, TOut>> Curry<T1, T2, TOut>(this Func<T1, T2, TOut> f)
{
first<T1, T2, TOut> local = new first<T1, T2, TOut>();
local.function = f;
return new Func<T1, Func<T2, TOut>>(local.Curry);
}
private sealed class first<T1, T2, TOut>
{
private sealed class second
{
public first<T1, T2, TOut> ancestor;
public T1 firstParameter;
public TOut Curry(T2 secondParameter)
{
return ancestor.function(firstParameter, secondParameter);
}
}
public Func<T1, T2, TOut> function;
public Func<T2, TOut> Curry(T1 firstParameter)
{
second local = new second();
local.ancestor = this;
local.firstParameter = firstParameter;
return new Func<T2, TOut>(local.Curry);
}
}
公共静态函数(此函数)
{
第一个本地=新的第一个();
局部函数=f;
返回新的Func(local.Curry);
}
私人密封头等舱
{
二等兵
{
公共始祖;
公共参数;
公开兜售咖喱(T2第二参数)
{
返回祖先函数(firstParameter,secondParameter);
}
}
公共职能;
公共本币(T1第一参数)
{
第二个本地=新的第二个();
local.祖先=这个;
local.firstParameter=firstParameter;
返回新的Func(local.Curry);
}
}
所以,当我们使用引用second.Curry的委托时,第二个类是嵌套的,第一个类不可用于垃圾收集。同时,我们在第一堂课上所需要的就是功能。也许我们可以将其复制(委托)到第二个类,然后收集第一个类?是的,我们也应该做第二类非嵌套的,但这似乎是可以的。据我所知,代理是“按值”复制的,因此我可以建议它相当慢,但同时我们复制firstParameter?!所以,也许任何人都可以解释,为什么编译器会做这些事情?)
我说的是这样的话:
private sealed class first<T1, T2, TOut>
{
public Func<T1, T2, TOut> function;
public Func<T2, TOut> Curry(T1 firstParameter)
{
second<T1, T2, TOut> local = new second<T1, T2, TOut>();
local.function = function;
local.firstParameter = firstParameter;
return new Func<T2, TOut>(local.Curry);
}
}
public sealed class second<T1, T2, TOut>
{
public T1 firstParameter;
public Func<T1, T2, TOut> function;
public TOut Curry(T2 secondParameter)
{
return function(firstParameter, secondParameter);
}
}
私有密封类优先
{
公共职能;
公共本币(T1第一参数)
{
第二个本地=新的第二个();
local.function=函数;
local.firstParameter=firstParameter;
返回新的Func(local.Curry);
}
}
公共密封二级
{
公共参数;
公共职能;
公开兜售咖喱(T2第二参数)
{
返回函数(第一个参数,第二个参数);
}
}
您已经使用了两次lambda操作符,因此您将获得两个匿名委托,其中捕获的变量被提升为状态类型
内部lambda的state类型通过引用保存外部lambda的state类型的原因是在C#中捕获是这样工作的:捕获变量,而不是它的值
其他一些语言(例如C++11 lambdas)有替代语法来表示按值捕获与按引用捕获。C#没有,只是通过引用捕获所有内容。支持按值语义并不是一个很好的理由,因为垃圾收集避免了在没有按值捕获模式的情况下C++11中可能存在的生存期问题
C#编译器是否注意到变量从未写入,因此按值捕获与按引用捕获无法区分?可能吧,但这是编译器中的额外逻辑,设计和代码的额外审查,额外测试。所有这些都是为了在内存占用和局部性方面实现一个小的、几乎微不足道的改进。这显然不符合成本效益标准,你可以在Eric Lippert的许多博客文章中找到讨论的内容。这个问题很难理解。让我澄清一下。您的建议是编译器可以生成
public static Func<T1, Func<T2, TOut>> Curry<T1, T2, TOut>(this Func<T1, T2, TOut> f)
{
first<T1, T2, TOut> local = new first<T1, T2, TOut>();
local.function = f;
return new Func<T1, Func<T2, TOut>>(local.Curry);
}
private sealed class first<T1, T2, TOut>
{
private sealed class second
{
//public first<T1, T2, TOut> ancestor;
public Func<T1, T2, TOut> function;
public T1 firstParameter;
public TOut Curry(T2 secondParameter)
{
return /*ancestor.*/function(firstParameter, secondParameter);
}
}
// public Func<T1, T2, TOut> function;
public Func<T2, TOut> Curry(T1 firstParameter)
{
second local = new second();
// local.ancestor = this;
local.function = function;
local.firstParameter = firstParameter;
return new Func<T2, TOut>(local.Curry);
}
}
然后无法收集第一个的实例。该实例不再可以通过makeAdder
访问,但可以通过addFive
访问
在建议的codegen中,可以在此场景中收集first
,因为无法通过addFive
访问实例
在这个特定场景中,优化是合法的,这是正确的。然而,由于Ben Voigt在其回答中描述的原因,一般来说,这是不合法的如果f
在Curry
内部发生变异,则local.function
必须发生变异。但是local
在执行外部委托之前无法访问second
的实例
C#编译器团队可以选择进行您确定的优化,但到目前为止,这一微小的节省根本不值得费心
我们考虑让Roslyn按照您描述的路线进行优化;也就是说,如果已知外部变量没有变异,则更积极地获取其值。我不知道这一优化是否让Roslyn受益。你需要的不仅仅是函数,你正在关闭参数
a
,所以你需要存储它。@SergeyBerezovskiy:Currying允许你将两个参数的函数转换为一个参数的函数,返回一个参数的函数。例如,假设您有一个函数add
,即(a,b)=>a+b
。通过Curry,您有一个函数makeAdder
,该函数接受一个数字并返回一个将该数字与数字相加的函数。@SergeyBerezovskiy看起来像是代码的标准实现,可能看起来像var add5=Add.Curry(5)
。Alexei,第二个类有祖先字段,所以我们的第一个类在第二个存在和第二个存在之前不能被收集,因为我们的委托在第二个存在上有引用。Curry@AlexeiLevenkov:我想OP是在指出,second
只使用祖先
获取函数
,那么,为什么不让第二个
保留功能
,而不是保留祖先
?他的想法是可以更早地收集祖先
。而且,JIT编译器在执行时可能会进一步简化此过程。@Grzenio:我认为合并类型不是允许JIT进行的优化之一。无论多么困难
Func<int, int, int> adder = (x, y)=>x+y;
Func<int, Func<int, int>> makeAdder = adder.Curry();
Func<int, int> addFive = makeAdder(5);
makeAdder = null;