- Notifications
You must be signed in to change notification settings - Fork1
Trace Function Parameter Types in R
License
mpadge/typetracer
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
typetracer is an R package to trace function parameter types. The Rlanguage includesa set of definedtypes,but the language itself is“absurdlydynamic”[1], and lacks any wayto specify which types are expected by any expression. Thetypetracerpackage enables code to be traced to extract detailed information on theproperties of parameters passed to R functions.typetracer can traceindividual functions or entire packages, as demonstrated below.
The stable version of the package can be installed with one of thefollowing commands:
# Stable version from CRAN:install.packages ("typetracer")# Current development version from r-universe:install.packages ( "typetracer", repos = c ("https://mpadge.r-universe.dev", "https://cloud.r-project.org"))Alternatively, for those who prefer to use other source code platforms,the package can also be installed by running any one of the followinglines:
remotes::install_git ("https://git.sr.ht/~mpadge/typetracer")remotes::install_git ("https://codeberg.org/mpadge/typetracer")remotes::install_bitbucket ("mpadge/typetracer")remotes::install_gitlab ("mpadge/typetracer")The package can then loaded for use by callinglibrary:
library (typetracer)typetracer works by “injecting” tracing code into the body of afunction usingtheinject_tracer()function.Locally-defined functions can be traced by simply passing the functionsdirectly toinject_tracer(). The following example includes fourparameters, including... to allow passing of additional and entirelyarbitrary parameter types and values.
f <- function (x, y, z, ...) { x * x + y * y}inject_tracer (f)After injecting thetypetracer code, calls to the function,f, will“trace” each parameter of the function, by capturing both unevaluatedand evaluated representations at the point at which the function isfirst called. These values can be accessed withtheload_tracesfunction,which returns adata.frame object (intibbleformat) with one row for each parameterfrom each function call.
val <- f ( x = 1:2, y = 3:4 + 0., a = "blah", b = list (a = 1, b = "b"), f = a ~ b)x <- load_traces ()x## # A tibble: 7 × 12## trace_number fn_name fn_call_hash par_name class typeof mode storage_mode## <int> <chr> <chr> <chr> <I<list>> <chr> <chr> <chr> ## 1 0 f yXYbicZQ x <chr [1]> integ… nume… integer ## 2 0 f yXYbicZQ y <chr [1]> double nume… double ## 3 0 f yXYbicZQ z <chr [1]> NULL NULL NULL ## 4 0 f yXYbicZQ ... <chr [1]> NULL NULL NULL ## 5 0 f yXYbicZQ a <chr [1]> chara… char… character ## 6 0 f yXYbicZQ b <chr [1]> list list list ## 7 0 f yXYbicZQ f <chr [1]> langu… call language ## # ℹ 4 more variables: length <int>, formal <named list>, uneval <I<list>>,## # eval <I<list>>Each row of the result returned byload_traces() represents oneparameter passed to one function call. Each function call itselfrepresents a single “trace” as enumerated by thetrace_number column,and also uniquely identified by an arbitrary function call hash(fn_call_hash). The remaining columns of the trace data define theproperties of each parameter,p, as:
par_name: Name of parameter.class: List of classes of parameter.typeof: Result oftypeof(p).mode: Result ofmode(p).storage_mode: Result ofstorage.mode(p).length: Result oflength(p).formal: Result offormals(f)[["p"]], as named list item withdefault value where specified.uneval: Parameters as passed to the function call prior toevaluation within function environment.eval: Evaluated version of parameter.
The results above show that all parameters of the function,f(), weresuccessfully traced, including the additional parameters,a,b, andf, passed as part of the... argument. Such additional parameterscan be identified through having a"formal" entry ofNULL,indicating that they are not part of the formal arguments to thefunction.
That result can also be used to demonstrate the difference between theunevaluated and evaluated forms of parameters:
x$uneval [x$par_name %in% c ("b", "f")]## $b## [1] "list(a = 1, b = \"b\")"## ## $f## [1] "a ~ b"x$eval [x$par_name %in% c ("b", "f")]## $b## $b$a## [1] 1## ## $b$b## [1] "b"## ## ## $f## a ~ b## <environment: 0x558a6fb5dda8>Unevaluated parameters are generally converted to equivalent characterexpressions.
Thetypeof,mode, andstorage_mode columns are similar, yet mayhold distinct information for certain types of parameters. Theconditions under which these values differ are complex, and depend amongother things on the version of R itself.typeof alone should generallyprovide sufficient information, althoughthis list ofdifferences may provide furtherinsight into whether the other columns may provide useful additionalinformation.
Traces themselves are saved in the temporary directory of the current Rsession, andtheload_traces()functionsimply loads all traces created in that session.The functionclear_traces()removes all traces, so thatload_traces()will only load new traces produced after that time.
It is important after applyingtheinject_tracer()functionto restore the functions back to their original form through callingthe obverseuninject_tracer()function.For the function,r, above, this simply requires,
uninject_tracer (f)## [1] TRUEAll traces can also be removed with this functions:
clear_traces ()Becausetypetracer modifies the internal code of functions as definedwithin a current R session, we strongly recommend restarting your Rsession after usingtypetracer, to ensure expected function behaviouris restored.
R has extensive support for list structures, notably including alldata.frame-like objects in which each column is actually a list item.typetracer also offers the ability to recurse into the list structuresof individual parameters, to recursively trace the properties of eachlist item. To do this, the traces themselves have to be injected withthe additional parameter,trace_lists = TRUE.
The final call above included an additional parameter passed as a list.The following code re-injects a tracer with the ability to traverse intolist structures:
inject_tracer (f, trace_lists = TRUE)val <- f ( x = 1:2, y = 3:4 + 0., a = "blah", b = list (a = 1, b = "b"), f = a ~ b)x_lists <- load_traces ()print (x_lists)## # A tibble: 9 × 12## trace_number fn_name fn_call_hash par_name class typeof mode storage_mode## <int> <chr> <chr> <chr> <I<list>> <chr> <chr> <chr> ## 1 0 f DPfsArXY x <chr [1]> integ… nume… integer ## 2 0 f DPfsArXY y <chr [1]> double nume… double ## 3 0 f DPfsArXY z <chr [1]> NULL NULL NULL ## 4 0 f DPfsArXY ... <chr [1]> NULL NULL NULL ## 5 0 f DPfsArXY a <chr [1]> chara… char… character ## 6 0 f DPfsArXY b <chr [1]> list list list ## 7 0 f DPfsArXY f <chr [1]> langu… call language ## 8 0 f DPfsArXY b$a <chr [1]> double nume… double ## 9 0 f DPfsArXY b$b <chr [1]> chara… char… character ## # ℹ 4 more variables: length <int>, formal <named list>, uneval <I<list>>,## # eval <I<list>>And that result now has 9 rows, or 2 more than the previous example,reflecting the two items passed as alist to the parameter,b.List-parameter items are identifiable in typetracer output through the“dollar-notation” in thepar_name field. The final two values in theabove table areb$a andb$b, representing the two elements of thelist passed as the parameter,b.
This section presents a more complex example tracing all function callsfromtherematch package,chosen because it has less code than almost any other package on CRAN.The following single line traces function calls in all examples for thenominated package.Thetrace_package()functionautomatically injects tracing code into every function within thepackage, so there is no need to explicitly calltheinject_tracer()function.
(This function also includes atrace_lists parameter, as demonstratedabove, with a default ofFALSE to not recurse into tracing liststructures.)
res <- trace_package ("rematch")res## # A tibble: 6 × 14## trace_number source_file_name fn_name fn_call_hash call_env par_name class ## <int> <chr> <chr> <chr> <chr> <chr> <I<list>## 1 0 man/re_match.Rd re_match wNDFeOta <NA> pattern <chr> ## 2 0 man/re_match.Rd re_match wNDFeOta <NA> text <chr> ## 3 0 man/re_match.Rd re_match wNDFeOta <NA> ... <chr> ## 4 1 man/re_match.Rd re_match oEujlJYt <NA> pattern <chr> ## 5 1 man/re_match.Rd re_match oEujlJYt <NA> text <chr> ## 6 1 man/re_match.Rd re_match oEujlJYt <NA> ... <chr> ## # ℹ 7 more variables: typeof <chr>, mode <chr>, storage_mode <chr>,## # length <int>, formal <named list>, uneval <I<list>>, eval <I<list>>Thedata.frame returned by thetrace_package() function includesthree more columns than the result directly returned byload_traces().These columns identify the sources and calling environments of eachfunction call being traces. The “call_env” column identifies thecalling environment which generated each trace, while“source_file_name” identifies the file.
unique (res$call_env)## [1] NAunique (res$source_file_name)## [1] "man/re_match.Rd"Although the “call_env” columns contains no useful information for thatpackage, it includes information on the full environment in which eachfunction was called. These “environments” include such things astryCatch calls expected to generate errors, or the variousexpect_functions of the“testthat” package. Theabove case of racing an installed package generally only extracts tracesfrom example code, as documented in help, or.Rd, files. These areidentified by the “rd_” prefix on the “source_file_name”, with therematch package including only one.Rd file.
Thetrace_package()functionalso includes an additional parameter,types, which defaults toc ("examples", "tests"), so that traces are also by default generatedfor all tests included with local source packages (or for packagesinstalled to include test files). The “source” column for test filesidentifies the names of each test, prefixed with “test_”.
The other two additional columns of “trace_file” and “call_env”respectively specify the source file and calling environment of eachtrace. These will generally only retain information from test files, inwhich case the source file will generally be the file name identified inthe “source” column, and “call_env” will specify the environment fromwhich that function call originated. Environments may, for example,include various types of expectation from the“testthat”package. These calling environments areuseful to discern whether, for example, a call was made with anexpectation that it should error.
Thetrace_package()functionalso accepts an argument,functions, specifying which functions from apackage should be traced. For example,
x <- trace_package ("stats", functions = "sd")## # A tibble: 2 × 16## trace_number trace_source fn_name fn_call_hash trace_file call_env par_name## <int> <chr> <chr> <chr> <chr> <chr> <chr> ## 1 0 examples sd EzasZOKV <NA> <NA> x ## 2 0 examples sd EzasZOKV <NA> <NA> na.rm ## # ℹ 9 more variables: class <I<list>>, typeof <chr>, mode <chr>,## # storage_mode <chr>, length <int>, formal <I<list>>, uneval <I<list>>,## # eval <I<list>>, source <chr>This package extends on concepts previously developed in other Rpackages, notably including:
Plus work explained in detail in this footnote:
[1] Alexi Turcotte & Jan Vitek (2019),Towards a Type System for R,ICOOOLPS ’19: Proceedings of the 14th Workshop on Implementation,Compilation, Optimization of Object-Oriented Languages, Programs andSystems. Article No. 4, Pages 1–5,https://doi.org/10.1145/3340670.3342426
About
Trace Function Parameter Types in R
Topics
Resources
License
Contributing
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Packages0
Uh oh!
There was an error while loading.Please reload this page.