elm / compiler

Compiler for Elm, a functional language for reliable webapps.
https://elm-lang.org/
BSD 3-Clause "New" or "Revised" License
7.51k stars 656 forks source link

`RangeError` occurs in CP styled functions #2221

Open magniff opened 3 years ago

magniff commented 3 years ago

Quick Summary: I've been playing with continuation passing styled functions and stumbled upon the code, that seems to confuse the compiler.

Here is the code:

g : Int -> (Int -> a) -> a
g value cont =
    case value of
        1 -> cont 1
        _ -> g (value-1) (\result -> cont (result * value))

Expected: function g computes value's factorial Actual (repl):

> g 1 (Debug.log "result")
result: 1
1 : Int
> g 2 (Debug.log "result")
RangeError: Maximum call stack size exceeded
github-actions[bot] commented 3 years ago

Thanks for reporting this! To set expectations:

Finally, please be patient with the core team. They are trying their best with limited resources.

tripack45 commented 3 years ago

This is the compiled output:

var $author$project$T$g = F2(
    function (value, cont) {
        g:
        while (true) {
            if (value === 1) {
                return cont(1);
            } else {
                var $temp$value = value - 1,
                    $temp$cont = function (result) {
                    return cont(result * value);
                };
                value = $temp$value;
                cont = $temp$cont;
                continue g;
            }
        }
    });

It looks like tail-call optimization accidentally broke capturing of arguments, changing the captured values. This problem is evident in perhaps another simpler example:

f x l = if x == 0 then l else f (x - 1) ((\_ -> x) :: l)
> List.map (\g -> g ()) (f 10 [])
[0,0,0,0,0,0,0,0,0,0]
    : List number

The output should be [10, 9, ... , 1]

miniBill commented 1 year ago

For people being bitten by this in the future, using https://github.com/micahhahn/elm-safe-recursion is both going to fix this and allow mutual recursion in TCO