Когда будет выполнено, будет ли это хвостовой вызов?

После компиляции и запуска это будет вести себя как хвостовой вызов?

let rec f accu = function
   | [] -> accu
   | h::t -> (h + accu) |> f <| t

Может быть, есть простой способ проверить поведение, о котором я не знаю, но это может быть другой вопрос.

2 ответа

Решение

Я думаю, что это гораздо легче увидеть, если вы не используете оператор конвейерной обработки. На самом деле, два оператора конвейерной передачи определены как inlineТаким образом, компилятор упростит код до следующего (и я думаю, что эта версия также более удобочитаема и понятна, поэтому я написал бы это):

let rec f accu = function
   | [] -> accu
   | h::t -> f (h + accu) t

Теперь вы можете прочитать определение tail-call в Википедии, которое гласит:

Хвостовой вызов - это вызов подпрограммы, который происходит внутри другой процедуры как ее конечное действие; он может произвести возвращаемое значение, которое затем немедленно возвращается вызывающей процедурой.

Так что да, призыв к f на последней строке - хвостовой вызов.

Если вы хотите проанализировать оригинальное выражение (h + accu) |> f <| t (не зная, что операторы конвейеризации встроены), тогда это на самом деле становится ((h + accu) |> f) <| t, Это означает, что выражение вызывает <| оператор с двумя аргументами и возвращает результат - поэтому вызов <| это хвостовой вызов.

<| Оператор определяется так:

let (<|) f a = f a

Теперь призыв к f внутри оператора конвейерной связи также есть хвостовой вызов (и аналогично для другого оператора конвейерной обработки).

Таким образом, если бы компилятор не делал встраивание, у вас была бы последовательность из трех хвостовых вызовов (скомпилированных с использованием.NET .tail инструкция, чтобы убедиться, что.NET выполняет хвостовой вызов). Однако, поскольку компилятор выполняет встраивание, он увидит, что у вас есть рекурсивный хвостовой вызов (f призвание f), и это может быть более эффективно скомпилировано в цикл. (Но вызовы нескольких функций или операторов не могут легко использовать циклы.)

Если вы посмотрите на ответ здесь, вы заметите, что f <| t такой же как f t (это имеет значение, только если вы поместите выражение вместо t который требует скобок).

также x |> y такой же как y x,

Это приводит к эквивалентному выражению, которое выглядит так: f (h + accu) tИтак, (при условии, что у компилятора нет ошибки или чего-то подобного), ваша функция должна быть хвостовой рекурсивной и, скорее всего, будет скомпилирована в какой-то цикл.

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