Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Caller-callee relationship for .R files in a package #2

Open
krlmlr opened this issue Oct 22, 2020 · 5 comments
Open

Caller-callee relationship for .R files in a package #2

krlmlr opened this issue Oct 22, 2020 · 5 comments

Comments

@krlmlr
Copy link

krlmlr commented Oct 22, 2020

In R packages, I try to organize the code in .R files so that if a function in a.R calls a function in b.R, no function from b.R calls back to a.R, also not indirectly. How can foreman help verify or visualize this?

Can we do a graphviz visualization with subgraphs -- each file creates a subgraph, each node is a function?

Can we create an igraph object, search for cycles, remove one edge, search again, ... to summarize? (We could label the edges so that they are named by the caller, to identify which function creates the cycle.)

I haven't tried the package yet, appreciate any pointers. Thanks!

@yonicd
Copy link
Owner

yonicd commented Oct 22, 2020

Do you have an example package with a structure like you want?

@krlmlr
Copy link
Author

krlmlr commented Oct 22, 2020

@yonicd
Copy link
Owner

yonicd commented Oct 22, 2020

assuming you have cloned fledge on your machine and are in the root of the package directory

library(ggraph)
library(igraph)

x <- foreman::unpack()
x_rel <- foreman::relationship(x)
x_rel_df <- as.data.frame(x_rel)
graph <- igraph::graph_from_data_frame(x_rel_df,directed = TRUE)
igraph::V(graph)$parents <- names(igraph::V(graph))

ggraph(graph) +
  geom_edge_link(
    aes(colour = file),
    arrow = grid::arrow(length = unit(0.05, "inches"))) +
  geom_node_text(aes(label = parents),size = 3) +
  labs(title = 'fledge function map') + 
  ggplot2::theme(legend.position   = 'bottom')

The file directional relationship is indicated in the colours.

This info is also in x_rel_df where can you create any output you want.

image

@krlmlr
Copy link
Author

krlmlr commented Oct 23, 2020

Very nice, thanks a lot!

I'm looking for evil circles like this. Here I'm interested in the relationship between files: ideally the directed graph should be cycle-free. This means that it must have a topological sort order. To detect the cycles, I'm computing the strongly connected components -- those with size > 1 are clusters of nodes that can be reached mutually in the connected graph, there must be a cycle in such a cluster. (Can't use igraph::girth() here, its's only for undirected graphs.)

Maybe a variant of this code could find its place here?

library(ggraph)
library(igraph)
library(tidyverse)

setwd("~/git/R/fledge")

x <- foreman::unpack()
#> Warning: No functions found in
#> /home/kirill/git/R/fledge/R/import.R
x_rel <- foreman::relationship(x)
x_rel_df <- as_tibble(as.data.frame(x_rel))

fun_file <-
  x %>% 
  map(names) %>% 
  enframe("file", "fun") %>% 
  unnest(fun)

child_file <- 
  fun_file %>% 
  rename(child = fun, child_file = file)

x_file_rel_df <- 
  x_rel_df %>% 
  left_join(child_file) %>% 
  count(file, child_file)
#> Joining, by = "child"

graph <- igraph::graph_from_data_frame(x_file_rel_df, directed = TRUE)

scc <- igraph::components(graph, "strong")

circle_comp <- which(scc$csize > 1)[[1]]

circle_files <- names(scc$membership)[scc$membership == circle_comp]

x_file_rel_df %>%
  filter(file %in% circle_files & child_file %in% circle_files) %>% 
  filter(file != child_file)
#> # A tibble: 5 x 3
#>   file                 child_file               n
#>   <chr>                <chr>                <int>
#> 1 api-bump-version.R   api-update-version.R     1
#> 2 api-bump-version.R   bump-version.R           1
#> 3 api-update-version.R update-version.R         1
#> 4 bump-version.R       api-update-version.R     1
#> 5 update-version.R     api-bump-version.R       1

plot(igraph::induced_subgraph(graph, circle_files))

Created on 2020-10-23 by the reprex package (v0.3.0)

@krlmlr
Copy link
Author

krlmlr commented Oct 23, 2020

(FWIW, this is a false positive for fledge -- there's a call of the form desc$bump_version(which) that seems to be detected as a call to fledge::bump_version() . If I remove that call, the cycle vanishes.)

library(ggraph)
library(igraph)
library(tidyverse)

setwd("~/git/R/fledge")

x <- foreman::unpack()
#> Warning: No functions found in
#> /home/kirill/git/R/fledge/R/import.R
x_rel <- foreman::relationship(x)
x_rel_df <- as_tibble(as.data.frame(x_rel))

fun_file <-
  x %>% 
  map(names) %>% 
  enframe("file", "fun") %>% 
  unnest(fun)

child_file <- 
  fun_file %>% 
  rename(child = fun, child_file = file)

x_file_rel_df <- 
  x_rel_df %>% 
  left_join(child_file) %>% 
  select(file, child_file, label = child) %>% 
  filter(file != child_file)
#> Joining, by = "child"

graph <- igraph::graph_from_data_frame(x_file_rel_df, directed = TRUE)

scc <- igraph::components(graph, "strong")

circle_comp <- which(scc$csize > 1)[[1]]

circle_files <- names(scc$membership)[scc$membership == circle_comp]

x_file_rel_df %>%
  filter(file %in% circle_files & child_file %in% circle_files) %>% 
  filter(file != child_file)
#> # A tibble: 5 x 3
#>   file                 child_file           label              
#>   <chr>                <chr>                <chr>              
#> 1 api-bump-version.R   api-update-version.R check_which        
#> 2 api-bump-version.R   bump-version.R       bump_version_impl  
#> 3 api-update-version.R update-version.R     update_version_impl
#> 4 bump-version.R       api-update-version.R update_version     
#> 5 update-version.R     api-bump-version.R   bump_version

plot(igraph::induced_subgraph(graph, circle_files))

Created on 2020-10-23 by the reprex package (v0.3.0)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants