Claude Code Plugins

Community-maintained marketplace

Feedback

lang-roc-library-dev

@aRustyDev/ai
0
0

Roc-specific library and platform development patterns. Use when creating reusable Roc packages, developing custom platforms, organizing package modules, exposing public APIs, managing platform interfaces, or publishing packages. Extends meta-library-dev with Roc's unique platform system and module patterns.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name lang-roc-library-dev
description Roc-specific library and platform development patterns. Use when creating reusable Roc packages, developing custom platforms, organizing package modules, exposing public APIs, managing platform interfaces, or publishing packages. Extends meta-library-dev with Roc's unique platform system and module patterns.
created 2025-12-28T22:00
updated 2025-12-28T22:00
tags lang, roc, library, dev
source arustydev/ai

Roc Library Development

Roc-specific patterns for library package and platform development. This skill extends meta-library-dev with Roc's unique platform architecture, package system, and module organization.

This Skill Extends

  • meta-library-dev - Foundational library patterns (API design, versioning, testing strategies)

For general concepts like semantic versioning, module organization principles, and testing pyramids, see the meta-skill first.

This Skill Adds

  • Roc platform system: Platform development, app/platform separation, I/O interfaces
  • Package organization: Module structure, package declarations, visibility patterns
  • Roc idioms: Pure functional patterns, platform interfaces, type inference
  • Roc ecosystem: Standard library modules, builtin modules, platform conventions

This Skill Does NOT Cover

  • General library patterns - see meta-library-dev
  • Roc application development - focus is on reusable packages/platforms
  • Roc-specific syntax basics - see language documentation
  • CLI application development - see CLI-specific skills

Overview

Roc has a unique architecture that separates applications from platforms:

┌─────────────────────────────────────────────────────────────────┐
│                    Roc Package Architecture                     │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ┌───────────────┐                                              │
│  │  Application  │  (your code)                                 │
│  │    (.roc)     │                                              │
│  └───────┬───────┘                                              │
│          │                                                      │
│          │ imports                                              │
│          ▼                                                      │
│  ┌───────────────┐                                              │
│  │    Package    │  (reusable modules)                          │
│  │  package {}   │                                              │
│  └───────┬───────┘                                              │
│          │                                                      │
│          │ depends on                                           │
│          ▼                                                      │
│  ┌───────────────┐         ┌──────────────┐                    │
│  │   Platform    │────────▶│  Runtime     │                    │
│  │  platform {}  │         │  (Rust, C)   │                    │
│  └───────────────┘         └──────────────┘                    │
│          │                                                      │
│          └─ Provides I/O primitives (no I/O in stdlib)         │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Key Concepts:

Concept Purpose I/O Allowed
Package Reusable modules (pure functions/types) No
Platform Runtime + I/O primitives Yes
App Executable application Yes (via platform)
Module Single .roc file with exports No (unless platform)

This skill focuses on creating Packages and Platforms for reuse.


Quick Reference

File Types

Type Declaration Purpose
app app [main] { ... } Executable application
package package [Module1, Module2] { ... } Reusable package
platform platform "name" requires {} exposes {} packages {} ... Platform for apps
module module [export1, export2] Single module file

Common Commands

Command Description
roc check Type-check without building
roc build Build executable
roc test Run tests
roc format Format code
roc docs Generate documentation

Package Development

Package Structure

my-package/
├── package/
│   ├── main.roc           # Package entry point
│   ├── Parser.roc         # Module files
│   ├── Types.roc
│   └── Internal.roc
├── examples/
│   └── basic.roc          # Example apps
├── platform/              # Optional: custom platform
│   └── main.roc
└── README.md

Package Declaration

package/main.roc:

package [
    # Public modules (exposed to users)
    Parser,
    Types,
    # Can also expose specific values
    parseConfig,
    defaultConfig,
]

Key Principles:

  1. Only expose what users need (minimal public API)
  2. Keep implementation modules private
  3. Re-export common types/functions at package level
  4. Use descriptive module names

Module Structure

package/Parser.roc:

module [
    # Public API
    parse,
    parseWithConfig,
    Config,
    ParseError,
]

import Types exposing [Document, Metadata]
import Internal  # Private import, not exposed

