Source Development Guide
Contributing parameter definitions to tidymodels/dials repository
This guide is for developers contributing parameters directly to the dials package via pull requests.
When to Use This Guide
Use source development when:
Contributing parameters to tidymodels/dials repository
Your working directory has
Package: dialsin DESCRIPTIONYou’re adding universal parameters like
num_features()to dials itselfYou need to use internal helper functions
Do NOT use this guide when:
Creating a new R package with custom parameters
Your DESCRIPTION has
Package: yourpackage→ Use Extension Development Guide instead
Prerequisites
Clone the Repository
git clone https://github.com/tidymodels/dials
cd dialsInstall Dependencies
# Install development dependencies
pak::pak()
# Or using remotes
remotes::install_deps(dependencies = TRUE)Load the Package
# Load all functions for interactive development
devtools::load_all()Verify Setup
# Check that you can access internal functions
ls("package:dials", all.names = TRUE)
# Test a simple parameter
penalty()Understanding dials Architecture
File Organization
dials follows strict file naming conventions:
R/
├── aaa_*.R # Infrastructure files (loaded first)
├── constructors.R # new_quant_param(), new_qual_param()
├── finalize.R # Finalization system
├── param_*.R # Individual parameter definitions
├── range.R # range_get(), range_set()
├── grids.R # Grid generation functions
└── values.R # value_sample(), value_seq()
Key principles:
One parameter per file (usually):
R/param_[name].RInfrastructure files prefixed with
aaa_to load firstCore constructors in dedicated file
Helper functions grouped by functionality
Core Constructors
Located in R/constructors.R:
new_quant_param(
type, # "double" or "integer"
range, # Two-element vector
inclusive, # Two-element logical
trans = NULL, # Transformation from scales
label, # Named character for display
finalize = NULL # Optional finalize function
)
new_qual_param(
type, # "character" or "logical"
values, # Vector of options
default = NULL, # Optional default (first value if NULL)
label, # Named character for display
finalize = NULL # Rarely used
)Infrastructure Files
Files prefixed with aaa_ provide core functionality:
aaa_unknown.R:unknown()placeholder for data-dependent rangesaaa_utils.R: Internal utility functionsaaa_globals.R: Global variables and constants
These load before other files due to R’s alphabetical loading order.
Working Without dials:: Prefix
Direct Function Calls
In source development, use functions directly:
# Source development (correct)
new_quant_param(
type = "double",
range = c(0, 1),
...
)
# Extension development (not needed here)
dials::new_quant_param(...)Accessing Internal Functions
Source development gives you access to unexported helpers:
# Internal validation
check_type(type)
check_range(range, type)
# Range manipulation
bounds <- range_get(object)
range_set(object, new_bounds)
# Value generation
value_sample(param, n = 10)
value_seq(param, n = 5)Internal Functions Available
Validation Functions
check_type(type)
Validates parameter type:
check_type("double") # OK
check_type("integer") # OK
check_type("numeric") # Errorcheck_range(range, type)
Validates range specification:
check_range(c(0, 1), "double") # OK
check_range(c(1L, 10L), "integer") # OK
check_range(c(1, 0), "double") # Error: lower > upper
check_range(c(0), "double") # Error: length != 2Range Manipulation
range_get(object)
Extract current range from parameter:
param <- penalty()
bounds <- range_get(param)
# Returns: list(lower = -10, upper = 0)range_set(object, range)
Set new range on parameter:
new_bounds <- list(lower = -5, upper = 0)
param <- range_set(param, new_bounds)Used in custom finalize functions:
custom_finalize <- function(object, x) {
bounds <- range_get(object)
bounds$upper <- ncol(x)
range_set(object, bounds)
}Value Generation
value_sample(param, n)
Generate random values from parameter:
param <- penalty()
value_sample(param, n = 5)
# [1] 0.001 0.1 0.00001 0.5 0.01value_seq(param, n)
Generate sequence of values:
param <- penalty()
value_seq(param, n = 5)
# [1] 1e-10 1e-07 1e-04 1e-01 1e+00Built-in Finalize Functions
Located in R/finalize.R:
get_p(): Set upper bound to number of predictorsget_n(): Set upper bound to number of observationsget_n_frac(): Set upper bound to fraction of observationsget_log_p(): Set upper bound to log of predictors
Example usage:
mtry <- function(range = c(1L, unknown()), trans = NULL) {
new_quant_param(
type = "integer",
range = range,
inclusive = c(TRUE, TRUE),
trans = trans,
label = c(mtry = "# Randomly Selected Predictors"),
finalize = get_p # Use built-in finalize
)
}File Naming Conventions
Parameter Files
Pattern: R/param_[name].R
Examples:
R/param_penalty.RR/param_learn_rate.RR/param_mtry.RR/param_activation.R
One parameter per file (usually). Related parameters may share a file:
R/param_activation.R: Containsactivation()andactivation_2()
Test Files
Tests for parameters go in shared test files:
tests/testthat/test-params.R: Range validation for all parameterstests/testthat/test-constructors.R: Constructor argument validationtests/testthat/test-finalize.R: Finalization teststests/testthat/test-grids.R: Grid generation tests
Not test-param_name.R like in extension packages.
See Testing Patterns (Source) for details.
Documentation Patterns
Use @inheritParams
Inherit parameter documentation from constructors:
#' @inheritParams new_quant_param
#' @param range A two-element vector with the lower and upper bounds.Common inherited parameters:
From
new_quant_param:trans,label,finalizeFrom
new_qual_param:label,finalize
Use @seealso
Link related parameters:
#' @seealso [penalty()], [mixture()]Use @details
Explain parameter usage and behavior:
#' @details
#' This parameter uses a log10 transformation, so the range is specified
#' in log10 units. The default range of c(-10, 0) represents values from
#' 10^-10 to 1.Use @examples
Show practical usage:
#' @examples
#' penalty()
#' penalty(range = c(-5, 0))
#'
#' # Generate grid
#' grid_regular(penalty(), levels = 5)
#'
#' # Sample values
#' set.seed(123)
#' value_sample(penalty(), n = 5)See Roxygen Documentation for complete patterns.
Creating Parameters
Simple Quantitative Parameter
# R/param_threshold.R
#' Threshold value
#'
#' A threshold parameter for classification decisions.
#'
#' @inheritParams new_quant_param
#' @param range A two-element vector with the lower and upper bounds.
#'
#' @details
#' This parameter controls the decision threshold for binary classification.
#'
#' @examples
#' threshold()
#' threshold(range = c(0.3, 0.7))
#' @export
threshold <- function(range = c(0, 1), trans = NULL) {
new_quant_param(
type = "double",
range = range,
inclusive = c(TRUE, TRUE),
trans = trans,
label = c(threshold = "Classification Threshold"),
finalize = NULL
)
}Transformed Quantitative Parameter
# R/param_penalty.R
#' Amount of regularization
#'
#' The total amount of regularization.
#'
#' @inheritParams new_quant_param
#' @param range A two-element vector with the lower and upper bounds
#' (in log10 units).
#'
#' @details
#' This parameter uses a log10 transformation. The default range of
#' c(-10, 0) represents penalty values from 10^-10 to 1.
#'
#' @examples
#' penalty()
#' penalty(range = c(-5, 0))
#'
#' @seealso [mixture()], [cost()]
#' @export
penalty <- function(range = c(-10, 0), trans = transform_log10()) {
new_quant_param(
type = "double",
range = range,
inclusive = c(TRUE, TRUE),
trans = trans,
label = c(penalty = "Amount of Regularization"),
finalize = NULL
)
}Data-Dependent Parameter
# R/param_mtry.R
#' Number of randomly sampled predictors
#'
#' The number of predictors randomly sampled at each split.
#'
#' @inheritParams new_quant_param
#' @param range A two-element vector with the lower and upper bounds.
#'
#' @details
#' The upper bound depends on the number of predictors in the data.
#' Use `finalize()` with training data to set the upper bound.
#'
#' @examples
#' mtry()
#'
#' # Finalize with data
#' mtry() %>% finalize(mtcars[, -1])
#'
#' @export
mtry <- function(range = c(1L, unknown()), trans = NULL) {
new_quant_param(
type = "integer",
range = range,
inclusive = c(TRUE, TRUE),
trans = trans,
label = c(mtry = "# Randomly Selected Predictors"),
finalize = get_p
)
}Custom Finalize Function
# R/param_num_terms.R
#' Number of MARS terms
#'
#' The number of terms in a MARS model.
#'
#' @inheritParams new_quant_param
#' @param range A two-element vector with the lower and upper bounds.
#'
#' @details
#' The upper bound is calculated using the earth package formula:
#' min(200, max(20, 2 * ncol(x))) + 1
#'
#' @examples
#' num_terms()
#'
#' # Finalize with data
#' num_terms() %>% finalize(mtcars[, -1])
#'
#' @export
num_terms <- function(range = c(1L, unknown()), trans = NULL) {
new_quant_param(
type = "integer",
range = range,
inclusive = c(TRUE, TRUE),
trans = trans,
label = c(num_terms = "# Model Terms"),
finalize = get_num_terms
)
}
get_num_terms <- function(object, x) {
upper_bound <- min(200, max(20, 2 * ncol(x))) + 1
upper_bound <- as.integer(upper_bound)
bounds <- range_get(object)
bounds$upper <- upper_bound
range_set(object, bounds)
}Qualitative Parameter
# R/param_activation.R
#' Activation functions
#'
#' The activation function for neural networks.
#'
#' @inheritParams new_qual_param
#' @param values A character vector of possible activation functions.
#'
#' @details
#' This parameter defines the activation function between layers.
#'
#' @examples
#' values_activation
#' activation()
#' activation(values = c("relu", "sigmoid"))
#'
#' @export
activation <- function(values = values_activation) {
new_qual_param(
type = "character",
values = values,
label = c(activation = "Activation Function")
)
}
#' @rdname activation
#' @export
values_activation <- c(
"relu", "sigmoid", "tanh", "softmax",
"elu", "selu", "softplus", "softsign"
)Development Best Practices
Focus on correctness and completeness:
✅ Provide complete, working examples in roxygen @examples ✅ Explain key concepts briefly (transformations, finalization) ✅ Show common patterns with code snippets ✅ Include comprehensive tests covering all features
⚠️ But avoid over-explanation:
Don’t repeat information already in linked references
Keep roxygen docs focused and concise
Examples in roxygen are sufficient; don’t create separate example files
Trust that maintainers can review reference implementations
Quality indicators:
All required parameter fields specified (type, range/values, label, etc.)
Tests cover correctness, edge cases, and grid integration
Documentation includes working examples
Code follows dials conventions (naming, structure, style)
PR description is clear and focused
File Creation Guidelines for PRs
═══════════════════════════════════════════════════════ ⚠️⚠️⚠️ CRITICAL: PR FILE DISCIPLINE ⚠️⚠️⚠️ ═══════════════════════════════════════════════════════
🛑 STOP! STOP! STOP! 🛑
Before you create even ONE file, read this ENTIRE section.
You will create EXACTLY 2 files. That’s it. Two. Not 3. Not 4. Not 5. TWO FILES ONLY.
═══════════════════════════════════════════════════════
MANDATORY Pre-Flight Checklist
READ EACH LINE. CHECK EACH BOX. DO NOT SKIP THIS.
═══════════════════════════════════════════════════════
The ONLY Files You Will Create
- R/param_[name].R - Parameter function with complete roxygen documentation
- tests/testthat/test-param_[name].R - Comprehensive test suite (or additions to test-params.R)
THAT’S IT. STOP THERE. DO NOT CREATE ANYTHING ELSE.
═══════════════════════════════════════════════════════
Files You Will ABSOLUTELY NOT Create
🛑 INSTRUCTIONS FOR CLAUDE: STOP IMMEDIATELY IF YOU ARE ABOUT TO CREATE ANY FILE NOT LISTED IN “THE ONLY FILES YOU WILL CREATE” SECTION ABOVE. 🛑
❌ NEVER, EVER CREATE THESE FILES FOR PRs:
Documentation files (ALL PROHIBITED):
❌ README.md or README.txt (dials already has one)
❌ IMPLEMENTATION_SUMMARY.md
❌ IMPLEMENTATION_NOTES.md or IMPLEMENTATION_NOTES.txt
❌ PR_CHECKLIST.md or PR_DESCRIPTION.md
❌ QUICK_REFERENCE.md, QUICK_START.md, or QUICKSTART.md
❌ INTEGRATION_GUIDE.md or USAGE_GUIDE.md
❌ SUMMARY.md, SUMMARY.txt, or OVERVIEW.md
❌ INDEX.md, FILE_GUIDE.md, or MANIFEST.md
❌ DELIVERY_SUMMARY.md or COMPLETION_REPORT.md
Changelog files (ALL PROHIBITED):
❌ NEWS_entry.md or NEWS.md (mention in conversation, maintainer adds to NEWS.md)
❌ CHANGELOG.md
Example/script files (ALL PROHIBITED):
❌ example_usage.R or USAGE_EXAMPLE.R (examples go in roxygen @examples)
❌ test_examples.R (tests go in test-param_[name].R)
❌ WORKFLOW_COMMANDS.sh or setup.sh
Helper files (ALL PROHIBITED):
❌ pkgdown_addition.yml or pkgdown_update.txt
❌ test-params-addition.R (just tell user in conversation)
ANY OTHER FILE NOT IN THE “ONLY FILES” LIST ABOVE IS PROHIBITED.
═══════════════════════════════════════════════════════
Where Content Actually Goes
CRITICAL: Everything has a place. No separate files.
| Content Type | ❌ WRONG (separate file) | ✅ CORRECT (where it goes) |
|---|---|---|
| Examples | example_usage.R | roxygen @examples in R/param_[name].R |
| Implementation notes | IMPLEMENTATION_NOTES.txt | roxygen @details in R/param_[name].R |
| Parameter description | README.md | roxygen title/description in R/param_[name].R |
| PR checklist | PR_CHECKLIST.md | Conversation with user |
| NEWS entry | NEWS_entry.md | Conversation (maintainer adds to NEWS.md) |
| Test additions | test-params-addition.R | Conversation (tell user what to add) |
| Usage guide | QUICK_REFERENCE.md | roxygen @examples in R/param_[name].R |
| File list | INDEX.md | Not needed (only 2 files) |
| Summary | SUMMARY.txt | Conversation with user |
═══════════════════════════════════════════════════════
FINAL CHECK Before Creating Files
🛑 STOP RIGHT HERE. ANSWER THESE QUESTIONS: 🛑
- How many files am I about to create? Answer: 2
- Are they R/param_[name].R and tests/testthat/test-param_[name].R? Answer: YES
- Am I about to create any .md, .txt, or other documentation files? Answer: NO
- Where do examples go? Answer: In roxygen @examples
- Where do implementation notes go? Answer: In roxygen @details
- Where does PR description go? Answer: In conversation
If you can’t answer all 6 questions correctly, RE-READ this entire section.
═══════════════════════════════════════════════════════
Why This Matters
PRs to dials should contain ONLY code and tests. Period.
Creating documentation files:
❌ Violates dials package conventions
❌ Forces maintainers to delete your files
❌ Clutters the PR with non-essential content
❌ Duplicates information that belongs in roxygen comments
❌ Shows you didn’t read the contribution guidelines
Everything except code and tests goes in either: 1. Roxygen comments (documentation, examples, details) 2. The conversation (PR description, checklists, notes) 3. Commit messages (brief summary of changes)
NOT in separate files.
═══════════════════════════════════════════════════════
PR Submission Checklist
Before submitting your pull request:
Code
Documentation
Testing
See Testing Patterns (Source) for complete testing guide.
Package Checks
NEWS.md Format
Add entry under “Development” section:
## Development
* New parameter `my_parameter()` for [use case] (#PR_NUMBER).Git
Next Steps
Learn the System
- Understand architecture: Parameter System Overview
- Study examples: Review existing parameters in
R/param_*.R - Learn patterns: Read Best Practices (Source)
Create Your Parameter
- Choose type: Quantitative or Qualitative
- Add features: Transformations or Finalization
- Test thoroughly: Testing Patterns (Source)
Get Help
Issues: Troubleshooting (Source)
Questions: Ask in tidymodels GitHub discussions
Examples: Study
repos/dials/cloned repository
Last Updated: 2026-03-31