Effect CommunityEC
Effect Communityβ€’2y agoβ€’
12 replies
Aldwin

Handling Union Types with Diverse Structures in TypeScript

How do you folks deal with edges that produce a bunch of arbitrarily unioned data? For example, one shape for when X happened, one shape for when Y happened, and one shape for when an error occurred. In code:

const ActionLedToX = S.struct({a, b, c})
const ActionLedToY = S.struct({c, d, e})
const ActionLedToError = S.struct({message})

const ActionAttempt = S.union(
  ActionLedToX,
  ActionLedToY,
  ActionLedToError,
)


This produces a type like:

type ActionAttempt = {a, b, c} | {c, d, e} | {message}


And when working with a type like that, discriminating the union requires exhaustively checking all fields, which S.is could do for me. However, I really like working with S.eitherFromUnion for scenarios where there's only two cases. This causes Schema to tag my types for me upon decoding.

To expand this way of working to n cases, I wrote a little tagged utility that uses S.transformOrFail to tag my type with a given tag:

const ActionAttempt = S.union(
  ActionLedToX.pipe(tagged('ActionLedToX')),
  ActionLedToY.pipe(tagged('ActionLedToY')),
  ActionLedToError.pipe(tagged('ActionLedToError')),
);


Now my type looks like this:

type ActionAttempt = {_tag: 'ActionLedToX', value: {a, b, c}}
                   | {_tag: 'ActionLedToY', value: {c, d, e}}
                   | {_tag: 'ActionLedToError', value: {message}}


I think this is neat, and it allows me to avoid doing exhaustive property checking. But now I need to manually create type guards, a pattern matching helper, and whatever other utils a type like Either provides out of the box 😞 it's a lot of boilerplate.

Is this something you have run into, and are there solutions out there that let me create my own union types like, deriving type guards and pattern matching?
Was this page helpful?