## Parse a document string.
##
## ```roc
## result = parse "# Title\nContent"
## expect result == Ok { title: "Title", content: "Content" }
## ```
parse : Str -> Result Document ParseError
parse = \input ->
    parseWithConfig input defaultConfig

## Configuration for parsing.
Config : {
    strict : Bool,
    preserveWhitespace : Bool,
}

defaultConfig : Config
defaultConfig = {
    strict: Bool.false,
    preserveWhitespace: Bool.false,
}

## Parse with custom configuration.
parseWithConfig : Str, Config -> Result Document ParseError
parseWithConfig = \input, config ->
    # Implementation
    # ...

Visibility Patterns

Module-level visibility:

module [
    # Public: Anyone importing this module can use these
    publicFunction,
    PublicType,
]

# Private: Only visible within this module
privateHelper : Str -> Str
privateHelper = \x -> x

# Public functions can call private helpers
publicFunction : Str -> Str
publicFunction = \input ->
    privateHelper input

Package-level visibility:

# main.roc
package [
    # These modules are visible to package users
    Parser,
    Types,
]

# Internal is NOT in package exports = private to package
import Internal

Platform Development

Platform Structure

my-platform/
├── platform/
│   └── main.roc           # Platform declaration
├── host/
│   ├── Cargo.toml         # Rust host implementation
│   └── src/
│       ├── lib.rs
│       └── main.rs
└── examples/
    └── hello.roc

Platform Declaration

platform/main.roc:

platform "cli"
    requires { Model } { main : Task Model [] }
    exposes [
        Task,
        Stdout,
        Stdin,
        File,
        Path,
        Env,
    ]
    packages {}
    imports []
    provides [mainForHost]

import Task
import Internal.Task

mainForHost : Task.Task Model [] as Fx
mainForHost = main

Key Components:

Section Purpose
requires What apps must provide (e.g., main function)
exposes Modules the platform provides to apps
packages Dependencies
imports Internal platform imports
provides Entry point for host

Platform Interface Design

Good platform design:

  1. Clear separation of concerns:
# Platform exposes pure types and Task-based I/O
module [
    # Pure types
    Request,
    Response,
    # Effectful operations
    serveHttp : Config, (Request -> Task Response []) -> Task {} []
]
  1. Type-safe I/O:
# File module
read : Path -> Task Str [FileNotFound, PermissionDenied]
write : Path, Str -> Task {} [PermissionDenied, DiskFull]

# Stdin module
line : Task Str [EndOfInput]
  1. Composable effects:
# Tasks can be chained
program : Task {} []
program =
    path <- Task.await (Env.var "CONFIG_PATH")
    contents <- Task.await (File.read path)
    config <- Task.await (parseConfig contents)
    runServer config

Module Organization Patterns

Single Responsibility Modules

# Types.roc - Just type definitions
module [Config, Document, Metadata]

Config : {
    timeout : U64,
    retries : U8,
}

Document : {
    title : Str,
    content : Str,
    metadata : Metadata,
}

Metadata : {
    author : Str,
    created : U64,
}

Extension Modules

# ConfigExt.roc - Operations on Config
module [default, withTimeout, validate]

import Types exposing [Config]

default : Config
default = {
    timeout: 30_000,
    retries: 3,
}

withTimeout : Config, U64 -> Config
withTimeout = \config, ms -> { config & timeout: ms }

validate : Config -> Result Config [InvalidTimeout, InvalidRetries]
validate = \config ->
    if config.timeout == 0 then
        Err InvalidTimeout
    else if config.retries > 10 then
        Err InvalidRetries
    else
        Ok config

Re-export Pattern

package/main.roc:

package [
    # Re-export commonly used types from multiple modules
    Config,
    Document,
    # Re-export main functions
    parse,
    parseWithConfig,
    # Expose modules for advanced usage
    Parser,
    Types,
]

# Import everything needed for re-exports
import Parser exposing [parse, parseWithConfig]
import Types exposing [Config, Document]

Type Design Patterns

Opaque Types

module [UserId, fromU64, toU64]

# Opaque: internals hidden from users
UserId := U64

## Create a UserId from a U64.
fromU64 : U64 -> UserId
fromU64 = @UserId

