---
title: "Variants"
image: "https://hem.run/social/hem.png"
---

> Documentation Index
> Fetch the complete documentation index at: https://hem.run/llms.txt
> Use this file to discover all available pages before exploring further.

# Variants

Algebraic data types, compiling to the discriminated unions TypeScript consumers already understand:

```hem title="payment.hem"
export type PaymentState =
  | Pending
  | Paid({ at: Date, method: string })
  | Refunded({ reason: string })
```

```ts emitted title="payment.ts — generated"
export type PaymentState =
  | { readonly _tag: "Pending" }
  | { readonly _tag: "Paid"; readonly at: Date; readonly method: string }
  | { readonly _tag: "Refunded"; readonly reason: string };

export const Pending: PaymentState = { _tag: "Pending" };
export const Paid = (fields: { readonly at: Date; readonly method: string }): PaymentState => ({ _tag: "Paid", ...fields });
export const Refunded = (fields: { readonly reason: string }): PaymentState => ({ _tag: "Refunded", ...fields });
```

A member carries no payload, one positional payload, or a record whose fields spread onto the variant. The `_tag` discriminant deliberately matches the Effect ecosystem's convention, so Part II's tooling consumes Hem variants natively, and so does any hand-written TypeScript.

Variants take type parameters like anything else. Application-specific states stay just as direct:

```hem title="validation.hem"
export type Validation<T, E> =
  | Valid(T)
  | Invalid(E)
```

```ts emitted title="validation.ts — generated"
export type Validation<T, E> =
  | { readonly _tag: "Valid"; readonly value: T }
  | { readonly _tag: "Invalid"; readonly value: E };

export const Valid = <T, E>(value: T): Validation<T, E> => ({ _tag: "Valid", value });
export const Invalid = <T, E>(value: E): Validation<T, E> => ({ _tag: "Invalid", value });
```

`match` stays exhaustive over a generic variant exactly as it is over a concrete one (§8), construction infers its type parameters from context the same way any generic call does, and the type parameters survive into `.d.ts` output for consumers outside this file.

Hem's canonical `Option` and `Result` are explicit standard-module imports rather than variants each
application reinvents; §19 gives them their stable cross-package ABI and carrier-binding rules.

**Brands.** A variant gives you a new shape; a brand gives an *existing* type a name tsc won't let you confuse with another value of the same shape. A `UserId` and an `OrderId` are both strings, but passing one where the other belongs is exactly the bug a nominal wrapper exists to catch:

```hem title="ids.hem"
type UserId = brand string
```

```ts emitted title="ids.ts — generated"
type UserId = string & { readonly __brand: "UserId"; readonly __identity: unique symbol };
const UserId = (value: string): UserId => value as UserId;
```

Constructing a brand is an identity operation at runtime (`UserId("u_42")` evaluates to `"u_42"`), matching variant constructor conventions. Unwrapping is free, because `UserId` is a subtype of `string`. Assigning a raw `string` where a `UserId` is required produces a type error, and the compiler diagnostic references the brand name rather than the intersection type.

**Structural equality: `deriving Eq`.** Two variant values are equal when they carry the same tag and their fields are equal. Because `===` performs reference equality checks, comparing complex types would otherwise require custom comparison logic. `deriving Eq` writes it for you:

```hem title="payment.hem"
export type PaymentState =
  | Pending
  | Paid({ at: Date, method: string })
  | Refunded({ reason: string })
  deriving Eq
```

alongside §7's union and constructors, this emits a comparator:

```ts emitted title="payment.ts — generated"
export const eqPaymentState = (a: PaymentState, b: PaymentState): boolean => {
  if (a._tag !== b._tag) {
return false;
  }
  switch (a._tag) {
case "Pending": {
  return true;
}
case "Paid": {
  const _hemB = b as Extract<PaymentState, { readonly _tag: "Paid" }>;
  return a.at === _hemB.at && a.method === _hemB.method;
}
case "Refunded": {
  const _hemB = b as Extract<PaymentState, { readonly _tag: "Refunded" }>;
  return a.reason === _hemB.reason;
}
  }
};
```

The relation is recursive SameValueZero: `===`, except that `NaN` equals itself and `-0` equals `0`. It traverses compiler-defined types, handles recursive definitions, and descends into nested variants that also derive `Eq`.

Foreign types follow different equality semantics. Anything genuinely foreign, a function or a `Date` or an imported class, compares by reference. This is a *structural* derive, not a deep-equality library, so two `Date` values with the same timestamp are not equal here. An `error` declaration derives the same way, comparing its declared fields and nothing inherited from `Error`.

The [variants, brands, and errors reference](https://hem.run/reference/variants-and-brands) has the full walk table, the generic form, the visibility rule, and both HEM2023 refusals.

Source: https://hem.run/tour/variants/index.mdx
