# Custom types

Every type so far is *structural*: two types are the same if they have the same shape. Any
`list(name = "Ada")` is a `list{name: character}`, and every `double` is interchangeable with every
other. That is the right default for R, where values are plain data, but it cannot tell Celsius from
Fahrenheit, or a user ID from an email address.

`alias` names a shape and keeps it structural, while `type` declares a *nominal* type, distinct by
name. Both also work for ry's own records, tuples and tag unions, as
[Records and tags](/type-checking/records-and-tags.md#structural-or-nominal) shows.

## Nominal types

A *nominal* type is distinct by name, even from a type with the same shape. `type` declares one,
and `@Money(value)` marks the one place that creates its values. The comment spelling is
`#: @type` and `#: @new`:

### Comments

```r
#: @type Money {list{amount: double, currency: character}}

#: fn(amount: double, currency: character) -> Money
money <- function(amount, currency) {
  if (amount < 0) stop("negative amount")
  #: @new Money
  structure(list(amount = amount, currency = currency), class = "Money")
}

#: fn(m: Money) -> double
amount_of <- function(m) m$amount

amount_of(list(amount = 1, currency = "EUR"))
```

### Inline

```r
type Money = list{amount: double, currency: character}

money <- function(amount: double, currency: character) -> Money {
  if (amount < 0) stop("negative amount")
  @Money(structure(list(amount = amount, currency = currency), class = "Money"))
}

amount_of <- function(m: Money) -> double m$amount

amount_of(list(amount = 1, currency = "EUR"))
```

```text
type-mismatch

  x expected `Money`, found `list{amount: double, currency: character}`. The value has the right shape, but `Money` is a nominal type, so only `@Money(...)` or `#: @new Money` creates one
```

A list with the right fields is still not a `Money`. Put `@Money(...)` in one constructor and
nowhere else, and every `Money` in the program has passed its checks: `@Money(...)` checks the
shape when ry analyzes the code, and `stop()` checks the values when it runs. At run time
`@Money(x)` is just `x`.

Scalars work the same way. With `type UserId = character` and `type Email = character`, passing an
`Email` where a `UserId` is expected is an error, although both are strings at run time.

## Operators

A nominal value is still accepted where its representation is, so `nchar()` works on a `UserId`.
Arithmetic gives the plain representation back: with `type Celsius = double`, the sum of two
`Celsius` values is a `double`, because ry cannot assume that adding two temperatures gives a
temperature. `Money` is a list, so `+` on it is an error until you declare the operator:

### Comments

```r
#: fn(a: Money, b: Money) -> Money
`+.Money` <- function(a, b) money(a$amount + b$amount, a$currency)
```

### Inline

```r
`+.Money` <- function(a: Money, b: Money) -> Money {
  money(a$amount + b$amount, a$currency)
}
```

Now `money(1, "EUR") + money(2, "EUR")` is a `Money`. R finds `+.Money` through the class attribute,
which is why the constructor sets one; ry itself needs no class to check the type.

## Parameters and aliases

A type can take parameters: with `type Page<T> = list{items: list[T], total: integer}`, reading
`items` from a `Page<integer>` gives a `list[integer]`.

`alias` names a type without making it nominal, so `alias Row = list{id: integer}` is a shorter
way to write the shape.

Definitions belong at the top level of a file. In the comment spelling, a `@type` or `@alias` line
needs its own `#:` block, separated from other annotations by a blank line. If you keep both
spellings, put the `#:` block directly above the inline definition: ry reads the pair as one
definition and warns when the two disagree.

## R6 and S4 objects

Existing R6 and S4 classes keep working, but their objects are [`Unknown`](/type-checking/unknown.md), and ry warns on each
class definition (`object-system`) because [records and tags](/type-checking/records-and-tags.md)
model data with every field checked. To check who receives one, give it a nominal type over [`Any`](/type-checking/unknown.md#any) and wrap its
constructor:

### Comments

```r
#: @type Account {Any}

#: fn() -> Account
new_account <- function() {
  #: @new Account
  AccountClass$new()
}
```

### Inline

```r
type Account = Any

new_account <- function() -> Account @Account(AccountClass$new())
```
