Using the following 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()
I fail to see why the following gives me a stack overflow:
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)
While the following doesn't:
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)
To fix your example, add this method to your definition of the monad:
Apparently the part that overflows is the destruction of the large input list in the recursive call of
map
. Delaying it solves the problem.Note that your second version puts the recursive call to
map
behind anotherlet!
which desugars toBind
and an extra lambda, in effect delaying the recursive call tomap
.I had to pursue a few false trails before reaching this conclusion. What helped was observing that
StackOverflow
is thrown byOCaml
as well (albeit at a higherN
) unless the recursive call is delayed. WhileF#
TCO has some quirks,OCaml
is more proven, so this convinced me that the problem is indeed with the code and not the compiler:The F# compiler is sometimes not very clever - in the first case it computes
map xs
thenf x
and then joins them, somap xs
is not in a tail position. In the second case, it can reorder themap xs
to be in tail position easily.