## Extract the U64 from a UserId.
toU64 : UserId -> U64
toU64 = \@UserId id -> id

Tagged Unions for Errors

module [ParseError, toStr]

ParseError : [
    InvalidSyntax Str,
    UnexpectedEnd,
    UnknownTag Str,
]

toStr : ParseError -> Str
toStr = \err ->
    when err is
        InvalidSyntax msg -> "Invalid syntax: $(msg)"
        UnexpectedEnd -> "Unexpected end of input"
        UnknownTag tag -> "Unknown tag: $(tag)"

Builder Pattern (Records with Defaults)

module [Config, default, build, withTimeout, withRetries]

Config : {
    timeout : U64,
    retries : U8,
    strict : Bool,
}

default : Config
default = {
    timeout: 30_000,
    retries: 3,
    strict: Bool.false,
}

build : {} -> Config
build = \{} -> default

withTimeout : Config, U64 -> Config
withTimeout = \config, ms -> { config & timeout: ms }

withRetries : Config, U8 -> Config
withRetries = \config, n -> { config & retries: n }

# Usage:
# config = build {} |> withTimeout 60_000 |> withRetries 5

Testing Patterns

Expect Blocks

## Parse a valid document.
parse : Str -> Result Document ParseError
parse = \input ->
    # Implementation
    # ...

# Test cases using expect
expect
    result = parse "# Title\nContent"
    result == Ok { title: "Title", content: "Content" }

expect
    result = parse ""
    result == Err UnexpectedEnd

expect
    result = parse "# Title\nContent"
    Result.isOk result

Test Modules

tests/ParserTests.roc:

module []

import Parser exposing [parse, ParseError]

expect
    # Happy path
    input = "# Hello\nWorld"
    result = parse input
    result == Ok { title: "Hello", content: "World" }

expect
    # Error case
    result = parse ""
    result == Err UnexpectedEnd

expect
    # Edge case
    input = Str.repeat "a" 10_000
    result = parse input
    Result.isOk result

Property-Based Testing Pattern

# Generate test cases
expectAll = \cases ->
    List.all cases \case ->
        expected = case.expected
        actual = parse case.input
        actual == expected

expect
    cases = [
        { input: "# A\nB", expected: Ok { title: "A", content: "B" } },
        { input: "", expected: Err UnexpectedEnd },
        { input: "###", expected: Err (InvalidSyntax "Too many #") },
    ]
    expectAll cases

Builtin Modules

Roc provides several builtin modules auto-imported into every file:

Module Purpose Common Functions
Str String operations concat, split, trim, to_utf8
Num Numeric operations add, sub, is_even, abs
List List operations map, filter, fold_left, len
Dict Dictionary/Map insert, get, keys, values
Set Set operations insert, contains, union
Bool Boolean operations true, false, not
Result Result type Ok, Err, map, try

Note: The standard library provides NO I/O operations. All I/O comes from platforms.


Breaking Changes (2025)

Snake Case Migration

Builtins are migrating from camelCase to snake_case:

# Old (deprecated)
Str.joinWith
List.keepIf

# New (current)
Str.join_with
List.keep_if

Action: Use new snake_case names in all new packages.

Task Deprecation (Purity Inference)

Task is deprecated in favor of purity inference:

# Old pattern
readConfig : Task Config [FileNotFound]
readConfig =
    path <- Task.await (Env.var "CONFIG")
    contents <- Task.await (File.read path)
    Task.ok (parse contents)

# New pattern (purity inferred)
readConfig : Config ![FileNotFound, EnvVarNotSet]
readConfig =
    path = Env.var! "CONFIG"
    contents = File.read! path
    parse contents

Action: For new platforms, use purity inference. For compatibility, check platform docs.


Publishing Packages

Pre-publish Checklist

  • roc check passes on all modules
  • roc test passes
  • roc format applied to all files
  • Documentation comments on all public functions
  • Examples in examples/ directory
  • README.md with usage examples
  • Version follows semantic versioning
  • No dependencies on unreleased packages
  • License file included

Package Metadata

Include in README.md:

# My Package

Brief description of what this package does.

## Installation

How to include this package in a Roc project.

