[How to] Write a purrr-like adverb

[This article was first published on Colin Fay, and kindly contributed to R-bloggers]. (You can report issue about the content on this page here)
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Create your own safely, compose and friends!

What is an adverb

If you read carefully the purrr documentation, you’ll find this simple explanation :

Adverbs modify the action of a function; taking a function as input and returning a function with modified action as output.

In other words, adverbs take a function, and return this function modified. Yes, just as an adverb modifies a verb. So if you do :

<span class="n">library</span><span class="p">(</span><span class="n">purrr</span><span class="p">)</span><span class="w">
</span><span class="n">safe_log</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="n">safely</span><span class="p">(</span><span class="n">log</span><span class="p">)</span><span class="w">
</span>

The returned object is another function that you can use just as a regular one.

<span class="nf">class</span><span class="p">(</span><span class="n">safe_log</span><span class="p">)</span><span class="w">
</span>
## [1] "function"
<span class="n">safe_log</span><span class="p">(</span><span class="s2">"a"</span><span class="p">)</span><span class="w">
</span>
## $result
## NULL
## 
## $error
## <simpleError in log(x = x, base = base): argument non numérique pour une fonction mathématique>

In computer science, these adverbs are what is called “high-order functions”.

How to write your own?

I’ve been playing with adverbs in {attempt}, notably through these adverbs :

<span class="n">library</span><span class="p">(</span><span class="n">attempt</span><span class="p">)</span><span class="w">

</span><span class="c1"># Silently only return the errors, and nothing if the function succeeds</span><span class="w">
</span><span class="n">silent_log</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="n">silently</span><span class="p">(</span><span class="n">log</span><span class="p">)</span><span class="w">
</span><span class="n">silent_log</span><span class="p">(</span><span class="m">1</span><span class="p">)</span><span class="w">
</span>
<span class="c1"># Surely make a function always work, without stopping the process</span><span class="w">
</span><span class="n">sure_log</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="n">surely</span><span class="p">(</span><span class="n">log</span><span class="p">)</span><span class="w">
</span><span class="n">sure_log</span><span class="p">(</span><span class="m">1</span><span class="p">)</span><span class="w">
</span>
## [1] 0
<span class="n">sure_log</span><span class="p">(</span><span class="s2">"a"</span><span class="p">)</span><span class="w">
</span>
<span class="c1"># with_message and with_warning</span><span class="w">
</span><span class="n">as_num_msg</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="n">with_message</span><span class="p">(</span><span class="n">as.numeric</span><span class="p">,</span><span class="w"> </span><span class="n">msg</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"We're performing a numeric conversion"</span><span class="p">)</span><span class="w">
</span><span class="n">as_num_warn</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="n">with_warning</span><span class="p">(</span><span class="n">as.numeric</span><span class="p">,</span><span class="w"> </span><span class="n">msg</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"We're performing a numeric conversion"</span><span class="p">)</span><span class="w">
</span><span class="n">as_num_msg</span><span class="p">(</span><span class="s2">"1"</span><span class="p">)</span><span class="w">
</span>
## We're performing a numeric conversion

## [1] 1
<span class="n">as_num_warn</span><span class="p">(</span><span class="s2">"1"</span><span class="p">)</span><span class="w">
</span>
## Warning in as_num_warn("1"): We're performing a numeric conversion

## [1] 1

So, how to implement this kind of behavior? Let’s take a simple example with sleepy, also shared on Twitter.

