error-message-catalog icon indicating copy to clipboard operation
error-message-catalog copied to clipboard

Recursive calls in `let` fail cryptically when using arrays/records

Open showell opened this issue 6 years ago • 1 comments

So the original motivation for this bug report was I using Parser to parse something akin to JSON. Getting a minimal repro on that was difficult, so let's go to our old friend "factorial".

The below code works fine (the key thing to note is that f is inside the let):

factorial num =
    let
        f n =
            if n == 0 then
                1

            else
                n * f (n - 1)
    in
    f num

And this code works fine:

eval f x =
    f () x


func =
    { f =
        \n ->
            if n == 0 then
                1

            else
                n * eval (\_ -> func.f) (n - 1)
    }


factorial num =
    func.f num

This nearly identical code won't compile:

factorial num =
    let
        eval f x =
            f () x


        func =
            { f =
                \n ->
                    if n == 0 then
                        1

                    else
                        n * eval (\_ -> func.f) (n - 1)
            }
    in
    func.f num

The error is below. It's misleading, because func is defined indirectly in terms of itself. func doesn't directly call itself--it uses a lambda. The error message should somehow suggest that recursive functions must be top-level (but it's more subtle than that--something about putting func in the record there is causing the error).

The `func` value is defined directly in terms of itself, causing an infinite
loop.

43|         func =
            ^^^^
Are you are trying to mutate a variable? Elm does not have mutation, so when I
see func defined in terms of func, I treat it as a recursive definition. Try
giving the new value a new name!

Maybe you DO want a recursive value? To define func we need to know what func
is, so let’s expand it. Wait, but now we need to know what func is, so let’s
expand it... This will keep going infinitely!

Hint: The root problem is often a typo in some variable name, but I recommend
reading <https://elm-lang.org/0.19.0/bad-recursion> for more detailed advice,
especially if you actually do need a recursive value.
Detected errors in 1 module.

showell avatar Nov 04 '19 14:11 showell

The original issue involved this code inside a let statement:

parseExpr : Parser Expr
parseExpr =
    oneOf
        [ value
        , Parser.lazy (\_ -> list parseExpr)
        ]

(I recommend debugging it with the factorial examples in the OP, though. Just providing this for context--I wasn't doing anything crazy.)

showell avatar Nov 04 '19 15:11 showell