Nesting polymorphic variants with single type parameter?

Hi all.
Im trying to compose a structure of objects that can produce an error state.
I want the nature of the error to be easily discoverable to our developers, and extensible. So with each child object producing its own error type poly variant (with its open type variable), and including those as children of an outer poly variant (with its open type variable), I guess Im looking for a way to include the child type variables as elements of the parent somehow. Is this possible?

something like:

type a<'a> = ([> #One | #Two('b)| #Three('c)]] as 'a)

https://rescript-lang.org/try?version=v10.1.2&code=C4TwDgpgBAhgPAchgPigXigCgNqoMQDyAdtAD5R4AqA7gPaYIBGAlALqwDOUSzAUEA

Thanks
A

1 Like

I know how to do this with normal variants: https://rescript-lang.org/try?version=v10.1.2&code=C4TwDgpgBAThDGUCGUC8UDyA7aAfKAKgO4D2AFAOQBGAlAFzIBQQA

Why do your “sub”-error types need to be open? Actually, you could set it up so none of your error types themselves are open…rather make the result type returned by your functions open instead. Something like this: playground

module Foo = {
  type error = [#SillyError | #YuckyError]

  let run: int => result<int, [> error]> = _ => failwith("TODO")
}

module Bar = {
  type error = [Foo.error | #NegativeArgError]

  let run: int => result<int, [> error]> = n => {
    if n < 0 {
      Error(#NegativeArgError)
    } else {
      Foo.run(n)
    }
  }
}

module Baz = {
  type error = [#WildError | #CrazyError]

  let run: int => result<int, [> error]> = _ => failwith("TODO")
}

module App = {
  type error = [Foo.error | Bar.error | Baz.error]

  // Eventually, you probably want to close the error type so you can ensure you don't
  // produce garbage polyvariants at some point by mistake.
  let run: int => result<int, error> = n => {
    let flatMap = Belt.Result.flatMap
    Foo.run(n)->flatMap(Bar.run)->flatMap(Baz.run)
  }
}

The errors compose just fine, you can see at a glance the error variants each individual function can return, and eventually, once you’re done you can specify a closed polymorphic variant type when you need to so you can prevent yourself from making a mistake with all this open-poly stuff. (E.g., putting a type like #CrazyEror instead of #CrazyError.)

validation functions can be provided by the application, including async calls to back end producing enums.

But each function presumably isn’t producing an open number of variant error payloads right?

Id like the functions to be ignorant of each other and be able to pass their results through.

Sure, the example I gave does that. Here it is tweaked slightly so that no function (other than your toplevel app function) knows about any other functions error variants.

Eventually some function will have to know about the error variants other functions can produce though if you want to handle them. And if you don’t care about handling them in a specific way depending on the specific error variant, then you shouldn’t be using variants for the payload at all, use some string or other unified error value and call it a day.

I am composing modules with functors to build form structures, so the error types you have above become opaque when passed through the module type of the functor argument.

Ah okay that is a bit of a different problem in that case.

Would you mind putting an example of what you’re trying to do, I’m pretty curious now.

Just because you are running things through a functor doesn’t mean that the error types need to be opaque. E.g., this composes the errors in the exact same way.

Edit: I must be misunderstanding what you are trying to do. Is there a reason you need the error type to also be abstract?