# Standard library

ry ships a small standard library modeled on Roc's builtins: `Result`, `List`, `Str`, `Num`,
`Bool`, `Dict` and `Set`. Every function is fully typed, never meets `NA`, and reports failure as a tag instead of
an error or a warning, so the pieces compose with [`match`](/type-checking/records-and-tags.md) and
the checker follows every step:

## Inline

```r
parse_ids <- function(text: character) -> Result<list[integer], :InvalidNumStr> {
  parts <- List.map(Str.split_on(text, ","), Str.trim)
  List.map_try(parts, Str.to_integer)
}

counts: Dict<character, integer> <- List.walk(list("a", "b", "a"), Dict.empty(), function(seen, word) {
  Dict.update(seen, word, function(found) :Ok(Result.with_default(found, 0L) + 1L))
})

List.sum(parse_ids("3, 1, 2"))
```

## Comments

```r
#: fn(text: character) -> Result<list[integer], :InvalidNumStr>
parse_ids <- function(text) {
  parts <- List.map(Str.split_on(text, ","), Str.trim)
  List.map_try(parts, Str.to_integer)
}

#: Dict<character, integer>
counts <- List.walk(list("a", "b", "a"), Dict.empty(), function(seen, word) {
  Dict.update(seen, word, function(found) :Ok(Result.with_default(found, 0L) + 1L))
})

List.sum(parse_ids("3, 1, 2"))
```

```text
type-mismatch

  x expected `list[T]`, found `:Ok(list[integer]) | :Err(:InvalidNumStr)`
```

`parse_ids("3, 1, 2")` is `:Ok(list(3L, 1L, 2L))`, `parse_ids("3, x")` is `:Err(:InvalidNumStr)`,
and `counts` is `Dict.from_list(list(("a", 2L), ("b", 1L)))`. The last line is the mistake the
checker catches: the result has to be unwrapped, with `match` or `Result.with_default`, before its
list can be summed.

The library runs only on ry's runtime, so run such a script with `ry run`, not `ry R`. GNU R does
not have these functions, and under `ry R` a call to one stops with an error that says so:

```text
Error: `List.map()` is part of ry's standard library, which only ry's runtime provides, and this session runs on GNU R; run the code with `ry run` or `ry load` instead
```

Plain `Rscript` knows nothing of ry, so there the call fails with "could not find function".

## Conventions

These rules hold in every module:

- **Names are plain R names.** `List.map` is one symbol, called like any R function; there is no
  method syntax. The names live in ry's `ry` package, attached at startup as `package:ry`, so a
  definition of your own, or one from a package you attach, comes first, and `ry::List.map` still
  reaches the library's function.
- **A collection is an R list.** `List.map` takes any list and returns a new one, and
  `Str.split_on` returns a list of strings. `List.from_vector` turns an atomic vector into a list,
  and `List.to_vector` turns a list of scalars back.
- **Positions are 1-based** `integer`s, as in `xs[[1L]]`. A whole double such as `2` is accepted
  too.
- **A string is a length-one `character`, and a number a length-one `integer` or `double`.** `NA`
  and `NaN` are refused with an error, because no function has an `NA` case to fall into. The one
  exception is `Num.is_nan`, which accepts `NaN` because telling it apart is its job.
- **Misuse is an error, not a tag.** An argument of the wrong type, an `NA`, or an `integer`
  `List.sum` or `List.product` that overflows stops with an R error that names the problem. So
  does an unchecked `Num` operation whose result does not exist, such as `Num.div(1, 0)`, where
  Roc would crash; its `_checked` variant returns the failure as a tag instead.
- **No function returns `NaN`.** A result that would be `NaN`, as `Num.sin(Inf)`,
  `Num.pow(-1, 0.5)` and `List.sum(list(Inf, -Inf))` would be, is an error, and a `_checked`
  function returns `:Err(:Undefined)` for it instead.
- **Failure is a tag.** A function that can fail returns `:Ok(value)` or `:Err(tag)`, typed
  `Result<T, E>`, and the tag names the reason: `:OutOfBounds`, `:NotFound`, `:ListWasEmpty`,
  `:KeyNotFound`, `:InvalidNumStr`, `:BadUtf8`, `:BadUtf16`, `:BadUtf32`, `:Overflow`, `:DivByZero`, `:Undefined`,
  `:SqrtOfNegative` or `:LogNeedsPositive`.
- **Equality is `identical()`**, wherever a function compares values (`List.contains`,
  `Dict.keep_shared`).
- **Orderings are tags.** `Num.compare` returns `:LT`, `:EQ` or `:GT`, and `List.sort_with` takes a
  function that does the same.
- **Every value is immutable.** A function that "changes" a list, dictionary or set returns a new
  one.

A function that gets the wrong kind of value stops with an error that names the parameter:

```text
Error in List.len(c(1, 2)) :
  `list` must be a list, not a double vector of length 2
```

## Dictionaries and sets

R has no dictionary or set with value semantics, so `Dict` and `Set` are values of their own, like
ry's records and tags. `typeof()` is `"dict"` or `"set"`, and the class is `ry_dict` or `ry_set`.
Both keep insertion order, print as the call that rebuilds them, and compare with `identical()`
regardless of order:

```r
d <- Dict.from_list(list(("b", 2), ("a", 1)))
d
#> Dict.from_list(list(("b", 2), ("a", 1)))
identical(d, Dict.from_list(list(("a", 1), ("b", 2))))
#> [1] TRUE
```

A key or element is a `logical`, `integer`, `double` or `character` scalar that is not `NA` or
`NaN`. Every key of one collection has the same type, which the checker enforces. At run time
integers and doubles mix, as in R's own `==`: `1L` and `1` are the same key, and the keys become
doubles. `-0` and `0` are the same key too. `identical()` still tells an integer-keyed collection
from a double-keyed one, as it tells `1L` from `1`.

