# Roc Language Reference

## REPL

Run with `roc repl`. Online: [roc-lang.org/repl](https://www.roc-lang.org/repl)

```roc
greeting = "Hi"
audience = "World"
```

Arithmetic: `1 + 2 * (3 - 4)` follows order of operations.

## Calling Functions

```roc
Str.concat("Hi ", "there.")  # "Hi there."
```

`Str` is a module, `concat` is a function in that module.

### String Interpolation

```roc
"${greeting} there, ${audience}."
"Two plus three is: ${Num.to_str(2 + 3)}"
```

## Building an Application

```roc
app [main!] { pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br" }

import pf.Stdout

main! = |_args|
    Stdout.line!("Hi there, from inside a Roc app. 🎉")
```

Run: `roc main.roc`

### Defs

```roc
birds = 3
iguanas = 2
total = Num.to_str(birds + iguanas)

main! = |_args|
    Stdout.line!("There are ${total} animals.")
```

Defs are constant—cannot be reassigned.

### Defining Functions

```roc
add_and_stringify = |num1, num2|
    Num.to_str(num1 + num2)
```

### if-then-else

```roc
add_and_stringify = |num1, num2|
    sum = num1 + num2
    if sum == 0 then
        ""
    else if sum < 0 then
        "negative"
    else
        Num.to_str(sum)
```

Every `if` must have both `then` and `else`.

### Comments

```roc
# Single line comment
## Doc comment (included in generated docs)
##     x = 2  # Code block in doc (5 spaces after ##)
```

### Records

```roc
add_and_stringify = |counts|
    Num.to_str(counts.birds + counts.iguanas)

total = add_and_stringify({ birds: 5, iguanas: 7 })
```

Functions accept records with extra fields:

```roc
total_with_note = add_and_stringify({ birds: 4, iguanas: 3, note: "Whee!" })
```

### Record Shorthands

```roc
return_foo = .foo  # Same as |record| record.foo
{ x: x, y: y }     # Same as { x, y }
```

### Record Destructuring

```roc
add_and_stringify = |{ birds, iguanas }|
    Num.to_str(birds + iguanas)

add_and_stringify = |{ birds, iguanas: lizards }|  # Rename field
    Num.to_str(birds + lizards)

{ x, y } = { x: 5, y: 10 }  # Destructure in defs
```

### Record Update

```roc
original = { birds: 5, zebras: 2, iguanas: 7, goats: 1 }
from_original = { original & birds: 4, iguanas: 3 }
```

`&` cannot introduce new fields or change types.

### Debugging with dbg

```roc
pluralize = |singular, plural, count|
    dbg count  # Prints: [pluralize.roc 6:8] 5
    if count == 1 then singular else plural

dbg Str.concat(singular, plural)  # Any expression
inc = |n| 1 + dbg n               # As function in expression
```

### Tuples

```roc
tuple = ("hello", 42, ["list"])
first = tuple.0   # "hello"
second = tuple.1  # 42

(first, second, third) = ("hello", 42, ["list"])  # Destructuring
```

## Pattern Matching

### Tags

```roc
stoplight_color =
    if something > 0 then Red
    else if something == 0 then Yellow
    else Green
```

Tags are capitalized literals (like numbers/strings, no definition needed).

### when/is

```roc
stoplight_str =
    when stoplight_color is
        Red -> "red"
        Green -> "green"
        Yellow -> "yellow"
```

Catch-all with `_`:

```roc
when stoplight_color is
    Red -> "red"
    _ -> "not red"
```

Multiple tags with `|`:

```roc
when stoplight_color is
    Red -> "red"
    Green | Yellow -> "not red"
```

Guards with `if`:

```roc
when stoplight_color is
    Red -> "red"
    Green | Yellow if contrast > 75 -> "high contrast"
    Green | Yellow -> "not red"
```

### Tags with Payloads

```roc
stoplight_color =
    if something > 100 then Red
    else if something > 0 then Yellow
    else Custom("some other color")

stoplight_str =
    when stoplight_color is
        Red -> "red"
        Green | Yellow -> "not red"
        Custom(description) -> description
```

Multi-value payloads: `Custom(40, 60, 80)` destructured as `Custom(r, g, b) ->`

### Booleans

`Bool.true` and `Bool.false` (not keywords). Prefer tags for data modeling:

```roc
{ name: "Richard", role: Admin }  # Better than is_admin: Bool.true
```

## Lists

```roc
names = ["Sam", "Lee", "Ari"]
List.append(names, "Jess")  # Returns new list (immutable)
```

### List.map

```roc
List.map([1, 2, 3], |num| num * 2)  # [2, 4, 6]
List.map([1, 2, 3], Num.is_odd)     # [Bool.true, Bool.false, Bool.true]
```

All list elements must share a type. Use tags for mixed types:

```roc
List.map([StrElem "A", NumElem 1], |elem|
    when elem is
        NumElem(num) -> Num.is_negative(num)
        StrElem(str) -> Str.starts_with(str, "A")
)
```

### Using Tags as Functions

```roc
List.map(["a", "b", "c"], Foo)  # Same as |str| Foo(str)
```

### List.any / List.all

```roc
List.any([1, 2, 3], Num.is_odd)       # Bool.true
List.all([1, 2, 3], Num.is_positive)  # Bool.true
```

### Removing Elements

```roc
List.drop_at(["Sam", "Lee", "Ari"], 1)  # ["Sam", "Ari"]
List.keep_if([1, 2, 3, 4, 5], Num.is_even)  # [2, 4]
List.drop_if([1, 2, 3, 4, 5], Num.is_even)  # [1, 3, 5]
```

### Getting Elements

```roc
List.get(["a", "b", "c"], 1)    # Ok "b"
List.get(["a", "b", "c"], 100)  # Err(OutOfBounds)
List.first(list)                # Err(ListWasEmpty) if empty
List.last(list)                 # Err(ListWasEmpty) if empty
```

## Error Handling

```roc
Result.with_default(List.get(["a", "b", "c"], 100), "")  # ""
Result.isOk(List.get(["a", "b", "c"], 1))  # Bool.true
```

### ? Postfix Operator

Unwraps `Ok`, returns early on `Err`:

```roc
get_letter : Str -> Result Str [OutOfBounds, InvalidNumStr]
get_letter = |index_str|
    index = Str.to_u64(index_str)?
    List.get(["a", "b", "c", "d"], index)
```

### ?? Infix Operator (Default on Error)

```roc
List.get(["a", "b", "c"], 100) ?? ""  # ""
```

### List.walk

```roc
List.walk([1, 2, 3, 4, 5], { evens: [], odds: [] }, |state, elem|
    if Num.is_even(elem) then
        { state & evens: List.append(state.evens, elem) }
    else
        { state & odds: List.append(state.odds, elem) }
)
# { evens: [2, 4], odds: [1, 3, 5] }
```

Arguments: list, initial state, function `(state, elem) -> state`

### Pattern Matching on Lists

```roc
when my_list is
    [] -> 0                      # empty
    [Foo, ..] -> 1               # starts with Foo
    [_, ..] -> 2                 # at least one element
    [Foo, Bar, Baz] -> 3         # exactly 3 elements
    [Foo, a, ..] -> 4            # second element named `a`
    [Ok a, ..] -> 5              # first is Ok with payload
    [.., Foo] -> 6               # ends with Foo
    [A, B, .., C, D] -> 7        # specific start and end
    [head, .. as tail] -> 8     # head and rest
```

Only one `..` (rest pattern) per pattern.

### Pipe Operator

```roc
["a", "b", "c"] |> List.get(1) |> Result.with_default("")
# Same as: Result.with_default(List.get(["a", "b", "c"], 1), "")
```

## Types

### Type Annotations

```roc
full_name : Str, Str -> Str
full_name = |first_name, last_name|
    "${first_name} ${last_name}"

first_name : Str
first_name = "Amy"

amy : { first_name : Str, last_name : Str }
amy = { first_name: "Amy", last_name: "Lee" }
```

### Type Aliases

```roc
Musician : { first_name : Str, last_name : Str }

amy : Musician
amy = { first_name: "Amy", last_name: "Lee" }
```

### Type Parameters

```roc
names : List Str
names = ["Amy", "Simone", "Tarja"]
```

### Wildcard Type (*)

```roc
is_empty : List * -> Bool  # Works on any list type
```

Empty list `[]` has type `List *`.

### Type Variables

```roc
reverse : List elem -> List elem  # Same element type in and out
```

Lowercase names (`elem`, `a`, `value`) are type variables.

### Tag Union Types

```roc
color_from_str : Str -> [Red, Green, Yellow]
color_from_str = |string|
    when string is
        "red" -> Red
        "green" -> Green
        _ -> Yellow
```

Tag unions accumulate:

```roc
|str|
    if Str.is_empty(str) then Ok "it was empty"
    else Err ["it was not empty"]
# Type: Str -> [Ok Str, Err (List Str)]
```

`Result ok err` is alias for `[Ok ok, Err err]`.

### Opaque Types

```roc
Username := Str

from_str : Str -> Username
from_str = |str| @Username(str)

to_str : Username -> Str
to_str = |@Username(str)| str
```

`@Username` only usable in defining module.

### Integers

| Type | Range |
|------|-------|
| U8   | 0 to 255 |
| I8   | -128 to 127 |
| U16  | 0 to 65,535 |
| I16  | -32,768 to 32,767 |
| U32  | 0 to 4,294,967,295 |
| I32  | -2,147,483,648 to 2,147,483,647 |
| U64  | 0 to 18+ quintillion |
| I64  | -9+ to 9+ quintillion |
| U128 | 0 to 340+ undecillion |
| I128 | ±170+ undecillion |

Overflow crashes the program.

### Fractions

| Type | Description |
|------|-------------|
| F32  | 32-bit floating-point |
| F64  | 64-bit floating-point |
| Dec  | 128-bit decimal fixed-point (18 decimal places) |

`Dec` is best for currency/base-10. `F32`/`F64` have precision loss with decimals.

### Num, Int, Frac

```roc
abs : Num a -> Num a           # Any number
bitwise_xor : Int a, Int a -> Int a  # Integers only
cos : Frac a -> Frac a         # Fractions only
```

Number literals have type `Num *` (or `Frac *` with decimal point).

### Number Literal Suffixes

```roc
1u8    # U8
5dec   # Dec
0xfe   # Hex (254)
0b1000 # Binary (8)
```

Suffixes: `u8`, `i8`, `u16`, `i16`, `u32`, `i32`, `u64`, `i64`, `u128`, `i128`, `f32`, `f64`, `dec`

### Default-Value Record Fields

```roc
table : { height : U64, width : U64, title ?? Str, description ?? Str } -> Table
table = |{ height, width, title ?? "oak", description ?? "a wooden table" }| ...
```

`??` in types marks optional fields. Only accessible via destructuring.

## Crashing

Crashes: integer overflow, out of memory, `crash` keyword.

```roc
when Str.from_utf8(bytes) is
    Ok(str) -> str
    Err(_) -> crash "This should never happen!"

# TODO marker
crash "TODO handle the x <= y case"
```

Not for error handling—use `Result` instead.

## Testing

```roc
pluralize = |singular, plural, count|
    if count == 1 then "${Num.to_str(count)} ${singular}"
    else "${Num.to_str(count)} ${plural}"

expect pluralize("cactus", "cacti", 1) == "1 cactus"
expect pluralize("cactus", "cacti", 2) == "2 cacti"
```

Run: `roc test`

### Inline Expects

```roc
pluralize = |singular, plural, count|
    if count == 1 then "${Num.to_str(count)} ${singular}"
    else
        expect count > 0
        "${Num.to_str(count)} ${plural}"
```

- `roc build`: discards all expects
- `roc dev`: runs inline expects during execution
- `roc test`: runs top-level expects and triggered inline expects

## Modules

Types: `app` (application), `module`, `package`, `platform`, `hosted`

### Builtin Modules (auto-imported)

Str, Num, Bool, Result, List, Dict, Set, Decode, Encode, Hash, Box, Inspect

### App Module Header

```roc
app [main!] { pf: platform "https://..." }

import pf.Stdout
import AdditionalModule
import uuid.Generate as Uuid
import pf.Stdout exposing [line!]
```

### Importing Files

```roc
import "some-file" as some_str : Str
import "some-file" as some_bytes : List U8
```

## Effectful Functions

Pure functions: `->`, effectful: `=>`

```roc
with_extension : Str -> Str           # Pure
read_file! : Str => Str               # Effectful
```

Effectful functions can call pure or effectful. Pure can only call pure.
`!` suffix is naming convention (compiler-enforced).

```roc
Stdout.line! : Str => Result {} [StdoutErr IOErr]
Stdin.line! : {} => Result Str [EndOfFile, StdinErr IOErr]
```

### Reading Input

```roc
main! = |_args|
    Stdout.line!("Type something:")?
    input = Stdin.line!({})?
    Stdout.line!("You entered: ${input}")
```

### Handling Failure

```roc
main! : List Arg => Result {} [Exit I32 Str]
main! = |_args|
    Result.map_err(my_function!({}), |err|
        when err is
            StdoutErr(_) -> Exit(1i32, "Error writing to stdout.")
            StdinErr(_) -> Exit(2i32, "Error writing to stdin.")
            EndOfFile -> Exit(3i32, "End of file reached.")
    )
```

### Tagging Errors

```roc
main! = |_args|
    Stdout.line!("Prompt") ? UnableToPrintPrompt
    input = Stdin.line!({}) ? UnableToReadInput
    Stdout.line!("You entered: ${input}") ? UnableToPrintInput
    Ok({})
```

### Inspect.to_str

```roc
Inspect.to_str(any_value)  # String representation for debugging
```

### Early return

```roc
if this_is_a_bad_time then
    return "Error message"
else
    continue_normally
```

## Advanced Concepts

### Open vs Closed Records

```roc
# Closed: exact fields only
full_name : { first_name : Str, last_name : Str } -> Str

# Open: at least these fields (note the *)
full_name : { first_name : Str, last_name : Str }* -> Str

# Constrained: same type in and out
add_https : { url : Str }a -> { url : Str }a
```

Inference:
- Creating record → closed
- Using as argument/destructuring → open
- Record update → constrained

### Type Alias with Variable

```roc
User a : { email : Str, first_name : Str, last_name : Str }a

is_valid : User * -> Bool        # Open
user_from_email : Str -> User {} # Closed
capitalize : User a -> User a    # Constrained
```

### Open vs Closed Tag Unions

```roc
# Open: might have unknown tags (needs _ -> branch)
example : [Foo Str, Bar Bool]* -> Bool
example = |tag|
    when tag is
        Foo(str) -> Str.is_empty(str)
        Bar(bool) -> bool
        _ -> Bool.false

# Closed: exactly these tags
example : [Foo Str, Bar Bool] -> Bool
example = |tag|
    when tag is
        Foo(str) -> Str.is_empty(str)
        Bar(bool) -> bool
```

New tags are inferred as open unions and can accumulate through conditionals.

### Constrained Tag Unions

```roc
example : [Foo Str, Bar Bool]a -> [Foo Str, Bar Bool]a
example = |tag|
    when tag is
        Foo(str) -> Bar(Str.is_empty(str))
        Bar(bool) -> Bar(Bool.false)
        other -> other
```

### Record Builder

```roc
user_tab_matcher =
    { combine_matchers <-
        _: exact_segment("users"),  # Ignored field
        user_id: u64_segment,
        tab: any_segment,
    }
```

Desugars to nested `combine_matchers` calls.

### Reserved Keywords

`as`, `crash`, `dbg`, `else`, `expect`, `expect-fx`, `if`, `import`, `is`, `return`, `then`, `try`, `when`

### Operator Desugaring

| Operator | Desugars To |
|----------|-------------|
| `a + b` | `Num.add(a, b)` |
| `a - b` | `Num.sub(a, b)` |
| `a * b` | `Num.mul(a, b)` |
| `a / b` | `Num.div(a, b)` |
| `a // b` | `Num.div_trunc(a, b)` |
| `a ^ b` | `Num.pow(a, b)` |
| `a % b` | `Num.rem(a, b)` |
| `-a` | `Num.neg(a)` |
| `a == b` | `Bool.is_eq(a, b)` |
| `a != b` | `Bool.is_not_eq(a, b)` |
| `a && b` | `Bool.and(a, b)` |
| `a \|\| b` | `Bool.or(a, b)` |
| `!a` | `Bool.not(a)` |
| `a \|> f` | `f(a)` |
