SKILL.md


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

Basic structure

Every Roc CLI script needs:

  1. Shebang line (use /usr/bin/env roc or full path to roc)
  2. App header declaring the platform
  3. Imports from platform
  4. 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 Result for errors; no try/catch
  • Functional: Functions are first-class; use |param| expression syntax

Platform imports

Common modules from cli platform:

  • cli.Stdout - Write to stdout
  • cli.Stdin - Read from stdin
  • cli.File - File operations
  • cli.Path - Path handling
  • cli.Arg - Command-line arguments
  • cli.Env - Environment variables
  • cli.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