# Vectors, lists and NULL

Three of R's most common runtime errors come from shapes: a vector of length two where an `if`
expects one value, a misspelled list field that silently reads as `NULL`, and a `NULL` that nobody
handled. ry's types record all three, so it reports them before the code runs, while your code stays
the same R.

## Scalars and vectors

R has no scalars: `1L` is an integer vector of length one. But an `if` condition, an operand of
`&&`, and an endpoint of `:` need exactly one element, and passing a longer vector there is a bug R
reports late or not at all. So ry tracks the length-one case as its own type:

```r
n    <- 1L                     # integer: exactly one element
ids  <- c(1L, 2L)              # integer[]: any length
ages <- c(ada = 36L, bo = 4L)  # integer[named]: any length, with names
```

A scalar is accepted where a vector is expected, but not the reverse, because a vector of unknown
length may not have exactly one element:

### Comments

```r
#: fn(flag: logical) -> character
label <- function(flag) if (flag) "yes" else "no"

label(c(TRUE, FALSE))
```

### Inline

```r
label <- function(flag: logical) -> character if (flag) "yes" else "no"

label(c(TRUE, FALSE))
```

```text
type-mismatch

  x expected `logical`, found `logical[]`
```

A `double` accepts an `integer` or a `logical`, which R converts without loss. A number is never
accepted where `character` is expected: R would convert it, but a silent number-to-string conversion
is usually a bug, so ry asks for an explicit `as.character()`.

## Four kinds of list

R uses one list type for four different jobs, and each job has its own mistakes, so ry gives each
its own type. Braces mean a fixed shape (`list{...}`), and brackets mean any length (`list[...]`). At run time all four are the same R list, so your code does not change.

### Comments

```r
pair   <- list(1L, "ok")                  # positional:  list{integer, character}
person <- list(name = "Ada", age = 36L)   # named fields: list{name: character, age: integer}
sizes  <- lapply(words, nchar)            # any length:   list[integer]

#: list[named: integer]
counts <- list(apples = 3L)               # by key:       list[named: integer]
```

### Inline

```r
pair   <- list(1L, "ok")                  # positional:  list{integer, character}
person <- list(name = "Ada", age = 36L)   # named fields: list{name: character, age: integer}
sizes  <- lapply(words, nchar)            # any length:   list[integer]

counts: list[named: integer] <- list(apples = 3L)   # by key
```

## Fixed-shape lists

A list of positional values or named fields has a fixed shape, so ry knows each position and field and its type.
That makes `$` checkable, which matters because R answers a misspelled field with a silent `NULL`
that fails somewhere else:

```r
person$nmae
```

```text
type-mismatch

  x field `nmae` does not exist in `list{name: character, age: integer}`. Did you mean `name`?
   --[R/person.R:2:8]
 2 | person$nmae
   |        ^^^^
```

A fixed shape is also exact, so a list with an extra field where a fixed shape is expected is an error,
because an unexpected field is usually a misspelled one.

## Lists of any length

A list of any length, or one looked up by key, can have any length, so ry knows only its element type. Reading a key
gives the element type or `NULL`, because the key may be missing:

```r
counts[["pears"]]   # integer | NULL
```

A `list(...)` literal infers as a fixed shape, so a dictionary that starts from a literal needs an
annotation, as `counts` does. An empty `list()` filled by computed keys, as in `d[[key]] <- value`,
needs none.

## `NULL`

Many R functions return `NULL` for "nothing", and code that forgets the case fails far from where the
`NULL` came from. A value that may be `NULL` has a union type, such as `character | NULL`.

The most common case is an optional argument that defaults to `NULL`. Its type says what a caller
may pass instead. In the `fn(...)` form, brackets around a name such as `[label]` mark an optional
argument:

### Comments

```r
#: fn(id: integer, [label]: character | NULL) -> character
label_for <- function(id, label = NULL) {
  if (is.null(label)) paste("item", id) else label
}
label_for(1L, label = 2)
```

### Inline

```r
label_for <- function(id: integer, label: character | NULL = NULL) -> character {
  if (is.null(label)) paste("item", id) else label
}
label_for(1L, label = 2)
```

```text
type-mismatch

  x expected `character | NULL`, found `double`
```

### Reading through a value that may be `NULL`

A field of a value that may be `NULL` may be `NULL` too, because R reads every field of `NULL` as
`NULL`. Returning such a field where an `integer` is declared is an error:

#### Comments

```r
#: fn(config: list{retries: integer} | NULL) -> integer
retries <- function(config) {
  config$retries
}
```

#### Inline

```r
retries <- function(config: list{retries: integer} | NULL) -> integer {
  config$retries
}
```

```text
type-mismatch

  x expected `integer`, found `integer | NULL`
```

Check for `NULL` first, and the error goes away:

#### Comments

```r
#: fn(config: list{retries: integer} | NULL) -> integer
retries <- function(config) {
  if (is.null(config)) return(3L)
  config$retries
}
```

#### Inline

```r
retries <- function(config: list{retries: integer} | NULL) -> integer {
  if (is.null(config)) return(3L)
  config$retries
}
```

The guard *narrows* `config` to the list for the rest of the function. `is.character()`,
`is.numeric()`, `is.list()`, and the other `is.*` tests narrow the same way.

Narrowing applies to variables only, so to test a field, copy it into a variable first:
`r <- config$retries; if (is.null(r)) ...`.

ry checks the `NULL` you declare. A variable that the body computes without an annotation, such as
the result of a `switch` without a default, is passed on as its value without the `NULL`, because the
code presumably never reaches the call on the `NULL` path. The same holds for the `NULL` of an
unannotated parameter, from a `NULL` default or an `is.null()` test. To have such a value checked, annotate the assignment or the function.
