Ответ 1
Чтобы исправить ваш пример, добавьте этот метод в свое определение монады:
member this.Delay(mk) = fun c -> mk () c
По-видимому, часть, которая переполняется, является уничтожением большого входного списка в рекурсивном вызове map
. Отсрочка этого решения решает проблему.
Обратите внимание, что вторая версия помещает рекурсивный вызов map
за другой let!
, который помещает на Bind
и дополнительную лямбду, фактически задерживая рекурсивный вызов map
.
Мне пришлось преследовать несколько ложных следов, прежде чем прийти к такому выводу. Что помогло наблюдать, что StackOverflow
также выбрано OCaml
(хотя и при более высоком N
), если рекурсивный вызов не задерживается. В то время как F#
TCO имеет некоторые причуды, OCaml
более доказана, поэтому это убедило меня в том, что проблема действительно связана с кодом, а не с компилятором:
let cReturn x = fun k -> k x
let cBind m f = fun c -> m (fun a -> f a c)
let map f xs =
(* inner map loop overflows trying to pattern-match long lists *)
let rec map xs =
match xs with
| [] -> cReturn []
| x :: xs ->
cBind (map xs) (fun xs -> cReturn (f x :: xs)) in
map xs (fun x -> x)
let map_fixed f xs =
(* works without overflowing by delaying the recursive call *)
let rec map xs =
match xs with
| [] -> cReturn []
| x :: xs ->
cBind (fun c -> map xs c) (fun xs -> cReturn (f x :: xs)) in
map xs (fun x -> x)
let map_fused f xs =
(* manually fused version avoids the problem by tail-calling `map` *)
let rec map xs k =
match xs with
| [] -> k []
| x :: xs ->
map xs (fun xs -> k (f x :: xs)) in
map xs (fun x -> x)