Package Function Structure
2026-02-03
1 Overview
This article demonstrates how to visualize the structure and dependencies of package functions using the foodwebr package. Understanding function dependencies helps developers:
- Navigate and understand codebases more quickly
- Identify potential refactoring opportunities
- Document architecture and design decisions
- Onboard new contributors more effectively
2 What is foodwebr?
The foodwebr package creates dependency graphs showing which functions call which other functions. These visualizations are particularly useful for:
- Exploring unfamiliar codebases
- Understanding function relationships
- Identifying central or isolated functions
- Planning refactoring efforts
3 Installing foodwebr
If you don’t have foodwebr installed, you can install it from CRAN:
Code
install.packages("foodwebr")Or from GitHub for the latest development version:
Code
devtools::install_github("lewinfox/foodwebr")4 Visualizing Package Structure
4.1 Basic Usage
The foodweb() function analyzes function dependencies. When examining a package, you typically want to look at specific functions or the entire package namespace:
Code
library(foodwebr)
# Create a foodweb for the example_function
fw <- foodweb(FUN = example_function)
fw
#> # A `foodweb`: 6 vertices and 7 edges
#> digraph 'foodweb' {
#> example_function() -> { calculate_statistic(), format_result() }
#> calculate_summary() -> { example_function(), format_result(), clean_data() }
#> calculate_statistic() -> { clean_data() }
#> format_result()
#> clean_data() -> { validate_input() }
#> validate_input()
#> }4.2 Plotting the Dependency Graph
The plot() method creates an interactive visualization using DiagrammeR:
Code
plot(fw)- Nodes represent individual functions
- Edges (arrows) show function calls
- An arrow from A to B means “A calls B”
- Isolated nodes indicate functions with no dependencies or dependents
4.3 Filtering Options
By default, foodwebr filters to show only functions directly related to the specified function. You can control this behavior:
4.4 Examining the Entire Package
To see all exported and internal functions in the package:
Code
# Get the package namespace environment
pkg_env <- asNamespace("rpt")
# Create foodweb for entire package
fw_pkg <- foodweb(env = pkg_env)
fw_pkg
#> # A `foodweb`: 6 vertices and 7 edges
#> digraph 'foodweb' {
#> calculate_statistic() -> { clean_data() }
#> calculate_summary() -> { clean_data(), example_function(), format_result() }
#> clean_data() -> { validate_input() }
#> example_function() -> { calculate_statistic(), format_result() }
#> format_result()
#> validate_input()
#> }Code
plot(fw_pkg)5 Analyzing Dependencies
5.1 Understanding Function Roles
From the dependency graph, you can identify different types of functions:
- Leaf functions: Functions that don’t call any other package functions (only base R or external packages)
- Root functions: Functions that are called by many others but don’t call package functions themselves
- Hub functions: Functions that both call and are called by many functions
- Isolated functions: Functions with no connections to others
5.2 Example Analysis
For our package:
Code
# Convert to text representation for inspection
cat(as.character(fw_pkg))
#> digraph 'foodweb' {
#> "calculate_statistic()" -> { "clean_data()" }
#> "calculate_summary()" -> { "clean_data()", "example_function()", "format_result()" }
#> "clean_data()" -> { "validate_input()" }
#> "example_function()" -> { "calculate_statistic()", "format_result()" }
#> "format_result()"
#> "validate_input()"
#> }In well-designed packages, you typically see:
- Clear hierarchy: Lower-level utility functions are called by higher-level API functions
- Limited cross-dependencies: Functions in the same “layer” don’t call each other excessively
- Focused functions: Each function has a clear purpose with limited dependencies
In the rpt package, we can see this pattern:
-
validate_input()is a low-level helper (no package dependencies) -
clean_data()callsvalidate_input() -
calculate_statistic()callsclean_data() -
format_result()is a low-level formatter (no package dependencies) -
example_function()is a high-level API that callscalculate_statistic()andformat_result() -
calculate_summary()is another high-level API that callsclean_data()andexample_function()
This creates a clear layered architecture with well-separated concerns.
6 Package Architecture
6.1 Current Structure
Let’s examine the actual structure of the rpt package:
Code
# List all functions in the package
pkg_functions <- ls(asNamespace("rpt"))
cat("Package functions:\n")
#> Package functions:
cat(paste("-", sort(pkg_functions), collapse = "\n"))
#> - calculate_statistic
#> - calculate_summary
#> - clean_data
#> - example_function
#> - format_result
#> - validate_inputThe dependency relationships are:
-
Exported functions (user-facing API):
-
example_function(): Callscalculate_statistic()→format_result() -
calculate_summary(): Callsclean_data()→example_function()
-
-
Internal functions (implementation details):
-
validate_input(): No package dependencies (leaf function) -
clean_data(): Callsvalidate_input() -
calculate_statistic(): Callsclean_data() -
format_result(): No package dependencies (leaf function)
-
This architecture demonstrates good separation of concerns with clear data flow from validation through cleaning to calculation and formatting.
7 Advanced Usage
7.1 Using with tidygraph
The tidygraph package provides powerful tools for graph analysis. You can convert a foodweb object to work with these tools:
Code
if (requireNamespace("tidygraph", quietly = TRUE)) {
tg <- tidygraph::as_tbl_graph(fw_pkg)
print(tg)
}
#> # A tbl_graph: 6 nodes and 7 edges
#> #
#> # A directed acyclic simple graph with 1 component
#> #
#> # Node Data: 6 × 1 (active)
#> name
#> <chr>
#> 1 calculate_statistic
#> 2 calculate_summary
#> 3 clean_data
#> 4 example_function
#> 5 format_result
#> 6 validate_input
#> #
#> # Edge Data: 7 × 2
#> from to
#> <int> <int>
#> 1 1 3
#> 2 2 3
#> 3 2 4
#> # ℹ 4 more rows7.2 Customizing Visualizations
You can pass additional arguments to customize the graph appearance:
Code
# These arguments are passed to DiagrammeR::grViz()
plot(fw_pkg,
width = 800,
height = 600)7.3 Exporting as Text
Get the graphviz DOT representation:
Code
# As text
foodweb_text <- foodweb(env = asNamespace("rpt"), as.text = TRUE)
cat(foodweb_text)
#> digraph 'foodweb' {
#> "calculate_statistic()" -> { "clean_data()" }
#> "calculate_summary()" -> { "clean_data()", "example_function()", "format_result()" }
#> "clean_data()" -> { "validate_input()" }
#> "example_function()" -> { "calculate_statistic()", "format_result()" }
#> "format_result()"
#> "validate_input()"
#> }8 Practical Applications
8.1 Code Review
Use dependency graphs during code review to:
- Verify that new functions follow existing architectural patterns
- Identify unexpected dependencies
- Ensure proper separation of concerns
- Detect circular dependencies
8.2 Refactoring
Before refactoring:
- Generate a dependency graph of the affected area
- Identify all functions that will be impacted
- Plan changes to minimize ripple effects
- After refactoring, regenerate the graph to verify improvements
8.3 Documentation
Include dependency graphs in:
- Developer documentation
- Architecture decision records
- Package vignettes (like this one!)
- README files for complex packages
When adding new functions or modifying dependencies, regenerate the foodweb graphs to keep documentation current. Consider adding automated checks in your CI/CD pipeline.
9 Exploring Specific Functions
You can focus on specific functions to understand their dependencies:
9.1 Example Function Dependencies
9.2 Summary Function Dependencies
These focused views help understand:
- What functions a specific function depends on (its dependencies)
- What functions depend on a specific function (its dependents)
- The complete call chain for a given function
10 Best Practices
When designing package architecture:
- Minimize dependencies: Each function should have a clear, focused purpose
- Avoid circular dependencies: A should not call B if B calls A
- Layer your functions: Separate low-level utilities from high-level APIs
-
Document dependencies: Use
foodwebrgraphs in your documentation - Review regularly: Check dependency graphs during code review
11 Learn More
- foodwebr documentation: https://lewinfox.com/foodwebr/
- Package website: https://ucd-serg.github.io/rpt/
- GitHub repository: https://github.com/UCD-SERG/rpt
- DiagrammeR: https://rich-iannone.github.io/DiagrammeR/
- tidygraph: https://tidygraph.data-imaginist.com/
12 Summary
This article demonstrated how to use foodwebr to visualize and understand package function dependencies. Key takeaways:
-
foodwebrcreates interactive dependency graphs - Filtering options help focus on specific functions or show entire packages
- Dependency graphs aid in code review, refactoring, and documentation
- Integration with
tidygraphenables advanced graph analysis - Regular visualization helps maintain clean architecture
The rpt package demonstrates a clean layered architecture with: - Low-level utility functions (validate_input(), format_result()) - Mid-level processing functions (clean_data(), calculate_statistic()) - High-level API functions (example_function(), calculate_summary())
This structure makes the code easier to understand, test, and maintain.