elm / error-message-catalog

A catalog of broken Elm programs / data to improve error messages
BSD 3-Clause "New" or "Revised" License
173 stars 17 forks source link

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

Open showell opened 4 years ago

showell commented 4 years ago

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 commented 4 years ago

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.)