Работает ли "Анонимная рекурсия" в.NET? Это в моно
Я зашел на этот сайт несколько дней назад на тему "Анонимная рекурсия в C#". Суть статьи в том, что следующий код не будет работать в C#:
Func<int, int> fib = n => n > 1 ? fib(n - 1) + fib(n - 2) : n;
Затем в статье подробно рассматриваются способы использования карри и Y-комбинатора для возврата к "Анонимной рекурсии" в 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));
}
}
Он построен и отлично работает!
Я использую 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#. Некоторые могут предложить использовать мемоизацию, но, поскольку это все еще медленнее, чем итеративный метод, они могут просто быть дрочителями.:-)
Однако в этот момент это становится скорее рекламой для функционального программирования, чем для чего-либо еще (поскольку рекурсивная версия намного приятнее). Это действительно не имеет никакого отношения к моему первоначальному вопросу, но некоторые из ответов считали, что это важно.
5 ответов
Это ошибка в компиляторе Mono. Это нарушает раздел §12.3.3 спецификации. Переменная fib не может использоваться в инициализаторе переменной, потому что она точно не назначена.
Как я отметил в комментарии выше, если Mono делает это, то у них есть ошибка. В спецификации ясно, что это должно быть обнаружено как ошибка. Ошибка, конечно, в основном безвредна, и большую часть времени делает то, что вы хотите. Мы рассмотрели изменение правил, чтобы сделать рекурсию такого рода законной; в основном, мы должны добавить к спецификации особый случай, который говорит, что этот узко определенный случай является законным. Это никогда не было достаточно высоким приоритетом.
Больше мыслей по этому вопросу смотрите в моей статье на эту тему:
http://blogs.msdn.com/b/ericlippert/archive/2006/08/18/706398.aspx
И, кстати, я бы не стал нанимать никого, кто дал бы мне прямую рекурсивную реализацию выдумки в интервью. Это крайне неэффективно; его время работы пропорционально размеру его выхода, а 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 достаточно умен, чтобы избежать этой ошибки (другими словами, он нарушает официальную спецификацию)