Hello all,
I am working through continuation-passing style as an exercise, using a small sensor tool for my data work. loadReadings generates a list and stands in for a CSV loader I have not written yet. Then I total the values in two ways. Both are written so the recursive call is the last thing that happens, as I understand it.
let rec sumK (xs: int list) (k: int -> int) =
match xs with
| [] -> k 0
| x :: rest -> sumK rest (fun s -> k (s + x))
let rec sumCheckedK (xs: Reading list) (k: int -> int) =
match xs with
| [] -> k 0
| r :: rest ->
try
if r.Value < 0 then
failwithf "faulty reading from %s" r.Sensor
sumCheckedK rest (fun s -> k (s + r.Value))
with ex ->
log ex.Message
sumCheckedK rest k
In main I call sumK values id, then sumCheckedK readings id, with the default 1,000,000 readings (every 250000th is a faulty -1).
dotnet run(Debug): dies almost immediately withStack overflow.and a huge repeating trace, before any total.dotnet run -c Release: thesumKtotal prints correctly, thenStack overflow.again, with a repeating frame mentioningsumCheckedK.- With
-- 10000both builds work.
My first suspicion was the list construction, but the Release run shows that is fine. Then I wondered whether the JIT gives up on deeply nested closures. I do not set anything special in the fsproj (net8.0, .NET 8 SDK, Rider on an M-series Mac).
Is this one problem or two? I would rather understand it than just enlarge the thread stack.