使用以下continuation monad:
type ContinuationMonad() =
member this.Bind (m, f) = fun c -> m (fun a -> f a c)
member this.Return x = fun k -> k x
let cont = ContinuationMonad()
我不明白为什么下面给我一个堆栈溢出:
let map f xs =
let rec map xs =
cont {
match xs with
| [] -> return []
| x :: xs ->
let! xs = map xs
return f x :: xs
}
map xs id;;
let q = [1..100000] |> map ((+) 1)
以下情况不是:
let map f xs =
let rec map xs =
cont {
match xs with
| [] -> return []
| x :: xs ->
let! v = fun g -> g(f x)
let! xs = map xs
return v :: xs
}
map xs id;;
let q = [1..100000] |> map ((+) 1)
答案 0 :(得分:7)
要修复您的示例,请将此方法添加到monad的定义中:
member this.Delay(mk) = fun c -> mk () c
显然,溢出的部分是map
的递归调用中的大输入列表的破坏。延迟它解决了这个问题。
请注意,您的第二个版本将map
的递归调用置于另一个let!
后面,Bind
和map
以及一个额外的lambda,实际上会延迟对StackOverflow
的递归调用
在得出这个结论之前,我不得不追求一些虚假的道路。除非递归调用被延迟,否则观察到OCaml
N
也会被F#
抛出(虽然更高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)
TCO有一些怪癖,但{{1}}更加成熟,所以这让我确信问题确实存在于代码中,而不是编译器:
{{1}}
答案 1 :(得分:4)
F#编译器有时不是很聪明 - 在第一种情况下它会计算map xs
然后计算f x
然后加入它们,因此map xs
不在尾部位置。在第二种情况下,它可以轻松地将map xs
重新排序为尾部位置。