## Quick Start

\```roc
import MyPackage exposing [parse]

main =
    result = parse "input"
    # ...
\```

## Modules

- `Parser` - Main parsing logic
- `Types` - Type definitions
- `Config` - Configuration utilities

## Examples

See `examples/` directory.

## License

MIT

Common Patterns

Error Handling

# Return Result for operations that can fail
parse : Str -> Result Document [InvalidSyntax Str, UnexpectedEnd]
parse = \input ->
    if Str.is_empty input then
        Err UnexpectedEnd
    else
        # Parse logic
        Ok document

# Chain Results
processDocument : Str -> Result ProcessedDoc [ParseError, ValidationError]
processDocument = \input ->
    parsed <- Result.try (parse input |> Result.map_err ParseError)
    validated <- Result.try (validate parsed |> Result.map_err ValidationError)
    Ok (process validated)

Polymorphic Functions

# Generic over any type
identity : a -> a
identity = \x -> x

# Constrained generics (via abilities - future feature)
compare : a, a -> [LT, EQ, GT] where a implements Ord

Pipeline Pattern

# Use |> for readable data transformations
processData : List Str -> List Str
processData = \items ->
    items
    |> List.map Str.trim
    |> List.keep_if (\s -> !(Str.is_empty s))
    |> List.map Str.to_lowercase
    |> List.sort_with Str.compare

Anti-Patterns

1. Leaking Implementation Details

# Bad: Exposing internal structure
module [Config, InternalState]

# Good: Only expose what's needed
module [Config, fromStr, toStr]

2. Overly Large Modules

# Bad: Single module with 1000+ lines

# Good: Split into focused modules
# - Types.roc (types)
# - Parser.roc (parsing)
# - Validator.roc (validation)
# - main.roc (re-exports)

3. Missing Documentation

# Bad: No docs
parse : Str -> Result Document ParseError

# Good: Documented
## Parse a document from a string.
##
## Returns a Document on success, or ParseError if the input is invalid.
##
## ```roc
## result = parse "# Title"
## expect result == Ok { title: "Title", ... }
## ```
parse : Str -> Result Document ParseError

4. Platform Doing Too Much

# Bad: Platform provides complex business logic
module [
    readUserFromDb,
    validateUser,
    sendEmail,
]

# Good: Platform provides primitives
module [
    dbQuery,
    httpRequest,
    # Let apps compose these into business logic
]

Troubleshooting

Module Not Found

Symptom: Module 'MyModule' not found

Causes & Fixes:

Cause Fix
Module not in package exports Add to package [...] list
File name doesn't match module Rename file to match (e.g., MyModule.roc)
Import path incorrect Use correct relative/absolute path

Type Inference Failures

Symptom: Cannot infer type for this expression

Fixes:

  1. Add explicit type annotation
  2. Provide more context (often happens in empty lists)
  3. Check for ambiguous number types (add U64, I32, etc.)
# Problem: Ambiguous type
nums = []

# Fix: Provide type context
nums : List U64
nums = []

# Or use in context
nums = List.range { start: At 1, end: At 10 }

Circular Dependencies

Symptom: Circular dependency detected

Fix: Restructure modules

# Before (circular):
# A.roc imports B.roc
# B.roc imports A.roc

# After: Extract shared types
# Types.roc (shared types)
# A.roc imports Types
# B.roc imports Types

Platform Examples

Basic CLI Platform

platform "basic-cli"
    requires { Model } { main : Task Model [] }
    exposes [
        Task,
        Stdout,
        Stdin,
        File,
        Path,
        Env,
        Arg,
    ]
    packages {}
    imports []
    provides [mainForHost]

mainForHost : Task Model [] as Fx
mainForHost = main

Web Platform

platform "basic-webserver"
    requires {} { main : Request -> Task Response [] }
    exposes [
        Request,
        Response,
        Task,
        Stdout,
    ]
    packages {}
    imports []
    provides [mainForHost]

Request : {
    method : [GET, POST, PUT, DELETE],
    url : Str,
    headers : Dict Str Str,
    body : List U8,
}

Response : {
    status : U16,
    headers : Dict Str Str,
    body : List U8,
}

References


Sources