diff --git a/_posts/2024-03-04-args-args-args-args/args-args-args-args.Rmd b/_posts/2024-03-04-args-args-args-args/args-args-args-args.Rmd index dd94532..57b582c 100644 --- a/_posts/2024-03-04-args-args-args-args/args-args-args-args.Rmd +++ b/_posts/2024-03-04-args-args-args-args/args-args-args-args.Rmd @@ -276,7 +276,7 @@ str(sum) The context for my absurd rant above is that [I was just complaining](https://fosstodon.org/@yjunechoe/112039945400602627) about how I think `args()` is a rather poorly designed function. -Let's try to redesign args. I'll do three takes: +Let's try to redesign `args()`. I'll do three takes: ### Take 1) Display is the side-effect; output is trivial @@ -325,7 +325,7 @@ args2(rnorm)(5) ``` -### Take 1) Display is the side-effect; output is meaningful +### Take 2) Display is the side-effect; output is meaningful One thing I neglected to mention in this blog post is that there are other ways to extract a function's arguments. One of them is `formals()`:^[But note that it has a [special behavior](https://adv-r.hadley.nz/functions.html?q=formals#primitive-functions) of returning `NULL` for primitive functions (written in C) that clearly have user-facing arguments on the R side. See also `formalArgs()`, for a shortcut to `names(formals())`] @@ -376,3 +376,10 @@ typeof( args4(rnorm) ) ``` Like, come on! + +## sessionInfo() + +```{r} +sessionInfo() +``` + diff --git a/_posts/2024-03-04-args-args-args-args/args-args-args-args.html b/_posts/2024-03-04-args-args-args-args/args-args-args-args.html index b3af243..dfbd436 100644 --- a/_posts/2024-03-04-args-args-args-args/args-args-args-args.html +++ b/_posts/2024-03-04-args-args-args-args/args-args-args-args.html @@ -1570,9 +1570,10 @@

Contents

  • Coda (serious): redesigning args()
  • +
  • sessionInfo()
  • @@ -1592,7 +1593,7 @@

    args()

      function (name) 
       .Internal(args(name))
    -  <bytecode: 0x0000020d66d37fb0>
    +  <bytecode: 0x0000024f98dbd180>
       <environment: namespace:base>

    But wouldn’t it be fun if I used args() itself to get this information?

    @@ -1890,7 +1891,7 @@

    TL;DR: str()

    args() is hereafter banned from my blog.

    Coda (serious): redesigning args()

    The context for my absurd rant above is that I was just complaining about how I think args() is a rather poorly designed function.

    -

    Let’s try to redesign args. I’ll do three takes:

    +

    Let’s try to redesign args(). I’ll do three takes:

    Take 1) Display is the side-effect; output is trivial

    If the whole point of args() is to display a function’s arguments for inspection in interactive usage, then that can simply be done as a side-effect.

    As I said above, str() surprisingly has this more sensible behavior out of the box. So let’s write our first redesign of args() which just calls str():

    @@ -1948,9 +1949,9 @@

    Take 1) Display is
    args2(rnorm)(5)
      function (..., na.rm = FALSE)
    -
      [1] -0.1377015 -1.1093728 -0.4732521  1.2939402 -1.9288874
    +
      [1] -0.5494891  1.2861975 -1.2755454  1.0817387 -0.7248563
    -

    Take 1) Display is the side-effect; output is meaningful

    +

    Take 2) Display is the side-effect; output is meaningful

    One thing I neglected to mention in this blog post is that there are other ways to extract a function’s arguments. One of them is formals():6

    @@ -2031,7 +2032,46 @@

    Take 3) Just remove the NULL [1] "closure"

    Like, come on!

    -
    +

    sessionInfo()

    +
    + +
      R version 4.3.3 (2024-02-29 ucrt)
    +  Platform: x86_64-w64-mingw32/x64 (64-bit)
    +  Running under: Windows 11 x64 (build 22631)
    +  
    +  Matrix products: default
    +  
    +  
    +  locale:
    +  [1] LC_COLLATE=English_United States.utf8 
    +  [2] LC_CTYPE=English_United States.utf8   
    +  [3] LC_MONETARY=English_United States.utf8
    +  [4] LC_NUMERIC=C                          
    +  [5] LC_TIME=English_United States.utf8    
    +  
    +  time zone: America/New_York
    +  tzcode source: internal
    +  
    +  attached base packages:
    +  [1] stats     graphics  grDevices utils     datasets  methods   base     
    +  
    +  other attached packages:
    +  [1] magrittr_2.0.3
    +  
    +  loaded via a namespace (and not attached):
    +   [1] crayon_1.5.2      vctrs_0.6.5       cli_3.6.1         knitr_1.45       
    +   [5] rlang_1.1.2       xfun_0.41         purrr_1.0.2       styler_1.10.2    
    +   [9] jsonlite_1.8.8    htmltools_0.5.7   sass_0.4.7        fansi_1.0.5      
    +  [13] rmarkdown_2.25    R.cache_0.16.0    evaluate_0.23     jquerylib_0.1.4  
    +  [17] distill_1.6       fastmap_1.1.1     yaml_2.3.7        lifecycle_1.0.4  
    +  [21] memoise_2.0.1     compiler_4.3.3    prettycode_1.1.0  downlit_0.4.3    
    +  [25] rstudioapi_0.15.0 R.oo_1.25.0       R.utils_2.12.3    digest_0.6.33    
    +  [29] R6_2.5.1          R.methodsS3_1.8.2 bslib_0.6.1       tools_4.3.3      
    +  [33] withr_3.0.0       cachem_1.0.8
    +
    +

      diff --git a/docs/blog.html b/docs/blog.html index e0c6bfe..1eccba5 100644 --- a/docs/blog.html +++ b/docs/blog.html @@ -3508,7 +3508,7 @@

      Categories

      More articles »
    - +

    Blog Posts

    diff --git a/docs/blog.xml b/docs/blog.xml index 42cae84..827f70c 100644 --- a/docs/blog.xml +++ b/docs/blog.xml @@ -53,7 +53,7 @@ start by looking at the argument that <code>args()</code> takes.< <pre class="r"><code>args</code></pre> <pre><code> function (name) .Internal(args(name)) - &lt;bytecode: 0x0000020d66d37fb0&gt; + &lt;bytecode: 0x0000024f98dbd180&gt; &lt;environment: namespace:base&gt;</code></pre> <p>But wouldn’t it be fun if I used <code>args()</code> itself to get this information?</p> @@ -284,7 +284,7 @@ class="footnote-ref" id="fnref2"><sup>2</sup></a></p> href="https://fosstodon.org/@yjunechoe/112039945400602627">I was just complaining</a> about how I think <code>args()</code> is a rather poorly designed function.</p> -<p>Let’s try to redesign args. I’ll do three takes:</p> +<p>Let’s try to redesign <code>args()</code>. I’ll do three takes:</p> <h3 id="take-1-display-is-the-side-effect-output-is-trivial">Take 1) Display is the side-effect; output is trivial</h3> <p>If the whole point of <code>args()</code> is to @@ -337,8 +337,8 @@ side-effect.</p> <pre><code> function (..., na.rm = FALSE)</code></pre> <pre class="r"><code>args2(rnorm)(5)</code></pre> <pre><code> function (..., na.rm = FALSE)</code></pre> -<pre><code> [1] 1.6536876 1.4528063 -0.3030989 -0.2270826 0.6305848</code></pre> -<h3 id="take-1-display-is-the-side-effect-output-is-meaningful">Take 1) +<pre><code> [1] 0.9164464 1.5802621 -1.3677842 0.1642751 -0.5970638</code></pre> +<h3 id="take-2-display-is-the-side-effect-output-is-meaningful">Take 2) Display is the side-effect; output is meaningful</h3> <p>One thing I neglected to mention in this blog post is that there are other ways to extract a function’s arguments. One of them is @@ -408,6 +408,44 @@ args4(sum)</code></pre> <pre class="r"><code>typeof( args4(rnorm) )</code></pre> <pre><code> [1] &quot;closure&quot;</code></pre> <p>Like, come on!</p> +<h2 id="sessioninfo">sessionInfo()</h2> +<pre class="r"><code>sessionInfo()</code></pre> +<pre><code> R version 4.3.3 (2024-02-29 ucrt) + Platform: x86_64-w64-mingw32/x64 (64-bit) + Running under: Windows 11 x64 (build 22631) + + Matrix products: default + + + locale: + [1] LC_COLLATE=English_United States.utf8 + [2] LC_CTYPE=English_United States.utf8 + [3] LC_MONETARY=English_United States.utf8 + [4] LC_NUMERIC=C + [5] LC_TIME=English_United States.utf8 + + time zone: America/New_York + tzcode source: internal + + attached base packages: + [1] stats graphics grDevices utils datasets methods base + + other attached packages: + [1] magrittr_2.0.3 + + loaded via a namespace (and not attached): + [1] jsonlite_1.8.8 compiler_4.3.3 crayon_1.5.2 xml2_1.3.6 + [5] jquerylib_0.1.4 png_0.1-8 yaml_2.3.7 fastmap_1.1.1 + [9] mime_0.12 R6_2.5.1 generics_0.1.3 knitr_1.45 + [13] bookdown_0.37 distill_1.6 rprojroot_2.0.4 openssl_2.1.1 + [17] lubridate_1.9.3 R.cache_0.16.0 bslib_0.6.1 R.utils_2.12.3 + [21] rlang_1.1.2 cachem_1.0.8 prettycode_1.1.0 xfun_0.41 + [25] sass_0.4.7 timechange_0.2.0 memoise_2.0.1 cli_3.6.1 + [29] withr_3.0.0 digest_0.6.33 rstudioapi_0.15.0 fontawesome_0.5.2 + [33] askpass_1.2.0 lifecycle_1.0.4 R.methodsS3_1.8.2 R.oo_1.25.0 + [37] vctrs_0.6.5 downlit_0.4.3 evaluate_0.23 styler_1.10.2 + [41] fansi_1.0.5 rmarkdown_2.25 purrr_1.0.2 tools_4.3.3 + [45] htmltools_0.5.7</code></pre> <pre class="r distill-force-highlighting-css"><code></code></pre> <div class="footnotes footnotes-end-of-document"> <hr /> diff --git a/docs/posts/2024-03-04-args-args-args-args/index.html b/docs/posts/2024-03-04-args-args-args-args/index.html index 7be714f..7387fdc 100644 --- a/docs/posts/2024-03-04-args-args-args-args/index.html +++ b/docs/posts/2024-03-04-args-args-args-args/index.html @@ -2704,9 +2704,10 @@

    Contents

  • Coda (serious): redesigning args()
  • +
  • sessionInfo()
  • @@ -2726,7 +2727,7 @@

    args()

      function (name) 
       .Internal(args(name))
    -  <bytecode: 0x0000020d66d37fb0>
    +  <bytecode: 0x0000024f98dbd180>
       <environment: namespace:base>

    But wouldn’t it be fun if I used args() itself to get this information?

    @@ -3024,7 +3025,7 @@

    TL;DR: str()

    args() is hereafter banned from my blog.

    Coda (serious): redesigning args()

    The context for my absurd rant above is that I was just complaining about how I think args() is a rather poorly designed function.

    -

    Let’s try to redesign args. I’ll do three takes:

    +

    Let’s try to redesign args(). I’ll do three takes:

    Take 1) Display is the side-effect; output is trivial

    If the whole point of args() is to display a function’s arguments for inspection in interactive usage, then that can simply be done as a side-effect.

    As I said above, str() surprisingly has this more sensible behavior out of the box. So let’s write our first redesign of args() which just calls str():

    @@ -3082,9 +3083,9 @@

    Take 1) Display is
    args2(rnorm)(5)
      function (..., na.rm = FALSE)
    -
      [1] -0.1377015 -1.1093728 -0.4732521  1.2939402 -1.9288874
    +
      [1] -0.5494891  1.2861975 -1.2755454  1.0817387 -0.7248563
    -

    Take 1) Display is the side-effect; output is meaningful

    +

    Take 2) Display is the side-effect; output is meaningful

    One thing I neglected to mention in this blog post is that there are other ways to extract a function’s arguments. One of them is formals():6

    @@ -3165,7 +3166,46 @@

    Take 3) Just remove the NULL [1] "closure"

    Like, come on!

    -
    +

    sessionInfo()

    +
    + +
      R version 4.3.3 (2024-02-29 ucrt)
    +  Platform: x86_64-w64-mingw32/x64 (64-bit)
    +  Running under: Windows 11 x64 (build 22631)
    +  
    +  Matrix products: default
    +  
    +  
    +  locale:
    +  [1] LC_COLLATE=English_United States.utf8 
    +  [2] LC_CTYPE=English_United States.utf8   
    +  [3] LC_MONETARY=English_United States.utf8
    +  [4] LC_NUMERIC=C                          
    +  [5] LC_TIME=English_United States.utf8    
    +  
    +  time zone: America/New_York
    +  tzcode source: internal
    +  
    +  attached base packages:
    +  [1] stats     graphics  grDevices utils     datasets  methods   base     
    +  
    +  other attached packages:
    +  [1] magrittr_2.0.3
    +  
    +  loaded via a namespace (and not attached):
    +   [1] crayon_1.5.2      vctrs_0.6.5       cli_3.6.1         knitr_1.45       
    +   [5] rlang_1.1.2       xfun_0.41         purrr_1.0.2       styler_1.10.2    
    +   [9] jsonlite_1.8.8    htmltools_0.5.7   sass_0.4.7        fansi_1.0.5      
    +  [13] rmarkdown_2.25    R.cache_0.16.0    evaluate_0.23     jquerylib_0.1.4  
    +  [17] distill_1.6       fastmap_1.1.1     yaml_2.3.7        lifecycle_1.0.4  
    +  [21] memoise_2.0.1     compiler_4.3.3    prettycode_1.1.0  downlit_0.4.3    
    +  [25] rstudioapi_0.15.0 R.oo_1.25.0       R.utils_2.12.3    digest_0.6.33    
    +  [29] R6_2.5.1          R.methodsS3_1.8.2 bslib_0.6.1       tools_4.3.3      
    +  [33] withr_3.0.0       cachem_1.0.8
    +
    +

      diff --git a/docs/posts/posts.json b/docs/posts/posts.json index db315ae..14c3f47 100644 --- a/docs/posts/posts.json +++ b/docs/posts/posts.json @@ -14,9 +14,9 @@ "args", "metaprogramming" ], - "contents": "\r\n\r\nContents\r\nargs()\r\nargs(args)\r\nargs(args)(args)\r\nargs(args(args)(args))\r\nad infinitum\r\nHad enough args() yet?\r\nTL;DR: str()\r\nCoda (serious): redesigning args()\r\nTake 1) Display is the side-effect; output is trivial\r\nTake 1) Display is the side-effect; output is meaningful\r\nTake 3) Just remove the NULL\r\n\r\n\r\nThe kind of blog posts that I have the most fun writing are those where I hyperfocus on a single function, like dplyr::slice(), purrr::reduce(), and ggplot2::stat_summary(). In writing blog posts of this kind, I naturally come across a point where I need to introduce the argument(s) that the function takes. I usually talk about them one at a time as needed, but I could start by front-loading that important piece of information first.\r\nIn fact, there’s a function in R that lets me do exactly that, called args().\r\nargs()\r\nargs() is, in theory, a very neat function. According to ?args:\r\n\r\nDisplays the argument names and corresponding default values of a (non-primitive or primitive) function.\r\n\r\nSo, for example, I know that sum() takes the arguments ... and na.rm (with the na.rm = FALSE default). The role of args() is to display exactly that piece of information using R code. This blog runs on rmarkdown, so surely I can use args() as a convenient and fancy way of showing information about a function’s arguments to my readers.\r\nIn this blog post, I want to talk about args(). So let’s start by looking at the argument that args() takes.\r\nOf course, I could just print args in the console:\r\n\r\n\r\nargs\r\n\r\n function (name) \r\n .Internal(args(name))\r\n \r\n \r\n\r\nBut wouldn’t it be fun if I used args() itself to get this information?\r\nargs(args)\r\n\r\n\r\nargs(args)\r\n\r\n function (name) \r\n NULL\r\n\r\nOkay, so I get the function (name) piece, which is the information I wanted to show. We can see that args() takes one argument, called name, with no defaults.\r\nBut wait - what’s that NULL doing there in the second line?\r\nHmm, I wonder if they forgot to invisible()-y return the NULL. args() is a function for displaying a function’s arguments after all, so maybe the arguments are printed to the console as a side-effect and the actual output of args() is NULL.\r\nIf that is true, we should be able to suppress the printing of NULL with invisible():\r\n\r\n\r\ninvisible(args(args))\r\n\r\n\r\nUh oh, now everything is invisible.\r\nAlright, enough games! What exactly are you, output of args()?!\r\n\r\n\r\ntypeof(args(args))\r\n\r\n [1] \"closure\"\r\n\r\nWhat?\r\nargs(args)(args)\r\nTurns out that args(args) is actually returning a whole function that’s a copy of args(), except with its body replaced with NULL.\r\nSo args(args) is itself a function that takes an argument called name and then returns NULL. Let’s assign it to a variable and call it like a function:\r\n\r\n\r\nabomination <- args(args)\r\n\r\n\r\n\r\n\r\nabomination(123)\r\n\r\n NULL\r\n\r\nabomination(mtcars)\r\n\r\n NULL\r\n\r\nabomination(stop())\r\n\r\n NULL\r\n\r\nThe body is just NULL, so the function doesn’t care what it receives1 - it just returns NULL.\r\nIn fact, we could even pass it… args:\r\n\r\n\r\nargs(args)(args)\r\n\r\n NULL\r\n\r\nargs(args(args)(args))\r\nBut wait, that’s not all! args() doesn’t just accept a function as its argument. From the documentation:\r\n\r\nValue\r\nNULL in case of a non-function.\r\n\r\nSo yeah - if args() receives a non-function, it just returns NULL:\r\n\r\n\r\nargs(123)\r\n\r\n NULL\r\n\r\nargs(mtcars)\r\n\r\n NULL\r\n\r\nThis applies to any non-function, including… NULL:\r\n\r\n\r\nargs(NULL)\r\n\r\n NULL\r\n\r\nAnd recall that:\r\n\r\n\r\nis.null( args(args)(args) )\r\n\r\n [1] TRUE\r\n\r\nTherefore, this is a valid expression in base R:\r\n\r\n\r\nargs(args(args)(args))\r\n\r\n NULL\r\n\r\nad infinitum\r\nFor our cursed usecase of using args(f) to return a copy of f with it’s body replaced with NULL only to then immediately call args(f)(f) to return NULL, it really doesn’t matter what the identity of f is as long as it’s a function.\r\nThat function can even be … args(args)!\r\nSo let’s take our args(args(args)(args)):\r\n\r\n\r\nargs( args( args )( args ))\r\n\r\n NULL\r\n\r\nAnd swap every args() with args(args):\r\n\r\n\r\nargs(args)( args(args)( args(args) )( args(args) ))\r\n\r\n NULL\r\n\r\nOr better yet, swap every args() with args(args(args)):\r\n\r\n\r\nargs(args(args))( args(args(args))( args(args(args)) )( args(args(args)) ))\r\n\r\n NULL\r\n\r\nThe above unhinged examples are a product of two patterns:\r\nThe fact that you always get function (name) NULL from wrapping args()s over args:\r\n\r\n\r\nlist(\r\n args( args),\r\n args( args(args)),\r\n args(args(args(args)))\r\n )\r\n\r\n [[1]]\r\n function (name) \r\n NULL\r\n\r\n [[2]]\r\n function (name) \r\n NULL\r\n\r\n [[3]]\r\n function (name) \r\n NULL\r\n\r\nThe fact that you can get this whole thing to return NULL by having function (name) NULL call the function object args. You can do this anywhere in the stack and the NULL will simply propagate:\r\n\r\n\r\nlist(\r\n args(args(args(args))) (args) ,\r\n args(args(args(args)) (args) ) ,\r\n args(args(args(args) (args) ))\r\n )\r\n\r\n [[1]]\r\n NULL\r\n\r\n [[2]]\r\n NULL\r\n\r\n [[3]]\r\n NULL\r\n\r\nWe could keep going but it’s tiring to type out and read all these nested args()… but did you know that there’s this thing called the pipe %>% that’s the solution to all code readability issues?\r\nHad enough args() yet?\r\nLet’s make an args() factory ARGS() …\r\n\r\n\r\nlibrary(magrittr)\r\nARGS <- function(n) {\r\n Reduce(\r\n f = \\(x,y) bquote(.(x) %>% args()),\r\n x = seq_len(n),\r\n init = quote(args)\r\n )\r\n}\r\n\r\n\r\n… to produce a sequence of args() …\r\n\r\n\r\nARGS(10)\r\n\r\n args %>% args() %>% args() %>% args() %>% args() %>% args() %>% \r\n args() %>% args() %>% args() %>% args() %>% args()\r\n\r\neval(ARGS(10))\r\n\r\n function (name) \r\n NULL\r\n\r\n… and tidy it up!\r\n\r\n\r\nARGS(10) %>% \r\n deparse1() %>% \r\n styler::style_text()\r\n\r\n args %>%\r\n args() %>%\r\n args() %>%\r\n args() %>%\r\n args() %>%\r\n args() %>%\r\n args() %>%\r\n args() %>%\r\n args() %>%\r\n args() %>%\r\n args()\r\n\r\nWanna see even more unhinged?\r\nLet’s try to produce a “matrix” of args(). You get a choice of i “rows” of piped lines, and j “columns” of args()-around-args each time - all to produce a NULL.\r\nReady?\r\n\r\n\r\nARGS2 <- function(i, j) {\r\n Reduce(\r\n f = \\(x,y) bquote(.(x) %>% (.(y))),\r\n x = rep(list(Reduce(\\(x,y) call(\"args\", x), seq_len(j), quote(args))), i)\r\n )\r\n}\r\n\r\n\r\n\r\n\r\nARGS2(5, 1) %>% \r\n deparse1() %>%\r\n styler::style_text()\r\n\r\n args(args) %>%\r\n (args(args)) %>%\r\n (args(args)) %>%\r\n (args(args)) %>%\r\n (args(args))\r\n\r\n\r\n\r\nARGS2(5, 3) %>% \r\n deparse1() %>%\r\n styler::style_text()\r\n\r\n args(args(args(args))) %>%\r\n (args(args(args(args)))) %>%\r\n (args(args(args(args)))) %>%\r\n (args(args(args(args)))) %>%\r\n (args(args(args(args))))\r\n\r\n\r\n\r\nARGS2(10, 5) %>% \r\n deparse1() %>%\r\n styler::style_text()\r\n\r\n args(args(args(args(args(args))))) %>%\r\n (args(args(args(args(args(args)))))) %>%\r\n (args(args(args(args(args(args)))))) %>%\r\n (args(args(args(args(args(args)))))) %>%\r\n (args(args(args(args(args(args)))))) %>%\r\n (args(args(args(args(args(args)))))) %>%\r\n (args(args(args(args(args(args)))))) %>%\r\n (args(args(args(args(args(args)))))) %>%\r\n (args(args(args(args(args(args)))))) %>%\r\n (args(args(args(args(args(args))))))\r\n\r\n\r\n\r\nlist(\r\n eval(ARGS2(5, 1)),\r\n eval(ARGS2(5, 3)),\r\n eval(ARGS2(10, 5))\r\n)\r\n\r\n [[1]]\r\n NULL\r\n \r\n [[2]]\r\n NULL\r\n \r\n [[3]]\r\n NULL\r\n\r\nYay!\r\nTL;DR: str()\r\nIf you want a version of args() that does what it’s supposed to, use str() instead:2\r\n\r\n\r\nstr(args)\r\n\r\n function (name)\r\n\r\nstr(sum)\r\n\r\n function (..., na.rm = FALSE)\r\n\r\nargs() is hereafter banned from my blog.\r\nCoda (serious): redesigning args()\r\nThe context for my absurd rant above is that I was just complaining about how I think args() is a rather poorly designed function.\r\nLet’s try to redesign args. I’ll do three takes:\r\nTake 1) Display is the side-effect; output is trivial\r\nIf the whole point of args() is to display a function’s arguments for inspection in interactive usage, then that can simply be done as a side-effect.\r\nAs I said above, str() surprisingly has this more sensible behavior out of the box. So let’s write our first redesign of args() which just calls str():\r\n\r\n\r\nargs1 <- function(name) {\r\n str(name)\r\n}\r\nargs1(sum)\r\n\r\n function (..., na.rm = FALSE)\r\n\r\nIn args1()/str(), information about the function arguments are sent to the console.3 We know this because we can’t suppress this with invisible but we can grab this via capture.output:\r\n\r\n\r\ninvisible( args1(sum) )\r\n\r\n function (..., na.rm = FALSE)\r\n\r\ncapture.output( args1(sum) )\r\n\r\n [1] \"function (..., na.rm = FALSE) \"\r\n\r\nFor functions whose purpose is to signal information to the console (and whose usage is limited to interactive contexts), we don’t particularly care about the output. In fact, because the focus isn’t on the output, the return value should be as trivial as possible.\r\nA recommended option is to just invisibly return NULL. This is now how args1() does it (via str()).4:\r\n\r\n\r\nprint( args1(sum) )\r\n\r\n function (..., na.rm = FALSE) \r\n NULL\r\n\r\nis.null( args1(sum) )\r\n\r\n function (..., na.rm = FALSE)\r\n [1] TRUE\r\n\r\nAlternatively, the function could just invisibly return what it receives,5 which is another common pattern for cases like this. Again, we return invisibly to avoid distracting from the fact that the point of the function is to display as the side-effect.\r\n\r\n\r\nargs2 <- function(name) {\r\n str(sum)\r\n invisible(name)\r\n}\r\n\r\n\r\n\r\n\r\nargs2(rnorm)\r\n\r\n function (..., na.rm = FALSE)\r\n\r\n\r\n\r\nargs2(rnorm)(5)\r\n\r\n function (..., na.rm = FALSE)\r\n [1] -0.1377015 -1.1093728 -0.4732521 1.2939402 -1.9288874\r\n\r\nTake 1) Display is the side-effect; output is meaningful\r\nOne thing I neglected to mention in this blog post is that there are other ways to extract a function’s arguments. One of them is formals():6\r\n\r\n\r\nformals(args)\r\n\r\n $name\r\n\r\nformals(rnorm)\r\n\r\n $n\r\n \r\n \r\n $mean\r\n [1] 0\r\n \r\n $sd\r\n [1] 1\r\n\r\nformals() returns the information about a function’s arguments in a list which is pretty boring, but it’s an object we can manipulate (unlike the return value of str()). So there’s some pros and cons.\r\nActually, we could just combine both formals() and str():\r\n\r\n\r\nargs3 <- function(name) {\r\n str(name)\r\n invisible(formals(name))\r\n}\r\n\r\n\r\n\r\n\r\narguments <- args3(rnorm)\r\n\r\n function (n, mean = 0, sd = 1)\r\n\r\narguments\r\n\r\n $n\r\n \r\n \r\n $mean\r\n [1] 0\r\n \r\n $sd\r\n [1] 1\r\n\r\narguments$mean\r\n\r\n [1] 0\r\n\r\nYou get the nice display as a side-effect (via str()) and then an informative output (via formals()). You could even turn this into a class with a print method, which is definitely the better way to go about this, but I’m running out of steam here and I don’t like OOP, so I won’t touch that here.\r\nTake 3) Just remove the NULL\r\nThis last redesign is the simplest of the three, and narrowly deals with the problem of that pesky NULL shown alongside the function arguments:\r\n\r\n\r\nargs(sum)\r\n\r\n function (..., na.rm = FALSE) \r\n NULL\r\n\r\nFine, I’ll give them that args() must, for compatibility with S whatever reason, return a whole new function object, which in turn requires a function body. But if that function is just as a placeholder and not meant to be called, can’t you just make the function body, like, empty?\r\n\r\n\r\nargs4 <- function(name) {\r\n f <- args(name)\r\n body(f) <- quote(expr=)\r\n f\r\n}\r\nargs4(sum)\r\n\r\n function (..., na.rm = FALSE)\r\n\r\nargs4(rnorm)\r\n\r\n function (n, mean = 0, sd = 1)\r\n\r\ntypeof( args4(rnorm) )\r\n\r\n [1] \"closure\"\r\n\r\nLike, come on!\r\n\r\nYou can even see lazy evaluation in action when it receives stop() without erroring.↩︎\r\nThough you have to remove the \"srcref\" attribute if the function has one. But also don’t actually do this!↩︎\r\nTechnically, the \"output\" stream.↩︎\r\nFor the longest time, I thought args() was doing this from how its output looked.↩︎\r\nEssentially acting like identity().↩︎\r\nBut note that it has a special behavior of returning NULL for primitive functions (written in C) that clearly have user-facing arguments on the R side. See also formalArgs(), for a shortcut to names(formals())↩︎\r\n", + "contents": "\r\n\r\nContents\r\nargs()\r\nargs(args)\r\nargs(args)(args)\r\nargs(args(args)(args))\r\nad infinitum\r\nHad enough args() yet?\r\nTL;DR: str()\r\nCoda (serious): redesigning args()\r\nTake 1) Display is the side-effect; output is trivial\r\nTake 2) Display is the side-effect; output is meaningful\r\nTake 3) Just remove the NULL\r\n\r\nsessionInfo()\r\n\r\nThe kind of blog posts that I have the most fun writing are those where I hyperfocus on a single function, like dplyr::slice(), purrr::reduce(), and ggplot2::stat_summary(). In writing blog posts of this kind, I naturally come across a point where I need to introduce the argument(s) that the function takes. I usually talk about them one at a time as needed, but I could start by front-loading that important piece of information first.\r\nIn fact, there’s a function in R that lets me do exactly that, called args().\r\nargs()\r\nargs() is, in theory, a very neat function. According to ?args:\r\n\r\nDisplays the argument names and corresponding default values of a (non-primitive or primitive) function.\r\n\r\nSo, for example, I know that sum() takes the arguments ... and na.rm (with the na.rm = FALSE default). The role of args() is to display exactly that piece of information using R code. This blog runs on rmarkdown, so surely I can use args() as a convenient and fancy way of showing information about a function’s arguments to my readers.\r\nIn this blog post, I want to talk about args(). So let’s start by looking at the argument that args() takes.\r\nOf course, I could just print args in the console:\r\n\r\n\r\nargs\r\n\r\n function (name) \r\n .Internal(args(name))\r\n \r\n \r\n\r\nBut wouldn’t it be fun if I used args() itself to get this information?\r\nargs(args)\r\n\r\n\r\nargs(args)\r\n\r\n function (name) \r\n NULL\r\n\r\nOkay, so I get the function (name) piece, which is the information I wanted to show. We can see that args() takes one argument, called name, with no defaults.\r\nBut wait - what’s that NULL doing there in the second line?\r\nHmm, I wonder if they forgot to invisible()-y return the NULL. args() is a function for displaying a function’s arguments after all, so maybe the arguments are printed to the console as a side-effect and the actual output of args() is NULL.\r\nIf that is true, we should be able to suppress the printing of NULL with invisible():\r\n\r\n\r\ninvisible(args(args))\r\n\r\n\r\nUh oh, now everything is invisible.\r\nAlright, enough games! What exactly are you, output of args()?!\r\n\r\n\r\ntypeof(args(args))\r\n\r\n [1] \"closure\"\r\n\r\nWhat?\r\nargs(args)(args)\r\nTurns out that args(args) is actually returning a whole function that’s a copy of args(), except with its body replaced with NULL.\r\nSo args(args) is itself a function that takes an argument called name and then returns NULL. Let’s assign it to a variable and call it like a function:\r\n\r\n\r\nabomination <- args(args)\r\n\r\n\r\n\r\n\r\nabomination(123)\r\n\r\n NULL\r\n\r\nabomination(mtcars)\r\n\r\n NULL\r\n\r\nabomination(stop())\r\n\r\n NULL\r\n\r\nThe body is just NULL, so the function doesn’t care what it receives1 - it just returns NULL.\r\nIn fact, we could even pass it… args:\r\n\r\n\r\nargs(args)(args)\r\n\r\n NULL\r\n\r\nargs(args(args)(args))\r\nBut wait, that’s not all! args() doesn’t just accept a function as its argument. From the documentation:\r\n\r\nValue\r\nNULL in case of a non-function.\r\n\r\nSo yeah - if args() receives a non-function, it just returns NULL:\r\n\r\n\r\nargs(123)\r\n\r\n NULL\r\n\r\nargs(mtcars)\r\n\r\n NULL\r\n\r\nThis applies to any non-function, including… NULL:\r\n\r\n\r\nargs(NULL)\r\n\r\n NULL\r\n\r\nAnd recall that:\r\n\r\n\r\nis.null( args(args)(args) )\r\n\r\n [1] TRUE\r\n\r\nTherefore, this is a valid expression in base R:\r\n\r\n\r\nargs(args(args)(args))\r\n\r\n NULL\r\n\r\nad infinitum\r\nFor our cursed usecase of using args(f) to return a copy of f with it’s body replaced with NULL only to then immediately call args(f)(f) to return NULL, it really doesn’t matter what the identity of f is as long as it’s a function.\r\nThat function can even be … args(args)!\r\nSo let’s take our args(args(args)(args)):\r\n\r\n\r\nargs( args( args )( args ))\r\n\r\n NULL\r\n\r\nAnd swap every args() with args(args):\r\n\r\n\r\nargs(args)( args(args)( args(args) )( args(args) ))\r\n\r\n NULL\r\n\r\nOr better yet, swap every args() with args(args(args)):\r\n\r\n\r\nargs(args(args))( args(args(args))( args(args(args)) )( args(args(args)) ))\r\n\r\n NULL\r\n\r\nThe above unhinged examples are a product of two patterns:\r\nThe fact that you always get function (name) NULL from wrapping args()s over args:\r\n\r\n\r\nlist(\r\n args( args),\r\n args( args(args)),\r\n args(args(args(args)))\r\n )\r\n\r\n [[1]]\r\n function (name) \r\n NULL\r\n\r\n [[2]]\r\n function (name) \r\n NULL\r\n\r\n [[3]]\r\n function (name) \r\n NULL\r\n\r\nThe fact that you can get this whole thing to return NULL by having function (name) NULL call the function object args. You can do this anywhere in the stack and the NULL will simply propagate:\r\n\r\n\r\nlist(\r\n args(args(args(args))) (args) ,\r\n args(args(args(args)) (args) ) ,\r\n args(args(args(args) (args) ))\r\n )\r\n\r\n [[1]]\r\n NULL\r\n\r\n [[2]]\r\n NULL\r\n\r\n [[3]]\r\n NULL\r\n\r\nWe could keep going but it’s tiring to type out and read all these nested args()… but did you know that there’s this thing called the pipe %>% that’s the solution to all code readability issues?\r\nHad enough args() yet?\r\nLet’s make an args() factory ARGS() …\r\n\r\n\r\nlibrary(magrittr)\r\nARGS <- function(n) {\r\n Reduce(\r\n f = \\(x,y) bquote(.(x) %>% args()),\r\n x = seq_len(n),\r\n init = quote(args)\r\n )\r\n}\r\n\r\n\r\n… to produce a sequence of args() …\r\n\r\n\r\nARGS(10)\r\n\r\n args %>% args() %>% args() %>% args() %>% args() %>% args() %>% \r\n args() %>% args() %>% args() %>% args() %>% args()\r\n\r\neval(ARGS(10))\r\n\r\n function (name) \r\n NULL\r\n\r\n… and tidy it up!\r\n\r\n\r\nARGS(10) %>% \r\n deparse1() %>% \r\n styler::style_text()\r\n\r\n args %>%\r\n args() %>%\r\n args() %>%\r\n args() %>%\r\n args() %>%\r\n args() %>%\r\n args() %>%\r\n args() %>%\r\n args() %>%\r\n args() %>%\r\n args()\r\n\r\nWanna see even more unhinged?\r\nLet’s try to produce a “matrix” of args(). You get a choice of i “rows” of piped lines, and j “columns” of args()-around-args each time - all to produce a NULL.\r\nReady?\r\n\r\n\r\nARGS2 <- function(i, j) {\r\n Reduce(\r\n f = \\(x,y) bquote(.(x) %>% (.(y))),\r\n x = rep(list(Reduce(\\(x,y) call(\"args\", x), seq_len(j), quote(args))), i)\r\n )\r\n}\r\n\r\n\r\n\r\n\r\nARGS2(5, 1) %>% \r\n deparse1() %>%\r\n styler::style_text()\r\n\r\n args(args) %>%\r\n (args(args)) %>%\r\n (args(args)) %>%\r\n (args(args)) %>%\r\n (args(args))\r\n\r\n\r\n\r\nARGS2(5, 3) %>% \r\n deparse1() %>%\r\n styler::style_text()\r\n\r\n args(args(args(args))) %>%\r\n (args(args(args(args)))) %>%\r\n (args(args(args(args)))) %>%\r\n (args(args(args(args)))) %>%\r\n (args(args(args(args))))\r\n\r\n\r\n\r\nARGS2(10, 5) %>% \r\n deparse1() %>%\r\n styler::style_text()\r\n\r\n args(args(args(args(args(args))))) %>%\r\n (args(args(args(args(args(args)))))) %>%\r\n (args(args(args(args(args(args)))))) %>%\r\n (args(args(args(args(args(args)))))) %>%\r\n (args(args(args(args(args(args)))))) %>%\r\n (args(args(args(args(args(args)))))) %>%\r\n (args(args(args(args(args(args)))))) %>%\r\n (args(args(args(args(args(args)))))) %>%\r\n (args(args(args(args(args(args)))))) %>%\r\n (args(args(args(args(args(args))))))\r\n\r\n\r\n\r\nlist(\r\n eval(ARGS2(5, 1)),\r\n eval(ARGS2(5, 3)),\r\n eval(ARGS2(10, 5))\r\n)\r\n\r\n [[1]]\r\n NULL\r\n \r\n [[2]]\r\n NULL\r\n \r\n [[3]]\r\n NULL\r\n\r\nYay!\r\nTL;DR: str()\r\nIf you want a version of args() that does what it’s supposed to, use str() instead:2\r\n\r\n\r\nstr(args)\r\n\r\n function (name)\r\n\r\nstr(sum)\r\n\r\n function (..., na.rm = FALSE)\r\n\r\nargs() is hereafter banned from my blog.\r\nCoda (serious): redesigning args()\r\nThe context for my absurd rant above is that I was just complaining about how I think args() is a rather poorly designed function.\r\nLet’s try to redesign args(). I’ll do three takes:\r\nTake 1) Display is the side-effect; output is trivial\r\nIf the whole point of args() is to display a function’s arguments for inspection in interactive usage, then that can simply be done as a side-effect.\r\nAs I said above, str() surprisingly has this more sensible behavior out of the box. So let’s write our first redesign of args() which just calls str():\r\n\r\n\r\nargs1 <- function(name) {\r\n str(name)\r\n}\r\nargs1(sum)\r\n\r\n function (..., na.rm = FALSE)\r\n\r\nIn args1()/str(), information about the function arguments are sent to the console.3 We know this because we can’t suppress this with invisible but we can grab this via capture.output:\r\n\r\n\r\ninvisible( args1(sum) )\r\n\r\n function (..., na.rm = FALSE)\r\n\r\ncapture.output( args1(sum) )\r\n\r\n [1] \"function (..., na.rm = FALSE) \"\r\n\r\nFor functions whose purpose is to signal information to the console (and whose usage is limited to interactive contexts), we don’t particularly care about the output. In fact, because the focus isn’t on the output, the return value should be as trivial as possible.\r\nA recommended option is to just invisibly return NULL. This is now how args1() does it (via str()).4:\r\n\r\n\r\nprint( args1(sum) )\r\n\r\n function (..., na.rm = FALSE) \r\n NULL\r\n\r\nis.null( args1(sum) )\r\n\r\n function (..., na.rm = FALSE)\r\n [1] TRUE\r\n\r\nAlternatively, the function could just invisibly return what it receives,5 which is another common pattern for cases like this. Again, we return invisibly to avoid distracting from the fact that the point of the function is to display as the side-effect.\r\n\r\n\r\nargs2 <- function(name) {\r\n str(sum)\r\n invisible(name)\r\n}\r\n\r\n\r\n\r\n\r\nargs2(rnorm)\r\n\r\n function (..., na.rm = FALSE)\r\n\r\n\r\n\r\nargs2(rnorm)(5)\r\n\r\n function (..., na.rm = FALSE)\r\n [1] -0.5494891 1.2861975 -1.2755454 1.0817387 -0.7248563\r\n\r\nTake 2) Display is the side-effect; output is meaningful\r\nOne thing I neglected to mention in this blog post is that there are other ways to extract a function’s arguments. One of them is formals():6\r\n\r\n\r\nformals(args)\r\n\r\n $name\r\n\r\nformals(rnorm)\r\n\r\n $n\r\n \r\n \r\n $mean\r\n [1] 0\r\n \r\n $sd\r\n [1] 1\r\n\r\nformals() returns the information about a function’s arguments in a list which is pretty boring, but it’s an object we can manipulate (unlike the return value of str()). So there’s some pros and cons.\r\nActually, we could just combine both formals() and str():\r\n\r\n\r\nargs3 <- function(name) {\r\n str(name)\r\n invisible(formals(name))\r\n}\r\n\r\n\r\n\r\n\r\narguments <- args3(rnorm)\r\n\r\n function (n, mean = 0, sd = 1)\r\n\r\narguments\r\n\r\n $n\r\n \r\n \r\n $mean\r\n [1] 0\r\n \r\n $sd\r\n [1] 1\r\n\r\narguments$mean\r\n\r\n [1] 0\r\n\r\nYou get the nice display as a side-effect (via str()) and then an informative output (via formals()). You could even turn this into a class with a print method, which is definitely the better way to go about this, but I’m running out of steam here and I don’t like OOP, so I won’t touch that here.\r\nTake 3) Just remove the NULL\r\nThis last redesign is the simplest of the three, and narrowly deals with the problem of that pesky NULL shown alongside the function arguments:\r\n\r\n\r\nargs(sum)\r\n\r\n function (..., na.rm = FALSE) \r\n NULL\r\n\r\nFine, I’ll give them that args() must, for compatibility with S whatever reason, return a whole new function object, which in turn requires a function body. But if that function is just as a placeholder and not meant to be called, can’t you just make the function body, like, empty?\r\n\r\n\r\nargs4 <- function(name) {\r\n f <- args(name)\r\n body(f) <- quote(expr=)\r\n f\r\n}\r\nargs4(sum)\r\n\r\n function (..., na.rm = FALSE)\r\n\r\nargs4(rnorm)\r\n\r\n function (n, mean = 0, sd = 1)\r\n\r\ntypeof( args4(rnorm) )\r\n\r\n [1] \"closure\"\r\n\r\nLike, come on!\r\nsessionInfo()\r\n\r\n\r\nsessionInfo()\r\n\r\n R version 4.3.3 (2024-02-29 ucrt)\r\n Platform: x86_64-w64-mingw32/x64 (64-bit)\r\n Running under: Windows 11 x64 (build 22631)\r\n \r\n Matrix products: default\r\n \r\n \r\n locale:\r\n [1] LC_COLLATE=English_United States.utf8 \r\n [2] LC_CTYPE=English_United States.utf8 \r\n [3] LC_MONETARY=English_United States.utf8\r\n [4] LC_NUMERIC=C \r\n [5] LC_TIME=English_United States.utf8 \r\n \r\n time zone: America/New_York\r\n tzcode source: internal\r\n \r\n attached base packages:\r\n [1] stats graphics grDevices utils datasets methods base \r\n \r\n other attached packages:\r\n [1] magrittr_2.0.3\r\n \r\n loaded via a namespace (and not attached):\r\n [1] crayon_1.5.2 vctrs_0.6.5 cli_3.6.1 knitr_1.45 \r\n [5] rlang_1.1.2 xfun_0.41 purrr_1.0.2 styler_1.10.2 \r\n [9] jsonlite_1.8.8 htmltools_0.5.7 sass_0.4.7 fansi_1.0.5 \r\n [13] rmarkdown_2.25 R.cache_0.16.0 evaluate_0.23 jquerylib_0.1.4 \r\n [17] distill_1.6 fastmap_1.1.1 yaml_2.3.7 lifecycle_1.0.4 \r\n [21] memoise_2.0.1 compiler_4.3.3 prettycode_1.1.0 downlit_0.4.3 \r\n [25] rstudioapi_0.15.0 R.oo_1.25.0 R.utils_2.12.3 digest_0.6.33 \r\n [29] R6_2.5.1 R.methodsS3_1.8.2 bslib_0.6.1 tools_4.3.3 \r\n [33] withr_3.0.0 cachem_1.0.8\r\n\r\n\r\nYou can even see lazy evaluation in action when it receives stop() without erroring.↩︎\r\nThough you have to remove the \"srcref\" attribute if the function has one. But also don’t actually do this!↩︎\r\nTechnically, the \"output\" stream.↩︎\r\nFor the longest time, I thought args() was doing this from how its output looked.↩︎\r\nEssentially acting like identity().↩︎\r\nBut note that it has a special behavior of returning NULL for primitive functions (written in C) that clearly have user-facing arguments on the R side. See also formalArgs(), for a shortcut to names(formals())↩︎\r\n", "preview": "posts/2024-03-04-args-args-args-args/preview.png", - "last_modified": "2024-03-05T12:31:46-05:00", + "last_modified": "2024-03-05T17:04:52-05:00", "input_file": "args-args-args-args.knit.md", "preview_width": 419, "preview_height": 300 diff --git a/docs/sitemap.xml b/docs/sitemap.xml index 098deed..4d7f347 100644 --- a/docs/sitemap.xml +++ b/docs/sitemap.xml @@ -30,7 +30,7 @@ https://yjunechoe.github.io/posts/2024-03-04-args-args-args-args/ - 2024-03-05T12:31:46-05:00 + 2024-03-05T17:04:52-05:00 https://yjunechoe.github.io/posts/2024-02-20-helloworld-print/