name: scripting-with-roc description: Creates Roc scripts for CLI automation with strong static types, helpful compiler errors, and functional programming. Use when the user wants Roc scripts, mentions Roc, functional scripting, type-safe scripts, strongly-typed scripts, or needs executable .roc files. license: AGPL-3.0-or-later metadata: author: Amolith amolith@secluded.site
Create scripts using Roc, a functional language with strong types, helpful compiler errors, and no runtime. Scripts can run with roc or compile to standalone binaries.
Platform version note: Examples use basic-cli 0.20.0. Check https://github.com/roc-lang/basic-cli/releases for the latest version and update URLs accordingly.
Reference documentation
- Tutorial: See llms.txt for comprehensive language guide
- Standard library: See builtins-llms.txt for all builtin modules
Basic structure
Every Roc CLI script needs:
- Shebang line (use
/usr/bin/env rocor full path to roc) - App header declaring the platform
- Imports from platform
- Main function with effectful signature
#!/usr/bin/env roc
app [main!] { cli: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br" }
import cli.Stdout
main! = |_args|
Stdout.line!("Hello, World!")
Make executable: chmod +x script.roc, then run: ./script.roc
If roc is not in PATH, use full path in shebang: #!/home/user/.local/share/roc/current/roc
Command-line arguments
#!/usr/bin/env roc
app [main!] {
cli: platform "https://github.com/roc-lang/basic-cli/releases/download/0.20.0/X73hGh05nNTkDHU06FHC0YfFaQB1pimX7gncRcao5mU.tar.br",
}
import cli.Stdout
import cli.Arg exposing [Arg]
main! : List Arg => Result {} _
main! = |raw_args|
args = List.map(raw_args, Arg.display)
# Index 0 is executable path, 1 is first real argument
when List.get(args, 1) is
Err(_) ->
Err(Exit(1, "Error: Expected one argument\n\tUsage: ./script.roc <input>"))
Ok(first_arg) ->
Stdout.line!("Received: ${first_arg}")
Code quality tools
Roc provides helpful tooling for maintaining code quality. Use these regularly:
Format code
roc format script.roc
Automatically fixes spacing, indentation, and formatting. Run before committing code.
Type check
roc check script.roc
Checks types and reports errors/warnings without executing. Catches:
- Type mismatches
- Unused definitions
- Unused arguments (suggests prefixing with
_) - Missing pattern matches
Example warnings:
── UNUSED DEFINITION in script.roc ──────────────────────────────────────
unused_var is not used anywhere in your code.
12│ unused_var = 42
^^^^^^^^^^
Run tests
roc test script.roc
Runs all expect expressions. Example:
add = |a, b| a + b
expect add(2, 3) == 5
expect add(0, 0) == 0
Warnings appear during script execution but don't prevent running. Fix them for clean code.
Common patterns
Reading stdin
app [main!] { cli: platform "..." }
import cli.Stdin
import cli.Stdout
main! = |_args|
input = Stdin.line!
Stdout.line!("You entered: ${input}")
File operations
app [main!] { cli: platform "..." }
import cli.File
import cli.Path
import cli.Stdout
main! = |_args|
path = Path.from_str("input.txt")
content = File.read_utf8!(path)?
Stdout.line!("File content: ${content}")
Error handling
Roc uses Result types, not exceptions. Use ? to short-circuit on errors:
main! = |_args|
path = Path.from_str("config.json")
content = File.read_utf8!(path)? # Returns early if error
# Process content...
Stdout.line!("Success!")
Or explicit pattern matching:
main! = |_args|
result = File.read_utf8!(Path.from_str("file.txt"))
when result is
Ok(content) -> Stdout.line!("Read: ${content}")
Err(err) -> Err(Exit(1, "Failed: ${Inspect.to_str(err)}"))
Working with lists
# Filter, map, fold
numbers = [1, 2, 3, 4, 5]
evens = List.keep_if(numbers, |n| n % 2 == 0)
doubled = List.map(evens, |n| n * 2)
sum = List.walk(doubled, 0, Num.add)
String manipulation
# Concatenation, splitting, trimming
text = Str.concat("Hello", " World")
words = Str.split(text, " ")
trimmed = Str.trim(" spaces ")
replaced = Str.replace_each(text, "World", "Roc")
Type signatures
Add type signatures for clarity (optional but helpful):
process_file : Str => Result Str [FileNotFound, InvalidUtf8]
process_file = |filename|
path = Path.from_str(filename)
content = File.read_utf8!(path)?
Ok(Str.to_upper(content))
main! : List Arg => Result {} _
main! = |args|
when List.get(args, 1) is
Ok(filename) ->
result = process_file!(filename)
when result is
Ok(content) -> Stdout.line!(content)
Err(_) -> Err(Exit(1, "Failed to process file"))
Err(_) ->
Err(Exit(1, "Usage: roc script.roc <filename>"))
Important notes
- Platform URL: Use current basic-cli release from https://github.com/roc-lang/basic-cli/releases
- No runtime: Compiled binaries are standalone
- Immutable: All values immutable; operations return new values
- Type inference: Types inferred but signatures help clarity
- Helpful errors: Compiler provides detailed, friendly error messages
- No exceptions: Use
Resultfor errors; no try/catch - Functional: Functions are first-class; use
|param| expressionsyntax
Platform imports
Common modules from cli platform:
cli.Stdout- Write to stdoutcli.Stdin- Read from stdincli.File- File operationscli.Path- Path handlingcli.Arg- Command-line argumentscli.Env- Environment variablescli.Dir- Directory operations
When to use Roc
Good for:
- Type-safe scripts that catch errors at compile time
- Functional programming enthusiasts
- Long-lived scripts benefiting from stability
- Learning functional programming
- No dependencies needed (strong stdlib)
Avoid for:
- Production-critical code (Roc pre-1.0, breaking changes possible)
- When you need mature ecosystem/libraries
- Quick throwaway scripts (compilation overhead)
- When team doesn't know functional programming
Maturity caveat
Roc lacks numbered releases (still alpha/pre-1.0). Core features work well, but expect:
- Breaking changes between versions
- Some missing features
- Smaller community/fewer examples
- Excellent for learning and experimentation, use production caution