How to pattern match over type unions?

Super excited to be using type unions in the 0.9.0 release - thanks guys!

Would it be possible to include some examples in the Union Type docs on how one might go about pattern matching over type unions, where these examples include cases resulting in None/Any or some overlap?

It’s a bit difficult for me to decipher through the commit where this was introduced to understand how I might do this.

A basic example:

type JSON = Nat or Text or HashMap or ...;

func toJSON(json: JSON): Text {
  switch(json) {
    case Nat n { ... };
    case Text t { ... };
    case HashMap hm { ... this could be recursive and call toJSON depending on the k/v type };
  }
}

I’ve seen an implementation of this that works, which is very similar to the answer given out last year in this forum post https://forum.dfinity.org/t/pattern-matching-with-dynamic-case/5139, but requires the use of variants and wrapping all the values in the variant as they are stored.

Is there a more elegant solution that type unions might provide?

I don’t believe you can since, as the documentation you linked to says, you’re effectively pattern matching on a value whose type is Any.

1 Like

If this is the case, then where does the value from type unions come in? Under what scenarios would it make more sense to choose to use a type union over a predetermined wrapped variant type?

Here’s an old example:

That example seems to ask for an intersection type, as opposed to a union type.

I’m imagining something like shown here in Elm lang that allows for destructuring based on types. It’s extremely powerful.

https://guide.elm-lang.org/types/pattern_matching.html

Right, but the same thing applies. You have some existing types and want to say “this or that”/“this and that” without having to create a new named type.

As the documentation says, it’s more useful when the types are compatible through sub typing.

The equivalent of this Elm code:

type User
  = Regular String Int
  | Visitor String

In Motoko is this variant:

type User = {
  #Regular : (Text, Int);
  #Visitor : Text;
};
2 Likes

I see, ok so then type unions intersections essentially favor the creation of new types through composition/reuse of smaller types. I’m assuming these would be usually combination of record types, for example something like:

type UserPermissions = {
  #createPost;
  #editPost;
  #deletePost;
  ...
};
type AdminPermissions = {
  #banUser;
  #createChannel;
} and UserPermissions;
1 Like

The equivalent of this Elm code: …

Got it, makes sense. So the variant pattern is the way to go then.

Thanks for the responses and help!

An important distinction is that these are variants, which are sum types (“this or this or…“) whereas records are product types (“this and this and…”)

Also, I suspect you meant and UserPermissions so that admin users have at least the same permissions as regular users.

1 Like

Oops, yes you’re right! Edited the above to not fool anyone who comes across this in the future.

This is a great mental model :bulb:

Ok I’m a huge fan of variants now, the lights are on :smile:

4 Likes