Работает ли “Анонимная рекурсия” в .NET?Это происходит в моно
-
14-11-2019 - |
Вопрос
Я занялась серфингом в этот сайт несколько дней назад на тему "Анонимная рекурсия в C#".Суть статьи заключается в том, что следующий код не будет работать на C#:
Func<int, int> fib = n => n > 1 ? fib(n - 1) + fib(n - 2) : n;
Затем в статье подробно рассказывается о том, как использовать приготовление карри и Y-комбинатор вернемся к "анонимной рекурсии" в C#.Это довольно интересно, но, боюсь, немного сложно для моего повседневного программирования.По крайней мере, на данный момент...
Мне нравится все видеть своими глазами, поэтому я открыл Mono CSharp REPL и вошел в эту строку.Ошибок нет.Итак, я вошел fib(8);
.К моему большому удивлению, это сработало!Представитель ответил в ответ: 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));
}
}
Он тоже был построен и работал идеально!
Я запускаю Mono 2.10 на Mac.Сейчас у меня нет доступа к компьютеру с Windows, поэтому я не могу протестировать это на .NET в Windows.
Было ли это исправлено и в .NET, или это скрытая функция Mono?Статье уже пару лет.
Если это только Mono, я не могу дождаться следующего собеседования, на котором меня попросят написать функцию Fibinocci на языке по моему выбору (Mono C#), где я должен указать оговорку, что .NET не будет работать.Ну, вообще-то я могу подождать, так как люблю свою работу.И все же интересно...
Обновление:
Mono на самом деле не выполняет "анонимную" рекурсию, поскольку использует fib
как именованный делегат.Моя вина.Тот факт, что компилятор Mono C# предполагает null
ценность для fib
перед назначением возникает ошибка, как указано ниже.Я говорю "компилятор", потому что .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#.Некоторые могли бы предложить использовать запоминание но, поскольку это все еще медленнее, чем итеративный метод, они могут просто быть придурками.:-)
Однако на данный момент это становится скорее рекламой функционального программирования, чем чего-либо еще (поскольку рекурсивная версия намного приятнее).На самом деле это не имеет никакого отношения к моему первоначальному вопросу, но некоторые ответы показались мне важными.
Решение
Это жук в компиляторе Mono.Это нарушает раздел §12.3.3 Закона спецификация.Переменная выдумка не может использоваться в инициализаторе переменной, поскольку она определенно не назначена.
Другие советы
Как я отметил в комментарии выше, если Mono делает это, значит, у них ошибка.В спецификации ясно указано, что это должно быть обнаружено как ошибка.Ошибка, конечно, в основном безвредна и большую часть времени делает то, что вы хотите.Мы рассматривали возможность изменения правил, чтобы сделать такого рода рекурсию законной;по сути, нам пришлось бы добавить особый случай в спецификацию, в котором говорится, что этот узко определенный случай является законным.Однако это никогда не было достаточно высоким приоритетом.
Подробнее об этом читайте в моей статье на эту тему:
http://blogs.msdn.com/b/ericlippert/archive/2006/08/18/706398.aspx
И, кстати, я бы не стал нанимать никого, кто дал бы мне прямую рекурсивную реализацию fib на собеседовании.Это чрезвычайно неэффективный;время его работы пропорционально размеру его выходных данных, и fib растет экспоненциально.Чтобы сделать это эффективным, используйте рекурсию с запоминанием, или реализовать очевидное итеративное решение.
попробуй это...
Func<int, int> fib = null;
fib = n => n > 1 ? fib(n - 1) + fib(n - 2) : n;
...Проблема в том, что fib не определен, когда вы пытаетесь использовать его в приведенном выше методе, поэтому статический анализатор сообщает об ошибке компилятора.
Похоже, что в своем волнении я в корне ошибся.Ни .NET, ни Mono не предоставляют "анонимную рекурсию" в том смысле, в каком это подразумевается в оригинальной статье.Вы не могли бы пройти мимо fib
как самодостаточная сущность.
Ознакомьтесь со следующей последовательностью в Mono C# REPL:
csharp> Func<int, int> fib = n => n > 1 ? fib(n - 1) + fib(n - 2) : n;
csharp> fibCopy = fib;
csharp> fib(6);
8
csharp> fibCopy(6);
8
csharp> fib = n => n * 2;
csharp> fib(6);
12
csharp> fibCopy(6);
18
Это потому что:
fib = n => n * 2;
fibCopy = n > 1 ? fib(n - 1) + fib(n - 2) : n;
Другими словами,
fibCopy = n > 1 ? (n - 1) * 2 + (n - 2) * 2 : n; // at the moment
Ясно, fibCopy
просто указывает на текущее определение fib
(делегат) и не у себя.Таким образом, Mono на самом деле просто предварительно присваивает значение null
к fib
во время первоначального присвоения, чтобы это присвоение было действительным.
Я гораздо предпочитаю удобство, заключающееся в том, что мне не нужно объявлять null
так что мне действительно нравится такое поведение.Тем не менее, на самом деле это не то, о чем говорится в оригинальной статье.
В компиляторе Microsoft C# это будет работать только в том случае, если вы сначала установите fib
к null
.
В противном случае это выдаст ошибку, потому что fib
используется до того, как оно будет назначено.
Компилятор Mono достаточно "умен", чтобы избежать этой ошибки (другими словами, он нарушает официальную спецификацию).