大家好,
我在拿延续传递风格做练习,用的是我做数据工作时的一个小型传感器工具。loadReadings 会生成一个列表,暂时代替我还没写的 CSV 加载器。然后我用两种方式对这些值求和。据我理解,两种写法都让递归调用成为最后执行的操作。
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
在 main 里我先调用 sumK values id,再调用 sumCheckedK readings id,读数用默认的 1,000,000 个(每第 250000 个是有问题的 -1)。
dotnet run(Debug)几乎立刻就崩溃,报 Stack overflow.,后面跟着一大段重复的堆栈跟踪,连一个总和都没打印出来。
dotnet run -c Release 下,sumK 的总和能正确打印,然后又报 Stack overflow.,重复的栈帧里提到了 sumCheckedK。
- 用
-- 10000 的话,两种构建都正常。
我一开始怀疑是列表构造的问题,但 Release 的运行结果说明这里没问题。后来我又想,是不是 JIT 对深层嵌套的闭包放弃了优化。我没在 fsproj 里设置任何特殊选项(net8.0,.NET 8 SDK,M 系列 Mac 上用 Rider)。
这是一个问题还是两个?我更想搞懂原因,而不是直接把线程栈调大。
从英语翻译·查看原文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 with Stack overflow. and a huge repeating trace, before any total.
dotnet run -c Release: the sumK total prints correctly, then Stack overflow. again, with a repeating frame mentioning sumCheckedK.
- With
-- 10000 both 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.