Является ли rec rec рекурсивной функцией rec?


Является ли эта функция хвостовой рекурсивной?

let rec rec_algo1 step J = 
    if step = dSs then J
    else
        let a = Array.init (Array2D.length1 M) (fun i -> minby1J i M J)
        let argmin = a|> Array.minBy snd |> fst
        rec_algo1 (step+1) (argmin::J)

В общем, есть способ формально проверить его?

Спасибо.

Ответ 1

Эта функция является хвостовой рекурсивной; Я могу сказать, заметив это.

В общем, не всегда легко сказать. Возможно, самая надежная/прагматичная вещь - просто проверить ее на большом входе (и убедитесь, что вы компилируете в режиме "Release", так как режим "Отладка" отключает хвостовые вызовы для лучшей отладки).

Ответ 2

Да, вы можете формально доказать, что функция является хвостовой рекурсивной. Каждое сокращение выражения имеет хвостовое положение, и если все рекурсии находятся в хвостовых позициях, тогда функция является хвостовой рекурсивной. Возможно, что функция является хвостовой рекурсивной в одном месте, но не в другой.

В выражении let pat = exprA in exprB только exprB находится в хвостовом положении. То есть, пока вы можете оценить exprA, вам все равно придется вернуться к оценке exprB с помощью exprA. Для каждого выражения на языке существует правило сокращения, указывающее, где находится позиция хвоста. В ExprA; ExprB он exprB снова. В if ExprA then ExprB else ExprC это как exprB, так и ExprC и т.д.

Компилятор, конечно, знает об этом. Однако многие выражения, доступные в F #, и множество внутренних оптимизаций, выполняемых компилятором по мере их использования, например, при компиляции совпадения с образцом выражения вычисления, такие как seq{} или async{}, могут знать, какие выражения в хвостовом положении неочевидны.

Практически говоря, с некоторой практикой для небольших функций легко определить хвостовой вызов, просто взглянув на ваши вложенные выражения и проверив слоты, которые НЕ находятся в хвостовых позициях для вызовов функций. (Помните, что вызов хвоста может быть другой функцией!)

Ответ 3

Ты спросил, как мы можем официально проверить это, чтобы у меня был удар. Сначала мы должны определить, что означает, что функция является хвосторекурсивной. Рекурсивное определение функции вида

let rec f x_1 ... x_n = e

является хвостовым рекурсивным, если все вызовы f внутри e являются хвостовыми вызовами, т.е. происходят в хвостовом контексте. Контекст хвоста C определяется индуктивно как член с отверстием []:

C ::= []
    | e
    | let p = e in C
    | e; C
    | match e with p_1 -> C | ... | p_n -> C
    | if e then C else C

где e - выражение F #, x - переменная, а p - шаблон. Мы должны расширить это до определения взаимно-рекурсивных функций, но я оставлю это как упражнение.

Теперь применим это к вашему примеру. Единственный вызов rec_algo1 в теле функции в этом контексте:

if step = dSs then J
else
    let a = Array.init (Array2D.length1 M) (fun i -> minby1J i M J)
    let argmin = a|> Array.minBy snd |> fst
    []

И поскольку это хвостовой контекст, функция является хвостовой рекурсивной. Вот как это делают функциональные программисты - сканируйте тело определения для рекурсивных вызовов, а затем убедитесь, что каждый из них встречается в хвостовом контексте. Более интуитивное определение хвостового вызова - это когда ничего другого не делается с результатом вызова, кроме его возврата.