diff --git a/_posts/2024-02-20-helloworld-print/helloworld-print.Rmd b/_posts/2024-02-20-helloworld-print/helloworld-print.Rmd index 59b5ced8..d5abb947 100644 --- a/_posts/2024-02-20-helloworld-print/helloworld-print.Rmd +++ b/_posts/2024-02-20-helloworld-print/helloworld-print.Rmd @@ -23,7 +23,6 @@ preview: preview.png --- ```{r setup, include=FALSE} -library(ggplot2) knitr::opts_chunk$set( comment = " ", echo = TRUE, 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 new file mode 100644 index 00000000..15610cb8 --- /dev/null +++ b/_posts/2024-03-04-args-args-args-args/args-args-args-args.Rmd @@ -0,0 +1,228 @@ +--- +title: 'args(args(args)(args))' +description: | + The unexpected sequal to "R is a language optimized for meme-ing" +categories: + - args +base_url: https://yjunechoe.github.io +author: + - name: June Choe + affiliation: University of Pennsylvania Linguistics + affiliation_url: https://live-sas-www-ling.pantheon.sas.upenn.edu/ + orcid_id: 0000-0002-0701-921X +date: "`r Sys.Date()`" +output: + distill::distill_article: + include-after-body: "highlighting.html" + toc: true + self_contained: false + css: "../../styles.css" +editor_options: + chunk_output_type: console +preview: preview.png +--- + +```{r setup, include=FALSE} +knitr::opts_chunk$set( + comment = " ", + echo = TRUE, + message = FALSE, + warning = FALSE, + R.options = list(width = 80) +) +``` + +The kind of blog posts that I have the most fun writing are those where I hyperfocus on a single function, like [dplyr::slice()](https://yjunechoe.github.io/posts/2023-06-11-row-relational-operations/), [purrr::reduce()](https://yjunechoe.github.io/posts/2020-12-13-collapse-repetitive-piping-with-reduce/), and [ggplot2::stat_summary()](https://yjunechoe.github.io/posts/2020-09-26-demystifying-stat-layers-ggplot2/). 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. + +In fact, there's a function in R that lets me do exactly that, called `args()`. + +## `args()` + +`args()` is, in theory, a very neat function. According to `?args`: + +> Displays the argument names and corresponding default values of a (non-primitive or primitive) function. + +So, 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. + +In this blog post, I want to talk about `args()`. So let's start by looking at the argument that `args()` takes. + +Of course, I could just print `args` in the console: + +```{r} +args +``` + +But wouldn't it be fun if I used `args()` itself to get this information? + +## `args(args)` + +```{r} +args(args) +``` + +Okay, 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. + +But wait - what's that `NULL` doing there in the second line? + +Hmm, 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`. + +If that is true, we should be able to suppress the printing of `NULL` with `invisible()`: + +```{r} +invisible(args(args)) +``` + +Uh oh, now *everything* is invisible. + +Alright, enough games! What exactly are you, output of `args()`?! + +```{r} +typeof(args(args)) +``` + +What? + +## `args(args)(args)` + +Turns out that `args(args)` is actually returning a whole function that's a *copy* of `args()`, except with its body replaced with NULL. + +So `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} +abomination <- args(args) +``` + +```{r} +abomination(123) +abomination(mtcars) +abomination(stop()) +``` + +The body is *just* `NULL`, so the function doesn't care what it receives^[You can even see lazy evaluation in action when it receives `stop()` without erroring.] - it just returns `NULL`. + +In fact, we could even pass it... `args`: + +```{r} +args(args)(args) +``` + +## `args(args(args)(args))` + +But wait, that's not all! `args()` doesn't *just* accept a function as its argument. From the documentation: + +> **Value** +> +> NULL in case of a non-function. + +So yeah - if `args()` receives a non-function, it just returns `NULL`: + +```{r} +args(123) +args(mtcars) +``` + +This applies to *any* non-function, including... `NULL`: + +```{r} +args(NULL) +``` + +And recall that: + +```{r} +is.null( args(args)(args) ) +``` + +Therefore, this is a valid expression in base R: + +```{r} +args(args(args)(args)) +``` + +## ad infinitum + +For 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. + +That function can even be ... `args(args)`! + +So let's take our `args(args(args)(args))`: + +```{r} +args( args( args )( args )) +``` + +And swap every `args()` with `args(args)`: + +```{r} +args(args)( args(args)( args(args) )( args(args) )) +``` + +Or better yet, swap every `args()` with `args(args(args))`: + +```{r} +args(args(args))( args(args(args))( args(args(args)) )( args(args(args)) )) +``` + +The above unhinged examples are a product of two patterns: + +1) The fact that you always get `function (name) NULL` from wrapping `args()` over `args(args)`: + + ```{r} + list( + args( args), + args( args(args)), + args(args(args(args))) + ) + ``` + +2) The 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} + list( + args(args(args(args))) (args) , + args(args(args(args)) (args) ) , + args(args(args(args) (args) )) + ) + ``` + +We 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? + +So why don't we just make an `args()` factory `ARGS()` ... + +```{r} +library(magrittr) +ARGS <- function(n) { + Reduce( + f = \(x,y) bquote(.(x) %>% args()), + x = seq_len(n), + init = quote(args(args)) + ) +} +``` + +... to produce a sequence of `args()` ... + +```{r} +ARGS(10) +eval(ARGS(10)) +``` + +... and tidy it up? + +```{r} +deparse1(ARGS(10)) |> + styler::style_text() +``` + +Yay! + +## TL;DR: `str()` + +If you want a version of `args()` that does what it's supposed to, use `str()`:^[But remember to remove the `"srcref"` attribute if the function has one.] + +```{r} +str(args) +str(sum) +``` + +`args()` is hereafter banned from my blog. 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 new file mode 100644 index 00000000..64628e5b --- /dev/null +++ b/_posts/2024-03-04-args-args-args-args/args-args-args-args.html @@ -0,0 +1,1836 @@ + + + + +
+ + + + + + + + + + + + + + + +The unexpected sequal to “R is a language optimized for meme-ing”
+The 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.
+In fact, there’s a function in R that lets me do exactly that, called args()
.
args()
args()
is, in theory, a very neat function. According to ?args
:
++Displays the argument names and corresponding default values of a (non-primitive or primitive) function.
+
So, 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.
In this blog post, I want to talk about args()
. So let’s start by looking at the argument that args()
takes.
Of course, I could just print args
in the console:
args
+ function (name)
+ .Internal(args(name))
+ <bytecode: 0x000001cee84380e0>
+ <environment: namespace:base>
+But wouldn’t it be fun if I used args()
itself to get this information?
args(args)
args(args)
+ function (name)
+ NULL
+Okay, 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.
But wait - what’s that NULL
doing there in the second line?
Hmm, 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
.
If that is true, we should be able to suppress the printing of NULL
with invisible()
:
Uh oh, now everything is invisible.
+Alright, enough games! What exactly are you, output of args()
?!
What?
+args(args)(args)
Turns out that args(args)
is actually returning a whole function that’s a copy of args()
, except with its body replaced with NULL.
So 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:
abomination <- args(args)
+The body is just NULL
, so the function doesn’t care what it receives1 - it just returns NULL
.
In fact, we could even pass it… args
:
args(args)(args)
+ NULL
+args(args(args)(args))
But wait, that’s not all! args()
doesn’t just accept a function as its argument. From the documentation:
++Value
+NULL in case of a non-function.
+
So yeah - if args()
receives a non-function, it just returns NULL
:
This applies to any non-function, including… NULL
:
args(NULL)
+ NULL
+And recall that:
+ +Therefore, this is a valid expression in base R:
+ +For 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.
That function can even be … args(args)
!
So let’s take our args(args(args)(args))
:
And swap every args()
with args(args)
:
Or better yet, swap every args()
with args(args(args))
:
The above unhinged examples are a product of two patterns:
+The fact that you always get function (name) NULL
from wrapping args()
over args(args)
:
The 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:
We 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?
So why don’t we just make an args()
factory ARGS()
…
… to produce a sequence of args()
…
ARGS(10)
+ args(args) %>% args() %>% args() %>% args() %>% args() %>% args() %>%
+ args() %>% args() %>% args() %>% args() %>% args()
+eval(ARGS(10))
+ function (name)
+ NULL
+… and tidy it up?
+deparse1(ARGS(10)) |>
+ styler::style_text()
+ args(args) %>%
+ args() %>%
+ args() %>%
+ args() %>%
+ args() %>%
+ args() %>%
+ args() %>%
+ args() %>%
+ args() %>%
+ args() %>%
+ args()
+Yay!
+str()
If you want a version of args()
that does what it’s supposed to, use str()
:2
args()
is hereafter banned from my blog.
`,e.githubCompareUpdatesUrl&&(t+=`View all changes to this article since it was first published.`),t+=` + If you see mistakes or want to suggest changes, please create an issue on GitHub.
+ `);const n=e.journal;return'undefined'!=typeof n&&'Distill'===n.title&&(t+=` +Diagrams and text are licensed under Creative Commons Attribution CC-BY 4.0 with the source available on GitHub, unless noted otherwise. The figures that have been reused from other sources don’t fall under this license and can be recognized by a note in their caption: “Figure from …”.
+ `),'undefined'!=typeof e.publishedDate&&(t+=` +For attribution in academic contexts, please cite this work as
+${e.concatenatedAuthors}, "${e.title}", Distill, ${e.publishedYear}.+
BibTeX citation
+${m(e)}+ `),t}var An=Math.sqrt,En=Math.atan2,Dn=Math.sin,Mn=Math.cos,On=Math.PI,Un=Math.abs,In=Math.pow,Nn=Math.LN10,jn=Math.log,Rn=Math.max,qn=Math.ceil,Fn=Math.floor,Pn=Math.round,Hn=Math.min;const zn=['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'],Bn=['Jan.','Feb.','March','April','May','June','July','Aug.','Sept.','Oct.','Nov.','Dec.'],Wn=(e)=>10>e?'0'+e:e,Vn=function(e){const t=zn[e.getDay()].substring(0,3),n=Wn(e.getDate()),i=Bn[e.getMonth()].substring(0,3),a=e.getFullYear().toString(),d=e.getUTCHours().toString(),r=e.getUTCMinutes().toString(),o=e.getUTCSeconds().toString();return`${t}, ${n} ${i} ${a} ${d}:${r}:${o} Z`},$n=function(e){const t=Array.from(e).reduce((e,[t,n])=>Object.assign(e,{[t]:n}),{});return t},Jn=function(e){const t=new Map;for(var n in e)e.hasOwnProperty(n)&&t.set(n,e[n]);return t};class Qn{constructor(e){this.name=e.author,this.personalURL=e.authorURL,this.affiliation=e.affiliation,this.affiliationURL=e.affiliationURL,this.affiliations=e.affiliations||[]}get firstName(){const e=this.name.split(' ');return e.slice(0,e.length-1).join(' ')}get lastName(){const e=this.name.split(' ');return e[e.length-1]}}class Gn{constructor(){this.title='unnamed article',this.description='',this.authors=[],this.bibliography=new Map,this.bibliographyParsed=!1,this.citations=[],this.citationsCollected=!1,this.journal={},this.katex={},this.publishedDate=void 0}set url(e){this._url=e}get url(){if(this._url)return this._url;return this.distillPath&&this.journal.url?this.journal.url+'/'+this.distillPath:this.journal.url?this.journal.url:void 0}get githubUrl(){return this.githubPath?'https://github.com/'+this.githubPath:void 0}set previewURL(e){this._previewURL=e}get previewURL(){return this._previewURL?this._previewURL:this.url+'/thumbnail.jpg'}get publishedDateRFC(){return Vn(this.publishedDate)}get updatedDateRFC(){return Vn(this.updatedDate)}get publishedYear(){return this.publishedDate.getFullYear()}get publishedMonth(){return Bn[this.publishedDate.getMonth()]}get publishedDay(){return this.publishedDate.getDate()}get publishedMonthPadded(){return Wn(this.publishedDate.getMonth()+1)}get publishedDayPadded(){return Wn(this.publishedDate.getDate())}get publishedISODateOnly(){return this.publishedDate.toISOString().split('T')[0]}get volume(){const e=this.publishedYear-2015;if(1>e)throw new Error('Invalid publish date detected during computing volume');return e}get issue(){return this.publishedDate.getMonth()+1}get concatenatedAuthors(){if(2
tag. We found the following text: '+t);const n=document.createElement('span');n.innerHTML=e.nodeValue,e.parentNode.insertBefore(n,e),e.parentNode.removeChild(e)}}}}).observe(this,{childList:!0})}}var Ti='undefined'==typeof window?'undefined'==typeof global?'undefined'==typeof self?{}:self:global:window,_i=f(function(e,t){(function(e){function t(){this.months=['jan','feb','mar','apr','may','jun','jul','aug','sep','oct','nov','dec'],this.notKey=[',','{','}',' ','='],this.pos=0,this.input='',this.entries=[],this.currentEntry='',this.setInput=function(e){this.input=e},this.getEntries=function(){return this.entries},this.isWhitespace=function(e){return' '==e||'\r'==e||'\t'==e||'\n'==e},this.match=function(e,t){if((void 0==t||null==t)&&(t=!0),this.skipWhitespace(t),this.input.substring(this.pos,this.pos+e.length)==e)this.pos+=e.length;else throw'Token mismatch, expected '+e+', found '+this.input.substring(this.pos);this.skipWhitespace(t)},this.tryMatch=function(e,t){return(void 0==t||null==t)&&(t=!0),this.skipWhitespace(t),this.input.substring(this.pos,this.pos+e.length)==e},this.matchAt=function(){for(;this.input.length>this.pos&&'@'!=this.input[this.pos];)this.pos++;return!('@'!=this.input[this.pos])},this.skipWhitespace=function(e){for(;this.isWhitespace(this.input[this.pos]);)this.pos++;if('%'==this.input[this.pos]&&!0==e){for(;'\n'!=this.input[this.pos];)this.pos++;this.skipWhitespace(e)}},this.value_braces=function(){var e=0;this.match('{',!1);for(var t=this.pos,n=!1;;){if(!n)if('}'==this.input[this.pos]){if(0 =k&&(++x,i=k);if(d[x]instanceof n||d[T-1].greedy)continue;w=T-x,y=e.slice(i,k),v.index-=i}if(v){g&&(h=v[1].length);var S=v.index+h,v=v[0].slice(h),C=S+v.length,_=y.slice(0,S),L=y.slice(C),A=[x,w];_&&A.push(_);var E=new n(o,u?a.tokenize(v,u):v,b,v,f);A.push(E),L&&A.push(L),Array.prototype.splice.apply(d,A)}}}}}return d},hooks:{all:{},add:function(e,t){var n=a.hooks.all;n[e]=n[e]||[],n[e].push(t)},run:function(e,t){var n=a.hooks.all[e];if(n&&n.length)for(var d,r=0;d=n[r++];)d(t)}}},i=a.Token=function(e,t,n,i,a){this.type=e,this.content=t,this.alias=n,this.length=0|(i||'').length,this.greedy=!!a};if(i.stringify=function(e,t,n){if('string'==typeof e)return e;if('Array'===a.util.type(e))return e.map(function(n){return i.stringify(n,t,e)}).join('');var d={type:e.type,content:i.stringify(e.content,t,n),tag:'span',classes:['token',e.type],attributes:{},language:t,parent:n};if('comment'==d.type&&(d.attributes.spellcheck='true'),e.alias){var r='Array'===a.util.type(e.alias)?e.alias:[e.alias];Array.prototype.push.apply(d.classes,r)}a.hooks.run('wrap',d);var l=Object.keys(d.attributes).map(function(e){return e+'="'+(d.attributes[e]||'').replace(/"/g,'"')+'"'}).join(' ');return'<'+d.tag+' class="'+d.classes.join(' ')+'"'+(l?' '+l:'')+'>'+d.content+''+d.tag+'>'},!t.document)return t.addEventListener?(t.addEventListener('message',function(e){var n=JSON.parse(e.data),i=n.language,d=n.code,r=n.immediateClose;t.postMessage(a.highlight(d,a.languages[i],i)),r&&t.close()},!1),t.Prism):t.Prism;var d=document.currentScript||[].slice.call(document.getElementsByTagName('script')).pop();return d&&(a.filename=d.src,document.addEventListener&&!d.hasAttribute('data-manual')&&('loading'===document.readyState?document.addEventListener('DOMContentLoaded',a.highlightAll):window.requestAnimationFrame?window.requestAnimationFrame(a.highlightAll):window.setTimeout(a.highlightAll,16))),t.Prism}();e.exports&&(e.exports=n),'undefined'!=typeof Ti&&(Ti.Prism=n),n.languages.markup={comment://,prolog:/<\?[\w\W]+?\?>/,doctype://i,cdata://i,tag:{pattern:/<\/?(?!\d)[^\s>\/=$<]+(?:\s+[^\s>\/=]+(?:=(?:("|')(?:\\\1|\\?(?!\1)[\w\W])*\1|[^\s'">=]+))?)*\s*\/?>/i,inside:{tag:{pattern:/^<\/?[^\s>\/]+/i,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"attr-value":{pattern:/=(?:('|")[\w\W]*?(\1)|[^\s>]+)/i,inside:{punctuation:/[=>"']/}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:/?[\da-z]{1,8};/i},n.hooks.add('wrap',function(e){'entity'===e.type&&(e.attributes.title=e.content.replace(/&/,'&'))}),n.languages.xml=n.languages.markup,n.languages.html=n.languages.markup,n.languages.mathml=n.languages.markup,n.languages.svg=n.languages.markup,n.languages.css={comment:/\/\*[\w\W]*?\*\//,atrule:{pattern:/@[\w-]+?.*?(;|(?=\s*\{))/i,inside:{rule:/@[\w-]+/}},url:/url\((?:(["'])(\\(?:\r\n|[\w\W])|(?!\1)[^\\\r\n])*\1|.*?)\)/i,selector:/[^\{\}\s][^\{\};]*?(?=\s*\{)/,string:{pattern:/("|')(\\(?:\r\n|[\w\W])|(?!\1)[^\\\r\n])*\1/,greedy:!0},property:/(\b|\B)[\w-]+(?=\s*:)/i,important:/\B!important\b/i,function:/[-a-z0-9]+(?=\()/i,punctuation:/[(){};:]/},n.languages.css.atrule.inside.rest=n.util.clone(n.languages.css),n.languages.markup&&(n.languages.insertBefore('markup','tag',{style:{pattern:/(
+
+
+ ${e.map(l).map((e)=>`
`)}}const Mi=`
+d-citation-list {
+ contain: layout style;
+}
+
+d-citation-list .references {
+ grid-column: text;
+}
+
+d-citation-list .references .title {
+ font-weight: 500;
+}
+`;class Oi extends HTMLElement{static get is(){return'd-citation-list'}connectedCallback(){this.hasAttribute('distill-prerendered')||(this.style.display='none')}set citations(e){x(this,e)}}var Ui=f(function(e){var t='undefined'==typeof window?'undefined'!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{}:window,n=function(){var e=/\blang(?:uage)?-(\w+)\b/i,n=0,a=t.Prism={util:{encode:function(e){return e instanceof i?new i(e.type,a.util.encode(e.content),e.alias):'Array'===a.util.type(e)?e.map(a.util.encode):e.replace(/&/g,'&').replace(/e.length)break tokenloop;if(!(y instanceof n)){c.lastIndex=0;var v=c.exec(y),w=1;if(!v&&f&&x!=d.length-1){if(c.lastIndex=i,v=c.exec(e),!v)break;for(var S=v.index+(g?v[1].length:0),C=v.index+v[0].length,T=x,k=i,p=d.length;T
+
+`);class Ni extends ei(Ii(HTMLElement)){renderContent(){if(this.languageName=this.getAttribute('language'),!this.languageName)return void console.warn('You need to provide a language attribute to your
Footnotes
+
+`,!1);class Fi extends qi(HTMLElement){connectedCallback(){super.connectedCallback(),this.list=this.root.querySelector('ol'),this.root.style.display='none'}set footnotes(e){if(this.list.innerHTML='',e.length){this.root.style.display='';for(const t of e){const e=document.createElement('li');e.id=t.id+'-listing',e.innerHTML=t.innerHTML;const n=document.createElement('a');n.setAttribute('class','footnote-backlink'),n.textContent='[\u21A9]',n.href='#'+t.id,e.appendChild(n),this.list.appendChild(e)}}else this.root.style.display='none'}}const Pi=ti('d-hover-box',`
+
+
+