Ответ 1
Начните с простой задачи, например, сопоставления элементов с 'a до' b в списке. Мы хотим написать функцию, которая имеет подпись
val map: ('a -> 'b) -> 'a list -> 'b list
Где
map (fun x -> x * 2) [1;2;3;4;5] == [2;4;6;8;10]
Начните с нерекурсивной версии:
let rec map f = function
| [] -> []
| x::xs -> f x::map f xs
Это не хвостовая рекурсия, потому что функция выполняет работу после выполнения рекурсивного вызова. ::
- синтаксический сахар для List.Cons(f x, map f xs)
.
Функция нерекурсивного функции может быть немного более очевидной, если я переписал последнюю строку как | x::xs -> let temp = map f xs; f x::temp
- очевидно, что она выполняет работу после рекурсивного вызова.
Используйте переменную аккумулятора, чтобы сделать ее хвостовой рекурсивной:
let map f l =
let rec loop acc = function
| [] -> List.rev acc
| x::xs -> loop (f x::acc) xs
loop [] l
Здесь мы создаем новый список в переменной acc
. Поскольку список создается в обратном порядке, нам нужно отменить список вывода, прежде чем вернуть его пользователю.
Если вы хотите немного поработать с мышью, вы можете использовать продолжение передачи, чтобы написать код более кратко:
let map f l =
let rec loop cont = function
| [] -> cont []
| x::xs -> loop ( fun acc -> cont (f x::acc) ) xs
loop id l
Поскольку вызов loop
и cont
- это последние функции, которые не имеют дополнительной работы, они являются хвостовыми рекурсивными.
Это работает, потому что продолжение cont
захватывается новым продолжением, которое, в свою очередь, захватывается другим, что приводит к некоторой древовидной структуре данных следующим образом:
(fun acc -> (f 1)::acc)
((fun acc -> (f 2)::acc)
((fun acc -> (f 3)::acc)
((fun acc -> (f 4)::acc)
((fun acc -> (f 5)::acc)
(id [])))))
который создает список в порядке, не требуя от него изменения.
Для чего стоит, начинайте писать функции нерекурсивным способом, их легче читать и работать.
Если у вас есть большой список для перехода, используйте переменную аккумулятора.
Если вы не можете найти способ использования аккумулятора в удобном виде, и у вас нет каких-либо других опций в вашем распоряжении, используйте продолжения. Я лично считаю нетривиальное, тяжелое использование продолжений, трудночитаемых.