<span class="n">sleepy</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="k">function</span><span class="p">(</span><span class="n">fun</span><span class="p">,</span><span class="w"> </span><span class="n">sleep</span><span class="p">){</span><span class="w">
  </span><span class="k">function</span><span class="p">(</span><span class="n">...</span><span class="p">){</span><span class="w">
    </span><span class="n">Sys.sleep</span><span class="p">(</span><span class="n">sleep</span><span class="p">)</span><span class="w">
    </span><span class="n">fun</span><span class="p">(</span><span class="n">...</span><span class="p">)</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="n">sleep_print</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="n">sleepy</span><span class="p">(</span><span class="n">Sys.time</span><span class="p">,</span><span class="w"> </span><span class="m">5</span><span class="p">)</span><span class="w">
</span><span class="nf">class</span><span class="p">(</span><span class="n">sleep_print</span><span class="p">)</span><span class="w">
</span>
## [1] "function"
<span class="c1"># Let's try</span><span class="w">
</span><span class="n">Sys.time</span><span class="p">()</span><span class="w">
</span>
## [1] "2018-04-19 10:20:58 CEST"
<span class="n">sleep_print</span><span class="p">()</span><span class="w">
</span>
## [1] "2018-04-19 10:21:03 CEST"

Let’s decompose what we’ve got here.

First of all, the function should return another function, so we need to start with :

<span class="n">talky</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="k">function</span><span class="p">(){</span><span class="w">
  </span><span class="k">function</span><span class="p">(){</span><span class="w">
    
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span>

What this function will take as a first argument is another function, that will be executed when our future new function is called.

So let’s do this:

<span class="n">talky</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="k">function</span><span class="p">(</span><span class="n">fun</span><span class="p">){</span><span class="w">
  </span><span class="k">function</span><span class="p">(){</span><span class="w">
    </span><span class="n">fun</span><span class="p">()</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span>

Because you know, with R referential transparency, you can create a variable that is a function:

<span class="n">plop</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="n">mean</span><span class="w">
</span><span class="n">plop</span><span class="p">(</span><span class="m">1</span><span class="o">:</span><span class="m">10</span><span class="p">)</span><span class="w">
</span>
## [1] 5.5

This simple skeleton will work if we take a function without any args:

<span class="n">sys_time</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="n">talky</span><span class="p">(</span><span class="n">Sys.time</span><span class="p">)</span><span class="w">
</span><span class="n">sys_time</span><span class="p">()</span><span class="w">
</span>
## [1] "2018-04-19 10:21:03 CEST"

But hey, this is not what we want: we need this new function to be able to take arguments. So let’s use our friend ....

<span class="n">talky</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="k">function</span><span class="p">(</span><span class="n">fun</span><span class="p">){</span><span class="w">
  </span><span class="k">function</span><span class="p">(</span><span class="n">...</span><span class="p">){</span><span class="w">
    </span><span class="n">fun</span><span class="p">(</span><span class="n">...</span><span class="p">)</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span>

Now, our new adverb creates a function that can take arguments. But as you’ve notice, this is still not really an adverb: we need to modify something. Now you’re only limited by your imagination 😉

<span class="c1"># Print the time</span><span class="w">
</span><span class="n">talky</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="k">function</span><span class="p">(</span><span class="n">fun</span><span class="p">){</span><span class="w">
  </span><span class="k">function</span><span class="p">(</span><span class="n">...</span><span class="p">){</span><span class="w">
    </span><span class="n">print</span><span class="p">(</span><span class="n">Sys.time</span><span class="p">())</span><span class="w">
    </span><span class="n">fun</span><span class="p">(</span><span class="n">...</span><span class="p">)</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="n">talky_sqrt</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="n">talky</span><span class="p">(</span><span class="n">sqrt</span><span class="p">)</span><span class="w">
</span><span class="n">talky_sqrt</span><span class="p">(</span><span class="m">10</span><span class="p">)</span><span class="w">
</span>
## [1] "2018-04-19 10:21:03 CEST"

## [1] 3.162278
<span class="c1"># Or with a kind message ? </span><span class="w">
</span><span class="n">talky</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="k">function</span><span class="p">(</span><span class="n">fun</span><span class="p">,</span><span class="w"> </span><span class="n">mess</span><span class="p">){</span><span class="w">
  </span><span class="k">function</span><span class="p">(</span><span class="n">...</span><span class="p">){</span><span class="w">
    </span><span class="n">message</span><span class="p">(</span><span class="n">mess</span><span class="p">)</span><span class="w">
    </span><span class="n">fun</span><span class="p">(</span><span class="n">...</span><span class="p">)</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="n">talky_sqrt</span><span class="o"><-</span><span class="w"> </span><span class="n">talky</span><span class="p">(</span><span class="n">fun</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">sqrt</span><span class="p">,</span><span class="w"> </span><span class="n">mess</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"Hey there! You Rock!"</span><span class="p">)</span><span class="w">
</span><span class="n">talky_sqrt</span><span class="p">(</span><span class="m">1</span><span class="p">)</span><span class="w">
</span>
## Hey there! You Rock!

## [1] 1
<span class="c1"># Run it or not ?</span><span class="w">
</span><span class="n">maybe</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="k">function</span><span class="p">(</span><span class="n">fun</span><span class="p">){</span><span class="w">
  </span><span class="k">function</span><span class="p">(</span><span class="n">...</span><span class="p">){</span><span class="w">
    </span><span class="n">num</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="n">sample</span><span class="p">(</span><span class="m">1</span><span class="o">:</span><span class="m">100</span><span class="p">,</span><span class="w"> </span><span class="m">1</span><span class="p">)</span><span class="w">
    </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">num</span><span class="w"> </span><span class="o">></span><span class="w"> </span><span class="m">50</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
      </span><span class="n">fun</span><span class="p">(</span><span class="n">...</span><span class="p">)</span><span class="w">
    </span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="n">maybe_sqrt</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="n">maybe</span><span class="p">(</span><span class="n">fun</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">sqrt</span><span class="p">)</span><span class="w">
</span><span class="n">maybe_sqrt</span><span class="p">(</span><span class="m">1</span><span class="p">)</span><span class="w">
</span><span class="n">maybe_sqrt</span><span class="p">(</span><span class="m">1</span><span class="p">)</span><span class="w">
</span>
## [1] 1
<span class="n">maybe_sqrt</span><span class="p">(</span><span class="m">1</span><span class="p">)</span><span class="w">
</span>
## [1] 1
<span class="c1"># Create a log file of a function </span><span class="w">
</span><span class="n">log_calls</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="k">function</span><span class="p">(</span><span class="n">fun</span><span class="p">,</span><span class="w"> </span><span class="n">file</span><span class="p">){</span><span class="w">
  </span><span class="k">function</span><span class="p">(</span><span class="n">...</span><span class="p">){</span><span class="w">
    </span><span class="n">write</span><span class="p">(</span><span class="nf">as.character</span><span class="p">(</span><span class="n">Sys.time</span><span class="p">()),</span><span class="w"> </span><span class="n">file</span><span class="p">,</span><span class="w"> </span><span class="n">append</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">TRUE</span><span class="p">,</span><span class="w"> </span><span class="n">sep</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"\n"</span><span class="p">)</span><span class="w">
    </span><span class="n">fun</span><span class="p">(</span><span class="n">...</span><span class="p">)</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="n">log_sqrt</span><span class="w"> </span><span class="o"><-</span><span class="w"> </span><span class="n">log_calls</span><span class="p">(</span><span class="n">sqrt</span><span class="p">,</span><span class="w"> </span><span class="n">file</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"logs"</span><span class="p">)</span><span class="w">
</span><span class="n">log_sqrt</span><span class="p">(</span><span class="m">10</span><span class="p">)</span><span class="w">
</span>
## [1] 3.162278
<span class="n">log_sqrt</span><span class="p">(</span><span class="m">13</span><span class="p">)</span><span class="w">
</span>
## [1] 3.605551
<span class="n">readLines</span><span class="p">(</span><span class="s2">"logs"</span><span class="p">)</span><span class="w">
</span>
## [1] "2018-04-19 10:21:03" "2018-04-19 10:21:03"

To leave a comment for the author, please follow the link and comment on their blog: Colin Fay.

R-bloggers.com offers daily e-mail updates about R news and tutorials about learning R and many other topics. Click here if you're looking to post or find an R/data-science job.
Want to share your content on R-bloggers? click here if you have a blog, or here if you don't.

Never miss an update!
Subscribe to R-bloggers to receive
e-mails with the latest R posts.
(You will not see this message again.)

Click here to close (This popup will not appear again)