Compiler error for recursive decoder using glennsl/rescript-json-combinators

Hi everyone,

Apologies in advance for what is probably a simple beginner mistake. I’ve run into a compiler error I think I need some help with while trying to create a recursive decoder using https://github.com/glennsl/rescript-json-combinators . The following code:

open JsonCombinators

type rec foo = {
    bar: int,
    subFoo: array<foo>
}

let rec decodeFoo = Json.Decode.object(field => {
    bar: field.required(. "bar", Json.Decode.int),
    subFoo: field.required(. "subFoo", Json.Decode.array(decodeFoo))
})

gives me the following compiler error:

“This kind of expression is not allowed as right-hand side of `let rec’”

Looking at the signature of Json.Decode.object (https://github.com/glennsl/rescript-json-combinators/blob/master/src/Json_Decode.resi) I can see that it doesn’t look like it returns a function, but since other ReScript types than functions can be recursive too I’m not sure I understand. Not completely sure where to go from here, in order to create a recursive decoder.

Any help is much appreciated! :slight_smile:

Only function values can be defined recursive. Among decoding libraries, only bs-json allows a convenient way to create recursive decoders. But it’s still possible to do with rescript-json-combinators, although a little bit hacky:

open JsonCombinators

type rec foo = {
  bar: int,
  subFoo: array<foo>,
}

let fooDecoder = {
  let rec recFooDecoder = (. json) => {
    switch Json.Decode.decode(
      json,
      Json.Decode.object(field => {
        bar: field.required(. "bar", Json.Decode.int),
        subFoo: field.required(. "subFoo", Json.Decode.array(Json.Decode.custom(recFooDecoder))),
      }),
    ) {
    | Ok(v) => v
    | Error(message) => raise(Json.Decode.DecodeError(message))
    }
  }
  Json.Decode.custom(recFooDecoder)
}

Also, I’m working on rescript-struct V3, which will allow easy decoding of recursive objects. But it’ll probably take me a month to finish.

Thanks so much for the quick response! :slight_smile:

As my ReScript skills aren’t yet good enough to fully understand your workaround example, I think I will stick to bs-json for now. I will keep a look out for rescript-struct!

One small question regarding your statement “Only function values can be defined recursive”. Does the type foo in my example not count as recursive?

No, your type is recursive. It’s different for types and expressions. It only works with function expressions because otherwise, it’d have a reference to an undefined variable.

For example, this:

type rec foo = {
  bar: int,
  subFoo: array<foo>,
}

let rec foo = {
  bar: 1,
  subFoo: [foo]
}

Would compile to this:

var foo = {
  bar: 1,
  subFoo: [undefined],
}

First, I create a recursive function with the name recFooDecoder that is compatible with the custom decoder type. It accepts json and should return decoded data.
To somehow decode the json inside of the recFooDecoder, I create an object decoder and immediately use it to decode the json.
Since it returns the result types, but the custom decoder function expects only decoded value, I extract it from the result and re-throw the error message.
And at the end, I use Json.Decode.custom to convert the recFooDecoder to a decoder type to be able to compose it with other decoders or use with Json.Decode.decode.

You can use recursive definitions for values besides functions. In the example above, there’s actually a recent bug in the compiler that doesn’t allow creating recursive arrays (see this topic), but I think that should be fixed soon. If you currently use any other type, such a list instead, then it works.

type rec foo = {bar: int, subFoo: list<foo>}
let rec foo = {bar: 1, subFoo: list{foo}}
let rec infinite_list = list{1, 2, 3, ...infinite_list}

However, there are still other kinds of expressions that are never allowed. In the code in the original post, the exact problem is that you can’t apply a function while defining a recursive value. Since functions can do tricky stuff when they’re applied, using one in a recursive definition wouldn’t always be safe.