`length()`, `identical()`, `all.equal()`, `format()`, `deparse()` and `as.list()` understand them.
Any other R function refuses one, before it runs and in the checker, so convert with `as.list()`,
which gives a list of `(key, value)` tuples or of elements:

```text
Error in sum(d) :
  `d` is a ry dict, not an R object; convert it with `as.list()`
```

## Updates in place

A function that returns an updated collection, such as `Dict.insert`, `Set.insert`, `List.append` or
`List.set`, changes its first argument in place when nothing else can see that value, and copies it
otherwise. The in-place path applies to a reassignment of the form `x <- Dict.insert(x, key, value)`,
where the first argument is the variable being assigned and no other variable, list or closure holds
the same value. A loop that grows a collection one entry at a time then costs amortized constant
time per entry instead of a full copy:

```r
seen <- Dict.empty()
for (i in 1:100000) seen <- Dict.insert(seen, i, i * 2L)
Dict.len(seen)
#> 100000L
```

When another binding holds the value, as in `old <- seen; seen <- Dict.insert(seen, 0L, 0L)`, the
function copies first, so `old` never changes. The semantics are the same either way; only the cost
differs. `List.reserve`, `Dict.reserve` and `Set.reserve` set aside room ahead of a known number of
insertions.

A collection inside a list updates in place too, when both sides name it through the same `$` or
`[[` path and nothing else holds the lists on that path:

```r
state <- list(seen = Dict.empty(), count = 0L)
for (i in 1:100000) state$seen <- Dict.insert(state$seen, i, TRUE)
```

A dictionary, set or list also updates in place through a helper function, when the call has the form
`x <- helper(x, ...)` and the helper either reassigns its own parameter or returns the update of
its first parameter directly, as in `function(seen, key) Dict.insert(seen, key, TRUE)`:

```r
remember <- function(seen, key) {
  seen <- Dict.insert(seen, key, TRUE)
  seen
}
seen <- Dict.empty()
for (i in 1:100000) seen <- remember(seen, i)
```

The value moves into the helper's parameter for the duration of the call, so the parameter holds
the only reference and its `Dict.insert` updates in place. The caller's variable cannot change
visibly while the helper runs, because the move happens only when nothing can read that variable
before the assignment replaces it. The helper's body and defaults, and the other arguments, must
call nothing but closures and simple builtins (arithmetic, comparisons, indexing, `paste`,
`length`, the standard library and similar), and must not name the caller's variable unless it is
the helper's own parameter. The helper's arguments must be positional, with no `...`. If code the
check did not cover runs during the call, such as a closure the helper calls, the move ends and the
caller's variable gets its value back first. If the helper fails, the variable keeps the value it
had before the call, even when the helper had already updated the collection.

A helper may define and call its own closures, as in `inc <- function(x) x + 1`, as long as each
closure's body passes the same check and none of them outlives the call.

Other helpers copy, as in GNU R, and so does a call whose result goes to another variable, as in
`y <- helper(x, k)`. A helper that reads a variable through an environment, as in `env$x`, ends the
move at that point. For those, loop inside the helper, or keep the update in the loop that owns the
variable:

```r
remember_all <- function(seen, keys) {
  for (key in keys) seen <- Dict.insert(seen, key, TRUE)
  seen
}
```

Here only the first insertion copies, so the whole call is linear.

## Checking

A collection holds keys of one type, so a key of another type is an error at run time rather than a
missing key. On a dictionary with `character` keys, `Dict.get(d, "z")` is `:Err(:KeyNotFound)`, but
`Dict.get(d, 1L)` stops:

```text
Error in Dict.get(d, 1L) :
  the dict's keys are character, and this one is integer; a collection holds one type
```

Integer and double keys are the one exception: they compare as numbers, so `1L` finds the key `1`.
With `typing = true`, the checker reports a mismatched key before the code runs:

```text
type-mismatch

  x expected `character`, found `integer`
   --[a.R:2:20]
 1 | d <- Dict.single("a", 1)
 2 | got <- Dict.get(d, 1L)
   |                    ^^
```

Hover and completion show each function's signature and a one-line description.

## The `std` namespace and the prelude

The library is the namespace `std`, so a qualified name such as `std::List.map` always reaches it,
the same way `base::paste` reaches base R. By default the prelude also puts every function in scope
by its bare name, which is why the examples on these pages write `List.map`. Setting
`prelude = false` under `[check]` in `ry.toml` turns that off: a bare `List.map` is then an unbound
name, and only `std::List.map` works. A project that defines its own `List.map` keeps it either
way. The types `Result`, `Dict`, `Set` and `Array` stay in scope, because they are part of the type
syntax rather than the namespace.

```r
std::List.map(list(1L, 2L), function(x) x * 10L)
#> list(10L, 20L)
```

The checker, hover, completion and goto follow the setting, and so does the runtime, which
attaches `package:std` only while the prelude is on.

## Reference

The [API reference](/reference/std.md) lists every module and function with its signature in both
annotation forms, its documentation and a runnable example. It is generated from
`types/std.Rtypes`, the file the checker reads, so the two cannot disagree.

| Module | Covers |
| --- | --- |
| [`Result`](/reference/std/result.md) | `:Ok` and `:Err` values |
| [`List`](/reference/std/list.md) | R lists and Arrays |
| [`Str`](/reference/std/str.md) | single strings |
| [`Num`](/reference/std/num.md) | single numbers and numeric constants |
| [`Bool`](/reference/std/bool.md) | `TRUE` and `FALSE` |
| [`Dict`](/reference/std/dict.md) | dictionaries |
| [`Set`](/reference/std/set.md) | sets |
