Why can't I define inline lazy recursive values?

Suppose I have a simple JSON decoder for integers and lists of integers:

import Json.Decode as JD

type MyType
    = Number Int
    | Numbers (List MyType)


myDecoder : JD.Decoder MyType
myDecoder =
    JD.oneOf
        [ JD.map Number JD.int
        , JD.map Numbers (JD.lazy (\_ -> myDecoder) |> JD.list)
        ]

Although myDecoder is a value with a recursive definition, this works fine because the recursive call is lazy. So I can decode nicely with, for example, JD.decodeString myDecoder "[1,2,3]"

What mystifies me is that I cannot move myDecoder inline. In other words, the following will not compile, generating a ‘value is defined directly in terms of itself’ error:

testDecoder : Result JD.Error MyType
testDecoder =
    let
        myDecoder =
            JD.oneOf
                [ JD.map Number JD.int
                , JD.map Numbers (JD.lazy (\_ -> myDecoder) |> JD.list)
                ]
    in
    JD.decodeString myDecoder "[1,2,3]"

Why does does the compiler allow top-level definition of the recursive value, but not an inline version?

For context, this came up when I was trying to create a general purpose JSON decoder and, thanks to help on Slack, was able to do so inline by making the decoder a unit function:

jsonToValue : String ->  Result JD.Error JE.Value
jsonToValue =
    let
        jsDecoder () =
            JD.oneOf
                [ JD.map JE.string JD.string
                , JD.map JE.int JD.int
                , JD.map JE.float JD.float
                , JD.map JE.bool JD.bool
                , JD.map (JE.list identity) (JD.lazy jsDecoder |> JD.list)
                , JD.map (Dict.toList >> JE.object) (JD.lazy jsDecoder |> JD.dict)
                , JD.null JE.null
                ]
    in
    JD.decodeString (jsDecoder ())

Nevertheless, I remain curious why the original value specification cannot be declared inline.

1 Like

According to this answer in stackoverflow, let-in statements are not lazily evaluated. Perhaps that’s the case?

1 Like

In Elm you can only have recursive values at the top level. Recursive functions are supported everywhere. See e.g. Cyclic value with lambda - #4 by evancz for the reasoning.

5 Likes

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.