Avoir cyclic function when returning itself?

I try to achieve something like:

type rec func<'a, 'b, 'i> = ('i) => ('b, func<'a, 'b, 'i>)

But I got a cyclic error. I want this pattern cause the func fonction will change, and I want to call it from outside without worrying which func is.

The only workaround I found is to define the return fonction and using a variant, like this:

type rec funcReturn<'a, 'b, 'i> = | Test(('b, ('i) => funcReturn<'a, 'b, 'i>))

type func<'a, 'b, 'i> = ('i) => funcReturn<'a, 'b, 'i>

But I would like to not having to use a variant here… any idea ? Can I have a further explaination why it work with a “variant wrapper” but not without it ?

My understanding is that this is a limitation due to how ReScript distinguishes between “type aliases” and new types. A tuple type creates an alias: type rec t = (int, t) // ERROR: The type abbreviation t is cyclic. Here, the typechecker will try to expand t infinitely ((int, (int, (int, (int, ...) and fail. However, a variant is a new, unique type that can’t be expanded, so this is not an error: type rec t2 = A(int, t2).

(Someone else can probably explain it better than me. I mostly know this from reading this discussion. )

As for your specific code, one easier solution may be to use a record instead of a variant. (Records also create new types.) This compiles fine:

type rec func<'a, 'b, 'i> = 'i => funcReturn<'a, 'b, 'i>
and funcReturn<'a, 'b, 'i> = {b: 'b, f: func<'a, 'b, 'i>}
let rec f = i => {b: 1, f: f}

The difference here is mainly readability and personal preference. A variant with a single constructor is basically the same as using a tuple. You can construct and deconstruct it the same way: let Test(b, func) = func(1).

3 Likes

Thank’s for the clarification and the other way around !