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