openlang

CPS-сумма переполняет стек в Debug, а в Release — только вторая функция

Всем привет,

Разбираюсь с continuation-passing style в качестве упражнения, на небольшой утилите для сенсоров, которую использую в работе с данными. 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, Rider на Mac с M-серией).

Это одна проблема или две? Хотелось бы разобраться, а не просто увеличить размер стека потока.

Переведено с английскогоПоказать оригинал

Разговор продолжается 2

Следуя доводам
  1. Ссылка на ответ

    Думаю, две проблемы.

    Во-первых, Debug по умолчанию отключает хвостовые вызовы, так что sumK там переполняет стек. Предполагаю, что в вашем fsproj нет строки <Tailcalls>, и Debug использует это значение по умолчанию. У меня такое было на клиентском проекте: в Release работало, в Debug падало, и я потерял на этом целый день.

    Во-вторых, sumCheckedK вызывает себя внутри try. Это не хвостовая позиция, поэтому в Release тоже остаётся фрейм на каждый элемент. Вынесите рекурсивный вызов за пределы try.

    Переведено с английскогоПоказать оригинал
  2. Ссылка на ответ

    Кроме того, sumCheckedK rest k внутри with — тоже не хвостовой вызов. Пусть try только решает, валидно показание или битое, возвращая bool или option, а оба рекурсивных вызова делайте уже после него. Я свой исправил так же.

    Переведено с английскогоПоказать оригинал

Другие стороны того же вопроса

  1. Один и тот же Int, два таргета, два разных результата

    Один и тот же исходник, один и тот же JSON-файл, а результат зависит от таргета. Это плохо. cohort_join читает JSON-экспорт и…

    Ответы 0Последняя активность gleam
  2. pure host-функцию кэшируют, и запись в её аргумент просто пропадает

    Проект на выходные: игрушечный вычислитель выражений с host-функциями на D, регистрируются по имени. Добавил в реестр memo-кэ…

    Ответы 2Последняя активность d
  3. тот же parseServices, а плохая строка ломает только на этапе сборки?

    Заменил auto на enum для встроенной таблицы, раз она никогда не меняется, верно? Теперь smtp 25 (без proto) ломает dub build,…

    Ответы 2Последняя активность d