-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathREADME.Rmd
178 lines (128 loc) · 4.91 KB
/
README.Rmd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
---
output: github_document
---
<!-- README.md is generated from README.Rmd. Please edit that file -->
```{r, include = FALSE}
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>",
fig.path = "man/figures/README-",
out.width = "100%",
fig.height = 2
)
```
# foodwebr
<!-- badges: start -->
[![R-CMD-check](https://github.com/lewinfox/foodwebr/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/lewinfox/foodwebr/actions/workflows/R-CMD-check.yaml)
<!-- badges: end -->
`foodwebr` makes it easy to visualise the dependency graph of a set of functions (i.e. who calls
who). This can be useful for exploring an unfamiliar codebase, or reminding yourself what you wrote
ten minutes ago
## Installation
You can install foodwebr from GitHub:
``` r
devtools::install_github("lewinfox/foodwebr")
```
## Basic usage
Say we have a bunch of functions in the global environment, some of which call each other:
```{r setup}
library(foodwebr)
f <- function() 1
g <- function() f()
h <- function() { f(); g() }
i <- function() { f(); g(); h() }
j <- function() j()
```
A call to `foodweb()` will calculate a graph of the dependencies.
```{r foodweb-basic}
fw <- foodweb()
```
Printing the object will show the [graphviz](https://graphviz.org/) representation:
```{r foodweb-print}
fw
```
Plotting will draw the graph.
```{r foodweb-plot, message=FALSE}
plot(fw)
```
`foodweb()` looks at its calling environment by default. If you want to look at another environment
you can either pass a function to the `FUN` argument of `foodweb()` or pass an environment to the
`env` argument. If `FUN` is provided then the value of `env` is ignored, and the environment of
`FUN` will be used.
### Filtering
If a specific function is passed to `FUN`, the default behaviour is to remove functions that are not
descendants or antecedents of that function.
```{r foodweb-filter}
# `j()` will not be included
foodweb(FUN = g)
# Force inclusion of unconnected functions by using `filter = FALSE`
foodweb(FUN = g, filter = FALSE)
```
You can use this feature when exploring code in other packages: calling `foodweb()` on a function in
another package will show you how functions in that package relate to each other. I'm using `cowsay`
here as it's small enough that the output is readable.
By default when calling `foodweb()` on a specific function we only see functions that are in the
direct line of descendants or antecendents of the specified function.
```{r foodweb-plot-package}
if (requireNamespace("cowsay", quietly = TRUE)) {
plot(foodweb(cowsay::say))
}
```
If we want to include _all_ functions in the package, we can pass `filter = FALSE`:
```{r foodweb-plot-package-no-filter}
if (requireNamespace("cowsay", quietly = TRUE)) {
plot(foodweb(cowsay::say, filter = FALSE))
}
```
### `graphviz` as text
In case you want to do something with the [graphviz](https://graphviz.org/) output (make it
prettier, for example), use `as.text = TRUE`. This returns the graphviz specification as a character
vector.
```{r foodweb-as-text}
foodweb(as.text = TRUE)
```
Calling `as.character()` on a `foodweb` object will have the same effect.
## Using `tidygraph`
The [`tidygraph`](https://tidygraph.data-imaginist.com/) package provides tools for graph analysis.
A `foodweb` object can be converted into a tidy graph object using `tidygraph::as_tbl_graph()` to
allow more sophisticated analysis and visualisation.
```{r foodweb-tidygraph}
if (requireNamespace("tidygraph", quietly = TRUE)) {
tg <- tidygraph::as_tbl_graph(foodweb())
tg
}
```
## How does it work?
Understanding the algorithm is important as there are some key limitations to be aware of. To
identify the relationships between functions, `foodwebr`:
* Lists all the functions in an environment.
* Tokenises the `body()` of each function.
* Compares each token against the list of function names.
* If a token matches a function name, (i.e. the name of function B appears in the body of function
A), records a link from A to B.
This last point leads to the possibility of name masking, where a function contains an internal
variable that matches the name of another function in the environment. This will lead to a false
link.
For example:
```{r clear-env, include=FALSE}
rm(list = ls())
```
```{r foodweb-false-link}
f1 <- function() {
1
}
f2 <- function() {
f1 <- 10 # This variable `f1` will be confused with the function `f1()`
2
}
# The foodweb mistakenly believes that function `f2()` calls function `f1()`
foodweb()
```
If you know how to fix this please leave a comment in
[#2](https://github.com/lewinfox/foodwebr/issues/2).
## See also
`foodwebr` is similar to these functions/packages:
* [`mvbutils::foodweb()`](): The OG of function dependency graphs in R, and the inspiration for
foodwebr. Less user-friendly output, in my opinion.
* [`DependenciesGraphs`](https://github.com/datastorm-open/DependenciesGraphs): Provides much nicer
visualisations but does not appear to be actively maintained.