Rules
Valid names in Roc have different rules depending on what they are used for.
Lowercase Names
Lowercase names, used by patterns (which include assignments), type variables, record fields, and package shorthands, must follow these rules:
- The name is a combination of ASCII letters, numbers, and underscores.
- Consecutive underscores are allowed, but discouraged stylistically.
- It must begin with either
_, $, or a lowercase ASCII letter.- The
$ prefix is only for reassignment with var, and must be followed by an ASCII lowercase letter. - The
_ prefix is only for naming things that don't actually get used, and must be followed by an ASCII lowercase letter.- The compiler will give a warning if a name begins with
_ and is referenced in the same scope. - Note that the
_ pattern is not a name and doesn't actually name anything.
- It can optionally end with
! if it's naming an effectful function.
Type variables, record fields, and package shorthands not only follow these rules, but also have the additional restriction that they may not include $ or ! anywhere. (All of them may still begin with an underscore to indicate that they are unused.)
Uppercase Names
Uppercase names, used by type names and tag names, have the same rules as lowercase names except:
- They must begin with an ASCII uppercase letter (which implies they may not begin with an underscore)
- They may not include
$ or ! - Stylistically, they should not include any underscores
Unused Names
The compiler gives a warning when a name is introduced but never used. For example:
total = |price, quantity| price * 2 # Warning: quantity is never used
To tell the compiler that a name is intentionally unused, begin it with an underscore:
total = |price, _quantity| price * 2 # No warning
The reverse is also true: if a name begins with _ but is used after all, the compiler
warns that the underscore is misleading and suggests removing it.
If you don't want to give a name to the value at all, you can use the
_ pattern instead:
total = |price, _| price * 2
Naming the unused value (as in _quantity) can still be worthwhile, because
the name documents what the value would have been.
This warning applies to names inside functions and blocks, including function arguments
and names introduced by patterns. Top-level names in a module don't get unused warnings.
Shadowing
Shadowing is when a new name has the same spelling as a name that's already in scope.
Roc gives a warning for shadowing:
count = 1
increment = |count| count + 1 # Warning: count shadows the count above
It's usually easier to understand code when each name refers to exactly one thing
in a given scope, so the warning suggests picking a different name. Shadowing is
a warning rather than an error, so the program can still be run; the new name
refers to the new value until the end of its scope.
This applies to redefining a name in the same scope too:
answer = 1
answer = 2 # Warning: answer is already defined
If you want a name whose value changes over time, use var instead.
Constants
A name introduced with an ordinary assignment is a constant. Once it
has been given a value, that value never changes.
pi = 3.14159
area = |radius| pi * radius * radius
Constants defined at the top level of a module are evaluated at
compile time whenever possible, so using them has no runtime cost beyond
reading the already-computed value.
Constants are the default in Roc; the only names whose values can change are
variables.
Variables (with var)
A variable is a name whose value can be changed after it has been assigned.
var $count = 0
for item in items {
$count = $count + 1
}
var keyword
Variables are declared by writing var before their first assignment. After that,
the variable can be reassigned with an ordinary =, without writing var again.
Variables may only be declared inside a function body or block, not at the top level of a module:
var $total = 0 # Error: var is not allowed at the top level
sum = |numbers| {
var $total = 0 # OK
for num in numbers {
$total = $total + num
}
$total
}
A variable can only be reassigned by the function that declared it. A nested function
(such as a lambda defined inside the function) can read the variable, but it cannot
reassign it:
count_items = |items| {
var $count = 0
add_one = || {
$count = $count + 1 # Error: can't reassign $count from a nested function
}
…
}
When a nested function reads a variable, it sees the value the variable had
when the nested function was defined, not whatever value the variable may have later.
Note that a variable is not a "box" that holds a value, like a mutable reference would be in some
other languages. It's just a name, and reassigning it changes which value the name refers to.
The value it used to refer to doesn't change. So reassigning a variable doesn't allocate or copy
anything by itself, and a variable costs the same at runtime as using a series of constants
would.
This works well with opportunistic mutation. In a loop like
this one, $list is the only thing referring to the list, so each append can add to the list in
place rather than copying it:
var $list = List.with_capacity(100)
for n in 0..<100 {
$list = $list.append(n * n)
}
Like every other value that isn't a function, a variable is never generalized. It has exactly one
type, and giving it a type annotation with a type variable in it is an error. (See
Generalization for more on this.)
$ prefix
Variable names must begin with $. The $ is part of the name, so $count and count are
different names.
The $ prefix makes it easy to tell at a glance which names might have their values
change over time, and which names are guaranteed to be constant. For example, when reading
$count + count, you can tell that $count might have a different value on the next
line, whereas count never will.
The compiler warns if a name declared with var does not begin with $, and also if a
constant's name begins with $:
var count = 0 # Warning: should be named $count
$total = 0 # Warning: $total is not declared with var
Type Variables
A type variable is a lowercase name used in a type annotation, which stands for
"any type." For example:
first : List(elem) -> Try(elem, [ListWasEmpty])
Here, elem is a type variable. It means first works on lists of any element type, and
whatever that element type is, the Ok payload will have the same type.
Type variables follow the rules for lowercase names, except that they can't
include $ or !. They can be longer than one letter, and descriptive names like elem, key,
or state often make annotations easier to read than single letters do.
Using the same type variable more than once in an annotation means those types must be the same.
To indicate that a type can be anything and doesn't need to be the same as any other type,
you can use a type variable that begins with an underscore (such as _elem), or just
an underscore by itself:
len : List(_elem) -> U64
is_empty : List(_) -> Bool
See Types for more on how type variables work.
Type Aliases
A type alias gives a new name to an existing type, using :. Type alias names follow
the rules for uppercase names.
Point : { x : F64, y : F64 }
The alias and the type it names are interchangeable; see Type Aliases
for details.
Parameterized Type Aliases
A type alias can have type parameters, which are written in parentheses after its name,
just like the type variables in a type annotation:
Pair(a) : (a, a)
swap : Pair(a) -> Pair(a)
swap = |(x, y)| (y, x)
When the alias is used, the arguments given for its parameters are substituted into its
definition. For example, Pair(Str) means (Str, Str).
Type parameters of an alias must be named. Underscores are not allowed in type
declarations, because a type declaration needs to say exactly what type it is.
Module Names
Type modules are named after the type they define. For example,
the type module that defines the Url type must be in a file named Url.roc. This means
type module names follow the same rules as uppercase names, plus the
.roc file extension.
Other modules, such as a package's main.roc or an application's main.roc, don't define
a type with the module's name, so their file names are not restricted in this way.
When you import a module from a package, the module's name is
qualified with the package's shorthand, as in import json.Parser. The shorthand
(json here) is a lowercase name chosen by the importing module; see
package shorthands.
as
The as keyword gives an additional name to something. It's used in a few places:
- In imports,
import json.Parser as JP makes the imported type available under the name JP instead of Parser. - In patterns,
Ok(n) as result matches an Ok and names its payload n, and also names the whole matched value result. - In list patterns,
[first, .. as rest] names the remaining elements of the list rest.
The name after as follows the usual rules for its position: an uppercase name
for an import, and a lowercase name in a pattern.