“匿名递归”是否有效?在 .NET 中工作?它在 Mono 中是这样的
我浏览了这个 几天前关于“C# 中的匿名递归”的网站。本文的主旨是以下代码在 C# 中不起作用:
Func<int, int> fib = n => n > 1 ? fib(n - 1) + fib(n - 2) : n;
然后文章详细介绍了如何使用 柯里化和Y-combinator回到“匿名递归” C#。这很有趣,但恐怕对于我的日常编码来说有点复杂。至少在这一点上...
我喜欢亲眼看到一些东西,所以我打开了 Mono CSharp REPL 并输入该行。没有错误。所以,我输入了fib(8);
。令我惊讶的是,它成功了! REPL 回复了 21
!
我想这可能是 REPL 的某种魔力,所以我启动了“vi”,输入以下程序并编译它。
using System;
public class Program
{
public static void Main(string[] args)
{
int x = int.Parse(args[0]);
Func<int, int> fib = n => n > 1 ? fib(n - 1) + fib(n - 2) : n;
Console.WriteLine(fib(x));
}
}
它的构建和运行也很完美!
我在 Mac 上运行 Mono 2.10。我现在无法访问 Windows 计算机,因此无法在 Windows 上的 .NET 上进行测试。
.NET 上也已修复此问题还是 Mono 的一个无声功能?这篇文章已有几年历史了。
如果只是 Mono,我就等不及下一次面试了,他们要求我用我选择的语言 (Mono C#) 编写一个 Fibinocci 函数,我必须警告 .NET 无法工作。好吧,实际上我可以等待,因为我热爱我的工作。不过,有趣的是...
更新:
Mono 并没有真正执行“匿名”递归,因为它使用 fib
作为命名委托。我的不好。事实上,Mono C# 编译器在赋值之前假定 fib
为 null
值,这是一个错误,如下所述。我说“编译器”是因为 .NET CLR 可以很好地运行生成的程序集,即使 .NET C# 编译器不会编译代码。
对于所有纳粹采访:
Func<int, int> fib = n => n > 1 ? fib(n - 1) + fib(n - 2) : n;
可以用迭代版本替换:
Func<int, int> fib = n =>
{
int old = 1;
int current = 1;
int next;
for (int i = 2; i < n; i++)
{
next = current + old;
old = current;
current = next;
}
return current;
};
您可能想要这样做,因为递归版本在像 C# 这样的语言中效率低下。有些人可能建议使用记忆化,但是,由于这仍然比迭代方法慢,他们可能只是是混蛋。 :-)
但在这一点上,这更像是函数式编程的广告而不是其他任何东西(因为递归版本要好得多)。这确实与我原来的问题没有任何关系,但有些答案认为这很重要。
I surfed into this site a few days ago on "Anonymous Recursion in C#". The thrust of the article is that the following code will not work in C#:
Func<int, int> fib = n => n > 1 ? fib(n - 1) + fib(n - 2) : n;
The article then goes into some detail about how to use currying and the Y-combinator to get back to "Anonymous Recursion" in C#. This is pretty interesting but a tad complex for my everyday coding I am afraid. At this point at least...
I like to see stuff for myself so I opened the Mono CSharp REPL and entered that line. No errors. So, I entered fib(8);
. Much to my great surprise, it worked! The REPL replied back with 21
!
I thought maybe this was some magic with the REPL, so I fired-up 'vi', typed in the following program, and compiled it.
using System;
public class Program
{
public static void Main(string[] args)
{
int x = int.Parse(args[0]);
Func<int, int> fib = n => n > 1 ? fib(n - 1) + fib(n - 2) : n;
Console.WriteLine(fib(x));
}
}
It built and ran perfectly too!
I am running Mono 2.10 on a Mac. I do not have access to a Windows machine right now so I cannot test this on .NET on Windows.
Has this been fixed on .NET as well or is this a silent feature of Mono? The article is a couple of years old.
If it is Mono only, I cannot wait for the next job interview where they ask me to write a Fibinocci function in the language of my choice (Mono C#) where I have to provide the caveat that .NET will not work. Well, actually I can wait since I love my job. Still, interesting...
Update:
Mono is not really doing "anonymous" recursion as it is using fib
as a named delegate. My bad. The fact that the Mono C# compiler assumes a null
value for fib
before assignment is a bug as noted below. I say "compiler" because the .NET CLR would run the resulting assembly just fine even though the .NET C# compiler would not compile the code.
For all the interview Nazis out there:
Func<int, int> fib = n => n > 1 ? fib(n - 1) + fib(n - 2) : n;
can be replaced with an iterative version:
Func<int, int> fib = n =>
{
int old = 1;
int current = 1;
int next;
for (int i = 2; i < n; i++)
{
next = current + old;
old = current;
current = next;
}
return current;
};
You might want to do this because the recursive version is inefficient in a language like C#. Some might suggest using memoization but, since this is still slower than the iterative method, they may just be being wankers. :-)
At this point though, this becomes more of an ad for functional programming than anything else (since the recursive version is so much nicer). It really does not have anything to do with my original question, but some of the answers thought that it was important.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
这是 Mono 编译器中的一个错误。它违反了规范的第 §12.3.3 节。变量fib不能在变量初始值设定项中使用,因为它没有明确分配。
This is a bug in the Mono compiler. It violates section §12.3.3 of the specification. The variable fib cannot be used in the variable initializer, because it isn't definitely assigned.
正如我在上面的评论中指出的,如果 Mono 这样做,那么他们就有一个错误。规范明确表明这应该被检测为错误。当然,该错误大多是无害的,并且大多数时候都会按照您的意愿行事。我们考虑过改变规则,使这种递归合法化;基本上,我们必须在规范中添加一个特殊情况,表明这种狭义定义的情况是合法的。但它从来都不是一个足够高的优先事项。
有关此问题的更多想法,请参阅我关于该主题的文章:
http://blogs .msdn.com/b/ericlippert/archive/2006/08/18/706398.aspx
顺便说一句,我不会雇用任何在面试中给我直接递归实现 fib 的人。它极其效率低下;它的运行时间与其输出的大小成正比,并且 fib 呈指数增长。为了使其高效,请使用带有记忆化的递归,或实现明显的迭代解决方案。
As I noted in a comment above, if Mono does that then they have a bug. The spec is clear that this is supposed to be detected as an error. The bug is of course mostly harmless, and most of the time does what you want. We've considered changing the rules to make this sort of recursion legal; basically we'd have to add a special case to the spec that says that this narrowly-defined case is legal. It's never been a high enough priority though.
For more thoughts on this issue see my article on the subject:
http://blogs.msdn.com/b/ericlippert/archive/2006/08/18/706398.aspx
And by the way, I would not hire anyone who gave me a straight-up recursive implementation of fib in an interview. It is extremely inefficient; its running time is proportional to the size of its output, and fib grows exponentially. To make it efficient use recursion with memoization, or implement the obvious iterative solution.
试试这个...
...问题是当您尝试在上述方法中使用 fib 时,它没有定义,因此静态分析器报告编译器错误。
try this...
... The problem is that fib isn't defined when you try to use it in the above method so the static analyzer reports a compiler error.
看来,在我的兴奋之中,我从根本上错了。 .NET 和 Mono 都没有按照原始文章的方式提供“匿名递归”。您无法将
fib
作为独立实体进行传递。在 Mono C# REPL 中查看以下序列:
这是因为:
换句话说,
显然,
fibCopy
只是指向fib
(委托)的当前定义,并且不是针对其本身。因此,Mono 实际上只是在初始赋值期间将null
值预先分配给fib
,以便赋值有效。我更喜欢不必声明 null 的便利,因此我确实喜欢这种行为。不过,这并不是原始文章真正讨论的内容。
It seems that, in my excitement, I was fundamentally mistaken. Neither .NET nor Mono provides "Anonymous Recursion" in the way that the original article means. You could not pass around
fib
as a self-contained entity.Check out the following sequence in the Mono C# REPL:
This is because:
In other words,
Clearly,
fibCopy
is just pointing at the current definition offib
(the delegate) and not at itself. So Mono is really just pre-assigning a value ofnull
tofib
during the initial assignment so that the assignment is valid.I much prefer the convenience of not having to declare
null
so I do like this behavior. Still, it is not really what the original article is talking about.在 Microsoft 的 C# 编译器中,仅当您首先将
fib
设置为null
时,它才会起作用。否则,它会给出错误,因为
fib
在分配之前就被使用了。Mono的编译器足够“聪明”,可以避免这个错误(换句话说,它违反了官方规范)
In Microsoft's C# compiler, it will only work if you first set
fib
tonull
.Otherwise, it will give an error because
fib
is used before it's assigned.Mono's compiler is "smart" enough to avoid this error (in other words, it violates the official spec)