Работает ли "Анонимная рекурсия" в.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 достаточно умен, чтобы избежать этой ошибки (другими словами, он нарушает официальную спецификацию)

Другие вопросы по тегам